summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.bazelrc2
-rw-r--r--.bazelversion2
-rw-r--r--.gitmodules4
-rw-r--r--.gitreview2
-rw-r--r--.mailmap3
-rw-r--r--.zuul.yaml3
-rw-r--r--BUILD6
-rw-r--r--Documentation/access-control.txt32
-rw-r--r--Documentation/backup.txt26
-rw-r--r--Documentation/cmd-ls-projects.txt6
-rw-r--r--Documentation/cmd-plugin-remove.txt1
-rw-r--r--Documentation/config-accounts.txt12
-rw-r--r--Documentation/config-gerrit.txt329
-rw-r--r--Documentation/config-groups.txt8
-rw-r--r--Documentation/config-labels.txt33
-rw-r--r--Documentation/config-robot-comments.txt1
-rw-r--r--Documentation/config-sso.txt4
-rw-r--r--Documentation/config-validation.txt9
-rw-r--r--Documentation/dev-bazel.txt112
-rw-r--r--Documentation/dev-build-plugins.txt6
-rw-r--r--Documentation/dev-cla.txt26
-rw-r--r--Documentation/dev-community.txt70
-rw-r--r--Documentation/dev-contributing.txt711
-rw-r--r--Documentation/dev-crafting-changes.txt306
-rw-r--r--Documentation/dev-design-doc-conclusion-template.md18
-rw-r--r--Documentation/dev-design-doc-index-template.md18
-rw-r--r--Documentation/dev-design-doc-solution-template.md72
-rw-r--r--Documentation/dev-design-doc-use-cases-template.md48
-rw-r--r--Documentation/dev-design-docs.txt153
-rw-r--r--Documentation/dev-design.txt44
-rw-r--r--Documentation/dev-e2e-tests.txt11
-rw-r--r--Documentation/dev-eclipse.txt7
-rw-r--r--Documentation/dev-inspector.txt2
-rw-r--r--Documentation/dev-intellij.txt6
-rw-r--r--Documentation/dev-plugins-lifecycle.txt254
-rw-r--r--Documentation/dev-plugins.txt133
-rw-r--r--Documentation/dev-processes.txt355
-rw-r--r--Documentation/dev-readme.txt147
-rw-r--r--Documentation/dev-release-deploy-config.txt2
-rw-r--r--Documentation/dev-release-jgit.txt52
-rw-r--r--Documentation/dev-release.txt4
-rw-r--r--Documentation/dev-roles.txt378
-rw-r--r--Documentation/dev-starter-projects.txt14
-rw-r--r--Documentation/error-change-does-not-belong-to-project.txt18
-rw-r--r--Documentation/error-change-not-found.txt17
-rw-r--r--Documentation/error-commit-already-exists.txt2
-rw-r--r--Documentation/error-messages.txt3
-rw-r--r--Documentation/error-push-refschanges-not-allowed.txt13
-rw-r--r--Documentation/i18n-readme.txt24
-rw-r--r--Documentation/index.txt30
-rw-r--r--Documentation/install.txt2
-rw-r--r--Documentation/intro-gerrit-walkthrough.txt2
-rw-r--r--Documentation/intro-user.txt30
-rw-r--r--Documentation/js-api.txt925
-rw-r--r--Documentation/js_licenses.txt43
-rw-r--r--Documentation/licenses.txt73
-rw-r--r--Documentation/linux-quickstart.txt11
-rw-r--r--Documentation/metrics.txt22
-rw-r--r--Documentation/pg-plugin-admin-api.txt8
-rw-r--r--Documentation/pg-plugin-dev.txt11
-rw-r--r--Documentation/pg-plugin-endpoints.txt5
-rw-r--r--Documentation/pg-plugin-style-object.txt33
-rw-r--r--Documentation/pg-plugin-styles-api.txt29
-rw-r--r--Documentation/pg-plugin-styling.txt14
-rw-r--r--Documentation/pgm-daemon.txt16
-rw-r--r--Documentation/prolog-cookbook.txt3
-rwxr-xr-xDocumentation/replace_macros.py2
-rw-r--r--Documentation/rest-api-accounts.txt49
-rw-r--r--Documentation/rest-api-changes.txt105
-rw-r--r--Documentation/rest-api-config.txt12
-rw-r--r--Documentation/rest-api-groups.txt6
-rw-r--r--Documentation/rest-api-plugins.txt18
-rw-r--r--Documentation/rest-api-projects.txt70
-rw-r--r--Documentation/user-inline-edit.txt2
-rw-r--r--Documentation/user-request-tracing.txt92
-rw-r--r--Documentation/user-search.txt12
-rw-r--r--Documentation/user-upload.txt77
-rw-r--r--WORKSPACE207
-rwxr-xr-xcontrib/abandon_stale.py225
-rw-r--r--contrib/benchmark-createchange.go103
-rwxr-xr-xcontrib/check-valid-commit.py2
-rwxr-xr-xcontrib/find-duplicate-usernames.sh56
-rwxr-xr-xcontrib/git-push-review2
-rwxr-xr-xcontrib/hooks/post-receive-move-tmp-refs78
-rwxr-xr-xcontrib/populate-fixture-data.py2
-rwxr-xr-xcontrib/refresh_plugin_in_testsite.sh63
-rw-r--r--contrib/reindex/.flake89
-rw-r--r--contrib/reindex/.gitignore1
-rw-r--r--contrib/reindex/Pipfile19
-rw-r--r--contrib/reindex/Pipfile.lock248
-rw-r--r--contrib/reindex/README.md63
-rwxr-xr-xcontrib/reindex/reindex.py189
-rwxr-xr-xcontrib/show_new_gerrit_doc_in_chrome.sh48
-rwxr-xr-xcontrib/start_testsite.sh63
-rwxr-xr-xe2e-tests/README53
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/AbandonChange.json6
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json3
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json6
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange-body.json2
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject-body.json3
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/RestoreChange.json6
-rw-r--r--e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChangeInBranch.json5
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/AbandonChange.scala71
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala14
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala2
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala6
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala65
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala33
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala6
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala6
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala16
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala2
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala2
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/RestoreChange.scala74
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala4
-rw-r--r--e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChangeInBranch.scala74
-rw-r--r--java/com/google/gerrit/acceptance/AbstractDaemonTest.java380
-rw-r--r--java/com/google/gerrit/acceptance/AbstractNotificationTest.java28
-rw-r--r--java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java2
-rw-r--r--java/com/google/gerrit/acceptance/AccountCreator.java8
-rw-r--r--java/com/google/gerrit/acceptance/AccountIndexedCounter.java58
-rw-r--r--java/com/google/gerrit/acceptance/BUILD198
-rw-r--r--java/com/google/gerrit/acceptance/ChangeIndexedCounter.java5
-rw-r--r--java/com/google/gerrit/acceptance/DisabledAccountIndex.java2
-rw-r--r--java/com/google/gerrit/acceptance/DisabledChangeIndex.java2
-rw-r--r--java/com/google/gerrit/acceptance/DisabledProjectIndex.java2
-rw-r--r--java/com/google/gerrit/acceptance/ExtensionRegistry.java253
-rw-r--r--java/com/google/gerrit/acceptance/GcAssert.java2
-rw-r--r--java/com/google/gerrit/acceptance/GerritServer.java75
-rw-r--r--java/com/google/gerrit/acceptance/GitClientVersion.java66
-rw-r--r--java/com/google/gerrit/acceptance/GitUtil.java35
-rw-r--r--java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java2
-rw-r--r--java/com/google/gerrit/acceptance/InProcessProtocol.java28
-rw-r--r--java/com/google/gerrit/acceptance/ProjectResetter.java10
-rw-r--r--java/com/google/gerrit/acceptance/PushOneCommit.java13
-rw-r--r--java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java2
-rw-r--r--java/com/google/gerrit/acceptance/ReindexGroupsAtStartup.java5
-rw-r--r--java/com/google/gerrit/acceptance/RestResponse.java34
-rw-r--r--java/com/google/gerrit/acceptance/SshdModule.java2
-rw-r--r--java/com/google/gerrit/acceptance/StandaloneSiteTest.java42
-rw-r--r--java/com/google/gerrit/acceptance/TestAccount.java2
-rw-r--r--java/com/google/gerrit/acceptance/UseClockStep.java42
-rw-r--r--java/com/google/gerrit/acceptance/UseSystemTime.java35
-rw-r--r--java/com/google/gerrit/acceptance/UseTimezone.java35
-rw-r--r--java/com/google/gerrit/acceptance/rest/CreateTestPlugin.java3
-rw-r--r--java/com/google/gerrit/acceptance/rest/GetTestPlugin.java2
-rw-r--r--java/com/google/gerrit/acceptance/rest/ListTestPlugin.java5
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java20
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java8
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java4
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java4
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java4
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/project/BUILD22
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java35
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java183
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java449
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java2
-rw-r--r--java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java2
-rw-r--r--java/com/google/gerrit/common/BUILD5
-rw-r--r--java/com/google/gerrit/common/FileUtil.java1
-rw-r--r--java/com/google/gerrit/common/PageLinks.java14
-rw-r--r--java/com/google/gerrit/common/UsedAt.java9
-rw-r--r--java/com/google/gerrit/common/auth/openid/OpenIdUrls.java1
-rw-r--r--java/com/google/gerrit/common/data/AccessSection.java2
-rw-r--r--java/com/google/gerrit/common/data/CommentDetail.java8
-rw-r--r--java/com/google/gerrit/common/data/ContributorAgreement.java2
-rw-r--r--java/com/google/gerrit/common/data/FilenameComparator.java2
-rw-r--r--java/com/google/gerrit/common/data/GarbageCollectionResult.java2
-rw-r--r--java/com/google/gerrit/common/data/GroupDescription.java4
-rw-r--r--java/com/google/gerrit/common/data/GroupReference.java22
-rw-r--r--java/com/google/gerrit/common/data/LabelFunction.java8
-rw-r--r--java/com/google/gerrit/common/data/LabelType.java25
-rw-r--r--java/com/google/gerrit/common/data/LabelTypes.java2
-rw-r--r--java/com/google/gerrit/common/data/PatchScript.java13
-rw-r--r--java/com/google/gerrit/common/data/PermissionRange.java4
-rw-r--r--java/com/google/gerrit/common/data/SubmitRecord.java2
-rw-r--r--java/com/google/gerrit/common/data/SubscribeSection.java10
-rw-r--r--java/com/google/gerrit/common/data/testing/BUILD2
-rw-r--r--java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java15
-rw-r--r--java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java44
-rw-r--r--java/com/google/gerrit/elasticsearch/BUILD4
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java31
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java64
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticConfiguration.java7
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java9
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java9
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java45
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java2
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java2
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticSetting.java8
-rw-r--r--java/com/google/gerrit/elasticsearch/ElasticVersion.java33
-rw-r--r--java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java6
-rw-r--r--java/com/google/gerrit/entities/Account.java (renamed from java/com/google/gerrit/reviewdb/client/Account.java)206
-rw-r--r--java/com/google/gerrit/entities/AccountGroup.java (renamed from java/com/google/gerrit/reviewdb/client/AccountGroup.java)99
-rw-r--r--java/com/google/gerrit/entities/AccountGroupByIdAudit.java66
-rw-r--r--java/com/google/gerrit/entities/AccountGroupMemberAudit.java74
-rw-r--r--java/com/google/gerrit/entities/BUILD (renamed from java/com/google/gerrit/reviewdb/BUILD)7
-rw-r--r--java/com/google/gerrit/entities/BooleanProjectConfig.java (renamed from java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java)2
-rw-r--r--java/com/google/gerrit/entities/BranchNameKey.java49
-rw-r--r--java/com/google/gerrit/entities/Change.java (renamed from java/com/google/gerrit/reviewdb/client/Change.java)187
-rw-r--r--java/com/google/gerrit/entities/ChangeMessage.java (renamed from java/com/google/gerrit/reviewdb/client/ChangeMessage.java)47
-rw-r--r--java/com/google/gerrit/entities/CodedEnum.java (renamed from java/com/google/gerrit/reviewdb/client/CodedEnum.java)2
-rw-r--r--java/com/google/gerrit/entities/Comment.java (renamed from java/com/google/gerrit/reviewdb/client/Comment.java)206
-rw-r--r--java/com/google/gerrit/entities/CommentRange.java (renamed from java/com/google/gerrit/reviewdb/client/CommentRange.java)2
-rw-r--r--java/com/google/gerrit/entities/CoreDownloadSchemes.java (renamed from java/com/google/gerrit/reviewdb/client/CoreDownloadSchemes.java)2
-rw-r--r--java/com/google/gerrit/entities/FixReplacement.java (renamed from java/com/google/gerrit/reviewdb/client/FixReplacement.java)2
-rw-r--r--java/com/google/gerrit/entities/FixSuggestion.java (renamed from java/com/google/gerrit/reviewdb/client/FixSuggestion.java)2
-rw-r--r--java/com/google/gerrit/entities/KeyUtil.java (renamed from java/com/google/gwtorm/client/StandardKeyEncoder.java)13
-rw-r--r--java/com/google/gerrit/entities/LabelId.java (renamed from java/com/google/gerrit/reviewdb/client/LabelId.java)29
-rw-r--r--java/com/google/gerrit/entities/Patch.java (renamed from java/com/google/gerrit/reviewdb/client/Patch.java)72
-rw-r--r--java/com/google/gerrit/entities/PatchSet.java233
-rw-r--r--java/com/google/gerrit/entities/PatchSetApproval.java139
-rw-r--r--java/com/google/gerrit/entities/PatchSetInfo.java (renamed from java/com/google/gerrit/reviewdb/client/PatchSetInfo.java)26
-rw-r--r--java/com/google/gerrit/entities/Project.java (renamed from java/com/google/gerrit/reviewdb/client/Project.java)60
-rw-r--r--java/com/google/gerrit/entities/RefNames.java (renamed from java/com/google/gerrit/reviewdb/client/RefNames.java)7
-rw-r--r--java/com/google/gerrit/entities/RobotComment.java53
-rw-r--r--java/com/google/gerrit/entities/SubmoduleSubscription.java80
-rw-r--r--java/com/google/gerrit/entities/UserIdentity.java (renamed from java/com/google/gerrit/reviewdb/client/UserIdentity.java)2
-rw-r--r--java/com/google/gerrit/entities/converter/AccountIdProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/AccountIdProtoConverter.java)8
-rw-r--r--java/com/google/gerrit/entities/converter/BranchNameKeyProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverter.java)22
-rw-r--r--java/com/google/gerrit/entities/converter/ChangeIdProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverter.java)8
-rw-r--r--java/com/google/gerrit/entities/converter/ChangeKeyProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverter.java)8
-rw-r--r--java/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverter.java)14
-rw-r--r--java/com/google/gerrit/entities/converter/ChangeMessageProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverter.java)10
-rw-r--r--java/com/google/gerrit/entities/converter/ChangeProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ChangeProtoConverter.java)18
-rw-r--r--java/com/google/gerrit/entities/converter/LabelIdProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/LabelIdProtoConverter.java)8
-rw-r--r--java/com/google/gerrit/entities/converter/ObjectIdProtoConverter.java52
-rw-r--r--java/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverter.java)22
-rw-r--r--java/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverter.java)45
-rw-r--r--java/com/google/gerrit/entities/converter/PatchSetIdProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverter.java)14
-rw-r--r--java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java96
-rw-r--r--java/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverter.java)8
-rw-r--r--java/com/google/gerrit/entities/converter/ProtoConverter.java (renamed from java/com/google/gerrit/reviewdb/converter/ProtoConverter.java)4
-rw-r--r--java/com/google/gerrit/exceptions/BUILD2
-rw-r--r--java/com/google/gerrit/exceptions/InvalidMergeStrategyException.java23
-rw-r--r--java/com/google/gerrit/exceptions/NoSuchGroupException.java2
-rw-r--r--java/com/google/gerrit/extensions/BUILD6
-rw-r--r--java/com/google/gerrit/extensions/api/changes/ChangeApi.java31
-rw-r--r--java/com/google/gerrit/extensions/api/changes/NotifyInfo.java25
-rw-r--r--java/com/google/gerrit/extensions/api/changes/RevertInput.java2
-rw-r--r--java/com/google/gerrit/extensions/api/changes/RevisionApi.java4
-rw-r--r--java/com/google/gerrit/extensions/api/groups/GroupInput.java1
-rw-r--r--java/com/google/gerrit/extensions/api/projects/ProjectApi.java8
-rw-r--r--java/com/google/gerrit/extensions/client/Comment.java4
-rw-r--r--java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java12
-rw-r--r--java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java35
-rw-r--r--java/com/google/gerrit/extensions/client/ListChangesOption.java8
-rw-r--r--java/com/google/gerrit/extensions/common/AccountDetailInfo.java1
-rw-r--r--java/com/google/gerrit/extensions/common/AccountInfo.java12
-rw-r--r--java/com/google/gerrit/extensions/common/AvatarInfo.java2
-rw-r--r--java/com/google/gerrit/extensions/common/ChangeConfigInfo.java1
-rw-r--r--java/com/google/gerrit/extensions/common/GroupAuditEventInfo.java21
-rw-r--r--java/com/google/gerrit/extensions/common/testing/BUILD2
-rw-r--r--java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java18
-rw-r--r--java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java13
-rw-r--r--java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java19
-rw-r--r--java/com/google/gerrit/extensions/common/testing/EditInfoSubject.java11
-rw-r--r--java/com/google/gerrit/extensions/common/testing/FileInfoSubject.java16
-rw-r--r--java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java6
-rw-r--r--java/com/google/gerrit/extensions/common/testing/FixReplacementInfoSubject.java12
-rw-r--r--java/com/google/gerrit/extensions/common/testing/FixSuggestionInfoSubject.java14
-rw-r--r--java/com/google/gerrit/extensions/common/testing/GitPersonSubject.java25
-rw-r--r--java/com/google/gerrit/extensions/common/testing/RangeSubject.java17
-rw-r--r--java/com/google/gerrit/extensions/common/testing/RobotCommentInfoSubject.java11
-rw-r--r--java/com/google/gerrit/extensions/events/AccountActivationListener.java42
-rw-r--r--java/com/google/gerrit/extensions/events/ChangeEvent.java5
-rw-r--r--java/com/google/gerrit/extensions/events/ChangeIndexedListener.java15
-rw-r--r--java/com/google/gerrit/extensions/events/RevisionEvent.java6
-rw-r--r--java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java5
-rw-r--r--java/com/google/gerrit/extensions/restapi/Response.java139
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestApiException.java8
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestCollectionCreateView.java17
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestCollectionDeleteMissingView.java20
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestCollectionModifyView.java22
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestModifyView.java22
-rw-r--r--java/com/google/gerrit/extensions/restapi/RestReadView.java21
-rw-r--r--java/com/google/gerrit/extensions/restapi/testing/BinaryResultSubject.java7
-rw-r--r--java/com/google/gerrit/extensions/validators/CommentForValidation.java48
-rw-r--r--java/com/google/gerrit/extensions/validators/CommentValidationFailure.java32
-rw-r--r--java/com/google/gerrit/extensions/validators/CommentValidator.java34
-rw-r--r--java/com/google/gerrit/git/BUILD5
-rw-r--r--java/com/google/gerrit/git/GitUpdateFailureException.java95
-rw-r--r--java/com/google/gerrit/git/LockFailureException.java24
-rw-r--r--java/com/google/gerrit/git/ObjectIds.java131
-rw-r--r--java/com/google/gerrit/git/RefUpdateUtil.java8
-rw-r--r--java/com/google/gerrit/git/testing/BUILD2
-rw-r--r--java/com/google/gerrit/git/testing/CommitSubject.java14
-rw-r--r--java/com/google/gerrit/git/testing/ObjectIdSubject.java12
-rw-r--r--java/com/google/gerrit/git/testing/PushResultSubject.java92
-rw-r--r--java/com/google/gerrit/gpg/BUILD8
-rw-r--r--java/com/google/gerrit/gpg/GerritPublicKeyChecker.java2
-rw-r--r--java/com/google/gerrit/gpg/PublicKeyStore.java10
-rw-r--r--java/com/google/gerrit/gpg/SignedPushModule.java4
-rw-r--r--java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java10
-rw-r--r--java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java10
-rw-r--r--java/com/google/gerrit/gpg/server/DeleteGpgKey.java2
-rw-r--r--java/com/google/gerrit/gpg/server/GpgKeys.java16
-rw-r--r--java/com/google/gerrit/gpg/server/PostGpgKeys.java62
-rw-r--r--java/com/google/gerrit/gpg/testing/BUILD2
-rw-r--r--java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java4
-rw-r--r--java/com/google/gerrit/httpd/BUILD7
-rw-r--r--java/com/google/gerrit/httpd/CacheBasedWebSession.java44
-rw-r--r--java/com/google/gerrit/httpd/ContainerAuthFilter.java4
-rw-r--r--java/com/google/gerrit/httpd/DirectChangeByCommit.java6
-rw-r--r--java/com/google/gerrit/httpd/GitOverHttpModule.java2
-rw-r--r--java/com/google/gerrit/httpd/GitOverHttpServlet.java67
-rw-r--r--java/com/google/gerrit/httpd/HttpServletResponseRecorder.java2
-rw-r--r--java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java4
-rw-r--r--java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java44
-rw-r--r--java/com/google/gerrit/httpd/ProjectOAuthFilter.java10
-rw-r--r--java/com/google/gerrit/httpd/RequestMetrics.java10
-rw-r--r--java/com/google/gerrit/httpd/RunAsFilter.java4
-rw-r--r--java/com/google/gerrit/httpd/UrlModule.java10
-rw-r--r--java/com/google/gerrit/httpd/WebSession.java2
-rw-r--r--java/com/google/gerrit/httpd/WebSessionManager.java4
-rw-r--r--java/com/google/gerrit/httpd/XsrfCookieFilter.java8
-rw-r--r--java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java22
-rw-r--r--java/com/google/gerrit/httpd/auth/oauth/BUILD5
-rw-r--r--java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java4
-rw-r--r--java/com/google/gerrit/httpd/auth/openid/BUILD6
-rw-r--r--java/com/google/gerrit/httpd/auth/openid/LoginForm.java4
-rw-r--r--java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java4
-rw-r--r--java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java9
-rw-r--r--java/com/google/gerrit/httpd/gitweb/GitwebServlet.java10
-rw-r--r--java/com/google/gerrit/httpd/init/BUILD3
-rw-r--r--java/com/google/gerrit/httpd/init/WebAppInitializer.java24
-rw-r--r--java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java52
-rw-r--r--java/com/google/gerrit/httpd/raw/CatServlet.java16
-rw-r--r--java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java148
-rw-r--r--java/com/google/gerrit/httpd/raw/IndexServlet.java98
-rw-r--r--java/com/google/gerrit/httpd/raw/StaticModule.java61
-rw-r--r--java/com/google/gerrit/httpd/restapi/RestApiMetrics.java18
-rw-r--r--java/com/google/gerrit/httpd/restapi/RestApiServlet.java587
-rw-r--r--java/com/google/gerrit/index/BUILD5
-rw-r--r--java/com/google/gerrit/index/IndexConfig.java15
-rw-r--r--java/com/google/gerrit/index/IndexType.java59
-rw-r--r--java/com/google/gerrit/index/RefState.java8
-rw-r--r--java/com/google/gerrit/index/Schema.java20
-rw-r--r--java/com/google/gerrit/index/SchemaUtil.java11
-rw-r--r--java/com/google/gerrit/index/project/BUILD2
-rw-r--r--java/com/google/gerrit/index/project/IndexedProjectQuery.java2
-rw-r--r--java/com/google/gerrit/index/project/ProjectData.java2
-rw-r--r--java/com/google/gerrit/index/project/ProjectField.java4
-rw-r--r--java/com/google/gerrit/index/project/ProjectIndex.java2
-rw-r--r--java/com/google/gerrit/index/project/ProjectIndexCollection.java2
-rw-r--r--java/com/google/gerrit/index/project/ProjectIndexer.java2
-rw-r--r--java/com/google/gerrit/index/query/AndSource.java85
-rw-r--r--java/com/google/gerrit/index/query/LazyResultSet.java56
-rw-r--r--java/com/google/gerrit/index/query/ListResultSet.java22
-rw-r--r--java/com/google/gerrit/index/query/QueryProcessor.java8
-rw-r--r--java/com/google/gerrit/index/query/TooManyTermsInQueryException.java29
-rw-r--r--java/com/google/gerrit/index/query/testing/TreeSubject.java15
-rw-r--r--java/com/google/gerrit/jgit/BUILD2
-rw-r--r--java/com/google/gerrit/json/BUILD1
-rw-r--r--java/com/google/gerrit/json/EnumTypeAdapterFactory.java78
-rw-r--r--java/com/google/gerrit/json/OutputFormat.java3
-rw-r--r--java/com/google/gerrit/json/SqlTimestampDeserializer.java10
-rw-r--r--java/com/google/gerrit/lucene/AbstractLuceneIndex.java27
-rw-r--r--java/com/google/gerrit/lucene/BUILD7
-rw-r--r--java/com/google/gerrit/lucene/ChangeSubIndex.java6
-rw-r--r--java/com/google/gerrit/lucene/LuceneAccountIndex.java38
-rw-r--r--java/com/google/gerrit/lucene/LuceneChangeIndex.java86
-rw-r--r--java/com/google/gerrit/lucene/LuceneGroupIndex.java4
-rw-r--r--java/com/google/gerrit/lucene/LuceneProjectIndex.java4
-rw-r--r--java/com/google/gerrit/lucene/QueryBuilder.java65
-rw-r--r--java/com/google/gerrit/lucene/WrappableSearcherManager.java2
-rw-r--r--java/com/google/gerrit/mail/BUILD2
-rw-r--r--java/com/google/gerrit/mail/HtmlParser.java2
-rw-r--r--java/com/google/gerrit/mail/MailComment.java2
-rw-r--r--java/com/google/gerrit/mail/ParserUtil.java2
-rw-r--r--java/com/google/gerrit/mail/TextParser.java2
-rw-r--r--java/com/google/gerrit/metrics/BUILD5
-rw-r--r--java/com/google/gerrit/metrics/DisabledMetricMaker.java6
-rw-r--r--java/com/google/gerrit/metrics/Field.java172
-rw-r--r--java/com/google/gerrit/metrics/Timer0.java7
-rw-r--r--java/com/google/gerrit/metrics/Timer1.java49
-rw-r--r--java/com/google/gerrit/metrics/Timer2.java64
-rw-r--r--java/com/google/gerrit/metrics/Timer3.java85
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java2
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java2
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/CounterImplN.java2
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/GetMetric.java7
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java2
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java2
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/ListMetrics.java5
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/MetricJson.java8
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java5
-rw-r--r--java/com/google/gerrit/metrics/dropwizard/TimerImplN.java9
-rw-r--r--java/com/google/gerrit/metrics/proc/JGitMetricModule.java3
-rw-r--r--java/com/google/gerrit/metrics/proc/ProcMetricModule.java10
-rw-r--r--java/com/google/gerrit/pgm/BUILD16
-rw-r--r--java/com/google/gerrit/pgm/Daemon.java58
-rw-r--r--java/com/google/gerrit/pgm/Init.java3
-rw-r--r--java/com/google/gerrit/pgm/Reindex.java27
-rw-r--r--java/com/google/gerrit/pgm/Rulec.java4
-rw-r--r--java/com/google/gerrit/pgm/http/jetty/BUILD2
-rw-r--r--java/com/google/gerrit/pgm/init/AccountsOnInit.java107
-rw-r--r--java/com/google/gerrit/pgm/init/BUILD4
-rw-r--r--java/com/google/gerrit/pgm/init/BaseInit.java17
-rw-r--r--java/com/google/gerrit/pgm/init/GroupsOnInit.java6
-rw-r--r--java/com/google/gerrit/pgm/init/InitAdminUser.java21
-rw-r--r--java/com/google/gerrit/pgm/init/InitIndex.java18
-rw-r--r--java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java4
-rw-r--r--java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java8
-rw-r--r--java/com/google/gerrit/pgm/init/api/BUILD5
-rw-r--r--java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java2
-rw-r--r--java/com/google/gerrit/pgm/init/api/Section.java4
-rw-r--r--java/com/google/gerrit/pgm/init/api/SequencesOnInit.java4
-rw-r--r--java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java4
-rw-r--r--java/com/google/gerrit/pgm/rules/PrologCompiler.java2
-rw-r--r--java/com/google/gerrit/pgm/util/BUILD4
-rw-r--r--java/com/google/gerrit/pgm/util/BatchProgramModule.java2
-rw-r--r--java/com/google/gerrit/pgm/util/SiteProgram.java9
-rw-r--r--java/com/google/gerrit/prettify/BUILD3
-rw-r--r--java/com/google/gerrit/proto/testing/BUILD1
-rw-r--r--java/com/google/gerrit/proto/testing/SerializedClassSubject.java25
-rw-r--r--java/com/google/gerrit/reviewdb/client/AccountGroupById.java104
-rw-r--r--java/com/google/gerrit/reviewdb/client/AccountGroupByIdAud.java181
-rw-r--r--java/com/google/gerrit/reviewdb/client/AccountGroupMember.java100
-rw-r--r--java/com/google/gerrit/reviewdb/client/AccountGroupMemberAudit.java186
-rw-r--r--java/com/google/gerrit/reviewdb/client/Branch.java115
-rw-r--r--java/com/google/gerrit/reviewdb/client/PatchLineComment.java361
-rw-r--r--java/com/google/gerrit/reviewdb/client/PatchSet.java304
-rw-r--r--java/com/google/gerrit/reviewdb/client/PatchSetApproval.java238
-rw-r--r--java/com/google/gerrit/reviewdb/client/RevId.java73
-rw-r--r--java/com/google/gerrit/reviewdb/client/RobotComment.java99
-rw-r--r--java/com/google/gerrit/reviewdb/client/SubmoduleSubscription.java114
-rw-r--r--java/com/google/gerrit/reviewdb/converter/PatchSetProtoConverter.java93
-rw-r--r--java/com/google/gerrit/reviewdb/converter/RevIdProtoConverter.java38
-rw-r--r--java/com/google/gerrit/server/ApprovalInference.java (renamed from java/com/google/gerrit/server/ApprovalCopier.java)293
-rw-r--r--java/com/google/gerrit/server/ApprovalsUtil.java85
-rw-r--r--java/com/google/gerrit/server/AssigneeStatusUpdate.java35
-rw-r--r--java/com/google/gerrit/server/BUILD17
-rw-r--r--java/com/google/gerrit/server/ChangeMessagesUtil.java13
-rw-r--r--java/com/google/gerrit/server/ChangeUtil.java12
-rw-r--r--java/com/google/gerrit/server/CmdLineParserModule.java8
-rw-r--r--java/com/google/gerrit/server/CommentsUtil.java51
-rw-r--r--java/com/google/gerrit/server/CreateGroupPermissionSyncer.java2
-rw-r--r--java/com/google/gerrit/server/CurrentUser.java2
-rw-r--r--java/com/google/gerrit/server/ExceptionHook.java42
-rw-r--r--java/com/google/gerrit/server/IdentifiedUser.java25
-rw-r--r--java/com/google/gerrit/server/PatchSetUtil.java49
-rw-r--r--java/com/google/gerrit/server/ProjectUtil.java13
-rw-r--r--java/com/google/gerrit/server/PublishCommentUtil.java55
-rw-r--r--java/com/google/gerrit/server/RequestInfo.java96
-rw-r--r--java/com/google/gerrit/server/RequestListener.java22
-rw-r--r--java/com/google/gerrit/server/ReviewerSet.java20
-rw-r--r--java/com/google/gerrit/server/ReviewerStatusUpdate.java2
-rw-r--r--java/com/google/gerrit/server/StarredChangesUtil.java49
-rw-r--r--java/com/google/gerrit/server/TraceRequestListener.java228
-rw-r--r--java/com/google/gerrit/server/WebLinks.java73
-rw-r--r--java/com/google/gerrit/server/account/AbstractGroupBackend.java2
-rw-r--r--java/com/google/gerrit/server/account/AbstractRealm.java4
-rw-r--r--java/com/google/gerrit/server/account/AccountCache.java2
-rw-r--r--java/com/google/gerrit/server/account/AccountCacheImpl.java56
-rw-r--r--java/com/google/gerrit/server/account/AccountConfig.java33
-rw-r--r--java/com/google/gerrit/server/account/AccountControl.java6
-rw-r--r--java/com/google/gerrit/server/account/AccountDeactivator.java10
-rw-r--r--java/com/google/gerrit/server/account/AccountDirectory.java5
-rw-r--r--java/com/google/gerrit/server/account/AccountExternalIdCreator.java2
-rw-r--r--java/com/google/gerrit/server/account/AccountLoader.java10
-rw-r--r--java/com/google/gerrit/server/account/AccountManager.java37
-rw-r--r--java/com/google/gerrit/server/account/AccountProperties.java15
-rw-r--r--java/com/google/gerrit/server/account/AccountResolver.java21
-rw-r--r--java/com/google/gerrit/server/account/AccountResource.java2
-rw-r--r--java/com/google/gerrit/server/account/AccountSshKey.java2
-rw-r--r--java/com/google/gerrit/server/account/AccountState.java228
-rw-r--r--java/com/google/gerrit/server/account/Accounts.java8
-rw-r--r--java/com/google/gerrit/server/account/AccountsConsistencyChecker.java12
-rw-r--r--java/com/google/gerrit/server/account/AccountsUpdate.java22
-rw-r--r--java/com/google/gerrit/server/account/AuthResult.java2
-rw-r--r--java/com/google/gerrit/server/account/AuthorizedKeys.java2
-rw-r--r--java/com/google/gerrit/server/account/CreateGroupArgs.java7
-rw-r--r--java/com/google/gerrit/server/account/DefaultRealm.java2
-rw-r--r--java/com/google/gerrit/server/account/DestinationList.java20
-rw-r--r--java/com/google/gerrit/server/account/Emails.java61
-rw-r--r--java/com/google/gerrit/server/account/FakeRealm.java2
-rw-r--r--java/com/google/gerrit/server/account/GroupBackend.java2
-rw-r--r--java/com/google/gerrit/server/account/GroupCache.java2
-rw-r--r--java/com/google/gerrit/server/account/GroupCacheImpl.java19
-rw-r--r--java/com/google/gerrit/server/account/GroupControl.java4
-rw-r--r--java/com/google/gerrit/server/account/GroupIncludeCache.java4
-rw-r--r--java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java13
-rw-r--r--java/com/google/gerrit/server/account/GroupMembers.java8
-rw-r--r--java/com/google/gerrit/server/account/GroupMembership.java2
-rw-r--r--java/com/google/gerrit/server/account/GroupUUID.java4
-rw-r--r--java/com/google/gerrit/server/account/HashedPassword.java9
-rw-r--r--java/com/google/gerrit/server/account/IncludingGroupMembership.java2
-rw-r--r--java/com/google/gerrit/server/account/InternalAccountDirectory.java54
-rw-r--r--java/com/google/gerrit/server/account/InternalAccountUpdate.java2
-rw-r--r--java/com/google/gerrit/server/account/InternalGroupBackend.java2
-rw-r--r--java/com/google/gerrit/server/account/ListGroupMembership.java2
-rw-r--r--java/com/google/gerrit/server/account/Preferences.java843
-rw-r--r--java/com/google/gerrit/server/account/ProjectWatches.java6
-rw-r--r--java/com/google/gerrit/server/account/Realm.java2
-rw-r--r--java/com/google/gerrit/server/account/SetInactiveFlag.java30
-rw-r--r--java/com/google/gerrit/server/account/StoredPreferences.java574
-rw-r--r--java/com/google/gerrit/server/account/UniversalGroupBackend.java4
-rw-r--r--java/com/google/gerrit/server/account/VersionedAccountDestinations.java4
-rw-r--r--java/com/google/gerrit/server/account/VersionedAccountQueries.java26
-rw-r--r--java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java4
-rw-r--r--java/com/google/gerrit/server/account/externalids/AllExternalIds.java4
-rw-r--r--java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java2
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalId.java12
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdCache.java2
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java25
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java277
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdModule.java6
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java27
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdReader.java2
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIds.java2
-rw-r--r--java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java3
-rw-r--r--java/com/google/gerrit/server/account/externalids/PasswordVerifier.java53
-rw-r--r--java/com/google/gerrit/server/account/externalids/testing/BUILD13
-rw-r--r--java/com/google/gerrit/server/account/externalids/testing/ExternalIdInserter.java25
-rw-r--r--java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java163
-rw-r--r--java/com/google/gerrit/server/api/BUILD4
-rw-r--r--java/com/google/gerrit/server/api/accounts/AccountApiImpl.java42
-rw-r--r--java/com/google/gerrit/server/api/accounts/AccountsImpl.java4
-rw-r--r--java/com/google/gerrit/server/api/accounts/EmailApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/ChangeApiImpl.java30
-rw-r--r--java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/ChangesImpl.java6
-rw-r--r--java/com/google/gerrit/server/api/changes/CommentApiImpl.java4
-rw-r--r--java/com/google/gerrit/server/api/changes/DraftApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/FileApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/RevisionApiImpl.java54
-rw-r--r--java/com/google/gerrit/server/api/changes/RevisionReviewerApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java2
-rw-r--r--java/com/google/gerrit/server/api/config/ServerImpl.java24
-rw-r--r--java/com/google/gerrit/server/api/groups/GroupApiImpl.java30
-rw-r--r--java/com/google/gerrit/server/api/groups/GroupsImpl.java8
-rw-r--r--java/com/google/gerrit/server/api/plugins/PluginApiImpl.java8
-rw-r--r--java/com/google/gerrit/server/api/plugins/PluginsImpl.java13
-rw-r--r--java/com/google/gerrit/server/api/projects/BranchApiImpl.java10
-rw-r--r--java/com/google/gerrit/server/api/projects/ChildProjectApiImpl.java10
-rw-r--r--java/com/google/gerrit/server/api/projects/CommitApiImpl.java6
-rw-r--r--java/com/google/gerrit/server/api/projects/DashboardApiImpl.java4
-rw-r--r--java/com/google/gerrit/server/api/projects/ProjectApiImpl.java54
-rw-r--r--java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java4
-rw-r--r--java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java4
-rw-r--r--java/com/google/gerrit/server/args4j/AccountIdHandler.java4
-rw-r--r--java/com/google/gerrit/server/args4j/ChangeIdHandler.java10
-rw-r--r--java/com/google/gerrit/server/args4j/PatchSetIdHandler.java2
-rw-r--r--java/com/google/gerrit/server/args4j/ProjectHandler.java4
-rw-r--r--java/com/google/gerrit/server/audit/AuditService.java4
-rw-r--r--java/com/google/gerrit/server/audit/BUILD12
-rw-r--r--java/com/google/gerrit/server/audit/group/GroupAuditEvent.java10
-rw-r--r--java/com/google/gerrit/server/audit/group/GroupMemberAuditEvent.java4
-rw-r--r--java/com/google/gerrit/server/audit/group/GroupSubgroupAuditEvent.java4
-rw-r--r--java/com/google/gerrit/server/auth/InternalAuthBackend.java5
-rw-r--r--java/com/google/gerrit/server/auth/ldap/Helper.java56
-rw-r--r--java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java11
-rw-r--r--java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java2
-rw-r--r--java/com/google/gerrit/server/auth/ldap/LdapModule.java4
-rw-r--r--java/com/google/gerrit/server/auth/ldap/LdapQuery.java8
-rw-r--r--java/com/google/gerrit/server/auth/ldap/LdapRealm.java19
-rw-r--r--java/com/google/gerrit/server/auth/oauth/OAuthRealm.java2
-rw-r--r--java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java9
-rw-r--r--java/com/google/gerrit/server/cache/CacheMetrics.java4
-rw-r--r--java/com/google/gerrit/server/cache/h2/BUILD2
-rw-r--r--java/com/google/gerrit/server/cache/h2/H2CacheImpl.java5
-rw-r--r--java/com/google/gerrit/server/cache/mem/BUILD2
-rw-r--r--java/com/google/gerrit/server/cache/serialize/BUILD4
-rw-r--r--java/com/google/gerrit/server/cache/serialize/CacheSerializer.java23
-rw-r--r--java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java38
-rw-r--r--java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java6
-rw-r--r--java/com/google/gerrit/server/cache/serialize/ObjectIdConverter.java9
-rw-r--r--java/com/google/gerrit/server/change/AbandonOp.java8
-rw-r--r--java/com/google/gerrit/server/change/AbandonUtil.java19
-rw-r--r--java/com/google/gerrit/server/change/AccountPatchReviewStore.java9
-rw-r--r--java/com/google/gerrit/server/change/AddReviewersEmail.java4
-rw-r--r--java/com/google/gerrit/server/change/AddReviewersOp.java28
-rw-r--r--java/com/google/gerrit/server/change/ArchiveFormat.java2
-rw-r--r--java/com/google/gerrit/server/change/BatchAbandon.java2
-rw-r--r--java/com/google/gerrit/server/change/ChangeETagComputation.java63
-rw-r--r--java/com/google/gerrit/server/change/ChangeFinder.java16
-rw-r--r--java/com/google/gerrit/server/change/ChangeInserter.java34
-rw-r--r--java/com/google/gerrit/server/change/ChangeJson.java41
-rw-r--r--java/com/google/gerrit/server/change/ChangeKeyAdapter.java51
-rw-r--r--java/com/google/gerrit/server/change/ChangeKindCache.java6
-rw-r--r--java/com/google/gerrit/server/change/ChangeKindCacheImpl.java30
-rw-r--r--java/com/google/gerrit/server/change/ChangeMessageResource.java2
-rw-r--r--java/com/google/gerrit/server/change/ChangeResource.java52
-rw-r--r--java/com/google/gerrit/server/change/ChangeTriplet.java16
-rw-r--r--java/com/google/gerrit/server/change/CommentResource.java6
-rw-r--r--java/com/google/gerrit/server/change/ConsistencyChecker.java73
-rw-r--r--java/com/google/gerrit/server/change/DeleteChangeOp.java61
-rw-r--r--java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java8
-rw-r--r--java/com/google/gerrit/server/change/DeleteReviewerOp.java39
-rw-r--r--java/com/google/gerrit/server/change/DraftCommentResource.java6
-rw-r--r--java/com/google/gerrit/server/change/EmailReviewComments.java8
-rw-r--r--java/com/google/gerrit/server/change/FileContentUtil.java2
-rw-r--r--java/com/google/gerrit/server/change/FileInfoJson.java24
-rw-r--r--java/com/google/gerrit/server/change/FileResource.java6
-rw-r--r--java/com/google/gerrit/server/change/FixResource.java2
-rw-r--r--java/com/google/gerrit/server/change/IncludedIn.java52
-rw-r--r--java/com/google/gerrit/server/change/IncludedInResolver.java17
-rw-r--r--java/com/google/gerrit/server/change/LabelNormalizer.java31
-rw-r--r--java/com/google/gerrit/server/change/LabelsJson.java40
-rw-r--r--java/com/google/gerrit/server/change/MergeabilityCache.java6
-rw-r--r--java/com/google/gerrit/server/change/MergeabilityCacheImpl.java4
-rw-r--r--java/com/google/gerrit/server/change/NotifyResolver.java4
-rw-r--r--java/com/google/gerrit/server/change/PatchSetInserter.java14
-rw-r--r--java/com/google/gerrit/server/change/PureRevert.java6
-rw-r--r--java/com/google/gerrit/server/change/RebaseChangeOp.java17
-rw-r--r--java/com/google/gerrit/server/change/RebaseUtil.java50
-rw-r--r--java/com/google/gerrit/server/change/ReviewerAdder.java44
-rw-r--r--java/com/google/gerrit/server/change/ReviewerJson.java10
-rw-r--r--java/com/google/gerrit/server/change/ReviewerResource.java4
-rw-r--r--java/com/google/gerrit/server/change/ReviewerSuggestion.java11
-rw-r--r--java/com/google/gerrit/server/change/RevisionJson.java42
-rw-r--r--java/com/google/gerrit/server/change/RevisionResource.java30
-rw-r--r--java/com/google/gerrit/server/change/RobotCommentResource.java6
-rw-r--r--java/com/google/gerrit/server/change/SetAssigneeOp.java4
-rw-r--r--java/com/google/gerrit/server/change/SetHashtagsOp.java4
-rw-r--r--java/com/google/gerrit/server/change/SetPrivateOp.java6
-rw-r--r--java/com/google/gerrit/server/change/SuggestedReviewer.java2
-rw-r--r--java/com/google/gerrit/server/change/WalkSorter.java14
-rw-r--r--java/com/google/gerrit/server/change/WorkInProgressOp.java6
-rw-r--r--java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java30
-rw-r--r--java/com/google/gerrit/server/config/AllProjectsName.java2
-rw-r--r--java/com/google/gerrit/server/config/AllUsersName.java2
-rw-r--r--java/com/google/gerrit/server/config/DownloadConfig.java2
-rw-r--r--java/com/google/gerrit/server/config/GerritGlobalModule.java22
-rw-r--r--java/com/google/gerrit/server/config/GerritIsReplica.java25
-rw-r--r--java/com/google/gerrit/server/config/GerritIsReplicaProvider.java46
-rw-r--r--java/com/google/gerrit/server/config/GerritServerConfigModule.java3
-rw-r--r--java/com/google/gerrit/server/config/GroupSetProvider.java2
-rw-r--r--java/com/google/gerrit/server/config/PluginConfigFactory.java2
-rw-r--r--java/com/google/gerrit/server/config/ProjectConfigEntry.java6
-rw-r--r--java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java2
-rw-r--r--java/com/google/gerrit/server/config/RepositoryConfig.java2
-rw-r--r--java/com/google/gerrit/server/config/SitePaths.java4
-rw-r--r--java/com/google/gerrit/server/config/UrlFormatter.java4
-rw-r--r--java/com/google/gerrit/server/data/ChangeAttribute.java2
-rw-r--r--java/com/google/gerrit/server/data/PatchAttribute.java2
-rw-r--r--java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java3
-rw-r--r--java/com/google/gerrit/server/edit/ChangeEdit.java4
-rw-r--r--java/com/google/gerrit/server/edit/ChangeEditJson.java4
-rw-r--r--java/com/google/gerrit/server/edit/ChangeEditModifier.java34
-rw-r--r--java/com/google/gerrit/server/edit/ChangeEditUtil.java21
-rw-r--r--java/com/google/gerrit/server/events/AssigneeChangedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ChangeAbandonedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ChangeDeletedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ChangeEvent.java8
-rw-r--r--java/com/google/gerrit/server/events/ChangeMergedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ChangeRestoredEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/CommentAddedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/CommitReceivedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/EventBroker.java23
-rw-r--r--java/com/google/gerrit/server/events/EventDispatcher.java8
-rw-r--r--java/com/google/gerrit/server/events/EventFactory.java101
-rw-r--r--java/com/google/gerrit/server/events/EventGson.java28
-rw-r--r--java/com/google/gerrit/server/events/EventGsonProvider.java36
-rw-r--r--java/com/google/gerrit/server/events/EventsMetrics.java3
-rw-r--r--java/com/google/gerrit/server/events/HashtagsChangedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/PatchSetCreatedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/PatchSetEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/PrivateStateChangedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ProjectCreatedEvent.java4
-rw-r--r--java/com/google/gerrit/server/events/ProjectEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ProjectNameKeyAdapter.java (renamed from java/com/google/gerrit/server/events/ProjectNameKeySerializer.java)18
-rw-r--r--java/com/google/gerrit/server/events/RefEvent.java6
-rw-r--r--java/com/google/gerrit/server/events/RefReceivedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/RefUpdatedEvent.java4
-rw-r--r--java/com/google/gerrit/server/events/ReviewerAddedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/ReviewerDeletedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/StreamEventsApiListener.java14
-rw-r--r--java/com/google/gerrit/server/events/TopicChangedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/VoteDeletedEvent.java2
-rw-r--r--java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/AssigneeChanged.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/ChangeDeleted.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/ChangeMerged.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/ChangeRestored.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/ChangeReverted.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/CommentAdded.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/EventUtil.java69
-rw-r--r--java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/HashtagsEdited.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/ReviewerAdded.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/RevisionCreated.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/TopicEdited.java2
-rw-r--r--java/com/google/gerrit/server/extensions/events/VoteDeleted.java4
-rw-r--r--java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java4
-rw-r--r--java/com/google/gerrit/server/extensions/webui/UiActions.java7
-rw-r--r--java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java4
-rw-r--r--java/com/google/gerrit/server/git/BanCommit.java6
-rw-r--r--java/com/google/gerrit/server/git/BranchOrderSection.java2
-rw-r--r--java/com/google/gerrit/server/git/ChangeMessageModifier.java4
-rw-r--r--java/com/google/gerrit/server/git/ChangeReportFormatter.java2
-rw-r--r--java/com/google/gerrit/server/git/CodeReviewCommit.java6
-rw-r--r--java/com/google/gerrit/server/git/CommitUtil.java114
-rw-r--r--java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java58
-rw-r--r--java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java2
-rw-r--r--java/com/google/gerrit/server/git/DelegateRefDatabase.java86
-rw-r--r--java/com/google/gerrit/server/git/DelegateRepository.java89
-rw-r--r--java/com/google/gerrit/server/git/GarbageCollection.java2
-rw-r--r--java/com/google/gerrit/server/git/GarbageCollectionQueue.java2
-rw-r--r--java/com/google/gerrit/server/git/GitRepositoryManager.java2
-rw-r--r--java/com/google/gerrit/server/git/GroupCollector.java12
-rw-r--r--java/com/google/gerrit/server/git/HookUtil.java33
-rw-r--r--java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java6
-rw-r--r--java/com/google/gerrit/server/git/MergeUtil.java121
-rw-r--r--java/com/google/gerrit/server/git/MergedByPushOp.java29
-rw-r--r--java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java2
-rw-r--r--java/com/google/gerrit/server/git/MultiProgressMonitor.java2
-rw-r--r--java/com/google/gerrit/server/git/NotesBranchUtil.java2
-rw-r--r--java/com/google/gerrit/server/git/NotifyConfig.java10
-rw-r--r--java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java185
-rw-r--r--java/com/google/gerrit/server/git/PermissionAwareRepository.java39
-rw-r--r--java/com/google/gerrit/server/git/PermissionAwareRepositoryManager.java32
-rw-r--r--java/com/google/gerrit/server/git/ProjectRunnable.java2
-rw-r--r--java/com/google/gerrit/server/git/PureRevertCache.java22
-rw-r--r--java/com/google/gerrit/server/git/ReceivePackInitializer.java2
-rw-r--r--java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java2
-rw-r--r--java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java13
-rw-r--r--java/com/google/gerrit/server/git/SystemReaderInstaller.java58
-rw-r--r--java/com/google/gerrit/server/git/TagCache.java2
-rw-r--r--java/com/google/gerrit/server/git/TagSet.java8
-rw-r--r--java/com/google/gerrit/server/git/TagSetHolder.java4
-rw-r--r--java/com/google/gerrit/server/git/TracingHook.java96
-rw-r--r--java/com/google/gerrit/server/git/UploadPackInitializer.java2
-rw-r--r--java/com/google/gerrit/server/git/UploadPackMetricsHook.java14
-rw-r--r--java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java93
-rw-r--r--java/com/google/gerrit/server/git/WorkQueue.java2
-rw-r--r--java/com/google/gerrit/server/git/meta/MetaDataUpdate.java2
-rw-r--r--java/com/google/gerrit/server/git/meta/VersionedMetaData.java37
-rw-r--r--java/com/google/gerrit/server/git/receive/AllRefsWatcher.java4
-rw-r--r--java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java54
-rw-r--r--java/com/google/gerrit/server/git/receive/BUILD5
-rw-r--r--java/com/google/gerrit/server/git/receive/BranchCommitValidator.java123
-rw-r--r--java/com/google/gerrit/server/git/receive/HackPushNegotiateHook.java36
-rw-r--r--java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java22
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveCommits.java3194
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java86
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java91
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveConfig.java2
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveConstants.java4
-rw-r--r--java/com/google/gerrit/server/git/receive/ReceiveRefFilter.java4
-rw-r--r--java/com/google/gerrit/server/git/receive/ReplaceOp.java40
-rw-r--r--java/com/google/gerrit/server/git/receive/ResultChangeIds.java2
-rw-r--r--java/com/google/gerrit/server/git/receive/testing/BUILD14
-rw-r--r--java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java87
-rw-r--r--java/com/google/gerrit/server/git/validators/AccountValidator.java6
-rw-r--r--java/com/google/gerrit/server/git/validators/CommitValidators.java42
-rw-r--r--java/com/google/gerrit/server/git/validators/MergeValidationListener.java6
-rw-r--r--java/com/google/gerrit/server/git/validators/MergeValidators.java29
-rw-r--r--java/com/google/gerrit/server/git/validators/OnSubmitValidationListener.java2
-rw-r--r--java/com/google/gerrit/server/git/validators/OnSubmitValidators.java2
-rw-r--r--java/com/google/gerrit/server/git/validators/RefOperationValidationException.java17
-rw-r--r--java/com/google/gerrit/server/git/validators/RefOperationValidators.java25
-rw-r--r--java/com/google/gerrit/server/git/validators/UploadValidationListener.java2
-rw-r--r--java/com/google/gerrit/server/git/validators/UploadValidators.java2
-rw-r--r--java/com/google/gerrit/server/group/GroupAuditService.java4
-rw-r--r--java/com/google/gerrit/server/group/GroupResolver.java4
-rw-r--r--java/com/google/gerrit/server/group/InternalGroup.java4
-rw-r--r--java/com/google/gerrit/server/group/InternalGroupDescription.java4
-rw-r--r--java/com/google/gerrit/server/group/PeriodicGroupIndexer.java2
-rw-r--r--java/com/google/gerrit/server/group/SubgroupResource.java2
-rw-r--r--java/com/google/gerrit/server/group/SystemGroupBackend.java10
-rw-r--r--java/com/google/gerrit/server/group/db/AuditLogFormatter.java10
-rw-r--r--java/com/google/gerrit/server/group/db/AuditLogReader.java76
-rw-r--r--java/com/google/gerrit/server/group/db/GroupConfig.java12
-rw-r--r--java/com/google/gerrit/server/group/db/GroupConfigEntry.java8
-rw-r--r--java/com/google/gerrit/server/group/db/GroupNameNotes.java13
-rw-r--r--java/com/google/gerrit/server/group/db/Groups.java8
-rw-r--r--java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java4
-rw-r--r--java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java6
-rw-r--r--java/com/google/gerrit/server/group/db/GroupsUpdate.java16
-rw-r--r--java/com/google/gerrit/server/group/db/InternalGroupCreation.java2
-rw-r--r--java/com/google/gerrit/server/group/db/InternalGroupUpdate.java4
-rw-r--r--java/com/google/gerrit/server/group/db/RenameGroupOp.java4
-rw-r--r--java/com/google/gerrit/server/group/db/testing/BUILD4
-rw-r--r--java/com/google/gerrit/server/group/testing/BUILD4
-rw-r--r--java/com/google/gerrit/server/group/testing/InternalGroupSubject.java57
-rw-r--r--java/com/google/gerrit/server/group/testing/TestGroupBackend.java4
-rw-r--r--java/com/google/gerrit/server/index/AbstractIndexModule.java24
-rw-r--r--java/com/google/gerrit/server/index/IndexModule.java22
-rw-r--r--java/com/google/gerrit/server/index/IndexUtils.java22
-rw-r--r--java/com/google/gerrit/server/index/account/AccountField.java53
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndex.java4
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndexCollection.java2
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndexDefinition.java2
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndexRewriter.java35
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndexer.java2
-rw-r--r--java/com/google/gerrit/server/index/account/AccountIndexerImpl.java15
-rw-r--r--java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java13
-rw-r--r--java/com/google/gerrit/server/index/account/AllAccountsIndexer.java2
-rw-r--r--java/com/google/gerrit/server/index/account/IndexedAccountQuery.java2
-rw-r--r--java/com/google/gerrit/server/index/account/StalenessChecker.java26
-rw-r--r--java/com/google/gerrit/server/index/change/AllChangesIndexer.java6
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeField.java54
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeIndex.java7
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeIndexCollection.java2
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java2
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java16
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeIndexer.java54
-rw-r--r--java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java72
-rw-r--r--java/com/google/gerrit/server/index/change/DummyChangeIndex.java2
-rw-r--r--java/com/google/gerrit/server/index/change/IndexedChangeQuery.java2
-rw-r--r--java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java82
-rw-r--r--java/com/google/gerrit/server/index/change/StalenessChecker.java7
-rw-r--r--java/com/google/gerrit/server/index/group/AllGroupsIndexer.java2
-rw-r--r--java/com/google/gerrit/server/index/group/GroupField.java8
-rw-r--r--java/com/google/gerrit/server/index/group/GroupIndex.java2
-rw-r--r--java/com/google/gerrit/server/index/group/GroupIndexCollection.java2
-rw-r--r--java/com/google/gerrit/server/index/group/GroupIndexDefinition.java2
-rw-r--r--java/com/google/gerrit/server/index/group/GroupIndexer.java2
-rw-r--r--java/com/google/gerrit/server/index/group/GroupIndexerImpl.java15
-rw-r--r--java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java6
-rw-r--r--java/com/google/gerrit/server/index/group/IndexedGroupQuery.java2
-rw-r--r--java/com/google/gerrit/server/index/group/StalenessChecker.java4
-rw-r--r--java/com/google/gerrit/server/index/project/AllProjectsIndexer.java2
-rw-r--r--java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java2
-rw-r--r--java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java17
-rw-r--r--java/com/google/gerrit/server/index/project/StalenessChecker.java4
-rw-r--r--java/com/google/gerrit/server/ioutil/BUILD6
-rw-r--r--java/com/google/gerrit/server/ioutil/BasicSerialization.java2
-rw-r--r--java/com/google/gerrit/server/logging/BUILD3
-rw-r--r--java/com/google/gerrit/server/logging/CallerFinder.java41
-rw-r--r--java/com/google/gerrit/server/logging/LoggingContext.java147
-rw-r--r--java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java39
-rw-r--r--java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java39
-rw-r--r--java/com/google/gerrit/server/logging/Metadata.java339
-rw-r--r--java/com/google/gerrit/server/logging/MutablePerformanceLogRecords.java55
-rw-r--r--java/com/google/gerrit/server/logging/MutableTags.java7
-rw-r--r--java/com/google/gerrit/server/logging/PerformanceLogContext.java117
-rw-r--r--java/com/google/gerrit/server/logging/PerformanceLogRecord.java64
-rw-r--r--java/com/google/gerrit/server/logging/PerformanceLogger.java50
-rw-r--r--java/com/google/gerrit/server/logging/PluginMetadata.java39
-rw-r--r--java/com/google/gerrit/server/logging/TraceContext.java132
-rw-r--r--java/com/google/gerrit/server/mail/EmailTokenVerifier.java2
-rw-r--r--java/com/google/gerrit/server/mail/MailUtil.java4
-rw-r--r--java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java2
-rw-r--r--java/com/google/gerrit/server/mail/receive/MailProcessor.java64
-rw-r--r--java/com/google/gerrit/server/mail/send/AbandonedSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/AddKeySender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/AddReviewerSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/ChangeEmail.java49
-rw-r--r--java/com/google/gerrit/server/mail/send/CommentSender.java32
-rw-r--r--java/com/google/gerrit/server/mail/send/CreateChangeSender.java14
-rw-r--r--java/com/google/gerrit/server/mail/send/DeleteKeySender.java12
-rw-r--r--java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java12
-rw-r--r--java/com/google/gerrit/server/mail/send/DeleteVoteSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/EmailArguments.java8
-rw-r--r--java/com/google/gerrit/server/mail/send/FromAddressGenerator.java2
-rw-r--r--java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java13
-rw-r--r--java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java6
-rw-r--r--java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java (renamed from java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java)31
-rw-r--r--java/com/google/gerrit/server/mail/send/MailSoyTemplateProvider.java43
-rw-r--r--java/com/google/gerrit/server/mail/send/MergedSender.java28
-rw-r--r--java/com/google/gerrit/server/mail/send/NewChangeSender.java6
-rw-r--r--java/com/google/gerrit/server/mail/send/NotificationEmail.java20
-rw-r--r--java/com/google/gerrit/server/mail/send/OutgoingEmail.java78
-rw-r--r--java/com/google/gerrit/server/mail/send/ProjectWatch.java50
-rw-r--r--java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java12
-rw-r--r--java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java12
-rw-r--r--java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java8
-rw-r--r--java/com/google/gerrit/server/mail/send/RestoredSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/RevertedSender.java10
-rw-r--r--java/com/google/gerrit/server/mail/send/SetAssigneeSender.java14
-rw-r--r--java/com/google/gerrit/server/notedb/AbstractChangeNotes.java16
-rw-r--r--java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java22
-rw-r--r--java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java116
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java110
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNoteUtil.java17
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNotes.java101
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNotesCache.java36
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNotesParser.java320
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNotesState.java152
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeRevisionNote.java61
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeUpdate.java64
-rw-r--r--java/com/google/gerrit/server/notedb/DeleteChangeMessageRewriter.java8
-rw-r--r--java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java47
-rw-r--r--java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java2
-rw-r--r--java/com/google/gerrit/server/notedb/DraftCommentNotes.java27
-rw-r--r--java/com/google/gerrit/server/notedb/IntBlob.java2
-rw-r--r--java/com/google/gerrit/server/notedb/InvalidServerIdException.java25
-rw-r--r--java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java401
-rw-r--r--java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java189
-rw-r--r--java/com/google/gerrit/server/notedb/NoteDbMetrics.java12
-rw-r--r--java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java233
-rw-r--r--java/com/google/gerrit/server/notedb/NoteDbUtil.java84
-rw-r--r--java/com/google/gerrit/server/notedb/OpenRepo.java176
-rw-r--r--java/com/google/gerrit/server/notedb/RepoSequence.java148
-rw-r--r--java/com/google/gerrit/server/notedb/ReviewerStateInternal.java16
-rw-r--r--java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java17
-rw-r--r--java/com/google/gerrit/server/notedb/RevisionNoteData.java2
-rw-r--r--java/com/google/gerrit/server/notedb/RevisionNoteMap.java29
-rw-r--r--java/com/google/gerrit/server/notedb/RobotCommentNotes.java17
-rw-r--r--java/com/google/gerrit/server/notedb/RobotCommentUpdate.java23
-rw-r--r--java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java2
-rw-r--r--java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java2
-rw-r--r--java/com/google/gerrit/server/notedb/Sequences.java18
-rw-r--r--java/com/google/gerrit/server/notedb/TooManyUpdatesException.java41
-rw-r--r--java/com/google/gerrit/server/patch/AutoMerger.java2
-rw-r--r--java/com/google/gerrit/server/patch/DiffSummaryLoader.java4
-rw-r--r--java/com/google/gerrit/server/patch/IntraLineDiff.java2
-rw-r--r--java/com/google/gerrit/server/patch/IntraLineDiffArgs.java2
-rw-r--r--java/com/google/gerrit/server/patch/PatchFile.java11
-rw-r--r--java/com/google/gerrit/server/patch/PatchList.java7
-rw-r--r--java/com/google/gerrit/server/patch/PatchListCache.java6
-rw-r--r--java/com/google/gerrit/server/patch/PatchListCacheImpl.java11
-rw-r--r--java/com/google/gerrit/server/patch/PatchListEntry.java10
-rw-r--r--java/com/google/gerrit/server/patch/PatchListKey.java3
-rw-r--r--java/com/google/gerrit/server/patch/PatchListLoader.java10
-rw-r--r--java/com/google/gerrit/server/patch/PatchScriptBuilder.java21
-rw-r--r--java/com/google/gerrit/server/patch/PatchScriptFactory.java87
-rw-r--r--java/com/google/gerrit/server/patch/PatchSetInfoFactory.java45
-rw-r--r--java/com/google/gerrit/server/patch/Text.java11
-rw-r--r--java/com/google/gerrit/server/permissions/ChangeControl.java20
-rw-r--r--java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java6
-rw-r--r--java/com/google/gerrit/server/permissions/DefaultRefFilter.java140
-rw-r--r--java/com/google/gerrit/server/permissions/FailedPermissionBackend.java2
-rw-r--r--java/com/google/gerrit/server/permissions/PermissionBackend.java16
-rw-r--r--java/com/google/gerrit/server/permissions/PermissionCollection.java4
-rw-r--r--java/com/google/gerrit/server/permissions/ProjectControl.java16
-rw-r--r--java/com/google/gerrit/server/permissions/ProjectPermission.java2
-rw-r--r--java/com/google/gerrit/server/permissions/RefControl.java12
-rw-r--r--java/com/google/gerrit/server/permissions/RefVisibilityControl.java11
-rw-r--r--java/com/google/gerrit/server/plugincontext/PluginContext.java40
-rw-r--r--java/com/google/gerrit/server/plugins/CopyConfigModule.java10
-rw-r--r--java/com/google/gerrit/server/plugins/DisablePlugin.java16
-rw-r--r--java/com/google/gerrit/server/plugins/EnablePlugin.java5
-rw-r--r--java/com/google/gerrit/server/plugins/GetStatus.java5
-rw-r--r--java/com/google/gerrit/server/plugins/ListPlugins.java6
-rw-r--r--java/com/google/gerrit/server/plugins/MandatoryPluginsCollection.java50
-rw-r--r--java/com/google/gerrit/server/plugins/MissingMandatoryPluginsException.java30
-rw-r--r--java/com/google/gerrit/server/plugins/PluginLoader.java94
-rw-r--r--java/com/google/gerrit/server/plugins/PluginModule.java1
-rw-r--r--java/com/google/gerrit/server/plugins/ReloadPlugin.java6
-rw-r--r--java/com/google/gerrit/server/project/AccessControlModule.java2
-rw-r--r--java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java2
-rw-r--r--java/com/google/gerrit/server/project/BranchResource.java6
-rw-r--r--java/com/google/gerrit/server/project/ChildProjects.java2
-rw-r--r--java/com/google/gerrit/server/project/ContributorAgreementsChecker.java10
-rw-r--r--java/com/google/gerrit/server/project/CreateProjectArgs.java6
-rw-r--r--java/com/google/gerrit/server/project/CreateRefControl.java16
-rw-r--r--java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java2
-rw-r--r--java/com/google/gerrit/server/project/GroupList.java6
-rw-r--r--java/com/google/gerrit/server/project/NoSuchChangeException.java2
-rw-r--r--java/com/google/gerrit/server/project/NoSuchProjectException.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectCache.java8
-rw-r--r--java/com/google/gerrit/server/project/ProjectCacheImpl.java15
-rw-r--r--java/com/google/gerrit/server/project/ProjectCacheWarmer.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectConfig.java55
-rw-r--r--java/com/google/gerrit/server/project/ProjectCreator.java8
-rw-r--r--java/com/google/gerrit/server/project/ProjectHierarchyIterator.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectJson.java6
-rw-r--r--java/com/google/gerrit/server/project/ProjectLevelConfig.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectNameLockManager.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectResource.java2
-rw-r--r--java/com/google/gerrit/server/project/ProjectState.java28
-rw-r--r--java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java11
-rw-r--r--java/com/google/gerrit/server/project/Reachable.java2
-rw-r--r--java/com/google/gerrit/server/project/RefFilter.java78
-rw-r--r--java/com/google/gerrit/server/project/RefPatternMatcher.java4
-rw-r--r--java/com/google/gerrit/server/project/RefUtil.java4
-rw-r--r--java/com/google/gerrit/server/project/RefValidationHelper.java4
-rw-r--r--java/com/google/gerrit/server/project/RemoveReviewerControl.java10
-rw-r--r--java/com/google/gerrit/server/project/SectionMatcher.java2
-rw-r--r--java/com/google/gerrit/server/project/SubmitRuleEvaluator.java118
-rw-r--r--java/com/google/gerrit/server/project/SubmitRuleOptions.java21
-rw-r--r--java/com/google/gerrit/server/project/SuggestParentCandidates.java2
-rw-r--r--java/com/google/gerrit/server/project/testing/BUILD6
-rw-r--r--java/com/google/gerrit/server/project/testing/TestLabels.java53
-rw-r--r--java/com/google/gerrit/server/project/testing/Util.java239
-rw-r--r--java/com/google/gerrit/server/query/account/AccountPredicates.java12
-rw-r--r--java/com/google/gerrit/server/query/account/AccountQueryBuilder.java4
-rw-r--r--java/com/google/gerrit/server/query/account/AccountQueryProcessor.java2
-rw-r--r--java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/account/InternalAccountQuery.java25
-rw-r--r--java/com/google/gerrit/server/query/change/AgePredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/AssigneePredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/ChangeData.java56
-rw-r--r--java/com/google/gerrit/server/query/change/ChangeIdPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java53
-rw-r--r--java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/CommentByPredicate.java6
-rw-r--r--java/com/google/gerrit/server/query/change/CommentPredicate.java11
-rw-r--r--java/com/google/gerrit/server/query/change/CommitPredicate.java14
-rw-r--r--java/com/google/gerrit/server/query/change/ConflictsPredicate.java22
-rw-r--r--java/com/google/gerrit/server/query/change/DestinationPredicate.java8
-rw-r--r--java/com/google/gerrit/server/query/change/EditByPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java12
-rw-r--r--java/com/google/gerrit/server/query/change/ExactTopicPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java7
-rw-r--r--java/com/google/gerrit/server/query/change/GroupPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/HasDraftByPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/HasStarsPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/InternalChangeQuery.java63
-rw-r--r--java/com/google/gerrit/server/query/change/IsReviewedPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/LabelPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/LegacyChangeIdStrPredicate.java (renamed from java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java)18
-rw-r--r--java/com/google/gerrit/server/query/change/MessagePredicate.java7
-rw-r--r--java/com/google/gerrit/server/query/change/OrSource.java45
-rw-r--r--java/com/google/gerrit/server/query/change/OutputStreamQuery.java4
-rw-r--r--java/com/google/gerrit/server/query/change/OwnerPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/OwnerinPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/ParentProjectPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/ProjectPredicate.java8
-rw-r--r--java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/RefPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/RegexProjectPredicate.java6
-rw-r--r--java/com/google/gerrit/server/query/change/RegexRefPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/RegexTopicPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/ReviewerPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/ReviewerinPredicate.java4
-rw-r--r--java/com/google/gerrit/server/query/change/SingleGroupUser.java2
-rw-r--r--java/com/google/gerrit/server/query/change/StarPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java2
-rw-r--r--java/com/google/gerrit/server/query/group/GroupPredicates.java4
-rw-r--r--java/com/google/gerrit/server/query/group/GroupQueryBuilder.java8
-rw-r--r--java/com/google/gerrit/server/query/group/InternalGroupQuery.java4
-rw-r--r--java/com/google/gerrit/server/query/project/ProjectPredicates.java2
-rw-r--r--java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java6
-rw-r--r--java/com/google/gerrit/server/quota/DefaultQuotaBackend.java6
-rw-r--r--java/com/google/gerrit/server/quota/QuotaBackend.java6
-rw-r--r--java/com/google/gerrit/server/quota/QuotaRequestContext.java6
-rw-r--r--java/com/google/gerrit/server/restapi/BUILD5
-rw-r--r--java/com/google/gerrit/server/restapi/access/ListAccess.java16
-rw-r--r--java/com/google/gerrit/server/restapi/account/AddSshKey.java2
-rw-r--r--java/com/google/gerrit/server/restapi/account/CreateAccount.java25
-rw-r--r--java/com/google/gerrit/server/restapi/account/CreateEmail.java6
-rw-r--r--java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java20
-rw-r--r--java/com/google/gerrit/server/restapi/account/DeleteEmail.java2
-rw-r--r--java/com/google/gerrit/server/restapi/account/DeleteSshKey.java2
-rw-r--r--java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java6
-rw-r--r--java/com/google/gerrit/server/restapi/account/EmailsCollection.java2
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetAccount.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetAgreements.java7
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetAvatarChangeUrl.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetCapabilities.java13
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetDetail.java11
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetDiffPreferences.java14
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetEditPreferences.java14
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetEmail.java7
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetEmails.java16
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetExternalIds.java7
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetGroups.java10
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetName.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetOAuthToken.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetPreferences.java9
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetSshKey.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetSshKeys.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetStatus.java5
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetUsername.java6
-rw-r--r--java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java18
-rw-r--r--java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java3
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutAgreement.java4
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutHttpPassword.java4
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutName.java6
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutPreferred.java6
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutStatus.java4
-rw-r--r--java/com/google/gerrit/server/restapi/account/PutUsername.java24
-rw-r--r--java/com/google/gerrit/server/restapi/account/QueryAccounts.java13
-rw-r--r--java/com/google/gerrit/server/restapi/account/SetDiffPreferences.java16
-rw-r--r--java/com/google/gerrit/server/restapi/account/SetEditPreferences.java16
-rw-r--r--java/com/google/gerrit/server/restapi/account/SetPreferences.java20
-rw-r--r--java/com/google/gerrit/server/restapi/account/Stars.java36
-rw-r--r--java/com/google/gerrit/server/restapi/change/Abandon.java7
-rw-r--r--java/com/google/gerrit/server/restapi/change/ApplyFix.java8
-rw-r--r--java/com/google/gerrit/server/restapi/change/ChangeEdits.java41
-rw-r--r--java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java9
-rw-r--r--java/com/google/gerrit/server/restapi/change/ChangeMessages.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/ChangesCollection.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/CherryPick.java15
-rw-r--r--java/com/google/gerrit/server/restapi/change/CherryPickChange.java166
-rw-r--r--java/com/google/gerrit/server/restapi/change/CherryPickCommit.java19
-rw-r--r--java/com/google/gerrit/server/restapi/change/CommentJson.java8
-rw-r--r--java/com/google/gerrit/server/restapi/change/Comments.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/CreateChange.java63
-rw-r--r--java/com/google/gerrit/server/restapi/change/CreateDraftComment.java18
-rw-r--r--java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java27
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteAssignee.java11
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteChange.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java10
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteComment.java9
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java12
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeletePrivate.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteReviewer.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/DeleteVote.java26
-rw-r--r--java/com/google/gerrit/server/restapi/change/DownloadContent.java12
-rw-r--r--java/com/google/gerrit/server/restapi/change/DraftComments.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/Files.java24
-rw-r--r--java/com/google/gerrit/server/restapi/change/Fixes.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetArchive.java12
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetAssignee.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetBlame.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetChangeMessage.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetComment.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetCommit.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetContent.java43
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetDescription.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetDiff.java26
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetDraftComment.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetMergeList.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetPastAssignees.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetPatch.java20
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetPureRevert.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetRelated.java23
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetReviewer.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetRobotComment.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/GetTopic.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/Index.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListChangeComments.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java7
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListChangeMessages.java8
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java16
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListReviewers.java7
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListRevisionComments.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java18
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java7
-rw-r--r--java/com/google/gerrit/server/restapi/change/ListRobotComments.java18
-rw-r--r--java/com/google/gerrit/server/restapi/change/Mergeable.java29
-rw-r--r--java/com/google/gerrit/server/restapi/change/Move.java53
-rw-r--r--java/com/google/gerrit/server/restapi/change/PostHashtags.java3
-rw-r--r--java/com/google/gerrit/server/restapi/change/PostPrivate.java5
-rw-r--r--java/com/google/gerrit/server/restapi/change/PostReview.java298
-rw-r--r--java/com/google/gerrit/server/restapi/change/PostReviewers.java9
-rw-r--r--java/com/google/gerrit/server/restapi/change/PreviewSubmit.java11
-rw-r--r--java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/PutAssignee.java21
-rw-r--r--java/com/google/gerrit/server/restapi/change/PutDescription.java10
-rw-r--r--java/com/google/gerrit/server/restapi/change/PutDraftComment.java15
-rw-r--r--java/com/google/gerrit/server/restapi/change/PutMessage.java11
-rw-r--r--java/com/google/gerrit/server/restapi/change/PutTopic.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/QueryChanges.java29
-rw-r--r--java/com/google/gerrit/server/restapi/change/Rebase.java38
-rw-r--r--java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java7
-rw-r--r--java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java33
-rw-r--r--java/com/google/gerrit/server/restapi/change/Restore.java13
-rw-r--r--java/com/google/gerrit/server/restapi/change/Revert.java74
-rw-r--r--java/com/google/gerrit/server/restapi/change/Reviewed.java8
-rw-r--r--java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java125
-rw-r--r--java/com/google/gerrit/server/restapi/change/Reviewers.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/ReviewersUtil.java106
-rw-r--r--java/com/google/gerrit/server/restapi/change/RevisionReviewers.java2
-rw-r--r--java/com/google/gerrit/server/restapi/change/Revisions.java33
-rw-r--r--java/com/google/gerrit/server/restapi/change/RobotComments.java4
-rw-r--r--java/com/google/gerrit/server/restapi/change/SetReadyForReview.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java6
-rw-r--r--java/com/google/gerrit/server/restapi/change/Submit.java99
-rw-r--r--java/com/google/gerrit/server/restapi/change/SubmittedTogether.java12
-rw-r--r--java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java46
-rw-r--r--java/com/google/gerrit/server/restapi/change/TestSubmitRule.java46
-rw-r--r--java/com/google/gerrit/server/restapi/change/TestSubmitType.java61
-rw-r--r--java/com/google/gerrit/server/restapi/change/Votes.java10
-rw-r--r--java/com/google/gerrit/server/restapi/config/CheckConsistency.java5
-rw-r--r--java/com/google/gerrit/server/restapi/config/ConfirmEmail.java2
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetCache.java5
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java7
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetEditPreferences.java7
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetPreferences.java7
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetServerInfo.java9
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetSummary.java5
-rw-r--r--java/com/google/gerrit/server/restapi/config/GetTask.java5
-rw-r--r--java/com/google/gerrit/server/restapi/config/IndexChanges.java2
-rw-r--r--java/com/google/gerrit/server/restapi/config/ListCaches.java16
-rw-r--r--java/com/google/gerrit/server/restapi/config/ListCapabilities.java12
-rw-r--r--java/com/google/gerrit/server/restapi/config/ListTasks.java11
-rw-r--r--java/com/google/gerrit/server/restapi/config/ReloadConfig.java16
-rw-r--r--java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java10
-rw-r--r--java/com/google/gerrit/server/restapi/config/SetEditPreferences.java10
-rw-r--r--java/com/google/gerrit/server/restapi/config/SetPreferences.java12
-rw-r--r--java/com/google/gerrit/server/restapi/config/TasksCollection.java2
-rw-r--r--java/com/google/gerrit/server/restapi/group/AddMembers.java27
-rw-r--r--java/com/google/gerrit/server/restapi/group/AddSubgroups.java19
-rw-r--r--java/com/google/gerrit/server/restapi/group/CreateGroup.java31
-rw-r--r--java/com/google/gerrit/server/restapi/group/DeleteMembers.java6
-rw-r--r--java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java2
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetAuditLog.java31
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetDescription.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetDetail.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetGroup.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetMember.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetName.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetOptions.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetOwner.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GetSubgroup.java5
-rw-r--r--java/com/google/gerrit/server/restapi/group/GroupJson.java2
-rw-r--r--java/com/google/gerrit/server/restapi/group/Index.java2
-rw-r--r--java/com/google/gerrit/server/restapi/group/ListGroups.java15
-rw-r--r--java/com/google/gerrit/server/restapi/group/ListMembers.java11
-rw-r--r--java/com/google/gerrit/server/restapi/group/ListSubgroups.java7
-rw-r--r--java/com/google/gerrit/server/restapi/group/PutDescription.java2
-rw-r--r--java/com/google/gerrit/server/restapi/group/PutName.java11
-rw-r--r--java/com/google/gerrit/server/restapi/group/PutOptions.java7
-rw-r--r--java/com/google/gerrit/server/restapi/group/PutOwner.java7
-rw-r--r--java/com/google/gerrit/server/restapi/group/QueryGroups.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/BanCommit.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/BranchesCollection.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/Check.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/CheckAccess.java19
-rw-r--r--java/com/google/gerrit/server/restapi/project/CheckAccessReadView.java3
-rw-r--r--java/com/google/gerrit/server/restapi/project/CheckMergeability.java10
-rw-r--r--java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java9
-rw-r--r--java/com/google/gerrit/server/restapi/project/CommitsCollection.java30
-rw-r--r--java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/CreateAccessChange.java15
-rw-r--r--java/com/google/gerrit/server/restapi/project/CreateBranch.java17
-rw-r--r--java/com/google/gerrit/server/restapi/project/CreateDashboard.java11
-rw-r--r--java/com/google/gerrit/server/restapi/project/CreateProject.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/CreateTag.java6
-rw-r--r--java/com/google/gerrit/server/restapi/project/DashboardsCollection.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/DeleteBranch.java10
-rw-r--r--java/com/google/gerrit/server/restapi/project/DeleteBranches.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/DeleteDashboard.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/DeleteRef.java8
-rw-r--r--java/com/google/gerrit/server/restapi/project/DeleteTag.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java10
-rw-r--r--java/com/google/gerrit/server/restapi/project/GarbageCollect.java6
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetAccess.java17
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetBranch.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetChildProject.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetCommit.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetConfig.java22
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetContent.java6
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetDashboard.java20
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetDescription.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetHead.java7
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetParent.java7
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetProject.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetReflog.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetStatistics.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/GetTag.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/Index.java13
-rw-r--r--java/com/google/gerrit/server/restapi/project/IndexChanges.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/ListBranches.java23
-rw-r--r--java/com/google/gerrit/server/restapi/project/ListChildProjects.java14
-rw-r--r--java/com/google/gerrit/server/restapi/project/ListDashboards.java11
-rw-r--r--java/com/google/gerrit/server/restapi/project/ListProjects.java20
-rw-r--r--java/com/google/gerrit/server/restapi/project/ListTags.java22
-rw-r--r--java/com/google/gerrit/server/restapi/project/ProjectNode.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/ProjectsCollection.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/PutBranch.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/PutConfig.java9
-rw-r--r--java/com/google/gerrit/server/restapi/project/PutDescription.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/PutTag.java4
-rw-r--r--java/com/google/gerrit/server/restapi/project/QueryProjects.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetAccess.java17
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetAccessUtil.java2
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetDashboard.java5
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java13
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetHead.java9
-rw-r--r--java/com/google/gerrit/server/restapi/project/SetParent.java9
-rw-r--r--java/com/google/gerrit/server/rules/DefaultSubmitRule.java17
-rw-r--r--java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java29
-rw-r--r--java/com/google/gerrit/server/rules/PrologOptions.java60
-rw-r--r--java/com/google/gerrit/server/rules/PrologRule.java24
-rw-r--r--java/com/google/gerrit/server/rules/PrologRuleEvaluator.java96
-rw-r--r--java/com/google/gerrit/server/rules/RulesCache.java4
-rw-r--r--java/com/google/gerrit/server/rules/StoredValues.java12
-rw-r--r--java/com/google/gerrit/server/rules/SubmitRule.java14
-rw-r--r--java/com/google/gerrit/server/schema/AllProjectsCreator.java6
-rw-r--r--java/com/google/gerrit/server/schema/AllProjectsInput.java2
-rw-r--r--java/com/google/gerrit/server/schema/AllUsersCreator.java4
-rw-r--r--java/com/google/gerrit/server/schema/BUILD8
-rw-r--r--java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java69
-rw-r--r--java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java14
-rw-r--r--java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java2
-rw-r--r--java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java25
-rw-r--r--java/com/google/gerrit/server/schema/NoteDbSchemaVersionManager.java2
-rw-r--r--java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java2
-rw-r--r--java/com/google/gerrit/server/schema/SchemaCreatorImpl.java6
-rw-r--r--java/com/google/gerrit/server/schema/VersionedAccountPreferences.java4
-rw-r--r--java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java13
-rw-r--r--java/com/google/gerrit/server/schema/testing/BUILD4
-rw-r--r--java/com/google/gerrit/server/securestore/testing/BUILD2
-rw-r--r--java/com/google/gerrit/server/ssh/NoSshKeyCache.java2
-rw-r--r--java/com/google/gerrit/server/ssh/SshKeyCreator.java2
-rw-r--r--java/com/google/gerrit/server/submit/ChangeSet.java10
-rw-r--r--java/com/google/gerrit/server/submit/CherryPick.java8
-rw-r--r--java/com/google/gerrit/server/submit/CommitMergeStatus.java15
-rw-r--r--java/com/google/gerrit/server/submit/EmailMerge.java6
-rw-r--r--java/com/google/gerrit/server/submit/FastForwardOp.java2
-rw-r--r--java/com/google/gerrit/server/submit/GitModules.java20
-rw-r--r--java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java34
-rw-r--r--java/com/google/gerrit/server/submit/MergeOneOp.java2
-rw-r--r--java/com/google/gerrit/server/submit/MergeOp.java120
-rw-r--r--java/com/google/gerrit/server/submit/MergeOpRepoManager.java18
-rw-r--r--java/com/google/gerrit/server/submit/MergeSuperSet.java2
-rw-r--r--java/com/google/gerrit/server/submit/RebaseSorter.java4
-rw-r--r--java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java9
-rw-r--r--java/com/google/gerrit/server/submit/SubmitDryRun.java10
-rw-r--r--java/com/google/gerrit/server/submit/SubmitStrategy.java39
-rw-r--r--java/com/google/gerrit/server/submit/SubmitStrategyFactory.java4
-rw-r--r--java/com/google/gerrit/server/submit/SubmitStrategyListener.java4
-rw-r--r--java/com/google/gerrit/server/submit/SubmitStrategyOp.java81
-rw-r--r--java/com/google/gerrit/server/submit/SubmoduleOp.java114
-rw-r--r--java/com/google/gerrit/server/submit/TestHelperOp.java2
-rw-r--r--java/com/google/gerrit/server/update/BatchUpdate.java39
-rw-r--r--java/com/google/gerrit/server/update/BatchUpdateOp.java2
-rw-r--r--java/com/google/gerrit/server/update/ChangeContext.java4
-rw-r--r--java/com/google/gerrit/server/update/CommentsRejectedException.java47
-rw-r--r--java/com/google/gerrit/server/update/Context.java8
-rw-r--r--java/com/google/gerrit/server/update/InsertChangeOp.java2
-rw-r--r--java/com/google/gerrit/server/update/RepoView.java2
-rw-r--r--java/com/google/gerrit/server/update/RetryHelper.java124
-rw-r--r--java/com/google/gerrit/server/update/RetryingRestCollectionModifyView.java26
-rw-r--r--java/com/google/gerrit/server/update/RetryingRestModifyView.java24
-rw-r--r--java/com/google/gerrit/server/util/CommitMessageUtil.java6
-rw-r--r--java/com/google/gerrit/server/util/IdGenerator.java8
-rw-r--r--java/com/google/gerrit/server/util/MagicBranch.java25
-rw-r--r--java/com/google/gerrit/server/util/OneOffRequestContext.java2
-rw-r--r--java/com/google/gerrit/server/util/ReplicaUtil.java25
-rw-r--r--java/com/google/gerrit/server/util/RequestScopePropagator.java2
-rw-r--r--java/com/google/gerrit/server/util/git/BUILD4
-rw-r--r--java/com/google/gerrit/server/util/git/DelegateSystemReader.java68
-rw-r--r--java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java18
-rw-r--r--java/com/google/gerrit/server/util/time/BUILD4
-rw-r--r--java/com/google/gerrit/server/util/time/TimeUtil.java55
-rw-r--r--java/com/google/gerrit/server/validators/AccountActivationValidationListener.java6
-rw-r--r--java/com/google/gerrit/server/validators/AssigneeValidationListener.java4
-rw-r--r--java/com/google/gerrit/server/validators/HashtagValidationListener.java2
-rw-r--r--java/com/google/gerrit/sshd/AbstractGitCommand.java13
-rw-r--r--java/com/google/gerrit/sshd/AliasCommand.java13
-rw-r--r--java/com/google/gerrit/sshd/BUILD8
-rw-r--r--java/com/google/gerrit/sshd/BaseCommand.java25
-rw-r--r--java/com/google/gerrit/sshd/ChangeArgumentParser.java6
-rw-r--r--java/com/google/gerrit/sshd/ChannelIdTrackingUnknownChannelReferenceHandler.java90
-rw-r--r--java/com/google/gerrit/sshd/CommandFactoryProvider.java25
-rw-r--r--java/com/google/gerrit/sshd/DatabasePubKeyAuth.java26
-rw-r--r--java/com/google/gerrit/sshd/DispatchCommand.java9
-rw-r--r--java/com/google/gerrit/sshd/GerritGSSAuthenticator.java6
-rw-r--r--java/com/google/gerrit/sshd/HostKeyProvider.java17
-rw-r--r--java/com/google/gerrit/sshd/InactiveAccountDisconnector.java60
-rw-r--r--java/com/google/gerrit/sshd/LogMaxConnectionsPerUserExceeded.java42
-rw-r--r--java/com/google/gerrit/sshd/NoShell.java57
-rw-r--r--java/com/google/gerrit/sshd/SshCommand.java23
-rw-r--r--java/com/google/gerrit/sshd/SshDaemon.java70
-rw-r--r--java/com/google/gerrit/sshd/SshKeyCacheEntry.java2
-rw-r--r--java/com/google/gerrit/sshd/SshKeyCacheImpl.java5
-rw-r--r--java/com/google/gerrit/sshd/SshKeyCreatorImpl.java2
-rw-r--r--java/com/google/gerrit/sshd/SshModule.java4
-rw-r--r--java/com/google/gerrit/sshd/SshSession.java2
-rw-r--r--java/com/google/gerrit/sshd/SshUtil.java32
-rw-r--r--java/com/google/gerrit/sshd/SuExec.java11
-rw-r--r--java/com/google/gerrit/sshd/commands/BanCommitCommand.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/CloseConnection.java51
-rw-r--r--java/com/google/gerrit/sshd/commands/CreateAccountCommand.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/CreateGroupCommand.java13
-rw-r--r--java/com/google/gerrit/sshd/commands/CreateProjectCommand.java4
-rw-r--r--java/com/google/gerrit/sshd/commands/DefaultCommandModule.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/FlushCaches.java8
-rw-r--r--java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/IndexChangesCommand.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/ListGroupsCommand.java4
-rw-r--r--java/com/google/gerrit/sshd/commands/ListMembersCommand.java4
-rw-r--r--java/com/google/gerrit/sshd/commands/LsUserRefs.java8
-rw-r--r--java/com/google/gerrit/sshd/commands/PatchSetParser.java16
-rw-r--r--java/com/google/gerrit/sshd/commands/PluginLsCommand.java2
-rw-r--r--java/com/google/gerrit/sshd/commands/Receive.java42
-rw-r--r--java/com/google/gerrit/sshd/commands/ReviewCommand.java23
-rw-r--r--java/com/google/gerrit/sshd/commands/ScpCommand.java3
-rw-r--r--java/com/google/gerrit/sshd/commands/SetAccountCommand.java23
-rw-r--r--java/com/google/gerrit/sshd/commands/SetMembersCommand.java6
-rw-r--r--java/com/google/gerrit/sshd/commands/SetParentCommand.java13
-rw-r--r--java/com/google/gerrit/sshd/commands/SetReviewersCommand.java6
-rw-r--r--java/com/google/gerrit/sshd/commands/ShowCaches.java59
-rw-r--r--java/com/google/gerrit/sshd/commands/ShowConnections.java7
-rw-r--r--java/com/google/gerrit/sshd/commands/ShowQueue.java9
-rw-r--r--java/com/google/gerrit/sshd/commands/StreamEvents.java91
-rw-r--r--java/com/google/gerrit/sshd/commands/Upload.java37
-rw-r--r--java/com/google/gerrit/sshd/commands/UploadArchive.java2
-rw-r--r--java/com/google/gerrit/testing/AssertableExecutorService.java65
-rw-r--r--java/com/google/gerrit/testing/BUILD34
-rw-r--r--java/com/google/gerrit/testing/ConfigSuite.java2
-rw-r--r--java/com/google/gerrit/testing/FakeAccountCache.java13
-rw-r--r--java/com/google/gerrit/testing/GerritServerTests.java2
-rw-r--r--java/com/google/gerrit/testing/GerritTestName.java (renamed from java/com/google/gerrit/testing/GerritBaseTests.java)23
-rw-r--r--java/com/google/gerrit/testing/InMemoryModule.java37
-rw-r--r--java/com/google/gerrit/testing/InMemoryRepositoryManager.java4
-rw-r--r--java/com/google/gerrit/testing/InMemoryTestEnvironment.java2
-rw-r--r--java/com/google/gerrit/testing/IndexConfig.java1
-rw-r--r--java/com/google/gerrit/testing/TestChanges.java36
-rw-r--r--java/com/google/gerrit/testing/TestCommentHelper.java107
-rw-r--r--java/com/google/gerrit/truth/BUILD1
-rw-r--r--java/com/google/gerrit/truth/CacheStatsSubject.java8
-rw-r--r--java/com/google/gerrit/truth/ConfigSubject.java129
-rw-r--r--java/com/google/gerrit/truth/ListSubject.java37
-rw-r--r--java/com/google/gerrit/truth/NullAwareCorrespondence.java83
-rw-r--r--java/com/google/gerrit/truth/OptionalSubject.java41
-rw-r--r--java/com/google/gerrit/util/cli/CmdLineParser.java36
-rw-r--r--java/com/google/gerrit/util/cli/OptionUtil.java7
-rw-r--r--java/com/google/gwtorm/BUILD7
-rw-r--r--java/com/google/gwtorm/client/CompoundKey.java108
-rw-r--r--java/com/google/gwtorm/client/IntKey.java80
-rw-r--r--java/com/google/gwtorm/client/Key.java45
-rw-r--r--java/com/google/gwtorm/client/KeyUtil.java91
-rw-r--r--java/com/google/gwtorm/client/StringKey.java86
-rw-r--r--java/gerrit/AbstractCommitUserIdentityPredicate.java2
-rw-r--r--java/gerrit/BUILD5
-rw-r--r--java/gerrit/PRED__load_commit_labels_1.java9
-rw-r--r--java/gerrit/PRED_change_branch_1.java6
-rw-r--r--java/gerrit/PRED_change_owner_1.java2
-rw-r--r--java/gerrit/PRED_change_project_1.java2
-rw-r--r--java/gerrit/PRED_change_topic_1.java2
-rw-r--r--java/gerrit/PRED_commit_delta_4.java2
-rw-r--r--java/gerrit/PRED_commit_edits_2.java2
-rw-r--r--java/gerrit/PRED_commit_stats_3.java2
-rw-r--r--java/gerrit/PRED_uploader_1.java6
-rw-r--r--javatests/com/google/gerrit/acceptance/BUILD3
-rw-r--r--javatests/com/google/gerrit/acceptance/MergeableFileBasedConfigTest.java6
-rw-r--r--javatests/com/google/gerrit/acceptance/ProjectResetterTest.java180
-rw-r--r--javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java17
-rw-r--r--javatests/com/google/gerrit/acceptance/annotation/BUILD1
-rw-r--r--javatests/com/google/gerrit/acceptance/annotation/UseClockStepTest.java57
-rw-r--r--javatests/com/google/gerrit/acceptance/annotation/UseSystemTimeTest.java34
-rw-r--r--javatests/com/google/gerrit/acceptance/annotation/UseTimezoneTest.java35
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java2491
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java14
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/AccountListenersIT.java212
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java152
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java92
-rw-r--r--javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java49
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java61
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java1694
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java58
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java113
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java52
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/MergeListIT.java26
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java290
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/PrivateChangeIT.java49
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java94
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/RevertIT.java454
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java151
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java44
-rw-r--r--javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/api/group/GroupsConsistencyIT.java49
-rw-r--r--javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java349
-rw-r--r--javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java28
-rw-r--r--javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java36
-rw-r--r--javatests/com/google/gerrit/acceptance/api/plugin/PluginLoaderIT.java42
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java107
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java41
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/CommitIT.java103
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java27
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java320
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java26
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java76
-rw-r--r--javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java7
-rw-r--r--javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java371
-rw-r--r--javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java142
-rw-r--r--javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java153
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractForcePush.java28
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java8
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java474
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractSubmitOnPush.java244
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java17
-rw-r--r--javatests/com/google/gerrit/acceptance/git/BUILD3
-rw-r--r--javatests/com/google/gerrit/acceptance/git/GitmodulesIT.java16
-rw-r--r--javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java8
-rw-r--r--javatests/com/google/gerrit/acceptance/git/PushAccountIT.java778
-rw-r--r--javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java92
-rw-r--r--javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java833
-rw-r--r--javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java69
-rw-r--r--javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java210
-rw-r--r--javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java76
-rw-r--r--javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java28
-rw-r--r--javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java5
-rw-r--r--javatests/com/google/gerrit/acceptance/pgm/InitIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java76
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/TraceIT.java827
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java26
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/BUILD7
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java27
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/CreateAccountIT.java41
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java31
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java338
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/GetAccountIT.java21
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java225
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java15
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java35
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/auth/BUILD7
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/binding/ConfigRestApiBindingsIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedChildRestApiBindingsIT.java99
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedRootRestApiBindingsIT.java16
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java13
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java627
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java34
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java106
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java143
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java92
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/BUILD8
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ChangeIdIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java15
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java83
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java29
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java132
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java89
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java63
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java171
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/DeleteVoteIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java39
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java31
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java229
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/PluginFieldsIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java26
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java104
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java51
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java13
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java215
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java98
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java19
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java6
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java257
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java148
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java19
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java19
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java110
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java16
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java80
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java121
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/BUILD10
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/CheckMergeabilityIT.java25
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java122
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java70
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java117
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java76
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java37
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/DeleteTagsIT.java40
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/FileBranchIT.java13
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java98
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java12
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/GetCommitIT.java25
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java6
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java50
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java14
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java25
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/ProjectAssert.java8
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/RefAssert.java9
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java130
-rw-r--r--javatests/com/google/gerrit/acceptance/rest/util/RestApiCallHelper.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java76
-rw-r--r--javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java103
-rw-r--r--javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java223
-rw-r--r--javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java90
-rw-r--r--javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java4
-rw-r--r--javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/server/config/BUILD7
-rw-r--r--javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java46
-rw-r--r--javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java334
-rw-r--r--javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java64
-rw-r--r--javatests/com/google/gerrit/acceptance/server/git/receive/BUILD8
-rw-r--r--javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java136
-rw-r--r--javatests/com/google/gerrit/acceptance/server/httpd/BUILD7
-rw-r--r--javatests/com/google/gerrit/acceptance/server/httpd/HttpLogoutServletIT.java112
-rw-r--r--javatests/com/google/gerrit/acceptance/server/mail/BUILD14
-rw-r--r--javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java62
-rw-r--r--javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java22
-rw-r--r--javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java122
-rw-r--r--javatests/com/google/gerrit/acceptance/server/notedb/BUILD3
-rw-r--r--javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java26
-rw-r--r--javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java16
-rw-r--r--javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java148
-rw-r--r--javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java54
-rw-r--r--javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java28
-rw-r--r--javatests/com/google/gerrit/acceptance/server/quota/DefaultQuotaBackendIT.java81
-rw-r--r--javatests/com/google/gerrit/acceptance/server/quota/MultipleQuotaPluginsIT.java99
-rw-r--r--javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java62
-rw-r--r--javatests/com/google/gerrit/acceptance/server/quota/RestApiQuotaIT.java79
-rw-r--r--javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java21
-rw-r--r--javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java35
-rw-r--r--javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java6
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java103
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/CreateProjectIT.java10
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java33
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java7
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/IndexIT.java3
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/PluginChangeFieldsIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/QueryIT.java5
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/SetReviewersIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java48
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java123
-rw-r--r--javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java128
-rw-r--r--javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java546
-rw-r--r--javatests/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdateTest.java202
-rw-r--r--javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java38
-rw-r--r--javatests/com/google/gerrit/common/AutoValueTest.java3
-rw-r--r--javatests/com/google/gerrit/common/BUILD1
-rw-r--r--javatests/com/google/gerrit/common/data/AccessSectionTest.java47
-rw-r--r--javatests/com/google/gerrit/common/data/BUILD2
-rw-r--r--javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java3
-rw-r--r--javatests/com/google/gerrit/common/data/FilenameComparatorTest.java3
-rw-r--r--javatests/com/google/gerrit/common/data/GroupReferenceTest.java38
-rw-r--r--javatests/com/google/gerrit/common/data/LabelFunctionTest.java40
-rw-r--r--javatests/com/google/gerrit/common/data/LabelTypeTest.java3
-rw-r--r--javatests/com/google/gerrit/common/data/ParameterizedStringTest.java3
-rw-r--r--javatests/com/google/gerrit/common/data/PermissionRuleTest.java39
-rw-r--r--javatests/com/google/gerrit/common/data/PermissionTest.java55
-rw-r--r--javatests/com/google/gerrit/common/data/SubmitRecordTest.java3
-rw-r--r--javatests/com/google/gerrit/elasticsearch/BUILD17
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java12
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticContainer.java10
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java3
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java64
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java87
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java64
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java64
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java2
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java30
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java2
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java2
-rw-r--r--javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java73
-rw-r--r--javatests/com/google/gerrit/entities/AccountGroupTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java)33
-rw-r--r--javatests/com/google/gerrit/entities/AccountTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/AccountTest.java)13
-rw-r--r--javatests/com/google/gerrit/entities/BUILD (renamed from javatests/com/google/gerrit/reviewdb/client/BUILD)6
-rw-r--r--javatests/com/google/gerrit/entities/BranchTest.java39
-rw-r--r--javatests/com/google/gerrit/entities/ChangeTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/ChangeTest.java)25
-rw-r--r--javatests/com/google/gerrit/entities/PatchSetApprovalTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java)17
-rw-r--r--javatests/com/google/gerrit/entities/PatchSetTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/PatchSetTest.java)47
-rw-r--r--javatests/com/google/gerrit/entities/PatchTest.java54
-rw-r--r--javatests/com/google/gerrit/entities/ProjectTest.java39
-rw-r--r--javatests/com/google/gerrit/entities/RefNamesTest.java (renamed from javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java)41
-rw-r--r--javatests/com/google/gerrit/entities/converter/AccountIdProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/AccountIdProtoConverterTest.java)13
-rw-r--r--javatests/com/google/gerrit/entities/converter/BUILD (renamed from javatests/com/google/gerrit/reviewdb/converter/BUILD)4
-rw-r--r--javatests/com/google/gerrit/entities/converter/BranchNameKeyProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverterTest.java)30
-rw-r--r--javatests/com/google/gerrit/entities/converter/ChangeIdProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverterTest.java)13
-rw-r--r--javatests/com/google/gerrit/entities/converter/ChangeKeyProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverterTest.java)13
-rw-r--r--javatests/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverterTest.java)14
-rw-r--r--javatests/com/google/gerrit/entities/converter/ChangeMessageProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverterTest.java)53
-rw-r--r--javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ChangeProtoConverterTest.java)102
-rw-r--r--javatests/com/google/gerrit/entities/converter/LabelIdProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/LabelIdProtoConverterTest.java)13
-rw-r--r--javatests/com/google/gerrit/entities/converter/ObjectIdProtoConverterTest.java75
-rw-r--r--javatests/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverterTest.java)34
-rw-r--r--javatests/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverterTest.java)115
-rw-r--r--javatests/com/google/gerrit/entities/converter/PatchSetIdProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverterTest.java)20
-rw-r--r--javatests/com/google/gerrit/entities/converter/PatchSetProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/PatchSetProtoConverterTest.java)106
-rw-r--r--javatests/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverterTest.java (renamed from javatests/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverterTest.java)8
-rw-r--r--javatests/com/google/gerrit/extensions/BUILD1
-rw-r--r--javatests/com/google/gerrit/extensions/api/lfs/LfsDefinitionsTest.java3
-rw-r--r--javatests/com/google/gerrit/extensions/client/ListOptionTest.java3
-rw-r--r--javatests/com/google/gerrit/extensions/client/RangeTest.java3
-rw-r--r--javatests/com/google/gerrit/extensions/conditions/BUILD1
-rw-r--r--javatests/com/google/gerrit/extensions/conditions/BooleanConditionTest.java3
-rw-r--r--javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java3
-rw-r--r--javatests/com/google/gerrit/git/BUILD8
-rw-r--r--javatests/com/google/gerrit/git/ObjectIdsTest.java156
-rw-r--r--javatests/com/google/gerrit/git/RefUpdateUtilRepoTest.java3
-rw-r--r--javatests/com/google/gerrit/git/RefUpdateUtilTest.java24
-rw-r--r--javatests/com/google/gerrit/git/testing/BUILD1
-rw-r--r--javatests/com/google/gerrit/git/testing/PushResultSubjectTest.java3
-rw-r--r--javatests/com/google/gerrit/gpg/BUILD7
-rw-r--r--javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java7
-rw-r--r--javatests/com/google/gerrit/gpg/PublicKeyCheckerTest.java3
-rw-r--r--javatests/com/google/gerrit/gpg/PublicKeyStoreTest.java3
-rw-r--r--javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java3
-rw-r--r--javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java245
-rw-r--r--javatests/com/google/gerrit/httpd/AllowRenderInFrameFilterTest.java75
-rw-r--r--javatests/com/google/gerrit/httpd/BUILD9
-rw-r--r--javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java293
-rw-r--r--javatests/com/google/gerrit/httpd/RemoteUserUtilTest.java3
-rw-r--r--javatests/com/google/gerrit/httpd/plugins/ContextMapperTest.java3
-rw-r--r--javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java77
-rw-r--r--javatests/com/google/gerrit/httpd/raw/IndexServletTest.java89
-rw-r--r--javatests/com/google/gerrit/httpd/raw/ResourceServletTest.java8
-rw-r--r--javatests/com/google/gerrit/httpd/restapi/HttpLogRedactTest.java3
-rw-r--r--javatests/com/google/gerrit/httpd/restapi/ParameterParserTest.java32
-rw-r--r--javatests/com/google/gerrit/index/BUILD3
-rw-r--r--javatests/com/google/gerrit/index/SchemaUtilTest.java10
-rw-r--r--javatests/com/google/gerrit/index/query/AndPredicateTest.java23
-rw-r--r--javatests/com/google/gerrit/index/query/FieldPredicateTest.java9
-rw-r--r--javatests/com/google/gerrit/index/query/LazyDataSourceTest.java106
-rw-r--r--javatests/com/google/gerrit/index/query/NotPredicateTest.java49
-rw-r--r--javatests/com/google/gerrit/index/query/OrPredicateTest.java23
-rw-r--r--javatests/com/google/gerrit/index/query/PredicateTest.java3
-rw-r--r--javatests/com/google/gerrit/index/query/QueryBuilderTest.java3
-rw-r--r--javatests/com/google/gerrit/index/query/QueryParserTest.java12
-rw-r--r--javatests/com/google/gerrit/integration/git/BUILD6
-rw-r--r--javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java382
-rw-r--r--javatests/com/google/gerrit/integration/git/UploadArchiveIT.java4
-rw-r--r--javatests/com/google/gerrit/integration/ssh/BUILD7
-rw-r--r--javatests/com/google/gerrit/integration/ssh/NoShellIT.java96
-rw-r--r--javatests/com/google/gerrit/json/BUILD3
-rw-r--r--javatests/com/google/gerrit/json/JavaSqlTimestampHelperTest.java9
-rw-r--r--javatests/com/google/gerrit/json/JsonEnumMappingTest.java80
-rw-r--r--javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java33
-rw-r--r--javatests/com/google/gerrit/mail/AbstractParserTest.java11
-rw-r--r--javatests/com/google/gerrit/mail/AddressTest.java14
-rw-r--r--javatests/com/google/gerrit/mail/BUILD6
-rw-r--r--javatests/com/google/gerrit/mail/HtmlParserTest.java2
-rw-r--r--javatests/com/google/gerrit/mail/MailHeaderParserTest.java3
-rw-r--r--javatests/com/google/gerrit/mail/ParserUtilTest.java3
-rw-r--r--javatests/com/google/gerrit/mail/RawMailParserTest.java3
-rw-r--r--javatests/com/google/gerrit/mail/TextParserTest.java2
-rw-r--r--javatests/com/google/gerrit/mail/data/SimpleTextMessage.java2
-rw-r--r--javatests/com/google/gerrit/metrics/dropwizard/BUILD1
-rw-r--r--javatests/com/google/gerrit/metrics/dropwizard/DropWizardMetricMakerTest.java3
-rw-r--r--javatests/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java25
-rw-r--r--javatests/com/google/gerrit/pgm/BUILD8
-rw-r--r--javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java10
-rw-r--r--javatests/com/google/gerrit/proto/ProtosTest.java59
-rw-r--r--javatests/com/google/gerrit/reviewdb/converter/RevIdProtoConverterTest.java66
-rw-r--r--javatests/com/google/gerrit/server/BUILD15
-rw-r--r--javatests/com/google/gerrit/server/ChangeUtilTest.java3
-rw-r--r--javatests/com/google/gerrit/server/IdentifiedUserTest.java12
-rw-r--r--javatests/com/google/gerrit/server/account/AccountResolverTest.java74
-rw-r--r--javatests/com/google/gerrit/server/account/AuthorizedKeysTest.java13
-rw-r--r--javatests/com/google/gerrit/server/account/DestinationListTest.java35
-rw-r--r--javatests/com/google/gerrit/server/account/GroupUUIDTest.java5
-rw-r--r--javatests/com/google/gerrit/server/account/HashedPasswordTest.java19
-rw-r--r--javatests/com/google/gerrit/server/account/PreferencesTest.java50
-rw-r--r--javatests/com/google/gerrit/server/account/QueryListTest.java3
-rw-r--r--javatests/com/google/gerrit/server/account/StoredPreferencesTest.java66
-rw-r--r--javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java66
-rw-r--r--javatests/com/google/gerrit/server/account/WatchConfigTest.java19
-rw-r--r--javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java15
-rw-r--r--javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java307
-rw-r--r--javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java6
-rw-r--r--javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java4
-rw-r--r--javatests/com/google/gerrit/server/cache/BUILD2
-rw-r--r--javatests/com/google/gerrit/server/cache/PerThreadCacheTest.java9
-rw-r--r--javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java13
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/BUILD4
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java13
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/CacheSerializerTest.java50
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java12
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java67
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java18
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java9
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java13
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/ObjectIdConverterTest.java14
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/ProtobufSerializerTest.java3
-rw-r--r--javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java27
-rw-r--r--javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java3
-rw-r--r--javatests/com/google/gerrit/server/change/HashtagsTest.java3
-rw-r--r--javatests/com/google/gerrit/server/change/IncludedInResolverTest.java29
-rw-r--r--javatests/com/google/gerrit/server/change/LabelNormalizerTest.java61
-rw-r--r--javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java3
-rw-r--r--javatests/com/google/gerrit/server/change/WalkSorterTest.java29
-rw-r--r--javatests/com/google/gerrit/server/config/AllProjectsNameTest.java40
-rw-r--r--javatests/com/google/gerrit/server/config/AllUsersNameTest.java40
-rw-r--r--javatests/com/google/gerrit/server/config/ConfigUtilTest.java26
-rw-r--r--javatests/com/google/gerrit/server/config/GitwebConfigTest.java3
-rw-r--r--javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java7
-rw-r--r--javatests/com/google/gerrit/server/config/RepositoryConfigTest.java59
-rw-r--r--javatests/com/google/gerrit/server/config/ScheduleConfigTest.java3
-rw-r--r--javatests/com/google/gerrit/server/config/SitePathsTest.java8
-rw-r--r--javatests/com/google/gerrit/server/edit/ChangeEditTest.java17
-rw-r--r--javatests/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java21
-rw-r--r--javatests/com/google/gerrit/server/edit/tree/TreeModificationSubject.java14
-rw-r--r--javatests/com/google/gerrit/server/events/BUILD15
-rw-r--r--javatests/com/google/gerrit/server/events/EventDeserializerTest.java294
-rw-r--r--javatests/com/google/gerrit/server/events/EventJsonTest.java28
-rw-r--r--javatests/com/google/gerrit/server/events/EventTypesTest.java3
-rw-r--r--javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java17
-rw-r--r--javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java105
-rw-r--r--javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java18
-rw-r--r--javatests/com/google/gerrit/server/fixes/StringModifierTest.java28
-rw-r--r--javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java14
-rw-r--r--javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java12
-rw-r--r--javatests/com/google/gerrit/server/git/GroupCollectorTest.java9
-rw-r--r--javatests/com/google/gerrit/server/git/JGitConfigTest.java84
-rw-r--r--javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java172
-rw-r--r--javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java62
-rw-r--r--javatests/com/google/gerrit/server/git/PureRevertCacheKeyTest.java5
-rw-r--r--javatests/com/google/gerrit/server/git/RepoRefCacheTest.java5
-rw-r--r--javatests/com/google/gerrit/server/git/TagSetHolderTest.java9
-rw-r--r--javatests/com/google/gerrit/server/git/TagSetTest.java21
-rw-r--r--javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java7
-rw-r--r--javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java17
-rw-r--r--javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java67
-rw-r--r--javatests/com/google/gerrit/server/group/db/BUILD6
-rw-r--r--javatests/com/google/gerrit/server/group/db/GroupConfigTest.java342
-rw-r--r--javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java171
-rw-r--r--javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java20
-rw-r--r--javatests/com/google/gerrit/server/index/account/AccountFieldTest.java22
-rw-r--r--javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java22
-rw-r--r--javatests/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java21
-rw-r--r--javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java7
-rw-r--r--javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java30
-rw-r--r--javatests/com/google/gerrit/server/ioutil/BUILD1
-rw-r--r--javatests/com/google/gerrit/server/ioutil/BasicSerializationTest.java3
-rw-r--r--javatests/com/google/gerrit/server/ioutil/ColumnFormatterTest.java3
-rw-r--r--javatests/com/google/gerrit/server/ioutil/HexFormatTest.java3
-rw-r--r--javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java5
-rw-r--r--javatests/com/google/gerrit/server/ioutil/StringUtilTest.java3
-rw-r--r--javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java68
-rw-r--r--javatests/com/google/gerrit/server/logging/MetadataTest.java37
-rw-r--r--javatests/com/google/gerrit/server/logging/MutableTagsTest.java13
-rw-r--r--javatests/com/google/gerrit/server/logging/PerformanceLogContextTest.java382
-rw-r--r--javatests/com/google/gerrit/server/logging/TraceContextTest.java20
-rw-r--r--javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java3
-rw-r--r--javatests/com/google/gerrit/server/mail/send/CommentFormatterTest.java3
-rw-r--r--javatests/com/google/gerrit/server/mail/send/CommentSenderTest.java3
-rw-r--r--javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java87
-rw-r--r--javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java44
-rw-r--r--javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java8
-rw-r--r--javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java35
-rw-r--r--javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java269
-rw-r--r--javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java819
-rw-r--r--javatests/com/google/gerrit/server/notedb/CommentTimestampAdapterTest.java12
-rw-r--r--javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java21
-rw-r--r--javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java98
-rw-r--r--javatests/com/google/gerrit/server/notedb/IntBlobTest.java48
-rw-r--r--javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java132
-rw-r--r--javatests/com/google/gerrit/server/patch/IntraLineLoaderTest.java32
-rw-r--r--javatests/com/google/gerrit/server/patch/PatchListEntryTest.java5
-rw-r--r--javatests/com/google/gerrit/server/patch/PatchListTest.java5
-rw-r--r--javatests/com/google/gerrit/server/permissions/DefaultPermissionsMappingTest.java3
-rw-r--r--javatests/com/google/gerrit/server/permissions/PluginPermissionsUtilTest.java10
-rw-r--r--javatests/com/google/gerrit/server/permissions/RefControlTest.java1195
-rw-r--r--javatests/com/google/gerrit/server/plugins/AutoRegisterModulesTest.java29
-rw-r--r--javatests/com/google/gerrit/server/project/CommitsCollectionTest.java110
-rw-r--r--javatests/com/google/gerrit/server/project/GroupListTest.java41
-rw-r--r--javatests/com/google/gerrit/server/project/ProjectConfigTest.java165
-rw-r--r--javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java55
-rw-r--r--javatests/com/google/gerrit/server/query/account/BUILD7
-rw-r--r--javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java284
-rw-r--r--javatests/com/google/gerrit/server/query/change/BUILD38
-rw-r--r--javatests/com/google/gerrit/server/query/change/ChangeDataTest.java34
-rw-r--r--javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java3
-rw-r--r--javatests/com/google/gerrit/server/query/change/LuceneQueryChangesLatestIndexVersionTest.java26
-rw-r--r--javatests/com/google/gerrit/server/query/change/LuceneQueryChangesPreviousIndexVersionTest.java36
-rw-r--r--javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java34
-rw-r--r--javatests/com/google/gerrit/server/query/change/RegexPathPredicateTest.java11
-rw-r--r--javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java30
-rw-r--r--javatests/com/google/gerrit/server/query/group/BUILD7
-rw-r--r--javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java38
-rw-r--r--javatests/com/google/gerrit/server/query/project/BUILD7
-rw-r--r--javatests/com/google/gerrit/server/rules/BUILD5
-rw-r--r--javatests/com/google/gerrit/server/rules/GerritCommonTest.java31
-rw-r--r--javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java31
-rw-r--r--javatests/com/google/gerrit/server/rules/PrologRuleEvaluatorTest.java3
-rw-r--r--javatests/com/google/gerrit/server/rules/PrologTestCase.java3
-rw-r--r--javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java12
-rw-r--r--javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java45
-rw-r--r--javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionCheckTest.java79
-rw-r--r--javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionManagerTest.java31
-rw-r--r--javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java3
-rw-r--r--javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java4
-rw-r--r--javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java5
-rw-r--r--javatests/com/google/gerrit/server/schema/TestGroup.java11
-rw-r--r--javatests/com/google/gerrit/server/update/BUILD14
-rw-r--r--javatests/com/google/gerrit/server/update/BatchUpdateTest.java259
-rw-r--r--javatests/com/google/gerrit/server/update/RepoViewTest.java7
-rw-r--r--javatests/com/google/gerrit/server/util/IdGeneratorTest.java3
-rw-r--r--javatests/com/google/gerrit/server/util/LabelVoteTest.java12
-rw-r--r--javatests/com/google/gerrit/server/util/MostSpecificComparatorTest.java3
-rw-r--r--javatests/com/google/gerrit/server/util/SocketUtilTest.java17
-rw-r--r--javatests/com/google/gerrit/server/util/git/BUILD8
-rw-r--r--javatests/com/google/gerrit/server/util/git/SubmoduleSectionParserTest.java110
-rw-r--r--javatests/com/google/gerrit/sshd/BUILD1
-rw-r--r--javatests/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java3
-rw-r--r--javatests/com/google/gerrit/testing/GerritJUnitTest.java6
-rw-r--r--javatests/com/google/gerrit/testing/IndexVersionsTest.java9
-rw-r--r--javatests/com/google/gerrit/util/http/BUILD2
-rw-r--r--javatests/com/google/gerrit/util/http/RequestUtilTest.java3
-rw-r--r--javatests/com/google/gerrit/util/http/testutil/BUILD2
-rw-r--r--javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java24
-rw-r--r--javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java2
-rw-r--r--lib/BUILD64
-rw-r--r--lib/LICENSE-shadycss20
-rw-r--r--lib/easymock/BUILD26
-rw-r--r--lib/errorprone/BUILD8
-rw-r--r--lib/greenmail/BUILD2
-rw-r--r--lib/guava.bzl4
-rw-r--r--lib/highlightjs/building.md23
-rw-r--r--lib/highlightjs/highlight.min.js4294
-rw-r--r--lib/jackson/BUILD1
-rw-r--r--lib/jetty/BUILD11
-rw-r--r--lib/jgit/BUILD0
-rw-r--r--lib/jgit/jgit.bzl75
-rw-r--r--lib/jgit/org.eclipse.jgit.archive/BUILD10
-rw-r--r--lib/jgit/org.eclipse.jgit.http.server/BUILD10
-rw-r--r--lib/jgit/org.eclipse.jgit.junit/BUILD11
-rw-r--r--lib/jgit/org.eclipse.jgit/BUILD20
-rw-r--r--lib/js/bower_archives.bzl80
-rw-r--r--lib/js/bower_components.bzl18
-rw-r--r--lib/js/npm.bzl4
-rw-r--r--lib/log/BUILD2
-rw-r--r--lib/mina/BUILD1
-rw-r--r--lib/mockito/BUILD7
-rwxr-xr-xlib/nongoogle_test.sh8
-rw-r--r--lib/powermock/BUILD69
m---------modules/jgit0
-rw-r--r--package.json2
-rw-r--r--plugins/BUILD21
m---------plugins/delete-project0
m---------plugins/download-commands0
m---------plugins/gitiles0
m---------plugins/hooks0
m---------plugins/plugin-manager0
m---------plugins/replication0
m---------plugins/reviewnotes0
m---------plugins/singleusergroup0
m---------plugins/webhooks0
-rw-r--r--polygerrit-ui/BUILD2
-rw-r--r--polygerrit-ui/Polymer2.md15
-rw-r--r--polygerrit-ui/README.md91
-rw-r--r--polygerrit-ui/app/BUILD2
-rw-r--r--polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html6
-rw-r--r--polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html55
-rw-r--r--polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html5
-rw-r--r--polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html (renamed from polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html)27
-rw-r--r--polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html (renamed from polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html)30
-rw-r--r--polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html11
-rw-r--r--polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html2
-rw-r--r--polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html6
-rw-r--r--polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html6
-rw-r--r--polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html2
-rw-r--r--polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html2
-rw-r--r--polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js4
-rw-r--r--polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html14
-rw-r--r--polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html12
-rw-r--r--polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html7
-rw-r--r--polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html9
-rw-r--r--polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html37
-rw-r--r--polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js40
-rw-r--r--polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html65
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js9
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html32
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html3
-rw-r--r--polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js5
-rw-r--r--polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html14
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html31
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.html12
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js1
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html19
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.html38
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js1
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html62
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html20
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js1
-rw-r--r--polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html4
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js2
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html15
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js3
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group/gr-group.html19
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group/gr-group.js11
-rw-r--r--polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html20
-rw-r--r--polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html21
-rw-r--r--polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js22
-rw-r--r--polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html34
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js2
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html3
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js2
-rw-r--r--polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html19
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js15
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html7
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js4
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js12
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html5
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js5
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html6
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html25
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js2
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html29
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js5
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html30
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.html24
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js9
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html9
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html22
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js8
-rw-r--r--polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html21
-rw-r--r--polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html13
-rw-r--r--polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js27
-rw-r--r--polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html50
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html20
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js15
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html14
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html7
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js11
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html34
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html21
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js33
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html146
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.html12
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js4
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html6
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html6
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js8
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html11
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js33
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html29
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.html2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html37
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.html2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js2
-rw-r--r--polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js185
-rw-r--r--polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html274
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html22
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js106
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html82
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html11
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html54
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js57
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html8
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html32
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html27
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js1
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html110
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js183
-rw-r--r--polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html149
-rw-r--r--polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html8
-rw-r--r--polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js1
-rw-r--r--polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html2
-rw-r--r--polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js11
-rw-r--r--polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js4
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html17
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html3
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html14
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html23
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js16
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html15
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js10
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html28
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js7
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js3
-rw-r--r--polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html8
-rw-r--r--polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html15
-rw-r--r--polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js30
-rw-r--r--polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html9
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html20
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js14
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html8
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html52
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js86
-rw-r--r--polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html52
-rw-r--r--polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html44
-rw-r--r--polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js16
-rw-r--r--polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html21
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html13
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js34
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html81
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js23
-rw-r--r--polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html25
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message.html56
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message.js20
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html18
-rw-r--r--polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js44
-rw-r--r--polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html7
-rw-r--r--polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js27
-rw-r--r--polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html6
-rw-r--r--polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html15
-rw-r--r--polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html54
-rw-r--r--polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js185
-rw-r--r--polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html380
-rw-r--r--polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html64
-rw-r--r--polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js21
-rw-r--r--polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html14
-rw-r--r--polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html12
-rw-r--r--polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js24
-rw-r--r--polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html115
-rw-r--r--polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html7
-rw-r--r--polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js15
-rw-r--r--polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html27
-rw-r--r--polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html7
-rw-r--r--polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js8
-rw-r--r--polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html14
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html2
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js1
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html3
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js2
-rw-r--r--polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js1
-rw-r--r--polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html17
-rw-r--r--polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js3
-rw-r--r--polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html44
-rw-r--r--polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js24
-rw-r--r--polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html100
-rw-r--r--polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html5
-rw-r--r--polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html6
-rw-r--r--polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector.js61
-rw-r--r--polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector_test.html78
-rw-r--r--polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html3
-rw-r--r--polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js124
-rw-r--r--polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html43
-rw-r--r--polygerrit-ui/app/elements/core/gr-router/gr-router.html5
-rw-r--r--polygerrit-ui/app/elements/core/gr-router/gr-router.js24
-rw-r--r--polygerrit-ui/app/elements/core/gr-router/gr-router_test.html16
-rw-r--r--polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html7
-rw-r--r--polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js3
-rw-r--r--polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html8
-rw-r--r--polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html4
-rw-r--r--polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js3
-rw-r--r--polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html6
-rw-r--r--polygerrit-ui/app/elements/custom-dark-theme_test.html101
-rw-r--r--polygerrit-ui/app/elements/custom-light-theme_test.html (renamed from polygerrit-ui/app/elements/gr-app-it_test.html)41
-rw-r--r--polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js1
-rw-r--r--polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.html2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js57
-rw-r--r--polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html3
-rw-r--r--polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js30
-rw-r--r--polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js3
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js11
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html205
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html101
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js79
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html103
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js30
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html34
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html13
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js19
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html22
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js8
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html12
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js265
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html320
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.html7
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js1
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html8
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.html13
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js9
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js541
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html642
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html27
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js35
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html44
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js149
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html145
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js171
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html118
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.js46
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html136
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js109
-rw-r--r--polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html149
-rw-r--r--polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html4
-rw-r--r--polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js22
-rw-r--r--polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html8
-rw-r--r--polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.html2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js22
-rw-r--r--polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html42
-rw-r--r--polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.html30
-rw-r--r--polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html3
-rw-r--r--polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html2
-rw-r--r--polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js13
-rw-r--r--polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html6
-rw-r--r--polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html1
-rw-r--r--polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.html3
-rw-r--r--polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js1
-rw-r--r--polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html8
-rw-r--r--polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.html4
-rw-r--r--polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js6
-rw-r--r--polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html8
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html46
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js16
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html15
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html5
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js6
-rw-r--r--polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html6
-rw-r--r--polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html19
-rw-r--r--polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js35
-rw-r--r--polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html8
-rw-r--r--polygerrit-ui/app/elements/gr-app-element.html238
-rw-r--r--polygerrit-ui/app/elements/gr-app-element.js468
-rw-r--r--polygerrit-ui/app/elements/gr-app.html249
-rw-r--r--polygerrit-ui/app/elements/gr-app.js454
-rw-r--r--polygerrit-ui/app/elements/gr-app_test.html46
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html8
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html7
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.html3
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js1
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html9
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.html4
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js7
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html26
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js18
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.html3
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js24
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html49
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js16
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html6
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js84
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html182
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js5
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html6
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html6
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-repo-api/gr-plugin-repo-command.html3
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html8
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html8
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html18
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js83
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html182
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.html5
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.html2
-rw-r--r--polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html8
-rw-r--r--polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html43
-rw-r--r--polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js13
-rw-r--r--polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html2
-rw-r--r--polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html14
-rw-r--r--polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html11
-rw-r--r--polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js14
-rw-r--r--polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html34
-rw-r--r--polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html37
-rw-r--r--polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js18
-rw-r--r--polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html39
-rw-r--r--polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.html22
-rw-r--r--polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js3
-rw-r--r--polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html8
-rw-r--r--polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.html27
-rw-r--r--polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.html2
-rw-r--r--polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js5
-rw-r--r--polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html24
-rw-r--r--polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html14
-rw-r--r--polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html10
-rw-r--r--polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html12
-rw-r--r--polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.html37
-rw-r--r--polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js7
-rw-r--r--polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html9
-rw-r--r--polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html40
-rw-r--r--polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js13
-rw-r--r--polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.html4
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js2
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.html2
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js2
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html50
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js5
-rw-r--r--polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html20
-rw-r--r--polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js1
-rw-r--r--polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html6
-rw-r--r--polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.html23
-rw-r--r--polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js3
-rw-r--r--polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html31
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js5
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html (renamed from polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html)11
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js101
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html113
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html12
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js27
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html62
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html (renamed from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html)13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js (renamed from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js)108
-rw-r--r--polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html (renamed from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html)199
-rw-r--r--polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html32
-rw-r--r--polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html12
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js25
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html10
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html29
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js28
-rw-r--r--polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html40
-rw-r--r--polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html95
-rw-r--r--polygerrit-ui/app/elements/shared/gr-button/gr-button.html59
-rw-r--r--polygerrit-ui/app/elements/shared/gr-button/gr-button.js4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html41
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.html10
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html23
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html35
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html9
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html91
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js56
-rw-r--r--polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html49
-rw-r--r--polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js3
-rw-r--r--polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html11
-rw-r--r--polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js39
-rw-r--r--polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js102
-rw-r--r--polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html306
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html11
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.html46
-rw-r--r--polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html29
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js10
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html152
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html12
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js20
-rw-r--r--polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html22
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js21
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html9
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html26
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js28
-rw-r--r--polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html18
-rw-r--r--polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.html3
-rw-r--r--polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html10
-rw-r--r--polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js5
-rw-r--r--polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html11
-rw-r--r--polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html31
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html10
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js112
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html78
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html16
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js196
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html100
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js60
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html158
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html11
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js397
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html502
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js359
-rw-r--r--polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html29
-rw-r--r--polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js9
-rw-r--r--polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html7
-rw-r--r--polygerrit-ui/app/elements/shared/gr-label/gr-label.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-label/gr-label.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js3
-rw-r--r--polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js15
-rw-r--r--polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js17
-rw-r--r--polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html15
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html31
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js5
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html70
-rw-r--r--polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js61
-rw-r--r--polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html20
-rw-r--r--polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js17
-rw-r--r--polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html13
-rw-r--r--polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html5
-rw-r--r--polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js682
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html405
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js431
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html177
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-select/gr-select.html4
-rw-r--r--polygerrit-ui/app/elements/shared/gr-select/gr-select.js14
-rw-r--r--polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html31
-rw-r--r--polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html25
-rw-r--r--polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js9
-rw-r--r--polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html23
-rw-r--r--polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js146
-rw-r--r--polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html165
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html2
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html8
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js1
-rw-r--r--polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html6
-rw-r--r--polygerrit-ui/app/elements/shared/revision-info/revision-info.html10
-rw-r--r--polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html6
-rw-r--r--polygerrit-ui/app/elements/test/plugin.html48
-rw-r--r--polygerrit-ui/app/embed/embed.html7
-rw-r--r--polygerrit-ui/app/embed/embed_test.html8
-rw-r--r--polygerrit-ui/app/embed/gr-diff.html6
-rw-r--r--polygerrit-ui/app/embed/test.html6
-rwxr-xr-xpolygerrit-ui/app/embed_test.sh17
-rw-r--r--polygerrit-ui/app/gr-diff/gr-diff-root.html5
-rw-r--r--polygerrit-ui/app/rules.bzl9
-rw-r--r--polygerrit-ui/app/samples/bind-parameters.html2
-rw-r--r--polygerrit-ui/app/samples/coverage-plugin.html15
-rw-r--r--polygerrit-ui/app/samples/repo-command.html2
-rw-r--r--polygerrit-ui/app/samples/some-screen.html2
-rw-r--r--polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js71
-rw-r--r--polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html140
-rw-r--r--polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js46
-rw-r--r--polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html99
-rw-r--r--polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js47
-rw-r--r--polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html106
-rw-r--r--polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js114
-rw-r--r--polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html260
-rw-r--r--polygerrit-ui/app/scripts/shadow.js421
-rw-r--r--polygerrit-ui/app/scripts/util.js89
-rw-r--r--polygerrit-ui/app/styles/dashboard-header-styles.html2
-rw-r--r--polygerrit-ui/app/styles/fonts.css2
-rw-r--r--polygerrit-ui/app/styles/gr-change-list-styles.html20
-rw-r--r--polygerrit-ui/app/styles/gr-change-metadata-shared-styles.html48
-rw-r--r--polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.html54
-rw-r--r--polygerrit-ui/app/styles/gr-form-styles.html30
-rw-r--r--polygerrit-ui/app/styles/gr-menu-page-styles.html14
-rw-r--r--polygerrit-ui/app/styles/gr-page-nav-styles.html14
-rw-r--r--polygerrit-ui/app/styles/gr-subpage-styles.html2
-rw-r--r--polygerrit-ui/app/styles/gr-table-styles.html12
-rw-r--r--polygerrit-ui/app/styles/gr-voting-styles.html1
-rw-r--r--polygerrit-ui/app/styles/main.css10
-rw-r--r--polygerrit-ui/app/styles/shared-styles.html70
-rw-r--r--polygerrit-ui/app/styles/themes/app-theme.html250
-rw-r--r--polygerrit-ui/app/styles/themes/dark-theme.html175
-rw-r--r--polygerrit-ui/app/template_test_srcs/template_test.js92
-rw-r--r--polygerrit-ui/app/test/common-test-setup.html10
-rw-r--r--polygerrit-ui/app/test/common-test-setup.js26
-rw-r--r--polygerrit-ui/app/test/index.html48
-rw-r--r--polygerrit-ui/app/types/custom-externs.js63
-rw-r--r--polygerrit-ui/app/types/types.js279
-rwxr-xr-xpolygerrit-ui/app/wct_test.sh3
-rw-r--r--polygerrit-ui/server.go54
-rw-r--r--prologtests/examples/BUILD15
-rw-r--r--prologtests/examples/README.md54
-rw-r--r--prologtests/examples/aosp_rules.pl148
-rwxr-xr-xprologtests/examples/dummy.sh6
-rw-r--r--prologtests/examples/load.pl26
-rw-r--r--prologtests/examples/rules.pl29
-rwxr-xr-xprologtests/examples/run.sh62
-rw-r--r--prologtests/examples/t1.pl20
-rw-r--r--prologtests/examples/t2.pl25
-rw-r--r--prologtests/examples/t3.pl69
-rw-r--r--prologtests/examples/utils.pl78
-rw-r--r--proto/cache.proto24
-rw-r--r--proto/entities.proto43
-rw-r--r--resources/com/google/gerrit/httpd/auth/openid/LoginForm.html5
-rw-r--r--resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy24
-rwxr-xr-xresources/com/google/gerrit/pgm/init/gerrit.sh5
-rw-r--r--resources/com/google/gerrit/server/mail/Abandoned.soy2
-rw-r--r--resources/com/google/gerrit/server/mail/InboundEmailRejection.soy5
-rw-r--r--resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy7
-rw-r--r--tools/BUILD107
-rw-r--r--tools/bzl/javadoc.bzl17
-rw-r--r--tools/bzl/js.bzl26
-rw-r--r--tools/bzl/junit.bzl3
-rw-r--r--tools/bzl/license-map.py2
-rw-r--r--tools/bzl/license.bzl2
-rw-r--r--tools/bzl/maven_jar.bzl24
-rw-r--r--tools/bzl/plugin.bzl2
-rwxr-xr-xtools/download_file.py9
-rwxr-xr-xtools/eclipse/project.py30
-rwxr-xr-xtools/js/download_bower.py10
-rwxr-xr-xtools/js/npm_pack.py2
-rw-r--r--tools/js/run_npm_binary.py2
-rw-r--r--tools/maven/gerrit-acceptance-framework_pom.xml19
-rw-r--r--tools/maven/gerrit-extension-api_pom.xml19
-rw-r--r--tools/maven/gerrit-plugin-api_pom.xml19
-rw-r--r--tools/maven/gerrit-war_pom.xml19
-rwxr-xr-xtools/maven/mvn.py2
-rw-r--r--tools/nongoogle.bzl93
-rw-r--r--tools/util_test.py2
-rwxr-xr-xtools/version.py2
-rw-r--r--tools/workspace_status.py2
-rw-r--r--tools/workspace_status_release.py195
-rw-r--r--version.bzl2
2580 files changed, 66591 insertions, 37421 deletions
diff --git a/.bazelrc b/.bazelrc
index bf3aa6c3b0..e560f2397a 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,4 +1,4 @@
-build --workspace_status_command="python ./tools/workspace_status.py" --strategy=Closure=worker
+build --workspace_status_command="python3 ./tools/workspace_status.py" --strategy=Closure=worker
build --repository_cache=~/.gerritcodereview/bazel-cache/repository
build --action_env=PATH
build --disk_cache=~/.gerritcodereview/bazel-cache/cas
diff --git a/.bazelversion b/.bazelversion
index 7c69a55dbb..fcdb2e109f 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.7.0
+4.0.0
diff --git a/.gitmodules b/.gitmodules
index 6844f6a54a..9f67e77676 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
+[submodule "modules/jgit"]
+ path = modules/jgit
+ url = ../jgit
+
[submodule "plugins/codemirror-editor"]
path = plugins/codemirror-editor
url = ../plugins/codemirror-editor
diff --git a/.gitreview b/.gitreview
index b3c37ad125..9df7aae035 100644
--- a/.gitreview
+++ b/.gitreview
@@ -2,4 +2,4 @@
host=gerrit-review.googlesource.com
scheme=https
project=gerrit.git
-defaultbranch=stable-3.0
+defaultbranch=stable-3.1
diff --git a/.mailmap b/.mailmap
index 38c2a5f2cc..721f3c0cb1 100644
--- a/.mailmap
+++ b/.mailmap
@@ -4,6 +4,7 @@ Alex Blewitt <alex.blewitt@gmail.com>
Alex Blewitt <alex.blewitt@gmail.com> <alex.blewitt@credit-suisse.com>
Alex Ryazantsev <alex.ryazantsev@gmail.com> alex <alex.ryazantsev@gmail.com>
Alex Ryazantsev <alex.ryazantsev@gmail.com> alex.ryazantsev <alex.ryazantsev@gmail.com>
+Alice Kober-Sotzek <aliceks@google.com> <aliceks@google.com>
Alexandre Philbert <alexandre.philbert@ericsson.com> <alexandre.philbert@hotmail.com>
Andrew Bonventre <andybons@chromium.org> <andybons@google.com>
Becky Siegel <beckysiegel@google.com> beckysiegel <beckysiegel@google.com>
@@ -14,6 +15,7 @@ Bruce Zu <bruce.zu.run10@gmail.com>
Carlos Eduardo Baldacin <carloseduardo.baldacin@sonyericsson.com> carloseduardo.baldacin <carloseduardo.baldacin@sonyericsson.com>
Chad Horohoe <chorohoe@wikimedia.org> <chadh@wikimedia.org>
Changcheng Xiao <xchangcheng@google.com> xchangcheng
+Cheng Ke <chengke.info@gmail.com> <chengke.info@gmail.com>
Dariusz Luksza <dluksza@collab.net> <dariusz@luksza.org>
Darrien Glasser <darrien@arista.com> darrien <darrien@arista.com>
Dave Borowitz <dborowitz@google.com> <dborowitz@google.com>
@@ -73,6 +75,7 @@ Rafael Rabelo Silva <rafael.rabelosilva@sonyericsson.com>
Réda Housni Alaoui <reda.housnialaoui@gmail.com> <alaoui.rda@gmail.com>
Richard Möhn <richard.moehn@posteo.de> <richard.moehn@fu-berlin.de>
Sam Saccone <samccone@google.com> <samccone@gmail.com>
+Sam Saccone <samccone@google.com> <samccone@google.com>
Saša Živkov <sasa.zivkov@sap.com> Sasa Zivkov <sasa.zivkov@sap.com>
Saša Živkov <sasa.zivkov@sap.com> Saša Živkov <zivkov@gmail.com>
Saša Živkov <sasa.zivkov@sap.com> Sasa Zivkov <zivkov@gmail.com>
diff --git a/.zuul.yaml b/.zuul.yaml
index 463bc51026..fe9dc800d8 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -6,7 +6,8 @@
This adds required projects needed for all Gerrit-related builds
(i.e., builds of Gerrit itself or plugins) on this branch.
- # No additional required projects required for this branch.
+ required-projects:
+ - jgit
- job:
name: gerrit-build
diff --git a/BUILD b/BUILD
index 3989a75bdd..c48b3b951e 100644
--- a/BUILD
+++ b/BUILD
@@ -4,16 +4,16 @@ load("//tools/bzl:pkg_war.bzl", "pkg_war")
package(default_visibility = ["//visibility:public"])
config_setting(
- name = "java9",
+ name = "java11",
values = {
- "java_toolchain": "@bazel_tools//tools/jdk:toolchain_java9",
+ "java_toolchain": "@bazel_tools//tools/jdk:toolchain_java11",
},
)
config_setting(
name = "java_next",
values = {
- "java_toolchain": "@bazel_tools//tools/jdk:toolchain_vanilla",
+ "java_toolchain": "//tools:toolchain_vanilla",
},
)
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 9f7c45782f..7961b7e955 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -727,13 +727,29 @@ changes, comments, code diffs, and Git access over SSH or HTTP.
A user must have this access granted in order to see a project, its
changes, or any of its data.
-This category has a special behavior, where the per-project ACL is
-evaluated before the global all projects ACL. If the per-project
-ACL has granted `Read` with 'DENY', and does not otherwise grant
-`Read` with 'ALLOW', then a `Read` in the all projects ACL
-is ignored. This behavior is useful to hide a handful of projects
+[[read_special_behaviors]]
+==== Special behaviors
+
+This category has multiple special behaviors:
+
+The per-project ACL is evaluated before the global all projects ACL.
+If the per-project ACL has granted `Read` with 'DENY', and does not
+otherwise grant `Read` with 'ALLOW', then a `Read` in the all projects
+ACL is ignored. This behavior is useful to hide a handful of projects
on an otherwise public server.
+You cannot grant `Read` on the `refs/tags/` namespace. Visibility to
+`refs/tags/` is derived from `Read` grants on refs namespaces other than
+`refs/tags/`, `refs/changes/`, and `refs/cache-automerge/` by finding
+tags reachable from those refs. For example, if a tag `refs/tags/test`
+points to a commit on the branch `refs/heads/master`, then allowing
+`Read` access to `refs/heads/master` would also allow access to
+`refs/tags/test`. If a tag is reachable from multiple refs, allowing
+access to any of those refs allows access to the tag.
+
+[[read_typical_usage]]
+==== Typical usage
+
For an open source, public Gerrit installation it is common to grant
`Read` to `Anonymous Users` in the `All-Projects` ACL, enabling
casual browsing of any project's changes, as well as fetching any
@@ -919,7 +935,7 @@ any changes.
Suggested access rights to grant:
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
* xref:category_push[`Push`] to 'refs/for/refs/heads/*'
* link:config-labels.html#label_Code-Review[`Code-Review`] with range '-1' to '+1' for 'refs/heads/*'
@@ -947,7 +963,7 @@ exclusively.
Suggested access rights to grant:
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
* xref:category_push[`Push`] to 'refs/for/refs/heads/*'
* xref:category_push_merge[`Push merge commit`] to 'refs/for/refs/heads/*'
* xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
@@ -1002,7 +1018,7 @@ and so the push right can be found under the optional section.
Suggested access rights to grant, that won't block changes:
-* xref:category_read[`Read`] on 'refs/heads/\*' and 'refs/tags/*'
+* xref:category_read[`Read`] on 'refs/heads/\*'
* link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-1' to '0' for 'refs/heads/*'
* link:config-labels.html#label_Verified[`Label: Verified`] with range '0' to '+1' for 'refs/heads/*'
diff --git a/Documentation/backup.txt b/Documentation/backup.txt
index ed044ba665..7220c740f0 100644
--- a/Documentation/backup.txt
+++ b/Documentation/backup.txt
@@ -139,11 +139,12 @@ Use a file system supporting snapshots to keep the period where the gerrit
server is read-only or down as short as possible.
[#cons-backup-read-only]
-=== Turn master read-only for backup
+=== Turn primary server read-only for backup
-Make the server read-only before taking the backup. This means read-access
-is still available during backup, because only write operations have to be
-stopped to ensure consistency. This can be implemented using the
+Make the primary server handling write operations read-only before taking the
+backup. This means read-access is still available from replica servers during
+backup, because only write operations have to be stopped to ensure consistency.
+This can be implemented using the
link:https://gerrit.googlesource.com/plugins/readonly/[_readonly_] plugin.
[#cons-backup-replicate]
@@ -162,9 +163,9 @@ link:https://gerrit.googlesource.com/plugins/pull-replication[pull-replication p
Best you use a filesystem supporting snapshots to create a backup archive
of such a replica.
-For 2.x Gerrit versions also set up a database slave for the data stored in the
+For 2.x Gerrit versions also set up a database replica for the data stored in the
SQL database. If you are using 2.16 and migrated to _NoteDb_ you may consider to
-skip setting up a database slave, instead take a backup of the database which only
+skip setting up a database replica, instead take a backup of the database which only
contains the current schema version in this case.
In addition you need to ensure that no write operations are in flight before you
take the replica offline. Otherwise the database backup might be inconsistent
@@ -176,15 +177,16 @@ Replication of repository deletions can be switched off using the
link:https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md[server option]
`remote.NAME.replicateProjectDeletions`.
-If you are using Gerrit slaves to offload read traffic you can use one of these
-slaves for creating backups.
+If you are using Gerrit replica to offload read traffic you can use one of these
+replica for creating backups.
[#cons-backup-offline]
-=== Take master offline for backup
+=== Take primary server offline for backup
-Shutdown the server before taking a backup. This is simple but means downtime
-for the users. Also crons and currently running cron jobs (e.g. repacking
-repositories) which affect the repositories may need to be shut down.
+Shut down the primary server handling write operations before taking a backup.
+This is simple but means downtime for the users. Also crons and currently
+running cron jobs (e.g. repacking repositories) which affect the repositories
+may need to be shut down.
[#backup-methods]
== Backup methods
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index fb35dc2227..1dd6720148 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -122,16 +122,16 @@ List visible projects:
$ ssh -p 29418 review.example.com gerrit ls-projects
platform/manifest
tools/gerrit
-tools/gwtorm
+tools/gitiles
$ curl http://review.example.com/projects/
platform/manifest
tools/gerrit
-tools/gwtorm
+tools/gitiles
$ curl http://review.example.com/projects/tools/
tools/gerrit
-tools/gwtorm
+tools/gitiles
----
Clone any project visible to the user:
diff --git a/Documentation/cmd-plugin-remove.txt b/Documentation/cmd-plugin-remove.txt
index f5fe56b2d6..012bf7b238 100644
--- a/Documentation/cmd-plugin-remove.txt
+++ b/Documentation/cmd-plugin-remove.txt
@@ -20,6 +20,7 @@ jars in the site path's `plugins` directory to `<plugin-jar-name>.disabled`.
* Caller must be a member of the privileged 'Administrators' group.
* link:config-gerrit.html#plugins.allowRemoteAdmin[plugins.allowRemoteAdmin]
must be enabled in `$site_path/etc/gerrit.config`.
+* Mandatory plugin cannot be disabled
== SCRIPTING
This command is intended to be used in scripts.
diff --git a/Documentation/config-accounts.txt b/Documentation/config-accounts.txt
index addada11fc..90150b1346 100644
--- a/Documentation/config-accounts.txt
+++ b/Documentation/config-accounts.txt
@@ -185,8 +185,6 @@ link:intro-user.html#preferences[general],
link:user-review-ui.html#diff-preferences[diff] and edit preferences:
----
-[general]
- showSiteHeader = false
[diff]
hideTopMenu = true
[edit]
@@ -412,10 +410,10 @@ stored in a local H2 database, but there is an extension point that
allows to plug in alternate implementations for storing the reviewed
flags. To replace the storage for reviewed flags a plugin needs to
implement the link:dev-plugins.html#account-patch-review-store[
-AccountPatchReviewStore] interface. E.g. to support a multi-master
-setup where reviewed flags should be replicated between the master
-nodes one could implement a store for the reviewed flags that is
-based on MySQL with replication.
+AccountPatchReviewStore] interface. E.g. to support a cluster setup with
+multiple primary servers handling write operations where reviewed flags should
+be replicated between the primary nodes one could implement a store for the
+reviewed flags that is based on MySQL with replication.
[[account-sequence]]
== Account Sequence
@@ -443,7 +441,7 @@ repository must be replicated:
* `refs/meta/external-ids` (external IDs)
* `refs/starred-changes/*` (star labels)
* `refs/sequences/accounts` (account sequence numbers, not needed for Gerrit
- slaves)
+ replicas)
GERRIT
------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index b39cd18e32..de1a67e5cb 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -582,7 +582,14 @@ it does not match, it is then validated against the `LDAP` password.
By default this is set to `LDAP` when link:#auth.type[`auth.type`] is `LDAP`
and `OAUTH` when link:#auth.type[`auth.type`] is `OAUTH`.
Otherwise, the default value is `HTTP`.
-
++
+When gitBasicAuthPolicy is set to `LDAP` or `HTTP_LDAP` and the user
+is authenticating with the LDAP username/password, the Git client config
+needs to have `http.cookieFile` set to a local file, otherwise every
+single call would trigger a full LDAP authentication and groups resolution
+which could introduce a noticeable latency on the overall execution
+and produce unwanted load to the LDAP server.
++
[[auth.gitOAuthProvider]]auth.gitOAuthProvider::
+
Selects the OAuth 2 provider to authenticate git over HTTP traffic with.
@@ -612,7 +619,9 @@ set to `${sAMAccountName.toLowerCase}`). It is important that for all
existing accounts this username is already in lower case. It is not
possible to convert the usernames of the existing accounts to lower
case because this would break the access to existing per-user
-branches and Gerrit provides no tool to do such a conversion.
+branches and Gerrit provides no tool to do such a conversion. Accounts
+created using the REST API or the `create-account` SSH command will
+be created with all lowercase characters, when this option is set.
+
Setting this parameter to `true` will prevent all users from login that
have a non-lower-case username.
@@ -833,7 +842,8 @@ changes for up to 1024 projects can be held in the cache.
+
Default value is 0 (disabled). It is disabled by default due to the fact
that change updates are not communicated between Gerrit servers. Hence
-this cache should be disabled in an multi-master/multi-slave setup.
+this cache should be disabled in a cluster setup using multiple primary
+or multiple replica nodes.
+
The cache should be flushed whenever the database changes table is modified
outside of Gerrit.
@@ -881,6 +891,12 @@ It is not recommended to change the in-memory attributes of this cache
away from the defaults. The cache may be persisted by setting
`diskLimit`, which is only recommended if cold start performance is
problematic.
++
+`external_ids_map` supports computing the new cache value based on a
+previously cached state. This applies modifications based on the Git
+diff and is almost always faster.
+`cache.external_ids_map.enablePartialReloads` turns this behavior on
+or off. The default is `true`.
cache `"git_tags"`::
+
@@ -1158,17 +1174,6 @@ Allow blame on side by side diff. If set to false, blame cannot be used.
+
Default is true.
-[[change.allowDrafts]]change.allowDrafts::
-+
-Legacy support for drafts workflow. If set to true, pushing a new change
-with draft option will create a private change. Pushing with draft option
-to an existing change will create change edit.
-+
-Enabling this option allows to push to the `refs/drafts/branch`. When
-disabled any push to `refs/drafts/branch` will be rejected.
-+
-Default is false.
-
[[change.api.excludeMergeableInChangeInfo]]change.api.excludeMergeableInChangeInfo::
+
If true, the mergeability bit in
@@ -1210,6 +1215,25 @@ in change tables and user dashboards.
+
By default 500.
+[[change.maxUpdates]]change.maxUpdates::
++
+Maximum number of updates to a change. Counts only updates to the main NoteDb
+meta ref; draft comments, robot comments, stars, etc. do not count towards the
+total.
++
+Many NoteDb operations require walking the entire change meta ref and loading
+its contents into memory, so changes with arbitrarily many updates may cause
+high CPU usage, memory pressure, persistent cache bloat, and other problems.
++
+The following operations are allowed even when a change is at the limit:
+* Abandon
+* Submit
+* Submit by push with `%submit`
+* Auto-close by pushing directly to the branch
+* Fix with link:rest-api-changes.html#fix-input[`expect_merged_as`]
++
+By default 1000.
+
[[change.maxSubmittableAtOnce]]change.maxSubmittableAtOnce::
+
Maximum number of changes that can be chained together in the same repository
@@ -1554,11 +1578,17 @@ line, separated by spaces.
Execute `java -jar gerrit.war daemon --help` to see all possible
options.
+[[container.replica]]container.replica::
++
+Used on Gerrit replica installations. If set to true the Gerrit JVM is
+called with the '--replica' switch, enabling replica mode. If no value is
+set (or any other value), Gerrit defaults to primary mode enabling write
+operations.
+
[[container.slave]]container.slave::
+
-Used on Gerrit slave installations. If set to true the Gerrit JVM is
-called with the '--slave' switch, enabling slave mode. If no value is
-set (or any other value), Gerrit defaults to master mode.
+Backward compatibility for link:#container.replica[`container.replica`]
+config setting.
[[container.startupTimeout]]container.startupTimeout::
+
@@ -1585,6 +1615,10 @@ If not set, defaults to '$site_path/bin/gerrit.war', or to
[[core]]
=== Section core
+[NOTE]
+The link:#jgitConfig[etc/jgit.config] file supports configuration of all JGit
+options.
+
[[core.packedGitWindowSize]]core.packedGitWindowSize::
+
Number of bytes of a pack file to load into memory in a single
@@ -2129,6 +2163,36 @@ A good name should be short but precise enough so that users can identify the in
+
Defaults to the full hostname of the Gerrit server.
+[[gerrit.experimentalRollingUpgrade]]gerrit.experimentalRollingUpgrade::
++
+Enable Gerrit rolling upgrade to the next version.
+For example if Gerrit v3.1 is version N (All-Projects:refs/meta/version=181)
+then its next version N+1 is v3.2 (All-Projects:refs/meta/version=183).
+Allow Gerrit to start even if the underlying schema version has been bumped to
+the next Gerrit version.
++
+Set to true if Gerrit is installed in
+[high-availability configuration](https://gerrit.googlesource.com/plugins/high-availability/+/refs/heads/master/README.md)
+during the rolling upgrade to the next version.
++
+By default false.
++
+The rolling upgrade process, at high level, assumes that Gerrit is installed
+on two or more nodes sharing the repositories over NFS. The upgrade is composed
+of the following steps:
++
+1. Set gerrit.experimentalRollingUpgrade to true on all Gerrit masters
+2. Set the first master unhealthy
+3. Shutdown the first master and [upgrade](install.html#init) to the next version
+4. Startup the first master, wait for the online reindex to complete
+5. Verify the the first master upgrade is successful and online reindex is complete
+6. Set the first master healthy
+7. Repeat steps 2. to 6. for all the other Gerrit nodes
++
+[WARNING]
+Rolling upgrade may or may not be possible depending on the changes introduced
+by the target version of the upgrade. Refer to the release notes and check whether
+the rolling upgrade is possible or not and the associated constraints.
[[gerrit.serverId]]gerrit.serverId::
+
@@ -2833,28 +2897,28 @@ Defaults to false.
==== Subsection index.scheduledIndexer
This section configures periodic indexing. Periodic indexing is
-intended to run only on slaves and only updates the group index.
-Replication to slaves happens on Git level so that Gerrit is not aware
-of incoming replication events. But slaves need an updated group index
+intended to run only on replicas and only updates the group index.
+Replication to replicas happens on Git level so that Gerrit is not aware
+of incoming replication events. But replicas need an updated group index
to resolve memberships of users for ACL validation. To keep the group
-index in slaves up-to-date the Gerrit slave periodically scans the
+index in replicas up-to-date the Gerrit replica periodically scans the
group refs in the All-Users repository to reindex groups if they are
stale.
The scheduled reindexer is not able to detect group deletions that
-happened while the slave was offline, but since group deletions are not
+happened while the replica was offline, but since group deletions are not
supported this should never happen. If nevertheless groups refs were
-deleted while a slave was offline a full offline link:pgm-reindex.html[
+deleted while a replica was offline a full offline link:pgm-reindex.html[
reindex] must be performed.
-This section is only used if Gerrit runs in slave mode, otherwise it is
+This section is only used if Gerrit runs in replica mode, otherwise it is
ignored.
[[index.scheduledIndexer.runOnStartup]]index.scheduledIndexer.runOnStartup::
+
Whether the scheduled indexer should run once immediately on startup.
-If set to `true` the slave startup is blocked until all stale groups
-were reindexed. Enabling this allows to prevent that slaves that were
+If set to `true` the replica startup is blocked until all stale groups
+were reindexed. Enabling this allows to prevent that replicas that were
offline for a longer period of time run with outdated group information
until the first scheduled indexing is done.
+
@@ -2864,7 +2928,7 @@ Defaults to `true`.
+
Whether the scheduled indexer is enabled. If the scheduled indexer is
disabled you must implement other means to keep the group index for the
-slave up-to-date (e.g. by using ElasticSearch for the indexes).
+replica up-to-date (e.g. by using ElasticSearch for the indexes).
+
Defaults to `true`.
@@ -3004,10 +3068,6 @@ WARNING: Support for Elasticsearch is still experimental and is not recommended
for production use. For compatibility information, please refer to the
link:https://www.gerritcodereview.com/elasticsearch.html[project homepage].
-In Elasticsearch version 6.2 or later, the open and closed changes are merged
-into the default `_doc` type. The latter is also used for the accounts and groups
-indices starting with Elasticsearch 6.2.
-
Note that when Gerrit is configured to use Elasticsearch, the Elasticsearch
server(s) must be reachable during the site initialization.
@@ -3037,7 +3097,7 @@ Sets the number of shards to use per index. Refer to the
link:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings[
Elasticsearch documentation] for details.
+
-Defaults to 5 for Elasticsearch versions 5 and 6, and to 1 starting with Elasticsearch 7.
+Defaults to 1.
[[elasticsearch.numberOfReplicas]]elasticsearch.numberOfReplicas::
+
@@ -3077,6 +3137,31 @@ Password used to connect to Elasticsearch.
+
Not set by default.
+[[event]]
+=== Section event
+
+[[event.payload.listChangeOptions]]events.payload.listChangeOptions::
++
+List of options that Gerrit applies when rendering the payload of an
+internal event. This is the same set of options that are documented
+link:rest-api-changes.html#query-options[here].
++
+Depending on the setup, these events might get serialized using stream
+events.
++
+This can be set to the set of minimal options that consumers of Gerrit's
+events need. A minimal set would be (`SKIP_MERGEABLE`,`SKIP_DIFFSTAT`).
++
+Every option that gets added here will have a performance impact. The
+general recommendation is therefore to set this to a minimal set of
+required options.
++
+Defaults to all available options minus `CHANGE_ACTIONS`,
+`CURRENT_ACTIONS` and `CHECK`. This is a rich default to make sure the
+config is backwards compatible with what the default was before the config
+was added.
+
+
[[ldap]]
=== Section ldap
@@ -3652,6 +3737,14 @@ Enable remote installation, enable and disable of plugins over HTTP
and SSH. If set to true Administrators can install new plugins
remotely, or disable existing plugins. Defaults to false.
+[[plugins.mandatory]]plugins.mandatory::
++
+List of mandatory plugins. If a plugin from this list does not load,
+Gerrit will fail to start.
++
+Disabling and restarting of a mandatory plugin is rejected, but reloading
+of a mandatory plugin is still possible.
+
[[plugins.jsLoadTimeout]]plugins.jsLoadTimeout::
+
Set the timeout value for loading JavaScript plugins in Gerrit UI.
@@ -3674,17 +3767,6 @@ Name of the groups of users that are allowed to execute
If no groups are added, any user will be allowed to execute
'receive-pack' on the server.
-[[receive.allowPushToRefsChanges]]receive.allowPushToRefsChanges::
-+
-If true, it is possible to push directly to a change using `refs/changes/`.
-The possibility to push to `refs/changes/` is deprecated and it might be
-removed in future releases.
-See link:user-upload.html#manual_replacement_mapping[Manual Replacement Mapping].
-+
-False means pushing to `refs/changes/` is prohibited.
-+
-Defaults to false.
-
[[receive.certNonceSeed]]receive.certNonceSeed::
+
If set to a non-empty value and server-side signed push validation is
@@ -3967,6 +4049,14 @@ attempt fails. `<operationType>` can be `ACCOUNT_UPDATE`, `CHANGE_UPDATE`,
Defaults to link:#retry.timeout[`retry.timeout`]; unit suffixes are supported,
and assumes milliseconds if not specified.
+[[retry.retryWithTraceOnFailure]]retry.retryWithTraceOnFailure::
++
+Whether Gerrit should automatically retry operations on failure with tracing
+enabled. The automatically generated traces can help with debugging. Please
+note that only some of the REST endpoints support automatic retry.
++
+By default this is set to false.
+
[[rules]]
=== Section rules
@@ -4383,8 +4473,8 @@ through slow networks, gits with huge amount of refs can benefit from
SSH-compression since git does not compress the ref announcement during
handshake.
+
-Compression can be especially useful when Gerrit slaves are being used
-for the larger clones and fetches and the master server mostly takes
+Compression can be especially useful when Gerrit replicas are being used
+for the larger clones and fetches and the primary server mostly takes
small receive-packs.
+
By default, `false`.
@@ -4740,6 +4830,72 @@ used for suggesting accounts when adding members to a group.
+
By default 0.
+[[tracing]]
+=== Section tracing
+
+[[tracing.performanceLogging]]tracing.performanceLogging::
++
+Whether performance logging is enabled.
++
+When performance logging is enabled, performance events for some
+operations are collected in memory while a request is running. At the
+end of the request the performance events are handed over to the
+link:dev-plugins.html#performance-logger[PerformanceLogger] plugins.
+This means if performance logging is enabled, the memory footprint of
+requests is slightly increased.
++
+This setting has no effect if no
+link:dev-plugins.html#performance-logger[PerformanceLogger] plugins are
+installed, because then performance logging is always disabled.
++
+By default, true.
+
+[[tracing.traceid]]
+==== Subsection tracing.<trace-id>
+
+There can be multiple `tracing.<trace-id>` subsections to configure
+automatic tracing of requests. To be traced a request must match all
+conditions of one `tracing.<trace-id>` subsection. The subsection name
+is used as trace ID. Using this trace ID administrators can find
+matching log entries.
+
+[[tracing.traceid.requestType]]tracing.<trace-id>.requestType::
++
+Type of request for which request tracing should be always enabled (can
+be `GIT_RECEIVE`, `GIT_UPLOAD`, `REST` and `SSH`).
++
+May be specified multiple times.
++
+By default, unset (all request types are matched).
+
+[[tracing.traceid.requestUriPattern]]tracing.<trace-id>.requestUriPattern::
++
+Regular expression to match request URIs for which request tracing
+should be always enabled. Request URIs are only available for REST
+requests. Request URIs never include the '/a' prefix.
++
+May be specified multiple times.
++
+By default, unset (all request URIs are matched).
+
+[[tracing.traceid.account]]tracing.<trace-id>.account::
++
+Account ID of an account for which request tracing should be always
+enabled.
++
+May be specified multiple times.
++
+By default, unset (all accounts are matched).
+
+[[tracing.traceid.projectPattern]]tracing.<trace-id>.projectPattern::
++
+Regular expression to match project names for which request tracing
+should be always enabled.
++
+May be specified multiple times.
++
+By default, unset (all projects are matched).
+
[[trackingid]]
=== Section trackingid
@@ -5093,6 +5249,89 @@ Other files support site customization.
+
* link:config-themes.html[Themes]
+[[jgitConfig]]
+== File `etc/jgit.config`
+
+Gerrit uses the `$site_path/etc/jgit.config` file instead of the
+system-wide and user-global Git configuration for its runtime JGit
+configuration.
+
+Sample `etc/jgit.config` file:
+----
+[core]
+ trustFolderStat = false
+----
+
+[[jgit-gc]]
+=== Section gc
+
+Options in section gc are used when command link:cmd-gc.html[gerrit gc] is used
+or scheduled via options link:cmd-gc.html#gc.startTime[gc.startTime] and
+link:cmd-gc.html#gc.interval[gc.interval].
+
+[[gc.auto]]gc.auto::
++
+When there are approximately more than this many loose objects in the repository,
+auto gc will pack them. Some commands use this command to perform a light-weight
+garbage collection from time to time. The default value is 6700.
++
+Setting this to 0 disables not only automatic packing based on the number of
+loose objects, but any other heuristic auto gc will otherwise use to determine
+if there’s work to do, such as link:#gc.autoPackLimit[gc.autoPackLimit].
+
+[[gc.autodetach]]gc.autodetach::
++
+Makes auto gc run in a background thread. Default is `true`.
+
+[[gc.autopacklimit]]gc.autopacklimit::
++
+When there are more than this many packs that are not marked with `*.keep` file
+in the repository, auto gc consolidates them into one larger pack. The
+default value is 50. Setting this to 0 disables it. Setting `gc.auto` to 0 will
+also disable this.
+
+[[gc.packRefs]]gc.packRefs::
++
+This variable determines whether gc runs git pack-refs. The default is `true`.
+
+[[gc.reflogExpire]]gc.reflogExpire::
++
+Removes reflog entries older than this time; defaults to 90 days. The value "now"
+expires all entries immediately, and "never" suppresses expiration altogether.
+
+[[gc.reflogExpireUnreachable]]gc.reflogExpireUnreachable::
++
+Removes reflog entries older than this time and not reachable from the
+current tip; defaults to 30 days. The value "now" expires all entries immediately,
+and "never" suppresses expiration altogether.
+
+[[jgit-protocol]]
+=== Section protocol
+
+[[protocol.version]]protocol.version::
++
+If set, the server will accept requests from a client attempting to communicate
+using the specified protocol version. Otherwise communication falls back to version 0.
+If set in file `etc/jgit.config` this option will be used for all repositories of
+the site. It can be overridden for a given repository by configuring a different
+value in the repository's `config` file.
++
+Supported versions:
+0:: the original wire protocol.
+1:: the original wire protocol with the addition of a version string in the initial response from the server.
+2:: wire protocol version 2. Speeds up fetches from repositories with many refs by allowing the client
+ to specify which refs to list before the server lists them.
+
+[[jgit-receive]]
+=== Section receive
+
+[[receive.autogc]]receive.autogc::
++
+By default, `git-receive-pack` will run auto gc after receiving data from git-push and updating refs.
+You can stop it by setting this variable to `false`. This is recommended in gerrit to avoid the
+additional load this creates. Instead schedule gc using link:cmd-gc.html#gc.startTime[gc.startTime]
+and link:cmd-gc.html#gc.interval[gc.interval] or e.g. in a cron job that runs gc in a separate process.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-groups.txt b/Documentation/config-groups.txt
index 4db4cb367c..afabbfce10 100644
--- a/Documentation/config-groups.txt
+++ b/Documentation/config-groups.txt
@@ -103,7 +103,7 @@ Group] reindex the affected groups manually.
== Replication
-In a replicated setting (eg. backups and or master/slave
-configurations), all refs in the `All-Users` project must be copied
-onto all replicas, including `refs/groups/*`, `refs/meta/group-names`
-and `refs/sequences/groups`.
+In a replicated setting (eg. backups and or primary/replica configurations), all
+refs in the `All-Users` project on primary nodes must be copied onto all
+replicas, including `refs/groups/*`, `refs/meta/group-names` and
+`refs/sequences/groups`.
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index ff43520962..193a96f6b7 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -262,6 +262,12 @@ the past and affect submission somehow.
Defaults to true.
+[[label_copyAnyScore]]
+=== `label.Label-Name.copyAnyScore`
+
+If true, any score for the label is copied forward when a new patch
+set is uploaded. Defaults to false.
+
[[label_copyMinScore]]
=== `label.Label-Name.copyMinScore`
@@ -297,12 +303,13 @@ Defaults to false.
[[label_copyAllScoresOnTrivialRebase]]
=== `label.Label-Name.copyAllScoresOnTrivialRebase`
-If true, all scores for the label are copied forward when a new patch
-set is uploaded that is a trivial rebase. A new patch set is considered
-as trivial rebase if the commit message is the same as in the previous
-patch set and if it has the same code delta as the previous patch set.
-This is the case if the change was rebased onto a different parent, or
-if the parent did not change at all.
+If true, all scores for the label are copied forward when a new patch set is
+uploaded that is a trivial rebase. A new patch set is considered to be trivial
+rebase if the commit message is the same as in the previous patch set and if it
+has the same diff (including context lines) as the previous patch set. This is
+the case if the change was rebased onto a different parent and that rebase did
+not require git to perform any conflict resolution, or if the parent did not
+change at all.
This can be used to enable sticky approvals, reducing turn-around for
trivial rebases prior to submitting a change.
@@ -313,13 +320,13 @@ Defaults to false.
[[label_copyAllScoresIfNoCodeChange]]
=== `label.Label-Name.copyAllScoresIfNoCodeChange`
-If true, all scores for the label are copied forward when a new patch
-set is uploaded that has the same parent tree as the previous patch
-set and the same code delta as the previous patch set. This means only
-the commit message is different. This can be used to enable sticky
-approvals on labels that only depend on the code, reducing turn-around
-if only the commit message is changed prior to submitting a change.
-For the Verified label that is optionally installed by the
+If true, all scores for the label are copied forward when a new patch set is
+uploaded that has the same parent tree as the previous patch set and the same
+code diff (including context lines) as the previous patch set. This means only
+the commit message is different; the change hasn't even been rebased. This can
+be used to enable sticky approvals on labels that only depend on the code,
+reducing turn-around if only the commit message is changed prior to submitting a
+change. For the Verified label that is optionally installed by the
link:pgm-init.html[init] site program this is enabled by default.
Defaults to false.
diff --git a/Documentation/config-robot-comments.txt b/Documentation/config-robot-comments.txt
index 00776972b3..f5185a4868 100644
--- a/Documentation/config-robot-comments.txt
+++ b/Documentation/config-robot-comments.txt
@@ -36,7 +36,6 @@ Robot comments can be dropped by deleting this ref.
== Limitations
-* Robot comments are not displayed in the web UI yet.
* There is no support for draft robot comments, but robot comments are
always published and visible to everyone who can see the change.
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index 6f3a32d826..88de85e85f 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -43,9 +43,9 @@ will match any OpenID provider on the Internet:
* `http://` -- trust all OpenID providers using the HTTP protocol
* `https://` -- trust all OpenID providers using the HTTPS protocol
-To trust only Yahoo!:
+To trust only Launchpad:
----
- git config --file $site_path/etc/gerrit.config auth.trustedOpenID https://me.yahoo.com
+ git config --file $site_path/etc/gerrit.config auth.trustedOpenID https://login.launchpad.net/+openid
----
=== Database Schema
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index 24932a8d00..cb953c1288 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -122,6 +122,15 @@ Plugins implementing the `AccountActivationValidationListener` interface can
perform validation when an account is activated or deactivated via the Gerrit
REST API or the Java extension API.
+[[review-comment-validation]]
+== Review comment validation
+
+
+The `CommentValidator` interface allows plugins to validate all review comments,
+i.e. inline comments, file comments and the review message. This works for the
+REST API, for `git push` when `--publish-comments` is used and for comments sent
+via email.
+
GERRIT
------
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 3f9ff2e84f..10ee7616bc 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -7,8 +7,9 @@ To build Gerrit from source, you need:
* A Linux or macOS system (Windows is not supported at this time)
* A JDK for Java 8|9|10|11|...
-* Python 2 or 3
-* Node.js
+* Python 3
+* link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm)]
+* Bower (`sudo npm install -g bower`)
* link:https://docs.bazel.build/versions/master/install.html[Bazel] -launched with
link:https://github.com/bazelbuild/bazelisk[Bazelisk]
* Maven
@@ -29,17 +30,31 @@ seamlessly uses Bazelisk, which then runs the proper `bazel` binary version.
[[java]]
=== Java
-[[java-10]]
-==== Java 10 support
+==== MacOS
-Java 10 (and newer) is supported through vanilla java toolchain
+On MacOS, ensure that "Java for MacOS X 10.5 Update 4" (or higher) is installed
+and that `JAVA_HOME` is set to the
+link:install.html#Requirements[required Java version].
+
+Java installations can typically be found in
+"/System/Library/Frameworks/JavaVM.framework/Versions".
+
+To check the installed version of Java, open a terminal window and run:
+
+`java -version`
+
+[[java-12]]
+==== Java 12 support
+
+Java 12 (and newer) is supported through vanilla java toolchain
link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option].
-To build Gerrit with Java 10 and newer, specify vanilla java toolchain and
+To build Gerrit with Java 12 and newer, specify vanilla java toolchain and
provide the path to JDK home:
```
$ bazel build \
- --define=ABSOLUTE_JAVABASE=<path-to-java-10> \
+ --define=ABSOLUTE_JAVABASE=<path-to-java-12> \
+ --javabase=@bazel_tools//tools/jdk:absolute_javabase \
--host_javabase=@bazel_tools//tools/jdk:absolute_javabase \
--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla \
--java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla \
@@ -51,7 +66,7 @@ bazel test runs the test using the target javabase:
```
$ bazel test \
- --define=ABSOLUTE_JAVABASE=<path-to-java-10> \
+ --define=ABSOLUTE_JAVABASE=<path-to-java-12> \
--javabase=@bazel_tools//tools/jdk:absolute_javabase \
--host_javabase=@bazel_tools//tools/jdk:absolute_javabase \
--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla \
@@ -64,7 +79,7 @@ they could be added to ~/.bazelrc resource file:
```
$ cat << EOF > ~/.bazelrc
-build --define=ABSOLUTE_JAVABASE=<path-to-java-10>
+build --define=ABSOLUTE_JAVABASE=<path-to-java-12>
build --javabase=@bazel_tools//tools/jdk:absolute_javabase
build --host_javabase=@bazel_tools//tools/jdk:absolute_javabase
build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla
@@ -75,36 +90,24 @@ EOF
Now, invoking Bazel with just `bazel build :release` would include
all those options.
-Note that the follow option must be added to `container.javaOptions`
-in `$gerrit_site/etc/gerrit.config` to run Gerrit with Java 10|11|...:
-
-```
-[container]
- javaOptions = --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
-```
-
-[[java-9]]
-==== Java 9 support
+[[java-11]]
+==== Java 11 support
-Java 9 is supported through alternative java toolchain
+Java 11 is supported through alternative java toolchain
link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option].
-The Java 9 support is backwards compatible. Java 8 is still the default.
-To build Gerrit with Java 9, specify JDK 9 java toolchain:
+To build Gerrit with Java 11, specify JDK 11 java toolchain:
```
$ bazel build \
- --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java9 \
- --java_toolchain=@bazel_tools//tools/jdk:toolchain_java9 \
+ --host_javabase=@bazel_tools//tools/jdk:remote_jdk11 \
+ --javabase=@bazel_tools//tools/jdk:remote_jdk11 \
+ --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 \
+ --java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 \
:release
```
-Note that the follow option must be added to `container.javaOptions`
-in `$gerrit_site/etc/gerrit.config` to run Gerrit with Java 9:
-
-```
-[container]
- javaOptions = --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
-```
+=== Node.js and npm packages
+See link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/README.md#installing-node_js-and-npm-packages[Installing Node.js and npm packages].
[[build]]
== Building on the Command Line
@@ -117,10 +120,6 @@ To build the Gerrit web application:
bazel build gerrit
----
-[NOTE]
-PolyGerrit UI may require additional tools (such as npm). Please read
-the polygerrit-ui/README.md for more info.
-
The output executable WAR will be placed in:
----
@@ -219,13 +218,6 @@ The output JAR file will be be placed in:
Note that when building an individual plugin, the `core.zip` package
is not regenerated.
-To build with all Error Prone warnings activated, run:
-
-----
- bazel build --java_toolchain //tools:error_prone_warnings_toolchain //...
-----
-
-
[[IDEs]]
== Using an IDE.
@@ -254,7 +246,7 @@ refreshed and missing dependency JARs can be downloaded by running
with BUILD Files` button of link:https://ij.bazel.build[Bazel plugin].
[[documentation]]
-=== Documentation
+== Documentation
To build only the documentation for testing or static hosting:
@@ -323,6 +315,12 @@ To exclude tests that require a Docker host:
bazel test --test_tag_filters=-docker //...
----
+To exclude tests that require very recent git client version:
+
+----
+ bazel test --test_tag_filters=-git-protocol-v2 //...
+----
+
To ignore cached test results:
----
@@ -343,6 +341,7 @@ The following values are currently supported for the group name:
* edit
* elastic
* git
+* git-protocol-v2
* git-upload-archive
* notedb
* pgm
@@ -419,16 +418,16 @@ repository has precedence.
== Building against unpublished Maven JARs
-To build against unpublished Maven JARs, like gwtorm or PrologCafe, the custom
-JARs must be installed in the local Maven repository (`mvn clean install`) and
+To build against unpublished Maven JARs, like PrologCafe, the custom JARs must
+be installed in the local Maven repository (`mvn clean install`) and
`maven_jar()` must be updated to point to the `MAVEN_LOCAL` Maven repository for
that artifact:
[source,python]
----
maven_jar(
- name = 'gwtorm',
- artifact = 'gwtorm:gwtorm:42',
+ name = 'prolog-runtime',
+ artifact = 'com.googlecode.prolog-cafe:prolog-runtime:42',
repository = MAVEN_LOCAL,
)
----
@@ -475,11 +474,18 @@ And corresponding WORKSPACE excerpt:
)
----
-[[consume-jgit-from-development-tree]]
+== Building against SNAPSHOT Maven JARs
-To consume the JGit dependency from the development tree, edit
-`lib/jgit/jgit.bzl` setting LOCAL_JGIT_REPO to a directory holding a
-JGit repository.
+To build against SNAPSHOT Maven JARs, the complete SNAPSHOT version must be used:
+
+[source,python]
+----
+ maven_jar(
+ name = "pac4j-core",
+ artifact = "org.pac4j:pac4j-core:3.5.0-SNAPSHOT-20190112.120241-16",
+ sha1 = "da2b1cb68a8f87bfd40813179abd368de9f3a746",
+ )
+----
[[bazel-local-caches]]
@@ -489,6 +495,10 @@ To accelerate builds, several caches are activated per default:
* ~/.gerritcodereview/bazel-cache/repository
* ~/.gerritcodereview/bazel-cache/cas
+The `downloaded-artifacts` cache can be relocated by setting the
+`GERRIT_CACHE_HOME` environment variable. The other two can be adjusted with
+`bazel build` options `--repository_cache` and `--disk_cache` respectively.
+
Currently none of these caches have a maximum size limit. See
link:https://github.com/bazelbuild/bazel/issues/5139[this bazel issue] for
details. Users should watch the cache sizes and clean them manually if
diff --git a/Documentation/dev-build-plugins.txt b/Documentation/dev-build-plugins.txt
index 60c6b9de0f..219861f6be 100644
--- a/Documentation/dev-build-plugins.txt
+++ b/Documentation/dev-build-plugins.txt
@@ -75,6 +75,12 @@ bazel-bin/plugins/<plugin-name>/<plugin-name>.jar
Some plugins describe their build process in `src/main/resources/Documentation/build.md`
file. It may worth checking.
+=== Error Prone checks
+
+Error Prone checks are enabled by default for core Gerrit and all core plugins. To
+enable the checks for custom plugins, add it in the `error_prone_packages` group
+in `tools/BUILD`.
+
=== Plugins with external dependencies ===
If the plugin has external dependencies, then they must be included from Gerrit's
diff --git a/Documentation/dev-cla.txt b/Documentation/dev-cla.txt
new file mode 100644
index 0000000000..5bc34a7737
--- /dev/null
+++ b/Documentation/dev-cla.txt
@@ -0,0 +1,26 @@
+= Gerrit Code Review - Contributor License Agreement
+
+In order to link:dev-community.html#how-to-contribute[contribute] to
+Gerrit a Contributor License Agreement must be completed before
+contributions are accepted. To view and accept the agreements do the
+following:
+
+. Click 'Sign In' at the top right corner of
+ https://gerrit-review.googlesource.com/
+. Sign In with your Google account
+. After signing in, go to the
+ link:https://gerrit-review.googlesource.com/#/settings/agreements[Agreements]
+ tab on the settings page
+. Click on 'New Contributor Agreement' and follow the instructions
+
+For reference, the actual agreements are linked below:
+
+* link:https://cla.developers.google.com/about/google-individual[Individual Agreement]
+* link:https://cla.developers.google.com/about/google-corporate[Corporate Agreement]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-community.txt b/Documentation/dev-community.txt
new file mode 100644
index 0000000000..4fb025f3fd
--- /dev/null
+++ b/Documentation/dev-community.txt
@@ -0,0 +1,70 @@
+= Gerrit Community
+
+Gerrit is developed as a
+link:https://gerrit-review.googlesource.com/[self-hosting open source project]
+and very much welcomes contributions from anyone with a
+link:dev-cla.html[contributor's agreement] on file with the project.
+
+[[project-information]]
+== Project Information
+
+* link:https://www.gerritcodereview.com/[Project Homepage]
+* link:https://www.gerritcodereview.com/codeofconduct.html[Code of Conduct]
+* link:https://www.gerritcodereview.com/releases-readme.html[Release Versions]
+* link:https://gerrit.googlesource.com/gerrit[Source]
+* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking]
+* link:https://gerrit-review.googlesource.com/q/status:open+project:gerrit[Change Review]
+* link:dev-design.html[System Design]
+* Processes
+** link:dev-processes.html#project-governance[Project Governance / Engineering Steering Committee]
+** link:#how-to-contribute[Contribution Processes]
+** link:dev-design-docs.html#review[Design doc reviews]
+** link:dev-processes.html#dev-in-stable-branches[Development in stable branches]
+** link:dev-processes.html#backporting[Backporting to stable branches]
+** link:dev-processes.html#security-issues[Dealing with Security Issues]
+** link:dev-processes.html#upgrading-libraries[Upgrading Libraries]
+** link:dev-processes.html#deprecating-features[Deprecating features]
+* Roles
+** link:dev-roles.html#supporter[Supporter]
+** link:dev-roles.html#contributor[Contributor]
+** link:dev-roles.html#maintainer[Maintainer]
+** link:dev-roles.html#mentor[Mentor]
+** link:dev-roles.html#steering-committee-member[Engineering Steering Committee Member]
+** link:dev-roles.html#community-manager[Community Manager]
+** link:dev-roles.html#release-manager[Release Manager]
+
+[[how-to-contribute]]
+== How to Contribute
+* link:dev-cla.html[Contributor License Agreement]
+* link:dev-contributing.html#contribution-processes[Contribution Processes]
+** link:dev-contributing.html#lightweight-contribution-process[Lightweight Contribution Process]
+** link:dev-contributing.html#design-driven-contribution-process[Design-Driven Contribution Process]
+** link:dev-contributing.html#mentorship[Mentorship]
+* link:dev-design-docs.html[Design Docs]
+* link:dev-readme.html[Developer Setup]
+* link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui[Polymer Frontend Developer Setup]
+* link:dev-crafting-changes.html[Crafting Changes]
+* link:dev-starter-projects.html[Starter Projects]
+
+[[plugin-development]]
+== Plugin Development
+* link:dev-plugins-lifecycle.html[Plugin Lifecycle]
+* link:dev-plugins.html[Developing Plugins]
+* link:dev-build-plugins.html[Building Gerrit plugins]
+* link:js-api.html[JavaScript Plugin API]
+* link:config-validation.html[Validation Interfaces]
+* link:dev-stars.html[Starring Changes]
+* link:quota.html[Quota Enforcement]
+
+[[maintainer]]
+== Maintainer
+* link:dev-release.html[Making a Gerrit Release]
+* link:dev-release-subproject.html[Making a Release of a Gerrit Subproject]
+* link:https://www.gerritcodereview.com/publishing.html[Publish Gerrit Homepage]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 0a7c1003eb..49f8fcf45e 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -1,410 +1,331 @@
= Gerrit Code Review - Contributing
-== Introduction
-Gerrit is developed as a
-link:https://gerrit-review.googlesource.com/[self-hosting open source project]
-and very much welcomes contributions from anyone with a contributor's
-agreement on file with the project.
-
+[[cla]]
== Contributor License Agreement
-A Contributor License Agreement must be completed before contributions
-are accepted. To view and accept the agreements do the following:
-
-* Click 'Sign In' at the top right corner of https://gerrit-review.googlesource.com/
-* Sign In with your Google account
-* After signing in, go to the
-link:https://gerrit-review.googlesource.com/#/settings/agreements[Agreements]
-tab on the settings page
-* Click 'New Contributor Agreement' and follow the instructions
-
-For reference, the actual agreements are linked below:
-* link:https://cla.developers.google.com/about/google-individual[Individual Agreement]
-* link:https://cla.developers.google.com/about/google-corporate[Corporate Agreement]
+In order to contribute to Gerrit a link:dev-cla.html[Contributor
+License Agreement] must be completed before contributions are accepted.
+
+[[contribution-processes]]
+== Contribution Processes
+
+The Gerrit project offers two contribution processes:
+
+* link:#lightweight-contribution-process[Lightweight Contribution
+ Process]
+* link:#design-driven-contribution-process[Design-Driven Contribution
+ Process]
+
+The lightweight contribution process has little overhead and is best
+suited for small contributions (documentation updates, bug fixes, small
+features). Contributions are pushed as changes and
+link:dev-roles.html#maintainer[maintainers] review them adhoc.
+
+For large/complex features, it is required to follow the
+link:#design-driven-contribution-process[design-driven contribution
+process] and specify the feature in a link:dev-design-docs.html[design
+doc] before starting with the implementation.
+
+If link:dev-roles.html#contributor[contributors] choose the lightweight
+contribution process and during the review it turns out that the feature
+is too large or complex, link:dev-roles.html#maintainer[maintainers] can
+require to follow the design-driven contribution process instead.
+
+If you are in doubt which process is right for you, consult the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+mailing list.
+
+These contribution processes apply to everyone who contributes code to
+the Gerrit project, including link:dev-roles.html#maintainer[maintainers].
+When reading this document, keep in mind that maintainers are also
+contributors when they contribute code.
+
+If a new feature is large or complex, it is often difficult to find a
+maintainer who can take the time that is needed for a thorough review,
+and who can help with getting the changes submitted. To avoid that this
+results in unpredictable long waiting times during code review,
+contributors can ask for link:#mentorship[mentor support]. A mentor
+helps with timely code reviews and technical guidance. Doing the
+implementation is still the responsibility of the contributor.
+
+[[comparison]]
+=== Quick Comparison
+
+[options="header"]
+|======================
+| |Lightweight Contribution Process|Design-Driven Contribution Process
+|Overhead|low (write good commit message, address review comments)|
+high (write link:dev-design-docs.html[design doc] and get it approved)
+|Technical Guidance|by reviewer|during the design review and by
+reviewer/mentor
+|Review |adhoc (when reviewer is available)|by a dedicated mentor (if
+a link:#mentorship[mentor] was assigned)
+|Caveats |features may get vetoed after the implementation was already
+done, maintainers may make the design-driven contribution process
+required if a change gets too complex/large|design doc must stay open
+for a minimum of 10 calendar days, a mentor may not be available
+immediately
+|Applicable to|documentation updates, bug fixes, small features|
+large/complex features
+|======================
+
+[[lightweight-contribution-process]]
+=== Lightweight Contribution Process
+
+The lightweight contribution process has little overhead and is best
+suited for small contributions (documentation updates, bug fixes, small
+features). For large/complex features the
+link:#design-driven-contribution-process[design-driven contribution
+process] is required.
-== Code Review
As Gerrit is a code review tool, naturally contributions will
be reviewed before they will get submitted to the code base. To
start your contribution, please make a git commit and upload it
-for review to the main Gerrit review server. To help speed up the
-review of your change, review these guidelines before submitting
-your change. You can view the pending Gerrit contributions and
-their statuses
+for review to the link:https://gerrit-review.googlesource.com/[
+gerrit-review.googlesource.com] Gerrit server. To help speed up the
+review of your change, review these link:dev-crafting-changes.html[
+guidelines] before submitting your change. You can view the pending
+Gerrit contributions and their statuses
link:https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit[here].
Depending on the size of that list it might take a while for
your change to get reviewed. Naturally there are fewer
-approvers than contributors; so anything that you can do to
-ensure that your contribution will undergo fewer revisions
-will speed up the contribution process. This includes helping
-out reviewing other people's changes to relieve the load from
-the approvers. Even if you are not familiar with Gerrit's
-internals, it would be of great help if you can download, try
-out, and comment on new features. If it works as advertised,
-say so, and if you have the privileges to do so, go ahead
-and give it a +1 Verified. If you would find the feature
-useful, say so and give it a +1 code review.
-
-And finally, the quicker you respond to the comments of your
-reviewers, the quicker your change might get merged! Try to
-reply to every comment after submitting your new patch,
-particularly if you decided against making the suggested change.
-Reviewers don't want to seem like nags and pester you if you
-haven't replied or made a fix, so it helps them know if you
-missed it or decided against it.
-
-
-== Review Criteria
-
-Here are some hints as to what approvers may be looking for
-before approving or submitting changes to the Gerrit project.
-Let's start with the simple nit picky stuff. You are likely
-excited that your code works; help us share your excitement
-by not distracting us with the simple stuff. Thanks to Gerrit,
-problems are often highlighted and we find it hard to look
-beyond simple spacing issues. Blame it on our short attention
-spans, we really do want your code.
-
-
-[[commit-message]]
-=== Commit Message
-
-It is essential to have a good commit message if you want your
-change to be reviewed.
-
- * Keep lines no longer than 72 chars
- * Start with a short one line summary
- * Followed by a blank line
- * Followed by one or more explanatory paragraphs
- * Use the present tense (fix instead of fixed)
- * Use the past tense when describing the status before this commit
- * Include a `Bug: Issue <#>` line if fixing a Gerrit issue, or a
- `Feature: Issue <#>` line if implementing a feature request.
- * Include a `Change-Id` line
-
-=== Setting up Vim for Git commit message
-
-Git uses Vim as the default commit message editor. Put this into your
-`$HOME/.vimrc` file to configure Vim for Git commit message formatting
-and writing:
-
-====
- " Enable spell checking, which is not on by default for commit messages.
- au FileType gitcommit setlocal spell
-
- " Reset textwidth if you've previously overridden it.
- au FileType gitcommit setlocal textwidth=72
-====
-
-
-[[git_commit_settings]]
-=== A sample good Gerrit commit message:
-====
- Add sample commit message to guidelines doc
-
- The original patch set for the contributing guidelines doc did not
- include a sample commit message, this new patchset does. Hopefully this
- makes things a bit clearer since examples can sometimes help when
- explanations don't.
-
- Note that the body of this commit message can be several paragraphs, and
- that I word wrap it at 72 characters. Also note that I keep the summary
- line under 50 characters since it is often truncated by tools which
- display just the git summary.
-
- Bug: Issue 98765605
- Change-Id: Ic4a7c07eeb98cdeaf44e9d231a65a51f3fceae52
-====
-
-The `Change-Id` line is, as usual, created by a local git hook. To install it,
-simply copy it from the checkout and make it executable:
-
-====
- cp ./gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg .git/hooks/
- chmod +x .git/hooks/commit-msg
-====
-
-If you are working on core plugins, you will also need to install the
-same hook in the submodules:
-
-====
- export hook=$(pwd)/.git/hooks/commit-msg
- git submodule foreach 'cp -p "$hook" "$(git rev-parse --git-dir)/hooks/"'
-====
-
-
-To set up git's remote for easy pushing, run the following:
-
-====
- git remote add gerrit https://gerrit.googlesource.com/gerrit
-====
-
-The HTTPS access requires proper username and password; this can be obtained
-by clicking the 'Obtain Password' link on the
-link:https://gerrit-review.googlesource.com/#/settings/http-password[HTTP
-Password tab of the user settings page].
-
-Alternately, you may use the
-link:https://pypi.org/project/git-review/[git-review] tool to submit changes
-to Gerrit. If you do, it will set up the Change-Id hook and `gerrit` remote
-for you. You will still need to do the HTTP access step.
-
-[[style]]
-=== Style
-
-This project has a policy of Eclipse's warning free code. Eclipse
-configuration is added to git and we expect the changes to be
-warnings free.
-
-We do not ask you to use Eclipse for editing, obviously. We do ask you
-to provide Eclipse's warning free patches only. If for some reasons, you
-are not able to set up Eclipse and verify, that your patch hasn't
-introduced any new Eclipse warnings, mention this in a comment to your
-change, so that reviewers will do it for you. Yes, the way to go is to
-extend gerrit CI to take care of this, but it's not yet implemented.
-
-Gerrit follows the
-link:https://google.github.io/styleguide/javaguide.html[Google Java Style
-Guide].
-
-To format Java source code, Gerrit uses the
-link:https://github.com/google/google-java-format[`google-java-format`]
-tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
-link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
-tool (version 3.5.0). Unused dependencies are found and removed using the
-link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`]
-build tool, a sibling of `buildifier`.
-
-These tools automatically apply format according to the style guides; this
-streamlines code review by reducing the need for time-consuming, tedious,
-and contentious discussions about trivial issues like whitespace.
-
-You may download and run `google-java-format` on your own, or you may
-run `./tools/setup_gjf.sh` to download a local copy and set up a
-wrapper script. If you run your own copy, please use the same version,
-as there may be slight differences between versions.
-
-When to use `final` modifier and when not (in new code):
-
-Always:
-
- * final fields: marking fields as final forces them to be
- initialized in the constructor or at declaration
- * final static fields: clearly communicates the intent
- * to use final variables in inner anonymous classes
-
-Optional:
-
- * final classes: use when appropriate, e.g. API restriction
- * final methods: similar to final classes
-
-Never:
-
- * local variables: it clutters the code, and makes the code less
- readable. When copying old code to new location, finals should
- be removed
- * method parameters: similar to local variables
-
-=== Code Organization
-
-Do your best to organize classes and methods in a logical way.
-Here are some guidelines that Gerrit uses:
-
- * Ensure a standard copyright header is included at the top
- of any new files (copy it from another file, update the year).
- * Always place loggers first in your class!
- * Define any static interfaces next in your class.
- * Define non static interfaces after static interfaces in your
- class.
- * Next you should define static types, static members, and
- static methods, in decreasing order of visibility (public to private).
- * Finally instance types, instance members, then constructors,
- and then instance methods.
- * Some common exceptions are private helper static methods, which
- might appear near the instance methods which they help (but may
- also appear at the top).
- * Getters and setters for the same instance field should usually
- be near each other barring a good reason not to.
- * If you are using assisted injection, the factory for your class
- should be before the instance members.
- * Annotations should go before language keywords (`final`, `private`, etc) +
- Example: `@Assisted @Nullable final type varName`
- * Prefer to open multiple AutoCloseable resources in the same
- try-with-resources block instead of nesting the try-with-resources
- blocks and increasing the indentation level more than necessary.
-
-Wow that's a lot! But don't worry, you'll get the habit and most
-of the code is organized this way already; so if you pay attention
-to the class you are editing you will likely pick up on it.
-Naturally new classes are a little harder; you may want to come
-back and consult this section when creating them.
-
-
-=== Design
-
-Here are some design level objectives that you should keep in mind
-when coding:
-
- * Most client pages should perform only one RPC to load so as to
- keep latencies down. Exceptions would apply to RPCs which need
- to load large data sets if splitting them out will help the
- page load faster. Generally page loads are expected to complete
- in under 100ms. This will be the case for most operations,
- unless the data being fetched is not using Gerrit's caching
- infrastructure. In these slower cases, it is worth considering
- mitigating this longer load by using a second RPC to fill in
- this data after the page is displayed (or alternatively it might
- be worth proposing caching this data).
- * `@Inject` should be used on constructors, not on fields. The
- current exceptions are the ssh commands, these were implemented
- earlier in Gerrit's development. To stay consistent, new ssh
- commands should follow this older pattern; but eventually these
- should get converted to eliminate this exception.
- * Don't leave repository objects (git or schema) open. A .close()
- after every open should be placed in a finally{} block.
- * Don't leave UI components, which can cause new actions to occur,
- enabled during RPCs which update Git repositories, including NoteDb.
- This is to prevent people from submitting actions more than once
- when operating on slow links. If the action buttons are disabled,
- they cannot be resubmitted and the user can see that Gerrit is still
- busy.
- * ...and so is Guava (previously known as Google Collections).
-
-
-=== Tests
-
- * Tests for new code will greatly help your change get approved.
-
-
-=== Change Size/Number of Files Touched
-
-And finally, I probably cannot say enough about change sizes.
-Generally, smaller is better, hopefully within reason. Do try to
-keep things which will be confusing on their own together,
-especially if changing one without the other will break something!
-
- * If a new feature is implemented and it is a larger one, try to
- identify if it can be split into smaller logical features; when
- in doubt, err on the smaller side.
- * Separate bug fixes from feature improvements. The bug fix may
- be an easy candidate for approval and should not need to wait
- for new features to be approved. Also, combining the two makes
- reviewing harder since then there is no clear line between the
- fix and the feature.
- * Separate supporting refactoring from feature changes. If your
- new feature requires some refactoring, it helps to make the
- refactoring a separate change which your feature change
- depends on. This way, reviewers can easily review the refactor
- change as a something that should not alter the current
- functionality, and feel more confident they can more easily
- spot errors this way. Of course, it also makes it easier to
- test and locate later on if an unfortunate error does slip in.
- Lastly, by not having to see refactoring changes at the same
- time, it helps reviewers understand how your feature changes
- the current functionality.
- * Separate logical features into separate changes. This
- is often the hardest part. Here is an example: when adding a
- new ability, make separate changes for the UI and the ssh
- commands if possible.
- * Do only what the commit message describes. In other words, things which
- are not strictly related to the commit message shouldn't be part of
- a change, even trivial things like externalizing a string somewhere
- or fixing a typo. This helps keep `git blame` more useful in the future
- and it also makes `git revert` more useful.
- * Use topics to link your separate changes together.
-
-[[process]]
-== Process
-
-[[dev-in-stable-branches]]
-=== Development in stable branches
-
-As their name suggests stable branches are intended to be stable. This means that generally
-only bug-fixes should be done on stable branches, however this is not strictly enforced and
-exceptions may apply:
-
- * When a stable branch is initially created to prepare a new release the Gerrit community
- discusses on the mailing list if there are pending features which should still make it into the
- release. Those features are blocking the release and should be implemented on the stable
- branch before the first release candidate is created.
- * To stabilize the code before doing a major release several release candidates are created. Once
- the first release candidate was done no more features should be accepted on the stable branch.
- If more features are found to be required they should be discussed with the Gerrit maintainers
- and should only be allowed if the risk of breaking things is considered to be low.
- * Once a major release is done only bug-fixes and documentation updates should be done on the
- stable branch. These updates will be included in the next minor release.
- * For minor releases new features are only acceptable if they are important to the Gerrit
- community, if they are backwards compatible and the risk of breaking things is low and if there
- are no objections from the Gerrit community.
- * In cases of doubt it's the responsibility of the release maintainer to evaluate the risk of new
- features and make a decision based on these rules and opinions from the Gerrit community.
- * The older a stable branch is the more stable it should be. This means old stable branches
- should only receive bug-fixes that are either important or low risk. Security fixes, including
- security updates for third party dependencies, are always considered as important and hence can
- always be done on stable branches.
-
-=== Backporting to stable branches
-
-From time to time bug fix releases are made for existing stable branches.
-
-Developers concerned with stable branches are encouraged to backport or push fixes to these
-branches, even if no new release is planned. Backporting features is only possible in compliance
-with the rules link:#dev-in-stable-branches[above].
-
-Fixes that are known to be needed for a particular release should be pushed for review on that
-release's stable branch. They will then be included into the master branch when the stable branch
-is merged back.
-
-=== Finding starter projects to work on
-
-We have created a
-link:https://bugs.chromium.org/p/gerrit/issues/list?can=2&q=label%3AStarterProject[StarterProject]
-category in the issue tracker and try to assign easy hack projects to it. If in
-doubt, do not hesitate to ask on the developer
-link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list].
-
-=== Upgrading Libraries
-
-Gerrit's library dependencies should only be upgraded if the new version contains
-something we need in Gerrit. This includes new features, API changes as well as bug
-or security fixes.
-An exception to this rule is that right after a new Gerrit release was branched
-off, all libraries should be upgraded to the latest version to prevent Gerrit
-from falling behind. Doing those upgrades should conclude at the latest two
-months after the branch was cut. This should happen on the master branch to ensure
-that they are vetted long enough before they go into a release and we can be sure
-that the update doesn't introduce a regression.
-
-[[deprecating-features]]
-=== Deprecating features
-
-Gerrit should be as stable as possible and we aim to add only features that last.
-However, sometimes we are required to deprecate and remove features to be able
-to move forward with the project and keep the code-base clean. The following process
-should serve as a guideline on how to deprecate functionality in Gerrit. Its purpose
-is that we have a structured process for deprecation that users, administrators and
-developers can agree and rely on.
-
-General process:
-
- * Make sure that the feature (e.g. a field on the API) is not needed anymore or blocks
- further development or improvement. If in doubt, consult the mailing list.
- * If you can provide a schema migration that moves users to a comparable feature, do
- so and stop here.
- * Mark the feature as deprecated in the documentation and release notes.
- * If possible, mark the feature deprecated in any user-visible interface. For example,
- if you are deprecating a Git push option, add a message to the Git response if
- the user provided the option informing them about deprecation.
- * Annotate the code with `@Deprecated` and `@RemoveAfter(x.xx)` if applicable.
- Alternatively, use `// DEPRECATED, remove after x.xx` (where x.xx is the version
- number that has to be branched off before removing the feature)
- * Gate the feature behind a config that is off by default (forcing admins to turn
- the deprecated feature on explicitly).
- * After the next release was branched off, remove any code that backed the feature.
-
-You can optionally consult the mailing list to ask if there are users of the feature you
-wish to deprecate. If there are no major users, you can remove the feature without
-following this process and without the grace period of one release.
+link:dev-roles.html#maintainer[maintainers], that can approve changes,
+than link:dev-roles.html#contributor[contributors];
+so anything that you can do to ensure that your contribution will undergo fewer
+revisions will speed up the contribution process. This includes
+helping out reviewing other people's changes to relieve the load from
+the maintainers. Even if you are not familiar with Gerrit's internals,
+it would be of great help if you can download, try out, and comment on
+new features. If it works as advertised, say so, and if you have the
+privileges to do so, go ahead and give it a `+1 Verified`. If you
+would find the feature useful, say so and give it a `+1 Code Review`.
+
+And finally, the quicker you respond to the comments of your reviewers,
+the quicker your change might get merged! Try to reply to every
+comment after submitting your new patch, particularly if you decided
+against making the suggested change. Reviewers don't want to seem like
+nags and pester you if you haven't replied or made a fix, so it helps
+them know if you missed it or decided against it.
+
+[[design-driven-contribution-process]]
+=== Design-driven Contribution Process
+
+The design-driven contribution process applies to large/complex
+features.
+
+For large/complex features it is important to:
+
+* agree on the functionality and scope before spending too much time
+ on the implementation
+* ensure that they are in line with Gerrit's project scope and vision
+* ensure that they are well aligned with other features
+* think about possibilities how the feature could be evolved over time
+
+This is why for large/complex features it is required to describe the
+feature in a link:dev-design-docs.html[design doc] and get it approved
+by the link:dev-processes.html#steering-committee[steering committee],
+before starting the implementation.
+
+The design-driven contribution process has the following steps:
+
+* A link:dev-roles.html#contributor[contributor]
+ link:dev-design-docs.html#propose[proposes] a new feature by uploading
+ a change with a link:dev-design-docs.html[design doc].
+* The design doc is link:dev-design-docs.html#review[reviewed]
+ by interested parties from the community. The design review is public
+ and everyone can comment and raise concerns.
+* Design docs should stay open for a minimum of 10 calendar days so
+ that everyone has a fair chance to join the review.
+* Within 14 calendar days the contributor should hear back from the
+ link:dev-processes.html#steering-committee[steering committee]
+ whether the proposed feature is in scope of the project and if it can
+ be accepted.
+* To be submitted, the design doc needs to be approved by the
+ link:dev-processes.html#steering-committee[steering committee].
+* After the design was approved, the implementation is done by pushing
+ changes for review, see link:#lightweight-contribution-process[
+ lightweight contribution process]. Changes that are associated with
+ a design should all share a common hashtag. The contributor is the
+ main driver of the implementation and responsible that it is done.
+ Others from the Gerrit community are usually much welcome to help
+ with the implementation.
+
+In order to be accepted/submitted, it is not necessary that the design
+doc fully specifies all the details, but the idea of the feature and
+how it fits into Gerrit should be sufficiently clear (judged by the
+steering committee). Contributors are expected to keep the design doc
+updated and fill in gaps while they go forward with the implementation.
+We expect that implementing the feature and updating the design doc
+will be an iterative process.
+
+While the design doc is still in review, contributors may already start
+with the implementation (e.g. do some prototyping to demonstrate parts
+of the proposed design), but those changes should not be submitted
+while the design wasn't approved yet.
+
+By approving a design, the steering committee commits to:
+
+* Accepting the feature when it is implemented.
+* Supporting the feature by assigning a link:dev-roles.html#mentor[
+ mentor] (if requested, see link:#mentorship[mentorship]).
+
+If the implementation of a feature gets stuck and it's unclear whether
+the feature gets fully done, it should be discussed with the steering
+committee how to proceed. If the contributor cannot commit to finish
+the implementation and no other contributor can take over, changes that
+have already been submitted for the feature might get reverted so that
+there is no unused or half-finished code in the code base.
+
+For contributors, the design-driven contribution process has the
+following advantages:
+
+* By writing a design doc, the feature gets more attention. During the
+ design review, feedback from various sides can be collected, which
+ likely leads to improvements of the feature.
+* Once a design was approved by the
+ link:dev-processes.html#steering-committee[steering committee],
+ the contributor can be almost certain that the feature will be accepted.
+ Hence, there is only a low risk to invest into implementing a feature
+ and see it being rejected later during the code review, as it can
+ happen with the lightweight contribution process.
+* The contributor can link:#mentorship[get a dedicated mentor assigned]
+ who provides timely reviews and serves as a contact person for
+ technical questions and discussing details of the design.
+
+[[mentorship]]
+== Mentorship
+
+For features for which a link:dev-design-docs.html[design]
+has been approved (see link:#design-driven-contribution-process[design-driven
+contribution process]), contributors can gain the support of a mentor
+if they are committed to implement the feature.
+
+A link:dev-roles.html#mentor[mentor] helps with:
+
+* doing timely reviews
+* providing technical guidance during code reviews
+* discussing details of the design
+* ensuring that the quality standards are met (well documented,
+ sufficient test coverage, backwards compatible etc.)
+
+A feature can have more than one mentor. To be able to deliver the
+promised support, at least one of the mentors must be a
+link:dev-roles.html#maintainer[maintainer].
+
+Mentors are assigned by the link:dev-processes.html#steering-committee[
+steering committee]. To gain a mentor, ask for a
+mentor in the link:dev-design-doc-template.html#implementation-plan[Implementation
+Plan] section of the design doc or ask the steering
+committee after the design has been approved.
+
+Mentors may not be available immediately. In this case, the steering
+committee should include the approved feature into the roadmap or
+prioritize it in the backlog. This way, it is transparent for the
+contributor when they can expect to be able to work on the feature with
+mentor support.
+
+Once the implementation phase starts, the contributor is expected to do
+the implementation in a timely manner.
+
+For every mentorship, the end must be clearly defined. The design doc
+must specify:
+
+* a maximum time frame for the mentorship, after which the mentorship
+ automatically ends, even if the feature is not done yet
+* done criteria that define when the feature is done and the mentorship
+ ends
+
+If a feature is not finished in time, it should be discussed with the
+steering committee how to proceed. If the contributor cannot commit to
+finish the implementation in time and no other contributor can take
+over, changes that have already been submitted for the feature might
+get reverted so that there is no unused or half-finished code in the
+code base.
+
+[[esc-dd-evaluation]]
+== How the ESC evaluates design documents
+This section describes how the ESC evaluates design documents. It’s
+meant as a guideline rather than being prescriptive for both ESC
+members and contributors.
+
+=== General Process
+As part of the design process, the ESC makes a final decision if a
+design gets to be implemented. If there are multiple alternative
+solutions, the ESC will decide which solution can be implemented.
+
+The ESC should wait until all contributors had the chance to
+voice their opinion in review comments or by proposing alternative
+solutions. Due to the infrequent ESC meetings (every 2-4 weeks)
+the ESC might discuss documents in cases where the discussion is
+already advanced far enough, but not make a decision yet. In this
+case, contributors can still voice concerns or discuss alternatives.
+The decision can be at the next meeting or via email in between
+meetings.
+
+=== Evaluation
+Product/Vision fit
+
+Q: `Do we believe this feature belongs to Gerrit Code Review use-cases?`
+
+* Yes: Customizable dashboards
+* No: UI for managing an LDAP server
+
+Q: `Is the proposed solution aligned with Gerrit’s vision?`
+
+* Yes: Showing comments of older patch sets in newer patch sets to
+ keep track (core code review)
+* No: Implement a bug tracker in Gerrit (not core code review).
+
+=== Impact
+Q: `Will the new feature have a measurable, positive impact?`
+
+* Yes: Increased productivity, faster/smoother workflow, etc.
+* Yes: Better latency, more reliable system.
+* No: Unclear impact or lacking metrics to measure.
+
+=== Complexity
+Q: `Can we support/maintain this feature once it is in Gerrit?`
+
+* Yes: Code will fit into codebase, be well tested, easy to
+ understand.
+* No: Will add code or a workflow that is hard to understand
+ and easy to misinterpret.
+
+Q: `Is the proposed feature or rework adding unnecessary complexity?`
+
+* Yes: Adding a dependency on a well-supported library.
+* No: Adding a dependency on a library that is not widely used
+ or not actively maintained.
+
+=== Core vs. Plugin decision
+Q: `Would this fit better in a plugin?`
+
+* Yes:The proposed feature or rework is an implementation (e.g. Lucene
+ is an index implementation) of a generic concept that others
+ might want to implement differently.
+* Yes: The proposed feature or rework is very specific to a custom setup.
+* No: The proposed feature or rework is applicable to a wider user
+ base.
+* No: The proposed feature or rework is a `core code review feature`.
+
+=== Commitment
+Q: `Is someone willing to implement it?` (this question is
+especially important when reviewers propose alternative designs
+to the author’s own solution).
+
+* Yes: The author or someone else commits to implementing the
+ proposed solution.
+* Yes: If a mentorship is required, a mentor is willing to help.
+* No: Unclear ownership, mentorship or implementation plan.
+
+=== Community
+Q: `If in doubt, is there a substantial benefit to a long-standing
+community member with many users?`
+
+* The community shapes the future of Gerrit as a product. In
+ cases of doubt, the ESC can be more generous with long-standing
+ community members compared to `drive-by` contributions.
GERRIT
------
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
new file mode 100644
index 0000000000..2bbbc453e5
--- /dev/null
+++ b/Documentation/dev-crafting-changes.txt
@@ -0,0 +1,306 @@
+= Gerrit Code Review - Crafting Changes
+
+Here are some hints as to what approvers may be looking for
+before approving or submitting changes to the Gerrit project.
+Let's start with the simple nit picky stuff. You are likely
+excited that your code works; help us share your excitement
+by not distracting us with the simple stuff. Thanks to Gerrit,
+problems are often highlighted and we find it hard to look
+beyond simple spacing issues. Blame it on our short attention
+spans, we really do want your code.
+
+
+[[branch]]
+== Branch
+
+Gerrit provides support for more than one version, which naturally
+raises the question of which branch you should start your contribution
+on. There are no hard and fast rules, but below we try to outline some
+guidelines:
+
+* Genuinely new and/or disruptive features, should generally start on
+ `master`. Also consider submitting a
+ link:dev-design-docs.html[design doc] beforehand to allow discussion
+ by the ESC and the community.
+* Improvements of existing features should also generally go into
+ `master`. But we understand that if you cannot run `master`, it
+ might take a while until you could benefit from it. In that case,
+ start on the newest `stable-*` branch that you can run.
+* Bug-fixes should generally at least cover the oldest affected and
+ still supported version. If you're affected and run an even older
+ version, you're welcome to upload to that older version, even if
+ it is no longer officially supported, bearing in mind that
+ verification and release may happen only once merged upstream.
+
+Regardless of the above, changes might get moved to a different branch
+before being submitted or might get cherry-picked/re-merged to a
+different branch even after they've landed.
+
+For each of the above items, you'll find ad-hoc exceptions. The point
+is: We'd much rather see your code and fixes than not see them.
+
+
+[[commit-message]]
+== Commit Message
+
+It is essential to have a good commit message if you want your
+change to be reviewed.
+
+ * Keep lines no longer than 72 chars
+ * Start with a short one line summary
+ * Followed by a blank line
+ * Followed by one or more explanatory paragraphs
+ * Use the present tense (fix instead of fixed)
+ * Use the past tense when describing the status before this commit
+ * Include a `Bug: Issue <#>` line if fixing a Gerrit issue, or a
+ `Feature: Issue <#>` line if implementing a feature request.
+ * Include a `Change-Id` line
+
+[[vim-setup]]
+=== Setting up Vim for Git commit message
+
+Git uses Vim as the default commit message editor. Put this into your
+`$HOME/.vimrc` file to configure Vim for Git commit message formatting
+and writing:
+
+====
+ " Enable spell checking, which is not on by default for commit messages.
+ au FileType gitcommit setlocal spell
+
+ " Reset textwidth if you've previously overridden it.
+ au FileType gitcommit setlocal textwidth=72
+====
+
+
+[[git-commit-settings]]
+=== A sample good Gerrit commit message:
+====
+ Add sample commit message to guidelines doc
+
+ The original patch set for the contributing guidelines doc did not
+ include a sample commit message, this new patchset does. Hopefully this
+ makes things a bit clearer since examples can sometimes help when
+ explanations don't.
+
+ Note that the body of this commit message can be several paragraphs, and
+ that I word wrap it at 72 characters. Also note that I keep the summary
+ line under 50 characters since it is often truncated by tools which
+ display just the git summary.
+
+ Bug: Issue 98765605
+ Change-Id: Ic4a7c07eeb98cdeaf44e9d231a65a51f3fceae52
+====
+
+The `Change-Id` line is, as usual, created by a local git hook. To install it,
+simply copy it from the checkout and make it executable:
+
+====
+ cp ./gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg .git/hooks/
+ chmod +x .git/hooks/commit-msg
+====
+
+If you are working on core plugins, you will also need to install the
+same hook in the submodules:
+
+====
+ export hook=$(pwd)/.git/hooks/commit-msg
+ git submodule foreach 'cp -p "$hook" "$(git rev-parse --git-dir)/hooks/"'
+====
+
+
+To set up git's remote for easy pushing, run the following:
+
+====
+ git remote add gerrit https://gerrit.googlesource.com/gerrit
+====
+
+The HTTPS access requires proper username and password; this can be obtained
+by clicking the 'Obtain Password' link on the
+link:https://gerrit-review.googlesource.com/#/settings/http-password[HTTP
+Password tab of the user settings page].
+
+Alternately, you may use the
+link:https://pypi.org/project/git-review/[git-review] tool to submit changes
+to Gerrit. If you do, it will set up the Change-Id hook and `gerrit` remote
+for you. You will still need to do the HTTP access step.
+
+[[style]]
+== Style
+
+This project has a policy of Eclipse's warning free code. Eclipse
+configuration is added to git and we expect the changes to be
+warnings free.
+
+We do not ask you to use Eclipse for editing, obviously. We do ask you
+to provide Eclipse's warning free patches only. If for some reasons, you
+are not able to set up Eclipse and verify, that your patch hasn't
+introduced any new Eclipse warnings, mention this in a comment to your
+change, so that reviewers will do it for you. Yes, the way to go is to
+extend gerrit CI to take care of this, but it's not yet implemented.
+
+Gerrit follows the
+link:https://google.github.io/styleguide/javaguide.html[Google Java Style
+Guide].
+
+To format Java source code, Gerrit uses the
+link:https://github.com/google/google-java-format[`google-java-format`]
+tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
+link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
+tool (version 4.0.0). Unused dependencies are found and removed using the
+link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`]
+build tool, a sibling of `buildifier`.
+
+These tools automatically apply format according to the style guides; this
+streamlines code review by reducing the need for time-consuming, tedious,
+and contentious discussions about trivial issues like whitespace.
+
+You may download and run `google-java-format` on your own, or you may
+run `./tools/setup_gjf.sh` to download a local copy and set up a
+wrapper script. If you run your own copy, please use the same version,
+as there may be slight differences between versions.
+
+[[code-rules]]
+== Code Rules
+=== Final
+When to use `final` modifier and when not (in new code):
+
+Always:
+
+ * final fields: marking fields as final forces them to be
+ initialized in the constructor or at declaration
+ * final static fields: clearly communicates the intent
+ * to use final variables in inner anonymous classes
+
+Optional:
+
+ * final classes: use when appropriate, e.g. API restriction
+ * final methods: similar to final classes
+
+Never:
+
+ * local variables: it clutters the code, and makes the code less
+ readable. When copying old code to new location, finals should
+ be removed
+ * method parameters: similar to local variables
+
+=== Optional / Nullable
+Recommended:
+
+ * Optionals in arguments are discouraged (use @Nullable instead)
+ * Return types should be objects or Optionals of objects, but not null/nullable
+
+[[code-organization]]
+== Code Organization
+
+Do your best to organize classes and methods in a logical way.
+Here are some guidelines that Gerrit uses:
+
+ * Ensure a standard copyright header is included at the top
+ of any new files (copy it from another file, update the year).
+ * Always place loggers first in your class!
+ * Define any static interfaces next in your class.
+ * Define non static interfaces after static interfaces in your
+ class.
+ * Next you should define static types, static members, and
+ static methods, in decreasing order of visibility (public to private).
+ * Finally instance types, instance members, then constructors,
+ and then instance methods.
+ * Some common exceptions are private helper static methods, which
+ might appear near the instance methods which they help (but may
+ also appear at the top).
+ * Getters and setters for the same instance field should usually
+ be near each other barring a good reason not to.
+ * If you are using assisted injection, the factory for your class
+ should be before the instance members.
+ * Annotations should go before language keywords (`final`, `private`, etc) +
+ Example: `@Assisted @Nullable final type varName`
+ * Prefer to open multiple AutoCloseable resources in the same
+ try-with-resources block instead of nesting the try-with-resources
+ blocks and increasing the indentation level more than necessary.
+
+Wow that's a lot! But don't worry, you'll get the habit and most
+of the code is organized this way already; so if you pay attention
+to the class you are editing you will likely pick up on it.
+Naturally new classes are a little harder; you may want to come
+back and consult this section when creating them.
+
+[[design]]
+== Design
+
+Here are some design level objectives that you should keep in mind
+when coding:
+
+ * Most client pages should perform only one RPC to load so as to
+ keep latencies down. Exceptions would apply to RPCs which need
+ to load large data sets if splitting them out will help the
+ page load faster. Generally page loads are expected to complete
+ in under 100ms. This will be the case for most operations,
+ unless the data being fetched is not using Gerrit's caching
+ infrastructure. In these slower cases, it is worth considering
+ mitigating this longer load by using a second RPC to fill in
+ this data after the page is displayed (or alternatively it might
+ be worth proposing caching this data).
+ * `@Inject` should be used on constructors, not on fields. The
+ current exceptions are the ssh commands, these were implemented
+ earlier in Gerrit's development. To stay consistent, new ssh
+ commands should follow this older pattern; but eventually these
+ should get converted to eliminate this exception.
+ * Don't leave repository objects (git or schema) open. Use a
+ try-with-resources statement to ensure that repository objects get
+ closed after use.
+ * Don't leave UI components, which can cause new actions to occur,
+ enabled during RPCs which update Git repositories, including NoteDb.
+ This is to prevent people from submitting actions more than once
+ when operating on slow links. If the action buttons are disabled,
+ they cannot be resubmitted and the user can see that Gerrit is still
+ busy.
+
+[[tests]]
+== Tests
+
+ * Tests for new code will greatly help your change get approved.
+
+[[change-size]]
+== Change Size/Number of Files Touched
+
+And finally, I probably cannot say enough about change sizes.
+Generally, smaller is better, hopefully within reason. Do try to
+keep things which will be confusing on their own together,
+especially if changing one without the other will break something!
+
+ * If a new feature is implemented and it is a larger one, try to
+ identify if it can be split into smaller logical features; when
+ in doubt, err on the smaller side.
+ * Separate bug fixes from feature improvements. The bug fix may
+ be an easy candidate for approval and should not need to wait
+ for new features to be approved. Also, combining the two makes
+ reviewing harder since then there is no clear line between the
+ fix and the feature.
+ * Separate supporting refactoring from feature changes. If your
+ new feature requires some refactoring, it helps to make the
+ refactoring a separate change which your feature change
+ depends on. This way, reviewers can easily review the refactor
+ change as a something that should not alter the current
+ functionality, and feel more confident they can more easily
+ spot errors this way. Of course, it also makes it easier to
+ test and locate later on if an unfortunate error does slip in.
+ Lastly, by not having to see refactoring changes at the same
+ time, it helps reviewers understand how your feature changes
+ the current functionality.
+ * Separate logical features into separate changes. This
+ is often the hardest part. Here is an example: when adding a
+ new ability, make separate changes for the UI and the ssh
+ commands if possible.
+ * Do only what the commit message describes. In other words, things which
+ are not strictly related to the commit message shouldn't be part of
+ a change, even trivial things like externalizing a string somewhere
+ or fixing a typo. This helps keep `git blame` more useful in the future
+ and it also makes `git revert` more useful.
+ * Use topics to link your separate changes together.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-design-doc-conclusion-template.md b/Documentation/dev-design-doc-conclusion-template.md
new file mode 100644
index 0000000000..0625f2b90d
--- /dev/null
+++ b/Documentation/dev-design-doc-conclusion-template.md
@@ -0,0 +1,18 @@
+---
+title: "Design Doc - ${title} - Conclusion"
+sidebar: gerritdoc_sidebar
+permalink: design-doc-${folder-name}-conclusion.html
+hide_sidebar: true
+hide_navtoggle: true
+toc: false
+folder: design-docs/${folder-name}
+---
+
+# Conclusion
+
+Describe which decision was made and what were the reasons for it.
+
+## <a id="implementation-plan"> Implementation Plan
+
+If known, say who is driving the implementation, for when the
+implementation is planned and which priority it has.
diff --git a/Documentation/dev-design-doc-index-template.md b/Documentation/dev-design-doc-index-template.md
new file mode 100644
index 0000000000..10b4a8137a
--- /dev/null
+++ b/Documentation/dev-design-doc-index-template.md
@@ -0,0 +1,18 @@
+---
+title: "Design Doc - ${title}"
+sidebar: gerritdoc_sidebar
+permalink: design-doc-${folder-name}.html
+hide_sidebar: true
+hide_navtoggle: true
+toc: false
+folder: design-docs/${folder-name}
+---
+
+# Design Doc - ${title}
+
+* [Use Cases](use-cases.html)
+* [Solution - ${solution-name-1}](solution-1.html)
+* [Solution - ${solution-name-2}](solution-2.html)
+* ...
+* [Conclusion](conclusion.html)
+
diff --git a/Documentation/dev-design-doc-solution-template.md b/Documentation/dev-design-doc-solution-template.md
new file mode 100644
index 0000000000..8b2a8c0f9e
--- /dev/null
+++ b/Documentation/dev-design-doc-solution-template.md
@@ -0,0 +1,72 @@
+---
+title: "Design Doc - ${title} - Solution - ${solution-name}"
+sidebar: gerritdoc_sidebar
+permalink: design-doc-${folder-name}-solution-${solution-name}.html
+hide_sidebar: true
+hide_navtoggle: true
+toc: false
+folder: design-docs/${folder-name}
+---
+
+# Solution - ${solution-name}
+
+## <a id="overview"> Overview
+
+High-level overview; put details in the next section and background in
+the 'Background' section (see dev-design-doc-use-cases-template.txt).
+
+Should be understandable by engineers that are not working on Gerrit.
+
+If a solution is a variant of another solution, that other solution
+should be linked here.
+
+## <a id="detailed-design"> Detailed Design
+
+How does the overall design work? Details about the algorithms,
+storage format, APIs, etc., should be included here.
+
+For the initial review, it is ok for this to lack implementation
+details of minor importance.
+
+### <a id="scalability"> Scalability
+
+How does the solution scale?
+
+If applicable, consider:
+
+* data size increase
+* traffic increase
+* effects on replication across sites (master-replica and master-master)
+
+## <a id="alternatives-considered"> Alternatives Considered
+
+Within the scope of this solution you may need to describe what you did
+not do or why simpler approaches don't work. Mention other things to
+watch out for (if any).
+
+Do not describe alternative solutions in this section, as each solution
+should be described in a separate file.
+
+## <a id="pros-and-cons"> Pros and Cons
+
+Objectively list all points that speak in favor/against this solution.
+
+## <a id="implementation-plan"> Implementation Plan
+
+If known, say who would be willing to drive the implementation.
+
+It is possible to contribute solutions without having resources to do
+the implementation. In this case, say so here.
+
+If mentor support is desired, say so here. Also briefly describe any
+circumstances that can help with finding a suitable mentor.
+
+## <a id="time-estimation"> Time Estimation
+
+A rough itemized estimation of how much time it takes to implement this
+feature. Break down the feature into work items and estimate each item
+separately.
+
+If a mentor is assigned, this section must define a maximum time frame
+after which the mentorship automatically ends even if the feature isn't
+fully done yet.
diff --git a/Documentation/dev-design-doc-use-cases-template.md b/Documentation/dev-design-doc-use-cases-template.md
new file mode 100644
index 0000000000..02c2fb508b
--- /dev/null
+++ b/Documentation/dev-design-doc-use-cases-template.md
@@ -0,0 +1,48 @@
+---
+title: "Design Doc - ${title} - Use Cases"
+sidebar: gerritdoc_sidebar
+permalink: design-doc-${folder-name}-use-cases.html
+hide_sidebar: true
+hide_navtoggle: true
+toc: false
+folder: design-docs/${folder-name}
+---
+
+# Use Cases
+
+In a few sentences, describe the use-cases as interactions between a
+user and a system to attain particular goals.
+
+Should be understandable by anyone who is familiar with using Gerrit.
+
+Optionally, differentiate between primary and secondary use-cases.
+Secondary use-cases are related to the primary use-cases, but
+addressing them within the scope of this design is not mandatory. This
+means they may not be covered by all proposed solutions. Secondary
+use-cases that are not addressed by the concluded solution, may be
+discussed in separate design docs. In this case links to these design
+docs should be added here.
+
+Optionally, define non-goals.
+
+It is possible that use-cases are specific to custom setups (e.g. the
+multi-master setup at Google). In this case, say so here.
+
+## <a id="acceptance-criteria"> Acceptance Criteria
+
+Describe conditions that must be satisfied to consider the feature as
+done.
+
+If a mentor is assigned, the mentorship ends when this state is reached.
+Please note that a mentorship can also end earlier if the maximum time
+frame for the mentorship has exceeded (see section 'Time Estimation'
+in dev-design-doc-conclusion-template.txt).
+
+## <a id="background"> Background
+
+Stuff one needs to know to understand the use-cases (e.g. motivating
+examples, previous versions and problems, links to related
+changes/design docs, etc.).
+
+Note: this is background; do not write about your design or ideas to
+solve problems here.
diff --git a/Documentation/dev-design-docs.txt b/Documentation/dev-design-docs.txt
new file mode 100644
index 0000000000..a339da396a
--- /dev/null
+++ b/Documentation/dev-design-docs.txt
@@ -0,0 +1,153 @@
+= Gerrit Code Review - Design Docs
+
+For the link:dev-contributing.html#design-driven-contribution-process[
+design-driven contribution process] it is required to specify features
+upfront in a design doc.
+
+[[structure]]
+== Design Doc Structure
+
+A design doc should discuss the following aspects:
+
+* Use-Cases:
+ The interactions between a user and a system to attain particular
+ goals.
+* Acceptance Criteria
+ Conditions that must be satisfied to consider the feature as done.
+* Background:
+ Stuff one needs to know to understand the use-cases (e.g. motivating
+ examples, previous versions and problems, links to related
+ changes/design docs, etc.)
+* Possible Solutions:
+ Possible solutions with the pros and cons, and explanation of
+ implementation details.
+* Conclusion:
+ Which decision was made and what were the reasons for it.
+
+[[collaboration]]
+As community we want to collaborate on design docs as much as possible
+and write them together, in an iterative manner. To make this work well
+design docs are split into multiple files that can be written and
+refined by several persons in parallel:
+
+* `index.md`:
+ Entry file that links to the files below (also see
+ 'dev-design-doc-index-template.md').
+* `use-cases.md`:
+ Describes the use-cases, acceptance criteria and background (also see
+ 'dev-design-doc-use-cases-template.md').
+* `solution-<n>.md`:
+ Each possible solution (with the pros and cons, and implementation
+ details) is described in a separate file (also see
+ 'dev-design-doc-solution-template.md').
+* `conclusion.md`:
+ Describes the conclusion of the design discussion (also see
+ 'dev-design-doc-conclusion-template.md').
+
+[[expectation]]
+It is expected that:
+
+* An agreement on the use-cases is achieved before solutions are being
+ discussed in detail.
+* Anyone who has ideas for an alternative solution uploads a change
+ with a `solution-<n>.md` that describes their solution. In case of
+ doubt whether an idea is a refinement of an existing solution or an
+ alternative solution, it's up to the owner of the discussed solution
+ to decide if the solution should be updated, or if the proposer
+ should start a new alternative solution.
+* All possible solutions are fairly discussed with their pros and cons,
+ and treated equally until a conclusion is made.
+* Unrelated issues (judged by the design doc owner) that are identified
+ during discussions may be extracted into new design docs (initially
+ consisting only of an `index.md` and a `use-cases.md` file). Doing so
+ is optional yet can be done by either the design owner or reviewers.
+* Changes making iterative improvements can be submitted frequently
+ (e.g. additional uses-cases can be added later, solutions can be
+ submitted without describing implementation details, etc.).
+* After a conclusion has been approved contributors are expected to
+ keep the design doc updated and fill in gaps while they go forward
+ with the implementation.
+
+[[propose]]
+== How to propose a new design?
+
+To propose a new design, upload a change to the
+link:https://gerrit-review.googlesource.com/admin/repos/homepage[
+homepage] repository that adds a new folder under `pages/design-docs/`
+which contains at least an `index.md` and a `uses-cases.md` file (see
+link:#structure[design doc structure] above).
+
+Pushing a design doc for review requires to be a
+link:dev-roles.html#contributor[contributor].
+
+When contributing design docs, contributors should make clear whether
+they are committed to do the implementation. It is possible to
+contribute designs without having resources to do the implementation,
+but in this case the implementation is only done if someone volunteers
+to do it (which is not guaranteed to happen).
+
+Only very few maintainers actively watch out for uploaded design docs.
+To raise awareness you may want to send a notification to the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+mailing list about your uploaded design doc. But the discussion should
+not take place on the mailing list, comments should be made by reviewing
+the change in Gerrit.
+
+[[review]]
+== Design doc review
+
+Everyone in the link:dev-roles.html[Gerrit community] is welcome to
+take part in the design review and comment on the design. As such, every
+design reviewer is expected to respect the community
+link:https://www.gerritcodereview.com/codeofconduct.html[Code of Conduct].
+
+Ideas for alternative solutions should be uploaded as a change that
+describes the solution (see link:#collaboration[above]). This should be
+done as early as possible during the review process, so that related
+comment threads stop there and do not clutter the current review. It is up
+to the alternative reviews to then host their related comments.
+
+Verification should be based on the generated `jekyll` site using the
+local `docker`, rather than via the rendering in `gitiles` (via
+`gerrit-review`).
+
+Changes which make a conclusion on a design (changes that add/change
+the `conclusion.md` file, see link:#structure[Design Doc Structure])
+should stay open for a minimum of 10 calendar days so that everyone has
+a fair chance to see them. It is important that concerns regarding a
+feature are raised during this time frame since once a conclusion is
+approved and submitted the implementation may start immediately.
+
+Other design doc changes can and should be submitted quickly so that
+collaboration and iterative refinements work smoothly (see
+link:#collaboration[above]).
+
+For proposed features the contributor should hear back from the
+link:dev-processes.html#steering-committee[engineering steering
+committee] within 14 calendar days whether the proposed feature is in
+scope of the project and if it can be accepted.
+
+[[meetings]]
+=== Meeting discussions
+
+If the Gerrit review doesn't start efficiently enough, stalls, gets off-track
+too much or becomes overly complex, one can use a meeting to refocus it. From
+that review thread, the organizer can volunteer oneself, or be proposed (even
+requested) by a reviewer. link:https://www.gerritcodereview.com/members.html#community-managers[
+Community managers] may help facilitate that if ultimately necessary.
+
+[[watch-designs]]
+== How to get notified for new design docs?
+
+. Go to the
+ link:https://gerrit-review.googlesource.com/settings/#Notifications[
+ notification settings]
+. Add a project watch for the `homepage` repository with the following
+ query: `dir:pages/design-docs`
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index 69af18d3a6..0243a585be 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -178,17 +178,6 @@ has been migrated out of the database and into the git
repositories for each project.
-== Project Information
-
-Gerrit is developed as a self-hosting open source project:
-
-* link:https://www.gerritcodereview.com/[Project Homepage]
-* link:https://www.gerritcodereview.com/download/index.html[Release Versions]
-* link:https://gerrit.googlesource.com/gerrit[Source]
-* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking]
-* link:https://review.source.android.com/[Change Review]
-
-
== Internationalization and Localization
As a source code review system for open source projects, where the
@@ -204,8 +193,6 @@ Gerrit code base. Some portions of the code have tried to take
RTL into consideration, while others probably need to be modified
before translating the UI to an RTL language.
-* link:i18n-readme.html[Gerrit's i18n Support]
-
== Accessibility Considerations
@@ -268,11 +255,7 @@ change through the native Git protocol.
Gerrit integrates with any OpenID provider for user authentication,
making it easier for users to join a Gerrit site and manage their
-authentication credentials to it. To make use of Google Accounts
-as an OpenID provider easier, Gerrit has a shorthand "Sign in with
-a Google Account" link on its sign-in screen. Gerrit also supports
-a shorthand sign in link for Yahoo!. Other providers may also be
-supported more directly in the future.
+authentication credentials to it.
Site administrators may limit the range of OpenID providers to
a subset of "reliable providers". Users may continue to use
@@ -533,7 +516,7 @@ from an actual installation's performance.)
Because of the distributed nature of Git, end-users don't need to
contact the central Gerrit Code Review server very often. For `git
-fetch` traffic, link:pgm-daemon.html[slave mode] is known to be an
+fetch` traffic, link:pgm-daemon.html[replica mode] is known to be an
effective way to offload traffic from the main server, permitting it
to scale to a large user base without needing an excessive number of
cores in a single system.
@@ -640,29 +623,6 @@ logs may be mined for usage information. This is outside of the
scope of Gerrit.
-== Testing Plan
-
-Gerrit is currently manually tested through its web UI.
-
-JGit has a fairly extensive automated unit test suite. Most new
-changes to JGit are rejected unless corresponding automated unit
-tests are included.
-
-
-== Caveats
-
-Rietveld can't be used as it does not provide the "submit over the
-web" feature that Gerrit provides for Git.
-
-Gitosis can't be used as it does not provide any code review
-features, but it does provide basic access controls.
-
-Email based code review does not scale to a project as large and
-complex as Android. Most contributors at least need some sort of
-dashboard to keep track of any pending reviews, and some way to
-correlate updated revisions back to the comments written on prior
-revisions of the same logical change.
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 20484e68a7..bf35c3259d 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -228,6 +228,17 @@ than the default, say `0.8`, will make scenarios wait somewhat less than how the
Scenario development is often done using locally running Gerrit systems under test, which are
sometimes dockerized.
+==== Number of users
+
+The `number_of_users` property can be used to scale scenario steps to run with the specified number
+of concurrent users. The value of this property remains `1` by default. For example, this sets the
+number of concurrent users to 10:
+
+* `-Dcom.google.gerrit.scenarios.number_of_users=10`
+
+This will make scenarios that support the `number_of_users` property to inject that many users
+concurrently for load testing.
+
== How to run tests
Run all tests:
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 420151b140..bdd6360c7d 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -86,11 +86,6 @@ Java 8 is still the default:
* Add JRE, e.g.: directory: /usr/lib64/jvm/java-9-openjdk, name: java-9-openjdk-9
* Change execution environment for gerrit project to: JavaSE-9 (java-9-openjdk-9)
* Check that compiler compliance level in gerrit project is set to: 9
-* Add this parameter to VM argument for gerrit_daemin launcher:
-----
- --add-modules java.activation \
- --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
-----
[[Formatting]]
== Code Formatter Settings
@@ -98,7 +93,7 @@ Java 8 is still the default:
To format source code, Gerrit uses the
link:https://github.com/google/google-java-format[`google-java-format`]
tool (version 1.7), which automatically formats code to follow the
-style guide. See link:dev-contributing.html#style[Code Style] for the
+style guide. See link:dev-crafting-changes.html#style[Code Style] for the
instruction how to set up command line tool that uses this formatter.
The Eclipse plugin is provided that allows to format with the same
formatter from within the Eclipse IDE. See
diff --git a/Documentation/dev-inspector.txt b/Documentation/dev-inspector.txt
index b1559caf7d..39736d7a0e 100644
--- a/Documentation/dev-inspector.txt
+++ b/Documentation/dev-inspector.txt
@@ -11,7 +11,7 @@ _java_ -jar gerrit.war _daemon_
[--enable-httpd | --disable-httpd]
[--enable-sshd | --disable-sshd]
[--console-log]
- [--slave]
+ [--replica]
-s
--
diff --git a/Documentation/dev-intellij.txt b/Documentation/dev-intellij.txt
index 50770798cc..81790dbbab 100644
--- a/Documentation/dev-intellij.txt
+++ b/Documentation/dev-intellij.txt
@@ -104,7 +104,7 @@ CodeStyleManager with a custom one. Thus, uses of *Reformat Code* either via
*Code -> Reformat Code*, keyboard shortcuts, or the commit dialog will use the
custom style defined by the `google-java-format` plugin.
-Please refer to the documentation on the <<dev-contributing#style,code style>>
+Please refer to the documentation on the <<dev-crafting-changes#style,code style>>
for which version of `google-java-format` is used with Gerrit.
==== Code style settings
@@ -159,7 +159,7 @@ This section is only relevant in case you want to use the Git integration
plugin in IntelliJ IDEA.
To simplify the creation of commit messages which are compliant with the
-<<dev-contributing#commit-message,Commit Message>> format, do the following:
+<<dev-crafting-changes#commit-message,Commit Message>> format, do the following:
. Go to *File -> Settings -> Version Control -> Commit Dialog*.
. In the *Commit message inspections*, activate the three inspections:
@@ -171,7 +171,7 @@ To simplify the creation of commit messages which are compliant with the
right margin*.
In addition, you should follow the instructions of
-<<dev-contributing#git_commit_settings,this section>> (if you haven't
+<<dev-crafting-changes#git-commit-settings,this section>> (if you haven't
done so already):
* Install the Git commit message hook for the `Change-Id` line.
diff --git a/Documentation/dev-plugins-lifecycle.txt b/Documentation/dev-plugins-lifecycle.txt
new file mode 100644
index 0000000000..b552472efe
--- /dev/null
+++ b/Documentation/dev-plugins-lifecycle.txt
@@ -0,0 +1,254 @@
+= Plugin Lifecycle
+
+Most of the plugins are hosted on the same instance as the
+link:https://gerrit-review.googlesource.com[Gerrit project itself] to make them
+more discoverable and have more chances to be reviewed by the whole community.
+
+[[hosting_lifecycle]]
+== Hosting Lifecycle
+
+The process of writing a new plugin goes through different phases:
+
+- Ideation and Discussion:
++
+The idea of creating a new plugin is posted and discussed on the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
++
+Also see section link#ideation_discussion[Ideation and discussion] below.
+
+- Prototyping (optional):
++
+The author of the plugin creates a working prototype on a public repository
+accessible to the community.
++
+Also see section link#plugin_prototyping[Plugin Prototyping] below.
+
+- Proposal and Hosting:
++
+The author proposes to release the plugin under the
+link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 OpenSource
+license] and requests the plugin to be hosted on
+link:https://gerrit-review.googlesource.com[the Gerrit project site]. The
+proposal must be accepted by at least one Gerrit maintainer. In case of
+disagreement between maintainers, the issue can be escalated to the
+link:dev-processes.html#steering-committee[Engineering Steering Committee]. If
+the plugin is accepted, the Gerrit maintainer creates the project under the
+plugins path on link:https://gerrit-review.googlesource.com[the Gerrit project
+site].
++
+Also see section link#plugin_proposal[Plugin Proposal] below.
+
+- Build:
++
+To make the consumption of the plugin easy and to notice plugin breakages early
+the plugin author should setup build jobs on
+link:https://gerrit-ci.gerritforge.com[the GerritForge CI] that build the
+plugin for each Gerrit version that it supports.
++
+Also see section link#build[Build] below.
+
+- Development and Contribution:
++
+The author develops a production-ready code base of the plugin, with
+contributions, reviews, and help from the Gerrit community.
++
+Also see section link#development_contribution[Development and contribution]
+below.
+
+- Release:
++
+The author releases the plugin by creating a Git tag and announcing the plugin
+on the link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+mailing list.
++
+Also see section link#plugin_release[Plugin release] below.
+
+- Maintenance:
++
+The author maintains their plugins as new Gerrit versions are released, updates
+them when necessary, develops further existing or new features and reviews
+incoming contributions.
+
+- Deprecation:
++
+The author declares that the plugin is not maintained anymore or is deprecated
+and should not be used anymore.
++
+Also see section link#plugin_deprecation[Plugin deprecation] below.
+
+[[ideation_discussion]]
+== Ideation and Discussion
+
+Starting a new plugin project is a community effort: it starts with the
+identification of a gap in the Gerrit Code Review product but evolves with the
+contribution of ideas and suggestions by the whole community.
+
+The ideator of the plugin starts with an RFC (Request For Comments) post on the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list
+with a description of the main reasons for starting a new plugin.
+
+Example of a post:
+
+----
+ [RFC] Code-Formatter plugin
+
+ Hello, community,
+ I am proposing to create a new plugin for Gerrit called 'Code-Formatter', see
+ the details below.
+
+ *The gap*
+ Often, when I post a new change to Gerrit, I forget to run the common code
+ formatting tool (e.g. Google-Java-Format for the Gerrit project). I would
+ like Gerrit to be in charge of highlighting these issues to me and save many
+ people's time.
+
+ *The proposal*
+ The Code-Formatter plugin reads the formatting rules in the project config
+ and applies them automatically to every patch-set. Any issue is reported as a
+ regular review comment to the patchset, highlighting the part of the code to
+ be changed.
+
+ What do you think? Did anyone have the same idea or need?
+----
+
+The idea is discussed on the mailing list and can evolve based on the needs and
+inputs from the entire community.
+
+After the discussion, the ideator of the plugin can decide to start prototyping
+on it or park the proposal, if the feedback provided an alternative solution to
+the problem. The prototype phase can be optionally skipped if the idea is clear
+enough and receives a general agreement from the Gerrit maintainers. The author
+can be given a "leap of faith" and can go directly to the format plugin
+proposal (see below) and the creation of the plugin repository.
+
+[[plugin_prototyping]]
+== Plugin Prototyping
+
+The initial idea is translated to code by the plugin author. The development
+can happen on any public or private source code repository and can involve one
+or more contributors. The purpose of prototyping is to verify that the idea can
+be implemented and provides the expected benefits.
+
+Once a working prototype is ready, it can be announced as a follow-up to the
+initial RFC proposal so that other members of the community can see the code
+and try the plugin themselves.
+
+[[plugin_proposal]]
+== Plugin Proposal
+
+The author decides that the plugin prototype makes sense as a general purpose
+plugin and decides to release the code with the same
+link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]
+as the Gerrit Code Review project and have it hosted on
+link:https://gerrit-review.googlesource.com[the Gerrit project site].
+
+The plugin author formalizes the proposal with a follow-up of the initial RFC
+post and asks for public opinion on it.
+
+Example:
+
+----
+ Re - [RFC] Code-Formatter plugin
+
+ Hello, community,
+ thanks for your feedback on the prototype. I have now decided to donate the
+ project to the Gerrit Code Review project and make it a plugin:
+
+ Plugin name:
+ /plugins/code-formatter
+
+ Plugin description:
+ Plugin to allow automatic posting review based on code-formatting rules
+----
+
+The community discusses the proposal and the value of the plugin for the whole
+project; the result of the discussion can end up in one of the following cases:
+
+- The plugin's project request is widely appreciated and formally accepted by
+ at least one Gerrit maintainer who creates the repository as child project of
+ 'Public-Projects' on link:https://gerrit-review.googlesource.com[the Gerrit
+ project site], creates an associated plugin owners group with "Owner"
+ permissions for the plugin and adds the plugin's author as member of it.
+- The plugin's project is widely appreciated; however, another existing plugin
+ already partially covers the same use-case and thus it would make more sense
+ to have the features integrated into the existing plugin. The new plugin's
+ author contributes his prototype commits refactored to be included as change
+ into the existing plugin.
+- The plugin's project is found useful; however, it is too specific to the
+ author's use-case and would not make sense outside of it. The plugin remains
+ in a public repository, widely accessible and OpenSource, but not hosted on
+ link:https://gerrit-review.googlesource.com[the Gerrit project site].
+
+[[build]]
+== Build
+
+The plugin's maintainer creates a job on the
+link:https://gerrit-ci.gerritforge.com[GerritForge CI] by creating a new YAML
+definition in the link:https://gerrit.googlesource.com/gerrit-ci-scripts[Gerrit
+CI Scripts] repository.
+
+Example of a YAML CI job for plugins:
+
+----
+ - project:
+ name: code-formatter
+ jobs:
+ - 'plugin-{name}-bazel-{branch}':
+ branch:
+ - master
+----
+
+[[development_contribution]]
+== Development and Contribution
+
+The plugin follows the same lifecycle as Gerrit Code Review and needs to be
+kept up-to-date with the current active branches, according to the
+link:https://www.gerritcodereview.com/#support[current support policy].
+During the development, the plugin's maintainer can reward contributors
+requesting to be more involved and making them maintainers of his plugin,
+adding them to the list of the project owners.
+
+[[plugin_release]]
+== Plugin Release
+
+The plugin's maintainer is the only person responsible for making and
+announcing the official releases, typically, but not limited to, in conjunction
+with the major releases of Gerrit Code Review. The plugin's maintainer may tag
+his plugin and follow the notation and semantics of the Gerrit Code Review
+project; however it is not mandatory and many of the plugins do not have any
+tags or releases.
+
+Example of a YAML CI job for a plugin compatible with multiple Gerrit versions:
+
+----
+ - project:
+ name: code-formatter
+ jobs:
+ - 'plugin-{name}-bazel-{branch}-{gerrit-branch}':
+ branch:
+ - master
+ gerrit-branch:
+ - master
+ - stable-3.0
+ - stable-2.16
+----
+
+[[plugin_deprecation]]
+== Plugin Deprecation
+
+The plugin's maintainer and the community have agreed that the plugin is not
+useful anymore or there isn't anyone willing to contribute to bringing it
+forward and keeping it up-to-date with the recent versions of Gerrit Code
+Review.
+
+The plugin's maintainer puts a deprecation notice in the README.md of the
+plugin and pushes it for review. If nobody is willing to bring the code
+forward, the change gets merged, and the master branch is removed from the list
+of branches to be built on the GerritFoge CI.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 21903261dc..f5af91440c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1,7 +1,8 @@
= Gerrit Code Review - Plugin Development
The Gerrit server functionality can be extended by installing plugins.
-This page describes how plugins for Gerrit can be developed.
+This page describes how plugins for Gerrit can be developed and hosted
+on gerrit-review.googlesource.com.
For PolyGerrit-specific plugin development, consult with
link:pg-plugin-dev.html[PolyGerrit Plugin Development] guide.
@@ -389,6 +390,10 @@ Allows to listen to events visible to the specified user. These are the
same link:cmd-stream-events.html#events[events] that are also streamed
by the link:cmd-stream-events.html[gerrit stream-events] command.
+* `com.google.gerrit.extensions.events.AccountActivationListener`:
++
+User account got activated or deactivated
+
* `com.google.gerrit.extensions.events.LifecycleListener`:
+
Plugin start and stop
@@ -1020,6 +1025,11 @@ Output:
}
----
+Implementors of the `ChangeAttributeFactory` interface should check whether
+they need to contribute to the link:#change-etag-computation[change ETag
+computation] to prevent callers using ETags from potentially seeing outdated
+plugin attributes.
+
[[simple-configuration]]
== Simple Configuration in `gerrit.config`
@@ -2105,6 +2115,14 @@ MySecureStore(@SitePath java.io.File sitePath) {
No Guice bindings or modules are required. Gerrit will automatically
discover and bind the implementation.
+[[gerrit-replica]]
+== Gerrit Replica
+
+Gerrit can be run as a read-only replica. Some plugins may need to know
+whether Gerrit is run as a primary- or a replica instance. For that purpose
+Gerrit exposes the `@GerritIsReplica` annotation. A boolean annotated with
+this annotation will indicate whether Gerrit is run as a replica.
+
[[accountcreation]]
== Account Creation
@@ -2357,7 +2375,8 @@ flags is growing without bound. The store must be able handle this data
volume efficiently.
Gerrit implements this extension point, but plugins may bind another
-implementation, e.g. one that supports multi-master.
+implementation, e.g. one that supports cluster setup with multiple
+primary Gerrit nodes handling write operations.
----
DynamicItem.bind(binder(), AccountPatchReviewStore.class)
@@ -2524,10 +2543,10 @@ the `addreviewer.pluginName-exportName.weight` value in `gerrit.config`.
[source, java]
----
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.Set;
@@ -2651,10 +2670,8 @@ If a change is ready to be submitted, `OK`. If it is not ready and requires
modifications, `NOT_READY`. Other statuses are available for particular cases.
A change can be submitted if all the plugins accept the change.
-Plugins may also decide not to vote on a given change by returning an empty
-Collection (ie: the plugin is not enabled for this repository), or to vote
-several times (ie: one SubmitRecord per project in the hierarchy).
-The results are handled as if multiple plugins voted for the change.
+Plugins may also decide not to vote on a given change by returning an
+`Optional.empty()` (ie: the plugin is not enabled for this repository).
If a plugin decides not to vote, it's name will not be displayed in the UI and
it will not be recoded in the database.
@@ -2689,20 +2706,20 @@ changes that are marked as WIP or that are closed (abandoned, merged) can't be m
[source, java]
----
-import java.util.Collection;
+import java.util.Optional;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRecord.Status;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.SubmitRule;
public class MyPluginRules implements SubmitRule {
- public Collection<SubmitRecord> evaluate(ChangeData changeData) {
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
// Implement your submitability logic here
// Assuming we want to prevent this change from being submitted:
- SubmitRecord record;
+ SubmitRecord record = new SubmitRecord();
record.status = Status.NOT_READY;
- return record;
+ return Optional.of(record);
}
}
----
@@ -2735,6 +2752,82 @@ to change in order to be compliant. These requirements should be kept once they
are met, but marked as `OK`. If the requirements were not displayed, reviewers
would need to use their precious time to manually check that they were met.
+Implementors of the `SubmitRule` interface should check whether they need to
+contribute to the link:#change-etag-computation[change ETag computation] to
+prevent callers using ETags from potentially seeing outdated submittability
+information.
+
+[[change-etag-computation]]
+== Change ETag Computation
+
+By implementing the `com.google.gerrit.server.change.ChangeETagComputation`
+interface plugins can contribute a value to the change ETag computation.
+
+Plugins can affect the result of the get change / get change details REST
+endpoints by:
+
+* providing link:#query_attributes[plugin defined attributes] in
+ link:rest-api-changes.html#change-info[ChangeInfo]
+* implementing a link:#pre-submit-evaluator[pre-submit evaluator] which affects
+ the computation of `submittable` field in
+ link:rest-api-changes.html#change-info[ChangeInfo]
+
+If the plugin defined part of link:rest-api-changes.html#change-info[
+ChangeInfo] depends on plugin specific data, callers that use change ETags to
+avoid unneeded recomputations of ChangeInfos may see outdated plugin attributes
+and/or outdated submittable information, because a ChangeInfo is only reloaded
+if the change ETag changes.
+
+By implementating the `com.google.gerrit.server.change.ChangeETagComputation`
+interface plugins can contribute to the ETag computation and thus ensure that
+the change ETag changes when the plugin data was changed. This way it can be
+ensured that callers do not see outdated ChangeInfos.
+
+IMPORTANT: Change ETags are computed very frequently and the computation must
+be cheap. Take good care to not perform any expensive computations when
+implementing this.
+
+[source, java]
+----
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.hash.Hasher;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.change.ChangeETagComputation;
+
+public class MyPluginChangeETagComputation implements ChangeETagComputation {
+ public String getETag(Project.NameKey projectName, Change.Id changeId) {
+ Hasher hasher = Hashing.murmur3_128().newHasher();
+
+ // Add hashes for all plugin-specific data that affects change infos.
+ hasher.putString(sha1OfPluginSpecificChangeRef, UTF_8);
+
+ return hasher.hash().toString();
+ }
+}
+----
+
+[[exception-hook]]
+== ExceptionHook
+
+An `ExceptionHook` allows implementors to control how certain
+exceptions should be handled.
+
+This interface is intended to be implemented for multi-master setups to
+control the behavior for handling exceptions that are thrown by a lower
+layer that handles the consensus and synchronization between different
+server nodes. E.g. if an operation fails because consensus for a Git
+update could not be achieved (e.g. due to slow responding server nodes)
+this interface can be used to retry the request instead of failing it
+immediately.
+
+[[mail-soy-template-provider]]
+== MailSoyTemplateProvider
+
+This extension point allows to provide soy templates for registration
+so that they can be used for sending emails from a plugin.
+
[[quota-enforcer]]
== Quota Enforcer
@@ -2823,6 +2916,20 @@ class ApiQpsEnforcer implements QuotaEnforcer {
}
----
+[[performance-logger]]
+== Performance Logger
+
+`com.google.gerrit.server.logging.PerformanceLogger` is an extension point that
+is invoked for all operations for which the execution time is measured. The
+invocation of the extension point does not happen immediately, but only at the
+end of a request (REST call, SSH call, git push). Implementors can write the
+execution times into a performance log for further analysis.
+
+[[request-listener]]
+== Request Listener
+
+`com.google.gerrit.server.RequestListener` is an extension point that is
+invoked each time the server executes a request from a user.
== SEE ALSO
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
new file mode 100644
index 0000000000..09dcbee9bc
--- /dev/null
+++ b/Documentation/dev-processes.txt
@@ -0,0 +1,355 @@
+= Gerrit Code Review - Development Processes
+
+[[project-governance]]
+[[steering-committee]]
+== Project Governance / Engineering Steering Committee
+
+The Gerrit project has an engineering steering committee (ESC) that is
+in charge of:
+
+* Gerrit core (the `gerrit` project) and the core plugins
+* defining the project vision and the project scope
+* maintaining a roadmap, a release plan and a prioritized backlog
+* ensuring timely design reviews
+* ensuring that new features are compatible with the project vision and
+ are well aligned with other features (give feedback on new
+ link:dev-design-docs.html[design docs] within 14 calendar days)
+* approving/rejecting link:dev-design-docs.html[designs], vetoing new
+ features
+* assigning link:dev-roles.html#mentor[mentors] for approved features
+* accepting new plugins as core plugins
+* making changes to the project governance process and the
+ link:dev-contributing.html#contribution-processes[contribution
+ processes]
+
+The steering committee has 5 members:
+
+* 3 Googlers that are appointed by Google
+* 2 non-Google maintainers, elected by non-Google maintainers for the
+ period of 1 year (see link:#steering-committee-election[below])
+
+Refer to the project homepage for the link:https://www.gerritcodereview.com/members.html#engineering-steering-committee[
+list of current committee members].
+
+The steering committee should act in the interest of the Gerrit project
+and the whole Gerrit community.
+
+For decisions, consensus between steering committee members and all
+other maintainers is desired. If consensus cannot be reached, decisions
+can also be made by simple majority in the steering committee (should
+be applied only in exceptional situations).
+
+The steering committee is empowered to overrule positive/negative votes
+from individual maintainers, but should do so only in exceptional
+situations after attempts to reach consensus have failed.
+
+As an integral part of the Gerrit community, the steering committee is
+committed to transparency and to answering incoming requests in a
+timely manner.
+
+[[steering-committee-election]]
+=== Election of non-Google steering committee members
+
+The election of the non-Google steering committee members happens once
+a year in May. Non-Google link:dev-roles.html#maintainer[maintainers]
+can nominate themselves by posting an informal application on the
+non-public maintainers mailing list by end of April (deadline for 2019
+is Mon 13th of May). By applying to be steering committee member, the
+candidate confirms to be able to dedicate the time that is needed to
+fulfill this role (also see
+link:dev-roles.html#steering-committee-member[steering committee
+member]).
+
+Each non-Google maintainer can vote for 2 candidates. The voting
+happens by posting on the maintainer mailing list. The voting period is
+14 calendar days from the nomination deadline (except for 2019, where
+the initial steering committee should be confirmed during the Munich
+hackathon, the voting period goes from 14th May to 16th May).
+
+Google maintainers do not take part in this vote, because Google
+already has dedicated seats in the steering committee (see section
+link:#steering-committee[steering committee]).
+
+[[contribution-process]]
+== Contribution Process
+
+See link:dev-contributing.html[here].
+
+[[design-doc-review]]
+== Design Doc Review
+
+See link:dev-design-docs.html#review[here].
+
+[[versioning]]
+== Semantic versioning
+
+Gerrit follows a light link:https://semver.org/[semantic versioning scheme] MAJOR.MINOR[.PATCH[.HOTFIX]]
+format:
+
+ * MAJOR is incremented when there are substantial incompatible changes and/or
+ new features in Gerrit.
+ * MINOR is incremented when there are changes that are typically backward compatible
+ with the earlier minor version. Features can be removed following the
+ link:#deprecating-features[feature deprecation process]. Dependencies can be upgraded
+ according to the link:dev-processes.html#upgrading-libraries[libraries upgrade policy].
+ * PATCH is incremented when there are backward-compatible bug fixes in Gerrit or its
+ dependencies. When PATCH is zero, it can be omitted.
+ * HOTFIX is present only when immediately after a patch release, some urgent
+ fixes in the code or the packaging format are required but do not justify a
+ new patch release.
+
+For every MAJOR.MINOR release there is an associated stable branch that follows well defined
+link:#dev-in-stable-branches[rules of development].
+
+Within a stable branch, there are multiple MAJOR.MINOR.PATCH tags created associated to the
+bug-fix releases of that stable release.
+
+Examples:
+
+* Gerrit v3.0.0 contains breaking incompatible changes in the functionality because
+ the ReviewDb storage has been totally removed.
+* Gerrit v2.15 contains brand-new features like NoteDb, however, still supports the existing
+ ReviewDb storage for changes and thus is considered a minor release.
+* Gerrit v2.14.20 is the 20th patch-release of the stable Gerrit v2.14.* and thus does not contain
+ new features but only bug-fixes.
+
+[[dev-in-stable-branches]]
+== Development in stable branches
+
+As their name suggests stable branches are intended to be stable. This means that generally
+only bug-fixes should be done on stable branches, however this is not strictly enforced and
+exceptions may apply:
+
+ * When a stable branch is initially created to prepare a new release the Gerrit community
+ discusses on the mailing list if there are pending features which should still make it into the
+ release. Those features are blocking the release and should be implemented on the stable
+ branch before the first release candidate is created.
+ * To stabilize the code before doing a major release several release candidates are created. Once
+ the first release candidate was done no more features should be accepted on the stable branch.
+ If more features are found to be required they should be discussed with the steering committee
+ and should only be allowed if the risk of breaking things is considered to be low.
+ * Once a major release is done only bug-fixes and documentation updates should be done on the
+ stable branch. These updates will be included in the next minor release.
+ * For minor releases new features could be acceptable if the following conditions are met:
+ ** they are result of a new feature introduced through a merge of an earlier stable branch
+ ** they are justified for completing, extending or fixing an existing feature
+ ** does not involve API, user-interface changes or data migrations
+ ** is backward compatible with all existing features
+ ** the parts of the code in common with existing features are properly covered by end-to-end tests
+ ** is important to the Gerrit community and no Gerrit maintainers have raised objections.
+ * In cases of doubt or conflicting opinions on new features, it's the responsibility of the
+ steering committee to evaluate the risk of new features and make a decision based on these
+ rules and opinions from the Gerrit community.
+ * The older a stable branch is the more stable it should be. This means old stable branches
+ should only receive bug-fixes that are either important or low risk. Security fixes, including
+ security updates for third party dependencies, are always considered as important and hence can
+ always be done on stable branches.
+
+Examples:
+
+* Gerrit v3.0.0-rc1 and v3.0.0-rc2 may contain new features and API changes without notice,
+ even if they are both cut on the same stable-3.0 branch.
+* Gerrit v2.14.8 introduced the support for ElasticSearch as a new feature. This was an exception
+ agreed amongst the Gerrit maintainers, did not touch the Lucene indexing code-base, was supported
+ by container-based E2E tests and represents a completion of an high-level feature.
+
+[[backporting]]
+== Backporting to stable branches
+
+From time to time bug fix releases are made for existing stable branches.
+
+Developers concerned with stable branches are encouraged to backport or push fixes to these
+branches, even if no new release is planned. Backporting features is only possible in compliance
+with the rules link:#dev-in-stable-branches[above].
+
+Fixes that are known to be needed for a particular release should be pushed for review on that
+release's stable branch. They will then be included into the master branch when the stable branch
+is merged back.
+
+[[security-issues]]
+== Dealing with Security Issues
+
+If a security vulnerability in Gerrit is discovered, we place an link:#embargo[
+embargo] on it until a fixed release or mitigation is available. Fixing the
+issue is usually pursued with high priority (depends on the severity of the
+security vulnerability). The embargo is lifted and the vulnerability is
+disclosed to the community as soon as a fix release or another mitigation is
+available.
+
+[[report-security-issue]]
+=== How to report a security vulnerability?
+
+To report a security vulnerability file a
+link:https://bugs.chromium.org/p/gerrit/issues/entry?template=Security+Issue[
+security issue] in the Gerrit issue tracker. The visibility of issues that are
+created with the `Security Issue` template is automatically restricted to
+Gerrit maintainers and a few long-term contributors. This means as a reporter
+you may not be able to see the issue once it is created. Security issues are
+created on the `ESC` component so that they will be discussed at the next
+meeting of the link:#steering-committee[Engineering Steering Committee] which
+takes place biweekly.
+
+If an existing issue is found to be a security vulnerability it should be
+turned into a security issue by:
+
+. Setting the component to `ESC`
+. Adding the labels `Security` and `NonPublic`
+
+In case of doubt, or if an issue cannot wait until the next ESC meeting,
+contact the link:#steering-committee[Engineering Steering Committee] directly
+by sending them an mailto:gerritcodereview-esc@googlegroups.com[email].
+
+If needed, the ESC will contact the reporter for additional details.
+
+[[embargo]]
+=== The Embargo
+
+Once an issue has been identified as security vulnerability, we keep it under
+embargo until a fixed release or a mitigation is available. This means that the
+issue is not discussed publicly, but only on issues with restricted visibility
+(see link:#report-security-issue[above]) and at the mailing lists of the ESC,
+community managers and Gerrit maintainers. Since the `repo-discuss` mailing
+list is public, security issues must not be discussed on this mailing list
+while the embargo is in place.
+
+The reason for keeping an embargo is to prevent attackers from taking advantage
+of a vulnerability while no fixed releases are available yet, and Gerrit
+administrators cannot make their systems secure.
+
+Once a fix release or mitigation is available, the embargo is lifted and the
+community is informed about the security vulnerability with the advise to
+address the security vulnerability immediately (either by upgrading to a fixed
+release or applying the mitigation). The information about the security
+vulnerability is disclosed via the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
+
+[[handle-security-issue]]
+=== Handling of the Security Vulnerability
+
+. Engineering Steering Committee evaluates the security vulnerability:
++
+The ESC discusses the security vulnerability and which actions should be taken
+to address it. One person, usually one of the Gerrit maintainers, should be
+appointed to drive and coordinate the investigation and the fix of the security
+vulnerability. This coordinator doesn't need to do all the work alone, but is
+responsible that the security vulnerability is getting fixed in a timely
+manner.
++
+If the security vulnerability affects multiple or older releases the ESC should
+decide which of the releases should be fixed. For critical security issue we
+also consider fixing old releases that are otherwise not receiving any
+bug-fixes anymore.
++
+It's also possible that the ESC decides that an issue is not a security issue
+and the embargo is lifted immediately.
+
+. Implementation of the security fix:
++
+To keep the embargo intact, security fixes cannot be developed and reviewed in
+the public `gerrit` repository. In particular it's not secure to use private
+changes for implementing and reviewing security fixes (see general notes about
+link:intro-user.html[security-fixes]).
++
+Instead security fixes should be implemented and reviewed in the non-public
+link:https://gerrit-review.googlesource.com/admin/repos/gerrit-security-fixes[
+gerrit-security-fixes] repository which is only accessible by Gerrit
+maintainers and Gerrit community members that work on security fixes.
++
+The change that fixes the security vulnerability should contain an integration
+test that verifies that the security vulnerability is no longer present.
++
+Review and approval of the security fixes must be done by the Gerrit
+maintainers. Verifications must be done manually since the Gerrit CI doesn't
+build and test changes of the `gerrit-security-fixes` repository (and it
+shouldn't because everything on the CI server is public which would break
+the embargo).
++
+Once a security fix is ready and submitted, it should be cherry-picked to all
+branches that should be fixed.
+
+. Creation of fixed releases and announcement of the security vulnerability:
++
+A release manager should create new bug fix releases for all fixed branches.
++
+The new releases should be tested against the security vulnerability to
+double-check that the release was built from the correct source that contains
+the fix for the security vulnerability.
++
+Before publishing the fixed releases, an announcement to the Gerrit community
+should be prepared. The announcement should clearly describe the security
+vulnerability, which releases are affected and which releases contain the fix.
+The announcement should recommend to upgrade to fixed releases immediately.
++
+Once all releases are ready and tested and the announcement is prepared, the
+releases should be all published at the same time. Immediately after that, the
+announcement should be sent out to the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
++
+This ends the embargo and any issue that discusses the security vulnerability
+should be made public.
+
+. Follow-Up
++
+The ESC should discuss if there are any learnings from the security
+vulnerability and define action items to follow up in the
+link:https://bugs.chromium.org/p/gerrit[issue tracker].
+
+[[upgrading-libraries]]
+== Upgrading Libraries
+
+Changes that add new libraries or upgrade existing libraries require an approval on the
+`Library-Compliance` label. For an approval the following things are checked:
+
+* The library has a license that is suitable for use within Gerrit.
+* If the library is used within Google, the version of the library must be compatible with the
+ version that is used at Google.
+
+Only maintainers from Google can vote on the `Library-Compliance` label.
+
+Gerrit's library dependencies should only be upgraded if the new version contains
+something we need in Gerrit. This includes new features, API changes as well as bug
+or security fixes.
+An exception to this rule is that right after a new Gerrit release was branched
+off, all libraries should be upgraded to the latest version to prevent Gerrit
+from falling behind. Doing those upgrades should conclude at the latest two
+months after the branch was cut. This should happen on the master branch to ensure
+that they are vetted long enough before they go into a release and we can be sure
+that the update doesn't introduce a regression.
+
+[[deprecating-features]]
+== Deprecating features
+
+Gerrit should be as stable as possible and we aim to add only features that last.
+However, sometimes we are required to deprecate and remove features to be able
+to move forward with the project and keep the code-base clean. The following process
+should serve as a guideline on how to deprecate functionality in Gerrit. Its purpose
+is that we have a structured process for deprecation that users, administrators and
+developers can agree and rely on.
+
+General process:
+
+ * Make sure that the feature (e.g. a field on the API) is not needed anymore or blocks
+ further development or improvement. If in doubt, consult the mailing list.
+ * If you can provide a schema migration that moves users to a comparable feature, do
+ so and stop here.
+ * Mark the feature as deprecated in the documentation and release notes.
+ * If possible, mark the feature deprecated in any user-visible interface. For example,
+ if you are deprecating a Git push option, add a message to the Git response if
+ the user provided the option informing them about deprecation.
+ * Annotate the code with `@Deprecated` and `@RemoveAfter(x.xx)` if applicable.
+ Alternatively, use `// DEPRECATED, remove after x.xx` (where x.xx is the version
+ number that has to be branched off before removing the feature)
+ * Gate the feature behind a config that is off by default (forcing admins to turn
+ the deprecated feature on explicitly).
+ * After the next release was branched off, remove any code that backed the feature.
+
+You can optionally consult the mailing list to ask if there are users of the feature you
+wish to deprecate. If there are no major users, you can remove the feature without
+following this process and without the grace period of one release.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index c014687cb2..ad25147a95 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -3,7 +3,9 @@
To build a developer instance, you'll need link:https://bazel.build/[Bazel] to
compile the code, preferably launched with link:https://github.com/bazelbuild/bazelisk[Bazelisk].
-== Getting the Source
+== Git Setup
+
+=== Getting the Source
Create a new client workspace:
@@ -12,62 +14,88 @@ Create a new client workspace:
cd gerrit
----
-The `--recursive` option is needed on `git clone` to ensure that
-the core plugins, which are included as git submodules, are also
-cloned.
+The `--recurse-submodules` option is needed on `git clone` to ensure that the
+core plugins, which are included as git submodules, are also cloned.
+
+=== Switching between branches
+
+When using `git checkout` without `--recurse-submodules` to switch between
+branches, submodule revisions are not altered, which can result in:
+
+* Incorrect or unneeded plugin revisions.
+* Missing plugins.
+
+After you switch branches, ensure that you have the correct versions of
+the submodules.
+
+CAUTION: If you store Eclipse or IntelliJ project files in the Gerrit source
+directories, do *_not_* run `git clean -fdx`. Doing so may remove untracked files and damage your project. For more information, see
+link:https://git-scm.com/docs/git-clean[git-clean].
+
+Run the following:
+
+----
+ git submodule update
+ git clean -ffd
+----
[[compile_project]]
== Compiling
For details, see <<dev-bazel#,Building with Bazel>>.
-== Configuring Eclipse
-To use the Eclipse IDE for development, see
-link:dev-eclipse.html[Eclipse Setup].
+== Testing
-To configure the Eclipse workspace with Bazel, see
-link:dev-bazel.html#eclipse[Eclipse integration with Bazel].
+[[tests]]
+=== Running the acceptance tests
-== Configuring IntelliJ IDEA
+Gerrit contains acceptance tests that validate the Gerrit daemon via REST, SSH,
+and the Git protocol.
-See <<dev-intellij#,IntelliJ Setup>> for details.
+A new review site is created for each test and the Gerrit daemon is
+then started on that site. When the test is completed, the Gerrit daemon is
+shut down.
-== MacOS
+For instructions on running the acceptance tests with Bazel,
+see <<dev-bazel#tests,Running Unit Tests with Bazel>>.
-On MacOS, ensure that "Java for MacOS X 10.5 Update 4" (or higher) is installed
-and that `JAVA_HOME` is set to the
-link:install.html#Requirements[required Java version].
+[[e2e]]
+=== End-to-end tests
-Java installations can typically be found in
-"/System/Library/Frameworks/JavaVM.framework/Versions".
+<<dev-e2e-tests#,This document>> describes how `e2e` (load or functional) test
+scenarios are implemented using link:https://gatling.io/[`Gatling`].
-To check the installed version of Java, open a terminal window and run:
-`java -version`
+== Local server
[[init]]
-== Site Initialization
+=== Site Initialization
After you compile the project <<compile_project,(above)>>, run the Gerrit
`init`
command to create a test site:
----
+ export GERRIT_SITE=~/gerrit_testsite
$(bazel info output_base)/external/local_jdk/bin/java \
- -jar bazel-bin/gerrit.war init -d ../gerrit_testsite
+ -jar bazel-bin/gerrit.war init --batch --dev -d $GERRIT_SITE
----
[[special_bazel_java_version]]
NOTE: You must use the same Java version that Bazel used for the build, which
is available at `$(bazel info output_base)/external/local_jdk/bin/java`.
-During initialization, change two settings from the defaults:
+This command takes two parameters:
-* To ensure the development instance is not externally accessible, change the
-listen addresses from '*' to 'localhost'.
-* To allow yourself to create and act as arbitrary test accounts on your
-development instance, change the auth type from 'OPENID' to 'DEVELOPMENT_BECOME_ANY_ACCOUNT'.
+* `--batch` assigns default values to several Gerrit configuration
+ options. To learn more about these options, see
+ link:config-gerrit.html[Configuration].
+* `--dev` configures the Gerrit server to use the authentication
+ option, `DEVELOPMENT_BECOME_ANY_ACCOUNT`, which enables you to
+ switch between different users to explore how Gerrit works. To learn more
+ about setting up Gerrit for development, see
+ link:dev-readme.html[Gerrit Code Review: Developer Setup].
After initializing the test site, Gerrit starts serving in the background. A
web browser displays the Start page.
@@ -81,12 +109,12 @@ On the Start page, you can:
To shut down the daemon, run:
----
- ../gerrit_testsite/bin/gerrit.sh stop
+ $GERRIT_SITE/bin/gerrit.sh stop
----
[[localdev]]
-== Working with the Local Server
+=== Working with the Local Server
To create more accounts on your development instance:
@@ -103,32 +131,26 @@ interface, run:
git clone ssh://username@localhost:29418/projectname
----
-To create changes as users of Gerrit would, run:
+To use the `HTTP` protocol, run:
----
-git push origin HEAD:refs/for/master
+git clone http://username@localhost:8080/projectname
----
-== Testing
+The default password for user `admin` is `secret`. You can regenerate a
+password in the UI under User Settings -- HTTP credentials. The password can be
+stored locally to avoid retyping it:
-[[tests]]
-=== Running the acceptance tests
-
-Gerrit contains acceptance tests that validate the Gerrit daemon via REST, SSH,
-and the Git protocol.
-
-A new review site is created for each test and the Gerrit daemon is
-then started on that site. When the test is completed, the Gerrit daemon is
-shut down.
-
-For instructions on running the acceptance tests with Bazel,
-see <<dev-bazel#tests,Running Unit Tests with Bazel>>.
+----
+git config --global credential.helper store
+git pull
+----
-[[e2e]]
-=== End-to-end tests
+To create changes as users of Gerrit would, run:
-<<dev-e2e-tests#,This document>> describes how `e2e` (load or functional) test
-scenarios are implemented using link:https://gatling.io/[`Gatling`].
+----
+git push origin HEAD:refs/for/master
+----
[[run_daemon]]
=== Running the Daemon
@@ -138,7 +160,7 @@ copying to the test site:
----
$(bazel info output_base)/external/local_jdk/bin/java \
- -jar bazel-bin/gerrit.war daemon -d ../gerrit_testsite \
+ -jar bazel-bin/gerrit.war daemon -d $GERRIT_SITE \
--console-log
----
@@ -170,7 +192,7 @@ To start the Inspector, add the '-s' option to the daemon start command:
----
$(bazel info output_base)/external/local_jdk/bin/java \
- -jar bazel-bin/gerrit.war daemon -d ../gerrit_testsite -s
+ -jar bazel-bin/gerrit.war daemon -d $GERRIT_SITE -s
----
NOTE: To learn why using `java -jar` isn't sufficient, see
@@ -194,27 +216,24 @@ interfaces (including HTTP and SSH) are available.
CAUTION: When using the Inspector, be careful not to modify the internal state
of the system.
-== Switching between branches
-When using `git checkout` without `--recurse-submodules` to switch between
-branches, submodule revisions are not altered, which can result in:
+== Setup for backend developers
-* Incorrect or unneeded plugin revisions.
-* Missing plugins.
+=== Configuring Eclipse
-After you switch branches, ensure that you have the correct versions of
-the submodules.
+To use the Eclipse IDE for development, see
+link:dev-eclipse.html[Eclipse Setup].
-CAUTION: If you store Eclipse or IntelliJ project files in the Gerrit source
-directories, do *_not_* run `git clean -fdx`. Doing so may remove untracked files and damage your project. For more information, see
-link:https://git-scm.com/docs/git-clean[git-clean].
+To configure the Eclipse workspace with Bazel, see
+link:dev-bazel.html#eclipse[Eclipse integration with Bazel].
-Run the following:
+=== Configuring IntelliJ IDEA
+
+See <<dev-intellij#,IntelliJ Setup>> for details.
+
+== Setup for frontend developers
+See link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/README.md[Frontend Developer Setup].
-----
- git submodule update
- git clean -ffd
-----
GERRIT
------
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index 541192775a..98a3df572d 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -91,7 +91,7 @@ Jar.
* `gerrit-maven`:
+
-Bucket to store Gerrit Subproject Artifacts (e.g. `gwtorm` etc.).
+Bucket to store Gerrit Subproject Artifacts (e.g. Prolog Cafe).
To upload artifacts to a bucket the user must authenticate with a
username and password. The username and password need to be retrieved
diff --git a/Documentation/dev-release-jgit.txt b/Documentation/dev-release-jgit.txt
deleted file mode 100644
index 1a8b501a3c..0000000000
--- a/Documentation/dev-release-jgit.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-= Making a Snapshot Release of JGit
-
-This step is only necessary if we need to create an unofficial JGit
-snapshot release and publish it to the
-link:https://developers.google.com/storage/[Google Cloud Storage].
-
-[[prepare-environment]]
-== Prepare the Maven Environment
-
-First, make sure you have done the necessary
-link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
-configuration in Maven `settings.xml`].
-
-To apply the necessary settings in JGit's `pom.xml`, follow the instructions
-in link:dev-release-deploy-config.html#deploy-configuration-subprojects[
-Configuration for Subprojects in `pom.xml`], or apply the provided diff by
-executing the following command in the JGit workspace:
-
-----
- git apply /path/to/gerrit/tools/jgit-snapshot-deploy-pom.diff
-----
-
-[[prepare-release]]
-== Prepare the Release
-
-Since JGit has its own release process we do not push any release tags. Instead
-we will use the output of `git describe` as the version of the current JGit
-snapshot.
-
-In the JGit workspace, execute the following command:
-
-----
- ./tools/version.sh --release $(git describe)
-----
-
-[[publish-release]]
-== Publish the Release
-
-To deploy the new snapshot, execute the following command in the JGit
-workspace:
-
-----
- mvn deploy
-----
-
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index c10457d84e..2131d00a69 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -150,7 +150,7 @@ Setting `GPG_TTY` this way or similar might also be necessary:
Tag the plugins:
----
- git submodule foreach git tag -s -m "v$version" "v$version"
+ git submodule foreach '[ "$path" == "modules/jgit" ] || git tag -s -m "v$version" "v$version"'
----
[[build-gerrit]]
@@ -409,7 +409,7 @@ master branch that were gated on the next release. Mostly, these are
feature-deprecations that we were holding off on to have a stable release where
the feature is still contained, but marked as deprecated.
-See link:dev-contributing.html#deprecating-features[Deprecating features] for
+See link:dev-processes.html#deprecating-features[Deprecating features] for
details.
GERRIT
diff --git a/Documentation/dev-roles.txt b/Documentation/dev-roles.txt
new file mode 100644
index 0000000000..9dbc450696
--- /dev/null
+++ b/Documentation/dev-roles.txt
@@ -0,0 +1,378 @@
+= Gerrit Code Review - Supporting Roles
+
+As an open source project Gerrit has a large community of people
+driving the project forward. There are many ways to engage with
+the project and get involved.
+
+[[supporter]]
+== Supporter
+
+Supporters are individuals who help the Gerrit project and the Gerrit
+community in any way. This includes users that provide feedback to the
+Gerrit community or get in touch by other means.
+
+There are many possibilities to support the project, e.g.:
+
+* get involved in discussions on the
+ link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+ mailing list (post your questions, provide feedback, share your
+ experiences, help other users)
+* attend community events like user summits (see
+ link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
+ community calendar])
+* report link:https://bugs.chromium.org/p/gerrit/issues/list[issues]
+ and help to clarify existing issues
+* provide feedback on
+ link:https://www.gerritcodereview.com/releases-readme.html[new
+ releases and release candidates]
+* review
+ link:https://gerrit-review.googlesource.com/q/status:open[changes]
+ and help to verify that they work as advertised, comment if you like
+ or dislike a feature
+* serve as contact person for a proprietary Gerrit installation and
+ channel feedback from users back to the Gerrit community
+
+Supporters can:
+
+* post on the
+ link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+ mailing list (Please note that the `repo-discuss` mailing list is
+ managed to prevent spam posts. This means posts from new participants
+ must be approved manually before they appear on the mailing list.
+ Approvals normally happen within 1 work day. Posts of people who
+ participate in mailing list discussions frequently are approved
+ automatically)
+* comment on
+ link:https://gerrit-review.googlesource.com/q/status:open[changes]
+ and vote from `-1` to `+1` on the `Code-Review` label (these votes
+ are important to understand the interest in a change and to address
+ concerns early, however link:#maintainer[maintainers] can
+ overrule/ignore these votes)
+* download changes to try them out, feedback can be provided as
+ comments and by voting (preferably on the `Verified` label,
+ permissions to vote on the `Verified` label are granted by request,
+ see below)
+* file issues in the link:https://bugs.chromium.org/p/gerrit/issues/list[
+ issue tracker] and comment on existing issues
+* support the
+ link:dev-processes.html#design-driven-contribution-process[
+ design-driven contribution process] by reviewing incoming
+ link:dev-design-docs.html[design docs] and raising concerns during
+ the design review
+
+Supporters who want to engage further can get additional privileges
+on request (ask for it on the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+mailing list):
+
+* become member of the `gerrit-verifiers` group, which allows to:
+** vote on the `Verified` and `Code-Style` labels
+** edit hashtags on all changes
+** edit topics on all open changes
+** abandon changes
+* approve posts to the
+ link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+ mailing list
+* administrate issues in the
+ link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker]
+
+Supporters can become link:#contributor[contributors] by signing a
+contributor license agreement and contributing code to the Gerrit
+project.
+
+[[contributor]]
+== Contributor
+
+Everyone who has a valid link:dev-cla.html[contributor license
+agreement] and who has link:dev-contributing.html[contributed] at least
+one change to any project on
+link:https://gerrit-review.googlesource.com/[
+gerrit-review.googlesource.com] is a contributor.
+
+Contributions can be:
+
+* new features
+* bug fixes
+* code cleanups
+* documentation updates
+* release notes updates
+* propose link:#dev-design-docs[design docs] as part of the
+ link:dev-contributing.html#design-driven-contribution-process[
+ design-driven contribution process]
+* scripts which are of interest to the community
+
+Contributors have all the permissions that link:#supporter[supporters]
+have. In addition they have signed a link:dev-cla.html[contributor
+license agreement] which enables them to push changes.
+
+Regular contributors can ask to be added to the `gerrit-verifiers`
+group, which allows to:
+
+* add patch sets to changes of other users
+* propose project config changes (push changes for the
+ `refs/meta/config` branch
+
+Being member of the `gerrit-verifiers` group includes further
+permissions (see link:#supporter[supporter] section above).
+
+It's highly appreciated if contributors engage in code reviews,
+link:dev-design-docs.html#review[design reviews] and mailing list
+discussions. If wanted, contributors can also serve as link:#mentor[
+mentors] to support other contributors with getting their features
+done.
+
+Contributors may also be invited to join the Gerrit hackathons which
+happen regularly (e.g. twice a year). Hackathons are announced on the
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+mailing list (also see
+link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
+community calendar]).
+
+Outstanding contributors that are actively engaged in the community, in
+activities outlined above, may be nominated as link:#maintainer[
+maintainers].
+
+[[maintainer]]
+== Maintainer
+
+Maintainers are the gatekeepers of the project and are in charge of
+approving and submitting changes. Refer to the project homepage for
+the link:https://www.gerritcodereview.com/members.html#maintainers[
+list of current maintainers].
+
+Maintainers should only approve changes that:
+
+* they fully understand
+* are in line with the project vision and project scope that are
+ defined by the link:dev-processes.html#steering-committee[engineering steering
+ committee], and should consult them, when in doubt
+* meet the quality expectations of the project (well-tested, properly
+ documented, scalable, backwards-compatible)
+* implement usable features or bug fixes (no incomplete/unusable
+ things)
+* are not authored by themselves (exceptions are changes which are
+ trivial according to the judgment of the maintainer and changes that
+ are required by the release process and branch management)
+
+Maintainers are trusted to assess changes, but are also expected to
+align with the other maintainers, especially if large new features are
+being added.
+
+Maintainers are highly encouraged to dedicate some of their time to the
+following tasks (but are not required to do so):
+
+* reviewing changes
+* mailing list discussions and support
+* bug fixing and bug triaging
+* supporting the
+ link:dev-processes.html#design-driven-contribution-process[
+ design-driven contribution process] by reviewing incoming
+ link:dev-design-docs.html[design docs] and raising concerns during
+ the design review
+* serving as link:#mentor[mentor]
+* doing releases (see link#release-manager[release manager])
+
+Maintainers can:
+
+* approve changes (vote `+2` on the `Code-Review` label); when
+ approving changes, `-1` votes on the `Code-Review` label can be
+ ignored if there is a good reason, in this case the reason should be
+ clearly communicated on the change
+* submit changes
+* block submission of changes if they disagree with how a feature is
+ being implemented (vote `-2` on the `Code-Review` label), but their
+ vote can be overruled by the steering committee, see
+ link:dev-processes.html#project-governance[Project Governance]
+* nominate new maintainers and vote on nominations (see below)
+* administrate the link:https://groups.google.com/d/forum/repo-discuss[
+ mailing list], the
+ link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker]
+ and the link:https://www.gerritcodereview.com/[homepage]
+* gain permissions to do Gerrit releases and publish release artifacts
+* create new projects and groups on
+ link:https://gerrit-review.googlesource.com/[
+ gerrit-review.googlesource.com]
+* administrate the Gerrit projects on
+ link:https://gerrit-review.googlesource.com/[
+ gerrit-review.googlesource.com] (e.g. edit ACLs, update project
+ configuration)
+* create events in the
+ link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
+ community calendar]
+* discuss with other maintainers on the private maintainers mailing
+ list and Slack channel
+
+In addition, maintainers from Google can:
+
+* approve/reject changes that update project dependencies (vote `-1` to
+ `+1` on the `Library-Compliance` label), see
+ link:dev-processes.html#upgrading-libraries[Upgrading Libraries]
+* edit permissions on the Gerrit core projects
+
+[[maintainer-election]]
+Maintainers can nominate new maintainers by posting a nomination on the
+non-public maintainers mailing list. Nominations should stay open for
+at least 14 calendar days so that all maintainers have a chance to
+vote. To be approved as maintainer a minimum of 5 positive votes and no
+negative votes is required. This means if 5 positive votes without
+negative votes have been reached and 14 calendar days have passed, any
+maintainer can close the vote and welcome the new maintainer. Extending
+the voting period during holiday season or if there are not enough
+votes is possible, but the voting period should not exceed 1 month. If
+there are negative votes that are considered unjustified, the
+link:dev-processes.html#steering-committee[engineering steering
+committee] may get involved to decide whether the new maintainer can be
+accepted anyway.
+
+To become a maintainer, a link:#contributor[contributor] should have a
+history of deep technical contributions across different parts of the
+core Gerrit codebase. However, it is not required to be an expert on
+everything. Things that we want to see from potential maintainers
+include:
+
+* high quality code contributions
+* high quality code reviews
+* activity on the mailing list
+
+[[steering-committee-member]]
+== Engineering Steering Committee Member
+
+The Gerrit project has an Engineering Steering Committee (ESC) that
+governs the project, see link:dev-processes.html#project-governance[Project Governance].
+
+Members of the steering committee are expected to act in the interest
+of the Gerrit project and the whole Gerrit community. Refer to the project
+homepage for the link:https://www.gerritcodereview.com/members.html#engineering-steering-committee[
+list of current committee members].
+
+For those that are familiar with scrum, the steering committee member
+role is similar to the role of an agile product owner.
+
+Steering committee members must be able to dedicate sufficient time to
+their role so that the steering committee can satisfy its
+responsibilities and live up to the promise of answering incoming
+requests in a timely manner.
+
+Community members may submit new items under the
+link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:ESC[ESC component]
+in the issue tracker, or add that component to existing items, to raise them to
+the attention of ESC members.
+
+Community members may contact the ESC members directly using
+mailto:gerritcodereview-esc@googlegroups.com[this mailing list].
+This is a group that remains private between the individual community
+member and ESC members.
+
+link:#maintainer[Maintainers] can become steering committee member by
+election, or by being appointed by Google (only for the seats that
+belong to Google).
+
+[[mentor]]
+== Mentor
+
+A mentor is a link:#maintainer[maintainer] or link:#contributor[
+contributor] who is assigned to support the development of a feature
+that was specified in a link:dev-design-docs.html[design doc] and was
+approved by the link:dev-processes.html#steering-committee[steering
+committee].
+
+The goal of the mentor is to make the feature successful by:
+
+* doing timely reviews
+* providing technical guidance during code reviews
+* discussing details of the design
+* ensuring that the quality standards are met (well documented,
+ sufficient test coverage, backwards compatible etc.)
+
+The implementation is fully done by the contributor, but optionally
+mentors can help out with contributing some changes.
+
+link:#maintainer[Maintainers] and link:#contributor[contributors] can
+volunteer to generally serve as mentors, or to mentor specific features
+(e.g. if they see an upcoming feature on the roadmap that they are
+interested in). To volunteer as mentor, contact the
+link:dev-processes.html#steering-committee[steering committee] or
+comment on a change that adds a link:dev-design-docs.html#propose[
+design doc].
+
+[[community-manager]]
+== Community Manager
+
+Community managers should act as stakeholders for the Gerrit community
+and focus on the health of the community. Refer to the project homepage
+for the link:https://www.gerritcodereview.com/members.html#community-managers[
+list of current community managers].
+
+Tasks:
+
+* act as stakeholder for the Gerrit community towards the
+ link:dev-processes.html#steering-committee[steering committee]
+* ensure that the link:dev-contributing.html#mentorship[mentorship
+ process] works
+* deescalate conflicts in the Gerrit community
+* constantly improve community processes (e.g. contribution process)
+* watch out for community issues and address them proactively
+* serve as contact person for community issues
+
+Community members may submit new items under the
+link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:Community[Community component]
+backlog, for community managers to refine. Only public topics should be
+issued through that backlog.
+
+Sensitive topics are to be privately discussed using
+mailto:gerritcodereview-community-managers@googlegroups.com[this mailing list].
+This is a group that remains private between the individual community
+member and community managers.
+
+The community managers should be a pair or trio that shares the work:
+
+* One Googler that is appointed by Google.
+* One or two non-Googlers, elected by the community if there are more
+ than two candidates. If there is no candidate, we only have the one
+ community manager from Google.
+
+Community managers must not be link:#steering-committee-member[
+steering committee members] at the same time so that they can represent
+the community without conflict of interest.
+
+Anybody from the Gerrit community can candidate as community manager.
+This means, in contrast to candidating for the ESC, candidating as
+community manager is not limited to Gerrit maintainers. Otherwise the
+nomination process, election process and election period for the
+non-Google community manager are the same as for
+link:dev-processes.html#steering-committee-election[steering committee
+members].
+
+[[release-manager]]
+== Release Manager
+
+Each major Gerrit release is driven by a Gerrit link:#maintainer[
+maintainer], the so called release manager.
+
+The release manager is responsible for:
+
+* identifying release blockers and informing about them
+* creating stable branches and updating version numbers
+* creating release candidates, the final major release and minor
+ releases
+* announcing releases on the mailing list and collecting feedback
+* ensuring that releases meet minimal quality expectations (Gerrit
+ starts, upgrade from previous version works)
+* publishing release artifacts
+* ensuring quality and completeness of the release notes
+* cherry-picking bug fixes, see link:dev-processes.html#backporting[
+ Backporting to stable branches]
+* estimating the risk of new features that are added on stable
+ branches, see link:dev-processes.html#dev-in-stable-branches[
+ Development in stable branches]
+
+Before each release, the release manager is appointed by consensus among
+the maintainers. Volunteers are welcome, but it's also a goal to fairly
+share this work between maintainers and contributing companies.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-starter-projects.txt b/Documentation/dev-starter-projects.txt
new file mode 100644
index 0000000000..ae40ea645f
--- /dev/null
+++ b/Documentation/dev-starter-projects.txt
@@ -0,0 +1,14 @@
+= Gerrit Code Review - Starter Projects
+
+We have created a
+link:https://bugs.chromium.org/p/gerrit/issues/list?can=2&q=label%3AStarterProject[StarterProject]
+category in the issue tracker and try to assign easy hack projects to it. If in
+doubt, do not hesitate to ask on the developer
+link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list].
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-change-does-not-belong-to-project.txt b/Documentation/error-change-does-not-belong-to-project.txt
deleted file mode 100644
index 21596b1467..0000000000
--- a/Documentation/error-change-does-not-belong-to-project.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-= change ... does not belong to project ...
-
-With this error message Gerrit rejects to push a commit to a change
-that belongs to another project.
-
-This error message means that the user explicitly pushed a commit to
-a change that belongs to another project by specifying it as target
-ref. This way of adding a new patch set to a change is deprecated as
-explained link:user-upload.html#manual_replacement_mapping[here]. It is recommended to only rely on Change-Ids for
-link:user-upload.html#push_replace[replacing changes].
-
-
-GERRIT
-------
-Part of link:error-messages.html[Gerrit Error Messages]
-
-SEARCHBOX
----------
diff --git a/Documentation/error-change-not-found.txt b/Documentation/error-change-not-found.txt
deleted file mode 100644
index df99388122..0000000000
--- a/Documentation/error-change-not-found.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-= change ... not found
-
-With this error message Gerrit rejects to push a commit to a change
-that cannot be found.
-
-This error message means that the user explicitly pushed a commit to
-a non-existing change by specifying it as target ref. This way of
-adding a new patch set to a change is deprecated as explained link:user-upload.html#manual_replacement_mapping[here].
-It is recommended to only rely on Change-Ids for link:user-upload.html#push_replace[replacing changes].
-
-
-GERRIT
-------
-Part of link:error-messages.html[Gerrit Error Messages]
-
-SEARCHBOX
----------
diff --git a/Documentation/error-commit-already-exists.txt b/Documentation/error-commit-already-exists.txt
index d2b7c9d8ed..2832c78739 100644
--- a/Documentation/error-commit-already-exists.txt
+++ b/Documentation/error-commit-already-exists.txt
@@ -1,6 +1,6 @@
= commit already exists
-With "commit already exists (as current patchset)" or
+With "commit(s) already exists (as current patchset)" or
"commit already exists (in the change)" error message
Gerrit rejects to push a commit to an existing change via
`refs/changes/n` if the commit was already successfully
diff --git a/Documentation/error-messages.txt b/Documentation/error-messages.txt
index b52366342b..eedae39fde 100644
--- a/Documentation/error-messages.txt
+++ b/Documentation/error-messages.txt
@@ -9,8 +9,6 @@ occurring and what can be done to solve it.
* link:error-branch-not-found.html[branch ... not found]
* link:error-change-closed.html[change ... closed]
-* link:error-change-does-not-belong-to-project.html[change ... does not belong to project ...]
-* link:error-change-not-found.html[change ... not found]
* link:error-commit-already-exists.html[commit already exists]
* link:error-contains-banned-commit.html[contains banned commit ...]
* link:error-has-duplicates.html[... has duplicates]
@@ -35,7 +33,6 @@ occurring and what can be done to solve it.
* link:error-same-change-id-in-multiple-changes.html[same Change-Id in multiple changes]
* link:error-too-many-commits.html[too many commits]
* link:error-upload-denied.html[Upload denied for project \'...']
-* link:error-push-refschanges-not-allowed.html[upload to refs/changes not allowed]
* link:error-not-allowed-to-upload-merges.html[you are not allowed to upload merges]
diff --git a/Documentation/error-push-refschanges-not-allowed.txt b/Documentation/error-push-refschanges-not-allowed.txt
deleted file mode 100644
index 2bbdc3e85a..0000000000
--- a/Documentation/error-push-refschanges-not-allowed.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-= upload to refs/changes not allowed
-
-Pushing to `refs/changes/` is deprecated and is not allowed on this Gerrit server.
-See the documentation for link:user-upload.html#push_create[creating changes] for
-alternate ways to push to existing changes.
-
-
-GERRIT
-------
-Part of link:error-messages.html[Gerrit Error Messages]
-
-SEARCHBOX
----------
diff --git a/Documentation/i18n-readme.txt b/Documentation/i18n-readme.txt
deleted file mode 100644
index 180fc5385b..0000000000
--- a/Documentation/i18n-readme.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-= Gerrit Code Review - i18n
-
-Aside from actually writing translations, there are some issues with
-the way the code produces output. Most of the UI should support
-right-to-left (RTL) languages.
-
-== Labels
-
-Labels and their values are defined in project.config by the Gerrit
-administrator or project owners. Only a single translation of these
-strings is supported.
-
-== /Gerrit Gerrit.html
-
-* The title of the host page is not translated.
-
-* The <noscript> tag is not translated.
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/index.txt b/Documentation/index.txt
index f9e39b5c2a..4de55a75b4 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -10,7 +10,11 @@
. link:intro-how-gerrit-works.html[How Gerrit Works]
. link:intro-gerrit-walkthrough.html[Basic Gerrit Walkthrough]
-== Guides
+== Contributor Guides
+. link:dev-community.html[Gerrit Community]
+. link:dev-community.html#how-to-contribute[How to Contribute]
+
+== User Guides
. link:intro-user.html[User Guide]
. link:intro-project-owner.html[Project Owner Guide]
. link:https://source.android.com/source/developing[Default Android Workflow] (external)
@@ -73,28 +77,6 @@
. link:config-accounts.html[Accounts on NoteDb]
. link:config-groups.html[Groups on NoteDb]
-== Developer
-. Getting Started
-.. link:dev-readme.html[Developer Setup]
-.. link:dev-bazel.html[Building with Bazel]
-.. link:dev-eclipse.html[Eclipse Setup]
-.. link:dev-intellij.html[IntelliJ Setup]
-.. link:dev-contributing.html[Contributing to Gerrit]
-. Plugin Development
-.. link:dev-plugins.html[Developing Plugins]
-.. link:dev-build-plugins.html[Building Gerrit plugins]
-.. link:js-api.html[JavaScript Plugin API]
-.. link:config-validation.html[Validation Interfaces]
-.. link:dev-stars.html[Starring Changes]
-.. link:quota.html[Quota Enforcement]
-. link:dev-design.html[System Design]
-. link:i18n-readme.html[i18n Support]
-
-== Maintainer
-. link:dev-release.html[Making a Gerrit Release]
-. link:dev-release-subproject.html[Making a Release of a Gerrit Subproject]
-. link:dev-release-jgit.html[Making a Release of JGit]
-
== Concepts
. link:config-labels.html[Review Labels]
. link:access-control.html[Access Controls]
@@ -105,7 +87,7 @@
== Resources
* link:licenses.html[Licenses and Notices]
* link:https://www.gerritcodereview.com/[Homepage]
-* link:https://www.gerritcodereview.com/download/index.html[Downloads]
+* link:https://gerrit-releases.storage.googleapis.com/index.html[Downloads]
* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking]
* link:https://gerrit.googlesource.com/gerrit[Source Code]
* link:https://www.gerritcodereview.com/about.md[A History of Gerrit Code Review]
diff --git a/Documentation/install.txt b/Documentation/install.txt
index aaefc86fbc..09ebbba6cd 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -46,7 +46,7 @@ found in `README.txt`.
== Download Gerrit
Current and past binary releases of Gerrit can be obtained from
-the link:https://www.gerritcodereview.com/download/index.html[
+the link:https://gerrit-releases.storage.googleapis.com/index.html[
Gerrit Releases site].
Download any current `*.war` package. The war will be referred to as
diff --git a/Documentation/intro-gerrit-walkthrough.txt b/Documentation/intro-gerrit-walkthrough.txt
index 1fba1dc04b..b4f799c220 100644
--- a/Documentation/intro-gerrit-walkthrough.txt
+++ b/Documentation/intro-gerrit-walkthrough.txt
@@ -28,7 +28,7 @@ project he works on. His first step is to get the source code that he wants to
modify. To get this code, he runs the following `git clone` command:
----
-clone ssh://gerrithost:29418/RecipeBook.git RecipeBook
+git clone ssh://gerrithost:29418/RecipeBook.git RecipeBook
----
After he clones the repository, he runs a couple of commands to add a
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 04778f1e8f..a54774bf2c 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -726,36 +726,6 @@ The user's preferences are stored in a `git config` style file named
The following preferences can be configured:
-- [[review-category]]`Display In Review Category`:
-+
-This setting controls how the values of the review labels in change
-lists and dashboards are visualized.
-+
-** `None`:
-+
-For each review label only the voting value is shown. Approvals are
-rendered as a green check mark icon, vetoes as a red X icon.
-+
-** `Show Name`:
-+
-For each review label the voting value is shown together with the full
-name of the voting user.
-+
-** `Show Email`:
-+
-For each review label the voting value is shown together with the email
-address of the voting user.
-+
-** `Show Username`:
-+
-For each review label the voting value is shown together with the
-username of the voting user.
-+
-** `Show Abbreviated Name`:
-+
-For each review label the voting value is shown together with the
-initials of the full name of the voting user.
-
- [[page-size]]`Maximum Page Size`:
+
The maximum number of entries that are shown on one page, e.g. used
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 4ef2a6ce8d..030541d43b 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -24,123 +24,17 @@ Gerrit.install(function (self) {
The plugin instance is passed to the plugin's initialization function
and provides a number of utility services to plugin authors.
-[[self_delete]]
-=== self.delete() / self.del()
-Issues a DELETE REST API request to the Gerrit server.
-
-.Signature
-[source,javascript]
-----
-Gerrit.delete(url, callback)
-Gerrit.del(url, callback)
-----
-
-* url: URL relative to the plugin's URL space. The JavaScript
- library prefixes the supplied URL with `/plugins/{getPluginName}/`.
-
-* callback: JavaScript function to be invoked with the parsed
- JSON result of the API call. DELETE methods often return
- `204 No Content`, which is passed as null.
-
-[[self_get]]
-=== self.get()
-Issues a GET REST API request to the Gerrit server.
-
-.Signature
-[source,javascript]
-----
-self.get(url, callback)
-----
-
-* url: URL relative to the plugin's URL space. The JavaScript
- library prefixes the supplied URL with `/plugins/{getPluginName}/`.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
[[self_getServerInfo]]
=== self.getServerInfo()
Returns the server's link:rest-api-config.html#server-info[ServerInfo]
data.
-[[self_getCurrentUser]]
-=== self.getCurrentUser()
-Returns the currently signed in user's AccountInfo data; empty account
-data if no user is currently signed in.
-
-[[Gerrit_getUserPreferences]]
-=== Gerrit.getUserPreferences()
-Returns the preferences of the currently signed in user; the default
-preferences if no user is currently signed in.
-
-[[Gerrit_refreshUserPreferences]]
-=== Gerrit.refreshUserPreferences()
-Refreshes the preferences of the current user.
-
[[self_getPluginName]]
=== self.getPluginName()
Returns the name this plugin was installed as by the server
administrator. The plugin name is required to access REST API
views installed by the plugin, or to access resources.
-[[self_post]]
-=== self.post()
-Issues a POST REST API request to the Gerrit server.
-
-.Signature
-[source,javascript]
-----
-self.post(url, input, callback)
-----
-
-* url: URL relative to the plugin's URL space. The JavaScript
- library prefixes the supplied URL with `/plugins/{getPluginName}/`.
-
-* input: JavaScript object to serialize as the request payload.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
-[source,javascript]
-----
-self.post(
- '/my-servlet',
- {start_build: true, platform_type: 'Linux'},
- function (r) {});
-----
-
-[[self_put]]
-=== self.put()
-Issues a PUT REST API request to the Gerrit server.
-
-.Signature
-[source,javascript]
-----
-self.put(url, input, callback)
-----
-
-* url: URL relative to the plugin's URL space. The JavaScript
- library prefixes the supplied URL with `/plugins/{getPluginName}/`.
-
-* input: JavaScript object to serialize as the request payload.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
-[source,javascript]
-----
-self.put(
- '/builds',
- {start_build: true, platform_type: 'Linux'},
- function (r) {});
-----
-
[[self_on]]
=== self.on()
Register a JavaScript callback to be invoked when events occur within
@@ -149,7 +43,7 @@ the web interface.
.Signature
[source,javascript]
----
-Gerrit.on(event, callback);
+self.on(event, callback);
----
* event: A supported event type. See below for description.
@@ -194,39 +88,26 @@ Supported events:
This event can be used to register a new language highlighter with
the highlight.js library before syntax highlighting begins.
-[[self_onAction]]
-=== self.onAction()
-Register a JavaScript callback to be invoked when the user clicks
-on a button associated with a server side `UiAction`.
+[[self_changeActions]]
+=== self.changeActions()
+Returns an instance of ChangeActions API.
.Signature
[source,javascript]
----
-self.onAction(type, view_name, callback);
+self.changeActions();
----
-* type: `'change'`, `'edit'`, `'revision'`, `'project'`, or `'branch'`
- indicating which type of resource the `UiAction` was bound to
- in the server.
-
-* view_name: string appearing in URLs to name the view. This is the
- second argument of the `get()`, `post()`, `put()`, and `delete()`
- binding methods in a `RestApiModule`.
-
-* callback: JavaScript function to invoke when the user clicks. The
- function will be passed a link:#ActionContext[action context].
-
[[self_screen]]
=== self.screen()
-Register a JavaScript callback to be invoked when the user navigates
+Register a module to be attached when the user navigates
to an extension screen provided by the plugin. Extension screens are
usually linked from the link:dev-plugins.html#top-menu-extensions[top menu].
-The callback can populate the DOM with the screen's contents.
.Signature
[source,javascript]
----
-self.screen(pattern, callback);
+self.screen(pattern, opt_moduleName);
----
* pattern: URL token pattern to identify the screen. Argument can be
@@ -234,52 +115,34 @@ self.screen(pattern, callback);
If a RegExp is used the matching groups will be available inside of
the context as `token_match`.
-* callback: JavaScript function to invoke when the user navigates to
+* opt_moduleName: The module to load when the user navigates to
the screen. The function will be passed a link:#ScreenContext[screen context].
-[[self_settingsScreen]]
-=== self.settingsScreen()
-Register a JavaScript callback to be invoked when the user navigates
-to an extension settings screen provided by the plugin. Extension settings
-screens are automatically linked from the settings menu under the given
-menu entry.
-The callback can populate the DOM with the screen's contents.
+[[self_settings]]
+=== self.settings()
+Returns the Settings API.
.Signature
[source,javascript]
----
-self.settingsScreen(path, menu, callback);
+self.settings();
----
-* path: URL path to identify the settings screen.
-
-* menu: The name of the menu entry in the settings menu that should
- link to the settings screen.
-
-* callback: JavaScript function to invoke when the user navigates to
- the settings screen. The function will be passed a
- link:#SettingsScreenContext[settings screen context].
-
-[[self_panel]]
-=== self.panel()
-Register a JavaScript callback to be invoked when a screen with the
-given extension point is loaded.
-The callback can populate the DOM with the panel's contents.
+[[self_registerCustomComponent]]
+=== self.registerCustomComponent()
+Register a custom component to a specific endpoint.
.Signature
[source,javascript]
----
-self.panel(extensionpoint, callback);
+self.registerCustomComponent(endpointName, opt_moduleName, opt_options);
----
-* extensionpoint: The name of the extension point that marks the
- position where the panel is added to an existing screen. The
- available extension points are described in the
- link:dev-plugins.html#panels[plugin development documentation].
+* endpointName: The endpoint this plugin should be reigistered to.
+
+* opt_moduleName: The module name the custom component will use.
-* callback: JavaScript function to invoke when a screen with the
- extension point is loaded. The function will be passed a
- link:#PanelContext[panel context].
+* opt_options: Options to register this custom component.
[[self_url]]
=== self.url()
@@ -293,398 +156,260 @@ self.url(); // "https://gerrit-review.googlesource.com/plugin
self.url('/static/icon.png'); // "https://gerrit-review.googlesource.com/plugins/demo/static/icon.png"
----
+[[self_restApi]]
+=== self.restApi()
+Returns an instance of the Plugin REST API.
-[[ActionContext]]
-== Action Context
-A new action context is passed to the `onAction` callback function
-each time the associated action button is clicked by the user. A
-context is initialized with sufficient state to issue the associated
-REST API RPC.
+.Signature
+[source,javascript]
+----
+self.restApi(prefix_url)
+----
-[[context_action]]
-=== context.action
-An link:rest-api-changes.html#action-info[ActionInfo] object instance
-supplied by the server describing the UI button the user used to
-invoke the action.
+* prefix_url: Base url for subsequent .get(), .post() etc requests.
-[[context_call]]
-=== context.call()
-Issues the REST API call associated with the action. The HTTP method
-used comes from `context.action.method`, hiding the JavaScript from
-needing to care.
+[[PluginRestAPI]]
+== Plugin Rest API
+
+[[plugin_rest_delete]]
+=== restApi.delete()
+Issues a DELETE REST API request to the Gerrit server.
+Returns a promise with the response of the request.
.Signature
[source,javascript]
----
-context.call(input, callback)
+restApi.delete(url)
----
-* input: JavaScript object to serialize as the request payload. This
- parameter is ignored for GET and DELETE methods.
+* url: URL relative to the base url.
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
+[[plugin_rest_get]]
+=== restApi.get()
+Issues a GET REST API request to the Gerrit server.
+Returns a promise with the response of the request.
+.Signature
[source,javascript]
----
-context.call(
- {message: "..."},
- function (result) {
- // ... use result here ...
- });
+restApi.get(url)
----
-[[context_change]]
-=== context.change
-When the action is invoked on a change a
-link:rest-api-changes.html#change-info[ChangeInfo] object instance
-describing the change. Available fields of the ChangeInfo may vary
-based on the options used by the UI when it loaded the change.
+* url: URL relative to the base url.
-[[context_delete]]
-=== context.delete()
-Issues a DELETE REST API call to the URL associated with the action.
+[[plugin_rest_post]]
+=== restApi.post()
+Issues a POST REST API request to the Gerrit server.
+Returns a promise with the response of the request.
.Signature
[source,javascript]
----
-context.delete(callback)
+restApi.post(url, opt_payload, opt_errFn, opt_contentType)
----
-* callback: JavaScript function to be invoked with the parsed
- JSON result of the API call. DELETE methods often return
- `204 No Content`, which is passed as null.
+* url: URL relative to the base url.
+
+* opt_payload: JavaScript object to serialize as the request payload.
+
+* opt_errFn: JavaScript function to be invoked when error occured.
+
+* opt_contentType: Content-Type to be sent along with the request.
[source,javascript]
----
-context.delete(function () {});
+restApi.post(
+ '/my-servlet',
+ {start_build: true, platform_type: 'Linux'});
----
-[[context_get]]
-=== context.get()
-Issues a GET REST API call to the URL associated with the action.
+[[plugin_rest_put]]
+=== restApi.put()
+Issues a PUT REST API request to the Gerrit server.
+Returns a promise with the response of the request.
.Signature
[source,javascript]
----
-context.get(callback)
+restApi.put(url, opt_payload, opt_errFn, opt_contentType)
----
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
+* url: URL relative to the base url.
+
+* opt_payload: JavaScript object to serialize as the request payload.
+
+* opt_errFn: JavaScript function to be invoked when error occured.
+
+* opt_contentType: Content-Type to be sent along with the request.
[source,javascript]
----
-context.get(function (result) {
- // ... use result here ...
-});
+restApi.put(
+ '/builds',
+ {start_build: true, platform_type: 'Linux'});
----
-[[context_go]]
-=== context.go()
-Go to a screen. Shorthand for link:#Gerrit_go[`Gerrit.go()`].
+[[ChangeActions]]
+== Change Actions API
+A new Change Actions API instance will be created when `changeActions()`
+is invoked.
-[[context_hide]]
-=== context.hide()
-Hide the currently visible popup displayed by
-link:#context_popup[`context.popup()`].
-
-[[context_post]]
-=== context.post()
-Issues a POST REST API call to the URL associated with the action.
+[[change_actions_add]]
+=== changeActions.add()
+Adds a new action to the change actions section.
+Returns the key of the newly added action.
.Signature
[source,javascript]
----
-context.post(input, callback)
+changeActions.add(type, label)
----
-* input: JavaScript object to serialize as the request payload.
+* type: The type of the action, either `change` or `revision`.
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
+* label: The label to be used in UI for this action.
[source,javascript]
----
-context.post(
- {message: "..."},
- function (result) {
- // ... use result here ...
- });
+changeActions.add("change", "test")
----
-[[context_popup]]
-=== context.popup()
-
-Displays a small popup near the activation button to gather
-additional input from the user before executing the REST API RPC.
-
-The caller is always responsible for closing the popup with
-link#context_hide[`context.hide()`]. Gerrit will handle closing a
-popup if the user presses `Escape` while keyboard focus is within
-the popup.
+[[change_actions_remove]]
+=== changeActions.remove()
+Removes an action from the change actions section.
.Signature
[source,javascript]
----
-context.popup(element)
+changeActions.remove(key)
----
-* element: an HTML DOM element to display as the body of the
- popup. This is typically a `div` element but can be any valid HTML
- element. CSS can be used to style the element beyond the defaults.
+* key: The key of the action.
-A common usage is to gather more input:
+[[change_actions_addTapListener]]
+=== changeActions.addTapListener()
+Adds a tap listener to an action that will be invoked when the action
+is tapped.
+.Signature
[source,javascript]
----
-self.onAction('revision', 'start-build', function (c) {
- var l = c.checkbox();
- var m = c.checkbox();
- c.popup(c.div(
- c.div(c.label(l, 'Linux')),
- c.div(c.label(m, 'Mac OS X')),
- c.button('Build', {onclick: function() {
- c.call(
- {
- commit: c.revision.name,
- linux: l.checked,
- mac: m.checked,
- },
- function() { c.hide() });
- });
-});
+changeActions.addTapListener(key, callback)
----
-[[context_put]]
-=== context.put()
-Issues a PUT REST API call to the URL associated with the action.
+* key: The key of the action.
+
+* callback: JavaScript function to be invoked when action tapped.
-.Signature
[source,javascript]
----
-context.put(input, callback)
+changeActions.addTapListener("__key_for_my_action__", () => {
+ // do something when my action gets clicked
+})
----
-* input: JavaScript object to serialize as the request payload.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
+[[change_actions_removeTapListener]]
+=== changeActions.removeTapListener()
+Removes an existing tap listener on an action.
+.Signature
[source,javascript]
----
-context.put(
- {message: "..."},
- function (result) {
- // ... use result here ...
- });
+changeActions.removeTapListener(key, callback)
----
-[[context_refresh]]
-=== context.refresh()
-Refresh the current display. Shorthand for
-link:#Gerrit_refresh[`Gerrit.refresh()`].
-
-[[context_revision]]
-=== context.revision
-When the action is invoked on a specific revision of a change,
-a link:rest-api-changes.html#revision-info[RevisionInfo]
-object instance describing the revision. Available fields of the
-RevisionInfo may vary based on the options used by the UI when it
-loaded the change.
-
-[[context_project]]
-=== context.project
-When the action is invoked on a specific project,
-the name of the project.
-
-=== HTML Helpers
-The link:#ActionContext[action context] includes some HTML helper
-functions to make working with DOM based widgets less painful.
-
-* `br()`: new `<br>` element.
-
-* `button(label, options)`: new `<button>` with the string `label`
- wrapped inside of a `div`. The optional `options` object may
- define `onclick` as a function to be invoked upon clicking. This
- calling pattern avoids circular references between the element
- and the onclick handler.
-
-* `checkbox()`: new `<input type='checkbox'>` element.
-* `div(...)`: a new `<div>` wrapping the (optional) arguments.
-* `hr()`: new `<hr>` element.
-
-* `label(c, label)`: a new `<label>` element wrapping element `c`
- and the string `label`. Used to wrap a checkbox with its label,
- `label(checkbox(), 'Click Me')`.
-
-* `prependLabel(label, c)`: a new `<label>` element wrapping element `c`
- and the string `label`. Used to wrap an input field with its label,
- `prependLabel('Greeting message', textfield())`.
-
-* `textarea(options)`: new `<textarea>` element. The options
- object may optionally include `rows` and `cols`. The textarea
- comes with an onkeypress handler installed to play nicely with
- Gerrit's keyboard binding system.
-
-* `textfield()`: new `<input type='text'>` element. The text field
- comes with an onkeypress handler installed to play nicely with
- Gerrit's keyboard binding system.
-
-* `select(a,i)`: a new `<select>` element containing one `<option>`
- element for each entry in the provided array `a`. The option with
- the index `i` will be pre-selected in the drop-down-list.
-
-* `selected(s)`: returns the text of the `<option>` element that is
- currently selected in the provided `<select>` element `s`.
-
-* `span(...)`: a new `<span>` wrapping the (optional) arguments.
-
-* `msg(label)`: a new label.
-
-
-[[ScreenContext]]
-== Screen Context
-A new screen context is passed to the `screen` callback function
-each time the user navigates to a matching URL.
-
-[[screen_body]]
-=== screen.body
-Empty HTML `<div>` node the plugin should add its content to. The
-node is already attached to the document, but is invisible. Plugins
-must call `screen.show()` to display the DOM node. Deferred display
-allows an implementor to partially populate the DOM, make remote HTTP
-requests, finish populating when the callbacks arrive, and only then
-make the view visible to the user.
-
-[[screen_token]]
-=== screen.token
-URL token fragment that activated this screen. The value is identical
-to `screen.token_match[0]`. If the URL is `/#/x/hello/list` the token
-will be `"list"`.
-
-[[screen_token_match]]
-=== screen.token_match
-Array of matching subgroups from the pattern specified to `screen()`.
-This is identical to the result of RegExp.exec. Index 0 contains the
-entire matching expression; index 1 the first matching group, etc.
-
-[[screen_onUnload]]
-=== screen.onUnload()
-Configures an optional callback to be invoked just before the screen
-is deleted from the browser DOM. Plugins can use this callback to
-remove event listeners from DOM nodes, preventing memory leaks.
+* key: The key of the action.
+
+* callback: JavaScript function to be removed.
+
+[[change_actions_setLabel]]
+=== changeActions.setLabel()
+Sets the label for an action.
.Signature
[source,javascript]
----
-screen.onUnload(callback)
+changeActions.setLabel(key, label)
----
-* callback: JavaScript function to be invoked just before the
- `screen.body` DOM element is removed from the browser DOM.
- This event happens when the user navigates to another screen.
+* key: The key of the action.
+
+* label: The label of the action.
-[[screen.setTitle]]
-=== screen.setTitle()
-Sets the heading text to be displayed when the screen is visible.
-This is presented in a large bold font below the menus, but above the
-content in `screen.body`. Setting the title also sets the window
-title to the same string, if it has not already been set.
+[[change_actions_setTitle]]
+=== changeActions.setTitle()
+Sets the title for an action.
.Signature
[source,javascript]
----
-screen.setPageTitle(titleText)
+changeActions.setTitle(key, title)
----
-[[screen.setWindowTitle]]
-=== screen.setWindowTitle()
-Sets the text to be displayed in the browser's title bar when the
-screen is visible. Plugins should always prefer this method over
-trying to set `window.title` directly. The window title defaults to
-the title given to `setTitle`.
+* key: The key of the action.
+
+* title: The title of the action.
+
+[[change_actions_setIcon]]
+=== changeActions.setIcon()
+Sets an icon for an action.
.Signature
[source,javascript]
----
-screen.setWindowTitle(titleText)
+changeActions.setIcon(key, icon)
----
-[[screen_show]]
-=== screen.show()
-Destroy the currently visible screen and display the plugin's screen.
-This method must be called after adding content to `screen.body`.
-
-[[SettingsScreenContext]]
-== Settings Screen Context
-A new settings screen context is passed to the `settingsScreen` callback
-function each time the user navigates to a matching URL.
-
-[[settingsScreen_body]]
-=== settingsScreen.body
-Empty HTML `<div>` node the plugin should add its content to. The
-node is already attached to the document, but is invisible. Plugins
-must call `settingsScreen.show()` to display the DOM node. Deferred
-display allows an implementor to partially populate the DOM, make
-remote HTTP requests, finish populating when the callbacks arrive, and
-only then make the view visible to the user.
-
-[[settingsScreen_onUnload]]
-=== settingsScreen.onUnload()
-Configures an optional callback to be invoked just before the screen
-is deleted from the browser DOM. Plugins can use this callback to
-remove event listeners from DOM nodes, preventing memory leaks.
+* key: The key of the action.
+
+* icon: The name of the icon.
+
+[[change_actions_setEnabled]]
+=== changeActions.setEnabled()
+Sets an action to enabled or disabled.
.Signature
[source,javascript]
----
-settingsScreen.onUnload(callback)
+changeActions.setEnabled(key, enabled)
----
-* callback: JavaScript function to be invoked just before the
- `settingsScreen.body` DOM element is removed from the browser DOM.
- This event happens when the user navigates to another screen.
+* key: The key of the action.
+
+* enabled: The status of the action, true to enable.
-[[settingsScreen.setTitle]]
-=== settingsScreen.setTitle()
-Sets the heading text to be displayed when the screen is visible.
-This is presented in a large bold font below the menus, but above the
-content in `settingsScreen.body`. Setting the title also sets the
-window title to the same string, if it has not already been set.
+[[change_actions_setActionHidden]]
+=== changeActions.setActionHidden()
+Sets an action to be hidden.
.Signature
[source,javascript]
----
-settingsScreen.setPageTitle(titleText)
+changeActions.setActionHidden(type, key, hidden)
----
-[[settingsScreen.setWindowTitle]]
-=== settingsScreen.setWindowTitle()
-Sets the text to be displayed in the browser's title bar when the
-screen is visible. Plugins should always prefer this method over
-trying to set `window.title` directly. The window title defaults to
-the title given to `setTitle`.
+* type: The type of the action.
+
+* key: The key of the action.
+
+* hidden: True to hide the action, false to show the action.
+
+[[change_actions_setActionOverflow]]
+=== changeActions.setActionOverflow()
+Sets an action to show in overflow menu.
.Signature
[source,javascript]
----
-settingsScreen.setWindowTitle(titleText)
+changeActions.setActionOverflow(type, key, overflow)
----
-[[settingsScreen_show]]
-=== settingsScreen.show()
-Destroy the currently visible screen and display the plugin's screen.
-This method must be called after adding content to
-`settingsScreen.body`.
+* type: The type of the action.
+
+* key: The key of the action.
+
+* overflow: True to move the action to overflow menu, false to move
+ the action out of the overflow menu.
[[PanelContext]]
== Panel Context
@@ -713,10 +438,12 @@ accessed through this name.
[[Gerrit_css]]
=== Gerrit.css()
+[WARNING]
+This method is deprecated. It doesn't work with Shadow DOM and
+will be removed in the future. Please, use link:pg-plugin-dev.html#plugin-styles[plugin.styles] instead.
+
Creates a new unique CSS class and injects it into the document.
The name of the class is returned and can be used by the plugin.
-See link:#Gerrit_html[`Gerrit.html()`] for an easy way to use
-generated class names.
Classes created with this function should be created once at install
time and reused throughout the plugin. Repeatedly creating the same
@@ -732,194 +459,6 @@ Gerrit.install(function(self)) {
});
----
-[[Gerrit_delete]]
-=== Gerrit.delete()
-Issues a DELETE REST API request to the Gerrit server. For plugin
-private REST API URLs see link:#self_delete[self.delete()].
-
-.Signature
-[source,javascript]
-----
-Gerrit.delete(url, callback)
-----
-
-* url: URL relative to the Gerrit server. For example to access the
- link:rest-api-changes.html[changes REST API] use `'/changes/'`.
-
-* callback: JavaScript function to be invoked with the parsed
- JSON result of the API call. DELETE methods often return
- `204 No Content`, which is passed as null.
-
-[source,javascript]
-----
-Gerrit.delete(
- '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
- function () {});
-----
-
-[[Gerrit_get]]
-=== Gerrit.get()
-Issues a GET REST API request to the Gerrit server. For plugin
-private REST API URLs see link:#self_get[self.get()].
-
-.Signature
-[source,javascript]
-----
-Gerrit.get(url, callback)
-----
-
-* url: URL relative to the Gerrit server. For example to access the
- link:rest-api-changes.html[changes REST API] use `'/changes/'`.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
-[source,javascript]
-----
-Gerrit.get('/changes/?q=status:open', function (open) {
- for (var i = 0; i < open.length; i++) {
- console.log(open[i].change_id);
- }
-});
-----
-
-[[Gerrit_getCurrentUser]]
-=== Gerrit.getCurrentUser()
-Returns the currently signed in user's AccountInfo data; empty account
-data if no user is currently signed in.
-
-[[Gerrit_getPluginName]]
-=== Gerrit.getPluginName()
-Returns the name this plugin was installed as by the server
-administrator. The plugin name is required to access REST API
-views installed by the plugin, or to access resources.
-
-Unlike link:#self_getPluginName[`self.getPluginName()`] this method
-must guess the name from the JavaScript call stack. Plugins are
-encouraged to use `self.getPluginName()` whenever possible.
-
-[[Gerrit_go]]
-=== Gerrit.go()
-Updates the web UI to display the screen identified by the supplied
-URL token. The URL token is the text after `#` in the browser URL.
-
-[source,javascript]
-----
-Gerrit.go('/admin/projects/');
-----
-
-If the URL passed matches `http://...`, `https://...`, or `//...`
-the current browser window will navigate to the non-Gerrit URL.
-The user can return to Gerrit with the back button.
-
-[[Gerrit_html]]
-=== Gerrit.html()
-Parses an HTML fragment after performing template replacements. If
-the HTML has a single root element or node that node is returned,
-otherwise it is wrapped inside a `<div>` and the div is returned.
-
-.Signature
-[source,javascript]
-----
-Gerrit.html(htmlText, options, wantElements);
-----
-
-* htmlText: string of HTML to be parsed. A new unattached `<div>` is
- created in the browser's document and the innerHTML property is
- assigned to the passed string, after performing replacements. If
- the div has exactly one child, that child will be returned instead
- of the div.
-
-* options: optional object reference supplying replacements for any
- `{name}` references in htmlText. Navigation through objects is
- supported permitting `{style.bar}` to be replaced with `"foo"` if
- options was `{style: {bar: "foo"}}`. Value replacements are HTML
- escaped before being inserted into the document fragment.
-
-* wantElements: if options is given and wantElements is also true
- an object consisting of `{root: parsedElement, elements: {...}}` is
- returned instead of the parsed element. The elements object contains
- a property for each element using `id={name}` in htmlText.
-
-.Example
-[source,javascript]
-----
-var style = {bar: Gerrit.css('background: yellow')};
-Gerrit.html(
- '<span class="{style.bar}">Hello {name}!</span>',
- {style: style, name: "World"});
-----
-
-Event handlers can be automatically attached to elements referenced
-through an attribute id. Object navigation is not supported for ids,
-and the parser strips the id attribute before returning the result.
-Handler functions must begin with `on` and be a function to be
-installed on the element. This approach is useful for onclick and
-other handlers that do not want to create circular references that
-will eventually leak browser memory.
-
-.Example
-[source,javascript]
-----
-var options = {
- link: {
- onclick: function(e) { window.close() },
- },
-};
-Gerrit.html('<a href="javascript:;" id="{link}">Close</a>', options);
-----
-
-When using options to install handlers care must be taken to not
-accidentally include the returned element into the event handler's
-closure. This is why options is built before calling `Gerrit.html()`
-and not inline as a shown above with "Hello World".
-
-DOM nodes can optionally be returned, allowing handlers to access the
-elements identified by `id={name}` at a later point in time.
-
-.Example
-[source,javascript]
-----
-var w = Gerrit.html(
- '<div>Name: <input type="text" id="{name}"></div>'
- + '<div>Age: <input type="text" id="{age}"></div>'
- + '<button id="{submit}"><div>Save</div></button>',
- {
- submit: {
- onclick: function(s) {
- var e = w.elements;
- window.alert(e.name.value + " is " + e.age.value);
- },
- },
- }, true);
-----
-
-To prevent memory leaks `w.root` and `w.elements` should be set to
-null when the elements are no longer necessary. Screens can use
-link:#screen_onUnload[screen.onUnload()] to define a callback function
-to perform this cleanup:
-
-[source,javascript]
-----
-var w = Gerrit.html(...);
-screen.body.appendElement(w.root);
-screen.onUnload(function() { w.clear() });
-----
-
-[[Gerrit_injectCss]]
-=== Gerrit.injectCss()
-Injects CSS rules into the document by appending onto the end of the
-existing rule list. CSS rules are global to the entire application
-and must be manually scoped by each plugin. For an automatic scoping
-alternative see link:#Gerrit_css[`css()`].
-
-[source,javascript]
-----
-Gerrit.injectCss('.myplugin_bg {background: #000}');
-----
-
[[Gerrit_install]]
=== Gerrit.install()
Registers a new plugin by invoking the supplied initialization
@@ -932,136 +471,6 @@ Gerrit.install(function (self) {
});
----
-[[Gerrit_post]]
-=== Gerrit.post()
-Issues a POST REST API request to the Gerrit server. For plugin
-private REST API URLs see link:#self_post[self.post()].
-
-.Signature
-[source,javascript]
-----
-Gerrit.post(url, input, callback)
-----
-
-* url: URL relative to the Gerrit server. For example to access the
- link:rest-api-changes.html[changes REST API] use `'/changes/'`.
-
-* input: JavaScript object to serialize as the request payload.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
-[source,javascript]
-----
-Gerrit.post(
- '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
- {topic: 'tests', message: 'Classify work as for testing.'},
- function (r) {});
-----
-
-[[Gerrit_put]]
-=== Gerrit.put()
-Issues a PUT REST API request to the Gerrit server. For plugin
-private REST API URLs see link:#self_put[self.put()].
-
-.Signature
-[source,javascript]
-----
-Gerrit.put(url, input, callback)
-----
-
-* url: URL relative to the Gerrit server. For example to access the
- link:rest-api-changes.html[changes REST API] use `'/changes/'`.
-
-* input: JavaScript object to serialize as the request payload.
-
-* callback: JavaScript function to be invoked with the parsed JSON
- result of the API call. If the API returns a string the result is
- a string, otherwise the result is a JavaScript object or array,
- as described in the relevant REST API documentation.
-
-[source,javascript]
-----
-Gerrit.put(
- '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
- {topic: 'tests', message: 'Classify work as for testing.'},
- function (r) {});
-----
-
-[[Gerrit_onAction]]
-=== Gerrit.onAction()
-Register a JavaScript callback to be invoked when the user clicks
-on a button associated with a server side `UiAction`.
-
-.Signature
-[source,javascript]
-----
-Gerrit.onAction(type, view_name, callback);
-----
-
-* type: `'change'`, `'edit'`, `'revision'`, `'project'` or `'branch'`
- indicating what sort of resource the `UiAction` was bound to in the server.
-
-* view_name: string appearing in URLs to name the view. This is the
- second argument of the `get()`, `post()`, `put()`, and `delete()`
- binding methods in a `RestApiModule`.
-
-* callback: JavaScript function to invoke when the user clicks. The
- function will be passed a link:#ActionContext[ActionContext].
-
-[[Gerrit_screen]]
-=== Gerrit.screen()
-Register a JavaScript callback to be invoked when the user navigates
-to an extension screen provided by the plugin. Extension screens are
-usually linked from the link:dev-plugins.html#top-menu-extensions[top menu].
-The callback can populate the DOM with the screen's contents.
-
-.Signature
-[source,javascript]
-----
-Gerrit.screen(pattern, callback);
-----
-
-* pattern: URL token pattern to identify the screen. Argument can be
- either a string (`'index'`) or a RegExp object (`/list\/(.*)/`).
- If a RegExp is used the matching groups will be available inside of
- the context as `token_match`.
-
-* callback: JavaScript function to invoke when the user navigates to
- the screen. The function will be passed link:#ScreenContext[screen context].
-
-[[Gerrit_refresh]]
-=== Gerrit.refresh()
-Redisplays the current web UI view, refreshing all information.
-
-[[Gerrit_refreshMenuBar]]
-=== Gerrit.refreshMenuBar()
-Refreshes Gerrit's menu bar.
-
-[[Gerrit_isSignedIn]]
-=== Gerrit.isSignedIn()
-Checks if user is signed in.
-
-[[Gerrit_url]]
-=== Gerrit.url()
-Returns the URL of the Gerrit Code Review server. If invoked with
-no parameter the URL of the site is returned. If passed a string
-the argument is appended to the site URL.
-
-[source,javascript]
-----
-Gerrit.url(); // "https://gerrit-review.googlesource.com/"
-Gerrit.url('/123'); // "https://gerrit-review.googlesource.com/123"
-----
-
-For a plugin specific version see link:#self_url()[`self.url()`].
-
-[[Gerrit_showError]]
-=== Gerrit.showError(message)
-Displays the given message in the Gerrit ErrorDialog.
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index bb1399d9d9..c2bdfbb320 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -3,7 +3,6 @@
Apache2.0
* fonts:robotofonts
-* js:web-animations-js
* polymer_externs:polymer_closure
[[Apache2_0_license]]
@@ -477,33 +476,33 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----
-[[promise-polyfill]]
-promise-polyfill
+[[shadycss]]
+shadycss
-* js:promise-polyfill
+* js:shadycss
-[[promise-polyfill_license]]
+[[shadycss_license]]
----
-Copyright (c) 2014 Taylor Hakes
-Copyright (c) 2014 Forbes Lindesay
+# License
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Everything in this repo is BSD style license unless otherwise specified.
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+Copyright (c) 2015 The Polymer Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+* Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
----
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index e94f29741a..9f7bd997a9 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -52,6 +52,7 @@ Apache2.0
* commons:pool
* commons:validator
* dropwizard:dropwizard-core
+* errorprone:annotations
* flogger:api
* fonts:robotofonts
* guice:guice
@@ -72,8 +73,7 @@ Apache2.0
* jetty:server
* jetty:servlet
* jetty:util
-* jgit/org.eclipse.jgit:javaewah
-* js:web-animations-js
+* jetty:util-ajax
* log:json-smart
* log:jsonevent-layout
* log:log4j
@@ -98,6 +98,7 @@ Apache2.0
* guava-retrying
* html-types
* j2objc
+* javaewah
* jsr305
* mime-util
* servlet-api
@@ -2428,9 +2429,9 @@ Database section 7.
[[jgit]]
jgit
-* jgit/org.eclipse.jgit.archive:jgit-archive
-* jgit/org.eclipse.jgit.http.server:jgit-servlet
-* jgit/org.eclipse.jgit:jgit
+* jgit
+* jgit-archive
+* jgit-servlet
[[jgit_license]]
----
@@ -3329,37 +3330,6 @@ party waives its rights to a jury trial in any resulting litigation.
----
-[[promise-polyfill]]
-promise-polyfill
-
-* js:promise-polyfill
-
-[[promise-polyfill_license]]
-----
-Copyright (c) 2014 Taylor Hakes
-Copyright (c) 2014 Forbes Lindesay
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-----
-
-
[[protobuf]]
protobuf
@@ -3404,6 +3374,37 @@ support library is itself covered by the above license.
----
+[[shadycss]]
+shadycss
+
+* js:shadycss
+
+[[shadycss_license]]
+----
+# License
+
+Everything in this repo is BSD style license unless otherwise specified.
+
+Copyright (c) 2015 The Polymer Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+* Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+----
+
+
[[slf4j]]
slf4j
diff --git a/Documentation/linux-quickstart.txt b/Documentation/linux-quickstart.txt
index bfebc6a2dd..643bde0570 100644
--- a/Documentation/linux-quickstart.txt
+++ b/Documentation/linux-quickstart.txt
@@ -29,10 +29,10 @@ From the Linux machine on which you want to install Gerrit:
. Download the desired Gerrit archive.
To view previous archives, see
-link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases]. The steps below install Gerrit 2.15.1:
+link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases]. The steps below install Gerrit 3.1.3:
....
-wget https://www.gerritcodereview.com/download/gerrit-2.15.1.war
+wget https://gerrit-releases.storage.googleapis.com/gerrit-3.1.3.war
....
NOTE: To build and install Gerrit from the source files, see
@@ -43,7 +43,8 @@ link:dev-readme.html[Gerrit Code Review: Developer Setup].
From the command line, enter:
....
-java -jar gerrit*.war init --batch --dev -d ~/gerrit_testsite
+export GERRIT_SITE=~/gerrit_testsite
+java -jar gerrit*.war init --batch --dev -d $GERRIT_SITE
....
This command takes two parameters:
@@ -78,7 +79,7 @@ To prevent outside connections from contacting your new Gerrit instance
`localhost`. For example:
....
-git config --file ~/gerrit_testsite/etc/gerrit.config httpd.listenUrl 'http://localhost:8080'
+git config --file $GERRIT_SITE/etc/gerrit.config httpd.listenUrl 'http://localhost:8080'
....
== Restart the Gerrit service
@@ -87,7 +88,7 @@ You must restart the Gerrit service for your authentication type and listen URL
changes to take effect:
....
-~/gerrit_testsite/bin/gerrit.sh restart
+$GERRIT_SITE/bin/gerrit.sh restart
....
== Viewing Gerrit
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index bd3a25e250..e64d1de673 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -15,10 +15,12 @@ The following metrics are reported.
=== Actions
-* `action/retry_attempt_counts`: Distribution of number of attempts made
-by RetryHelper to execute an action (1 == single attempt, no retry)
+* `action/retry_attempt_count`: Number of retry attempts made
+by RetryHelper to execute an action (0 == single attempt, no retry)
* `action/retry_timeout_count`: Number of action executions of RetryHelper
that ultimately timed out
+* `action/auto_retry_count`: Number of automatic retries with tracing
+* `action/failures_on_auto_retry_count`: Number of failures on auto retry
=== Pushes
@@ -68,6 +70,11 @@ computed by default. They can be enabled via the
link:config.gerrit.html#cache.enableDiskStatMetrics[`cache.enableDiskStatMetrics`]
setting.
+=== Change
+
+* `change/submit_rule_evaluation`: Latency for evaluating submit rules on a change.
+* `change/submit_type_evaluation`: Latency for evaluating the submit type on a change.
+
=== HTTP
==== Jetty
@@ -89,6 +96,13 @@ setting.
* `http/server/jetty/threadpool/pool_size`: Current thread pool size
* `http/server/jetty/threadpool/queue_size`: Queued requests waiting for a thread
+==== LDAP
+
+* `ldap/login_latency`: Latency of logins.
+* `ldap/user_search_latency`: Latency for searching the user account.
+* `ldap/group_search_latency`: Latency for querying the group memberships of an account.
+* `ldap/group_expansion_latency`: Latency for expanding nested groups.
+
==== REST API
* `http/server/error_count`: Rate of REST API error responses.
@@ -184,6 +198,10 @@ excluding reindexing
* `notedb/stage_update_latency`: Latency for staging updates to NoteDb by table.
* `notedb/read_latency`: NoteDb read latency by table.
* `notedb/parse_latency`: NoteDb parse latency by table.
+* `notedb/external_id_cache_load_count`: Total number of times the external ID
+ cache loader was called.
+* `notedb/external_id_partial_read_latency`: Latency for generating a new external ID
+ cache state from a prior state.
* `notedb/external_id_update_count`: Total number of external ID updates.
* `notedb/read_all_external_ids_latency`: Latency for reading all
external ID's from NoteDb.
diff --git a/Documentation/pg-plugin-admin-api.txt b/Documentation/pg-plugin-admin-api.txt
index 084fa2c78a..1a41778363 100644
--- a/Documentation/pg-plugin-admin-api.txt
+++ b/Documentation/pg-plugin-admin-api.txt
@@ -4,14 +4,18 @@ This API is provided by link:pg-plugin-dev.html#plugin-admin[plugin.admin()]
and provides customization of the admin menu.
== addMenuLink
-`adminApi.addMenuLink(text, url, opt_external, opt_capabilities)`
+`adminApi.addMenuLink(text, url, opt_capability)`
Add a new link to the end of the admin navigation menu.
.Params
- *text* String text to appear in the link.
- *url* String of the destination URL for the link.
+- *opt_capability* String of capability required to show this link.
When adding an external link, the URL provided should be a full URL. Otherwise,
a non-external link should be relative beginning with a slash. For example, to
-create a link to open changes, use the value `/q/status:open`. \ No newline at end of file
+create a link to open changes, use the value `/q/status:open`.
+
+See more about capability from
+link:rest-api-accounts.html#list-account-capabilities[List Account Capabilities]. \ No newline at end of file
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index 8fb5655214..d90185147e 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -360,6 +360,16 @@ screen.
Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
+[[plugin-styles]]
+=== styles
+`plugin.styles()`
+
+.Params:
+- none
+
+.Returns:
+- Instance of link:pg-plugin-styles-api.html[GrStylesApi]
+
=== changeMetadata
`plugin.changeMetadata()`
@@ -372,6 +382,7 @@ Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
=== theme
`plugin.theme()`
+
Note: TODO
=== url
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index 3d66dd4aae..a8b333076a 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -134,6 +134,11 @@ This endpoint decorator wraps the voting buttons in the reply dialog.
=== header-title
This endpoint wraps the title-text in the application header.
+=== confirm-revert-change
+This endpoint is inside the confirm revert dialog. By default it displays a
+generic confirmation message regarding reverting the change. Plugins may add
+content to this message or replace it entirely.
+
=== confirm-submit-change
This endpoint is inside the confirm submit dialog. By default it displays a
generic confirmation message regarding submission of the change. Plugins may add
diff --git a/Documentation/pg-plugin-style-object.txt b/Documentation/pg-plugin-style-object.txt
new file mode 100644
index 0000000000..cdcfb557d7
--- /dev/null
+++ b/Documentation/pg-plugin-style-object.txt
@@ -0,0 +1,33 @@
+= Gerrit Code Review - GrStyleObject
+
+Store information about css style properties. You can't create this object
+directly. Instead you should use the link:pg-plugin-styles-api.html#css[css] method.
+This object allows to apply style correctly to elements within different shadow
+subtree.
+
+[[get-class-name]]
+== getClassName
+`styleObject.getClassName(element)`
+
+.Params
+- `element` - an HTMLElement.
+
+.Returns
+- `string` - class name. The class name is valid only within the shadow root of `element`.
+
+Creates a new unique CSS class and injects it into the appropriate place
+in DOM (it can be document or shadow root for element). This class can be later
+added to the element or to any other element in the same shadow root. It is guarantee,
+that method adds CSS class only once for each shadow root.
+
+== apply
+`styleObject.apply(element)`
+
+.Params
+- `element` - element to apply style.
+
+Create a new unique CSS class (see link:#get-class-name[getClassName]) and
+adds class to the element.
+
+
+
diff --git a/Documentation/pg-plugin-styles-api.txt b/Documentation/pg-plugin-styles-api.txt
new file mode 100644
index 0000000000..a8293251ec
--- /dev/null
+++ b/Documentation/pg-plugin-styles-api.txt
@@ -0,0 +1,29 @@
+= Gerrit Code Review - Plugin styles API
+
+This API is provided by link:pg-plugin-dev.html#plugin-styles[plugin.styles()]
+and provides a way to apply dynamically created styles to elements in a
+document.
+
+[[css]]
+== css
+`styles.css(rulesStr)`
+
+.Params
+- `*string* rulesStr` string with CSS styling declarations.
+
+Example:
+----
+const styleObject = plugin.styles().css('background: black; color: white;');
+...
+const className = styleObject.getClassName(element)
+...
+element.classList.add(className);
+...
+styleObject.apply(someOtherElement);
+----
+
+.Returns
+- Instance of link:pg-plugin-style-object.html[GrStyleObject].
+
+
+
diff --git a/Documentation/pg-plugin-styling.txt b/Documentation/pg-plugin-styling.txt
index 301da51c13..2453bad0b8 100644
--- a/Documentation/pg-plugin-styling.txt
+++ b/Documentation/pg-plugin-styling.txt
@@ -23,13 +23,15 @@ Plugins should provide Style Module, for example:
``` html
<dom-module id="some-style">
- <style>
- :root {
- --css-mixin-name: {
- property: value;
+ <template>
+ <style>
+ html {
+ --css-mixin-name: {
+ property: value;
+ }
}
- }
- </style>
+ </style>
+ </template>
</dom-module>
```
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
index 25ca4dd706..cf6560b344 100644
--- a/Documentation/pgm-daemon.txt
+++ b/Documentation/pgm-daemon.txt
@@ -11,7 +11,7 @@ _java_ -jar gerrit.war _daemon_
[--enable-httpd | --disable-httpd]
[--enable-sshd | --disable-sshd]
[--console-log]
- [--slave]
+ [--replica]
[--headless]
[--init]
[-s]
@@ -32,15 +32,15 @@ per the local copy of link:config-gerrit.html[gerrit.config] located under
--enable-httpd::
--disable-httpd::
Enable (or disable) the internal HTTP daemon, answering
- web requests. Enabled by default when --slave is not used.
+ web requests. Enabled by default when --replica is not used.
--enable-sshd::
--disable-sshd::
Enable (or disable) the internal SSH daemon, answering SSH
clients and remotely executed commands. Enabled by default.
---slave::
- Run in slave mode, permitting only read operations
+--replica::
+ Run in replica mode, permitting only read operations
by clients. Commands which modify state such as
link:cmd-receive-pack.html[receive-pack] (creates new changes
or updates existing ones) or link:cmd-review.html[review]
@@ -81,9 +81,9 @@ is automatically rotated at 12:00 AM GMT each day, allowing an
external log cleaning service to clean up the prior logs.
== KNOWN ISSUES
-Slave daemon caches can quickly become out of date when modifications
-are made on the master. The following configuration is suggested in
-a slave to reduce the maxAge for each cache entry, so that changes
+Replica daemon caches can quickly become out of date when modifications
+are made on the primary node. The following configuration is suggested in
+a replica to reduce the maxAge for each cache entry, so that changes
are recognized in a reasonable period of time:
----
@@ -107,7 +107,7 @@ and if LDAP support was enabled, also include:
maxAge = 5 min
----
-Automatic cache coherency between master and slave systems is
+Automatic cache coherency between primary and replica systems is
planned to be implemented in a future version.
GERRIT
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 9a23a27b36..f291920d4a 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -74,6 +74,9 @@ For interactive testing and playing with Prolog, Gerrit provides the
link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
Prolog interpreter shell.
+For batch or unit tests, see the examples in Gerrit source directory
+link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/prologtests/examples/[prologtests/examples].
+
[NOTE]
The interactive shell is just a prolog shell, it does not load
a gerrit server environment and thus is not intended for
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index aaa9223bf0..f2702313db 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding=utf-8
# Copyright (C) 2013 The Android Open Source Project
#
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 1d4f6fb14a..6f0d828af9 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -58,8 +58,8 @@ generally disabled by default. Optional fields are:
[[details]]
--
-* `DETAILS`: Includes full name, preferred email, username and avatars
-for each account.
+* `DETAILS`: Includes full name, preferred email, username, avatars,
+status and state for each account.
--
[[all-emails]]
@@ -1254,13 +1254,10 @@ any account.
)]}'
{
"changes_per_page": 25,
- "show_site_header": true,
- "use_flash_clipboard": true,
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "ABBREV",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"work_in_progress_by_default": true,
@@ -1309,13 +1306,10 @@ link:#preferences-input[PreferencesInput] entity.
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"expand_inline_diffs": true,
"date_format": "STD",
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
- "review_category_strategy": "NAME",
"diff_view": "SIDE_BY_SIDE",
"mute_common_path_prefixes": true,
"my": [
@@ -1359,13 +1353,10 @@ link:#preferences-info[PreferencesInfo] entity.
)]}'
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"expand_inline_diffs": true,
"date_format": "STD",
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
- "review_category_strategy": "NAME",
"diff_view": "SIDE_BY_SIDE",
"publish_comments_on_push": true,
"work_in_progress_by_default": true,
@@ -2201,7 +2192,7 @@ The `AccountDetailInfo` entity contains detailed information about an
account.
`AccountDetailInfo` has the same fields as link:#account-info[
-AccountInfo]. In addition `AccountDetailInfo` has the following fields:
+AccountInfo]. In addition `AccountDetailInfo` has the following field:
[options="header",cols="1,^1,5"]
|=================================
@@ -2209,8 +2200,6 @@ AccountInfo]. In addition `AccountDetailInfo` has the following fields:
|`registered_on` ||
The link:rest-api.html#timestamp[timestamp] of when the account was
registered.
-|`inactive` |not set if `false`|
-Whether the account is inactive.
|=================================
[[account-external-id-info]]
@@ -2261,9 +2250,14 @@ Only set if detailed account information is requested. +
See option link:rest-api-changes.html#detailed-accounts[
DETAILED_ACCOUNTS] for change queries +
and option link:#details[DETAILS] for account queries.
+|`avatars` |optional|List of link:#avatar-info[AvatarInfo] +
+entities that provide information about avatar images of the account.
|`_more_accounts` |optional, not set if `false`|
Whether the query would deliver more results if not limited. +
Only set on the last account that is returned.
+|`status` |optional|Status message of the account.
+|`inactive` |not set if `false`|
+Whether the account is inactive.
|===============================
[[account-input]]
@@ -2309,6 +2303,19 @@ for an account.
If not set or if set to an empty string, the account status is deleted.
|=============================
+[[avatar-info]]
+=== AvatarInfo
+The `AccountInfo` entity contains information about an avatar image of
+an account.
+
+[options="header",cols="1,6"]
+|======================
+|Field Name|Description
+|`url` |The URL to the avatar image.
+|`height` |The height of the avatar image in pixels.
+|`width` |The width of the avatar image in pixels.
+|======================
+
[[capability-info]]
=== CapabilityInfo
The `CapabilityInfo` entity contains information about the global
@@ -2705,10 +2712,6 @@ The `PreferencesInfo` entity contains information about a user's preferences.
|`changes_per_page` ||
The number of changes to show on each page.
Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header` |not set if `false`|
-Whether the site header should be shown.
-|`use_flash_clipboard` |not set if `false`|
-Whether to use the flash clipboard widget.
|`expand_inline_diffs` |not set if `false`|
Whether to expand diffs inline instead of opening as separate page
(PolyGerrit only).
@@ -2731,9 +2734,6 @@ Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
Whether to show the change sizes as colored bars in the change table.
|`legacycid_in_change_table` |not set if `false`|
Whether to show change number in the change table.
-|`review_category_strategy` ||
-The strategy used to displayed info in the review category column.
-Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
|`mute_common_path_prefixes` |not set if `false`|
Whether to mute common path prefixes in file names in the file table.
|`signed_off_by` |not set if `false`|
@@ -2774,10 +2774,6 @@ user preferences. Fields which are not set will not be updated.
|`changes_per_page` |optional|
The number of changes to show on each page.
Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header` |optional|
-Whether the site header should be shown.
-|`use_flash_clipboard` |optional|
-Whether to use the flash clipboard widget.
|`expand_inline_diffs` |not set if `false`|
Whether to expand diffs inline instead of opening as separate page
(PolyGerrit only).
@@ -2798,9 +2794,6 @@ Allowed values are `SIDE_BY_SIDE`, `UNIFIED_DIFF`.
Whether to show the change sizes as colored bars in the change table.
|`legacycid_in_change_table` |optional|
Whether to show change number in the change table.
-|`review_category_strategy` |optional|
-The strategy used to displayed info in the review category column.
-Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
|`mute_common_path_prefixes` |optional|
Whether to mute common path prefixes in file names in the file table.
|`signed_off_by` |optional|
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 199c13d58a..e39355c71b 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -138,6 +138,12 @@ limit or a supplied `n` query parameter, the last change object has a
The `S` or `start` query parameter can be supplied to skip a number
of changes from the list.
+Administrators can use the `skip-visibility` query parameter to skip visibility filtering.
+This can be used to ensure that no changes are missed e.g. when querying for changes which
+need to be reindexed. Without this parameter query results the user has no permission to read
+are filtered out. REST requests with the skip-visibility option are rejected when the current
+user doesn't have the ADMINISTRATE_SERVER capability.
+
Clients are allowed to specify more than one query by setting the `q`
parameter multiple times. In this case the result is an array of
arrays, one per query in the same order the queries were given in.
@@ -318,6 +324,11 @@ effect.
A change's mergeability can be requested separately by calling the
link:#get-mergeable[get-mergeable] endpoint.
--
+[[skip_diffstat]]
+--
+* `SKIP_DIFFSTAT`: skip the 'insertions' and 'deletions' field in link:#change-info[ChangeInfo].
+ For large trees, their computation may be expensive.
+--
[[submittable]]
--
@@ -844,8 +855,10 @@ returned that describes the resulting change.
Creates a new patch set with a new commit message.
The new commit message must be provided in the request body inside a
-link:#commit-message-input[CommitMessageInput] entity and contain the change ID footer if
-link:project-configuration.html#require-change-id[Require Change-Id] was specified.
+link:#commit-message-input[CommitMessageInput] entity. If a Change-Id
+footer is specified, it must match the current Change-Id footer. If
+the Change-Id footer is absent, the current Change-Id is added to the
+message.
.Request
----
@@ -2628,6 +2641,14 @@ content isn't provided, it is wiped out for that file. As response
HTTP/1.1 204 No Content
----
+When the change edit is a no-op, for example when providing the same file
+content that the file already has, '409 no changes were made' is returned.
+
+.Response
+----
+ HTTP/1.1 409 no changes were made
+----
+
[[post-edit]]
=== Restore file content or rename files in Change Edit
--
@@ -2974,7 +2995,20 @@ As result a list of link:#reviewer-info[ReviewerInfo] entries is returned.
Suggest the reviewers for a given query `q` and result limit `n`. If result
limit is not passed, then the default 10 is used.
-Groups can be excluded from the results by specifying 'e=f'.
+This REST endpoint only suggests accounts that
+
+* are active
+* can see the change
+* are visible to the calling user
+* are not already reviewer on the change
+* don't own the change
+
+Groups can be excluded from the results by specifying the 'exclude-groups'
+request parameter:
+
+--
+'GET /changes/link:#change-id[\{change-id\}]/suggest_reviewers?q=J&n=5&exclude-groups'
+--
As result a list of link:#suggested-reviewer-info[SuggestedReviewerInfo] entries is returned.
@@ -3009,6 +3043,14 @@ As result a list of link:#suggested-reviewer-info[SuggestedReviewerInfo] entries
]
----
+To suggest CCs `reviewer-state=CC` can be specified as additional URL
+parameter. This includes existing reviewers in the result, but excludes
+existing CCs.
+
+--
+'GET /changes/link:#change-id[\{change-id\}]/suggest_reviewers?q=J&reviewer-state=CC'
+--
+
[[get-reviewer]]
=== Get Reviewer
--
@@ -3054,6 +3096,12 @@ Adds one user or all members of one group as reviewer to the change.
The reviewer to be added to the change must be provided in the request
body as a link:#reviewer-input[ReviewerInput] entity.
+Users can be moved from reviewer to CC and vice versa. This means if a
+user is added as CC that is already a reviewer on the change, the
+reviewer state of that user is updated to CC. If a user that is already
+a CC on the change is added as reviewer, the reviewer state of that
+user is updated to reviewer.
+
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
@@ -3097,6 +3145,12 @@ members are added as reviewer.
If a group with many members is added as reviewer a confirmation may be
required.
+If a group is added as CC and members of this group are already
+reviewers on the change, these members stay reviewers on the change
+(they are not downgraded to CC). However if a group is added as
+reviewer, all group members become reviewer of the change, even if they
+have been added as CC before.
+
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
@@ -4331,8 +4385,10 @@ a project-specific rule.
R = label('Any-Label-Name', reject(_)).
----
-The response is a list of link:#submit-record[SubmitRecord] entries
-describing the permutations that satisfy the tested submit rule.
+The response is a link:#submit-record[SubmitRecord] describing the
+permutations that satisfy the tested submit rule.
+
+If the submit rule was a no-op, the response is "`204 No Content`".
.Response
----
@@ -4341,14 +4397,12 @@ describing the permutations that satisfy the tested submit rule.
Content-Type: application/json; charset=UTF-8
)]}'
- [
- {
- "status": "NOT_READY",
- "reject": {
- "Any-Label-Name": {}
- }
+ {
+ "status": "NOT_READY",
+ "reject": {
+ "Any-Label-Name": {}
}
- ]
+ }
----
When testing with the `curl` command line client the
@@ -4905,7 +4959,8 @@ should make two requests.
For merge commits only, the integer-valued request parameter `parent`
changes the response to return a map of the files which are different
in this commit compared to the given parent commit. The value is the
-1-based index of the parent's position in the commit object. If not
+1-based index of the parent's position in the commit object,
+with the first parent always belonging to the target branch. If not
specified, the response contains a map of the files different in the
auto merge result.
@@ -5966,7 +6021,9 @@ The `CherryPickInput` entity contains information for cherry-picking a change to
[options="header",cols="1,^1,5"]
|===========================
|Field Name ||Description
-|`message` ||Commit message for the cherry-pick change
+|`message` |optional|
+Commit message for the cherry-pick change. If not set, the commit message of
+the cherry-picked commit is used.
|`destination` ||Destination branch
|`base` |optional|
40-hex digit SHA-1 of the commit which will be the parent commit of the newly created change.
@@ -6246,10 +6303,12 @@ in a file.
|`a` |optional|Content only in the file on side A (deleted in B).
|`b` |optional|Content only in the file on side B (added in B).
|`ab` |optional|Content in the file on both sides (unchanged).
-|`edit_a` |only present during a replace, i.e. both `a` and `b` are present|
+|`edit_a` |only present when the `intraline` parameter is set and the
+DiffContent is a replace, i.e. both `a` and `b` are present|
Text sections deleted from side A as a
link:#diff-intraline-info[DiffIntralineInfo] entity.
-|`edit_b` |only present during a replace, i.e. both `a` and `b` are present|
+|`edit_b` |only present when the `intraline` parameter is set and the
+DiffContent is a replace, i.e. both `a` and `b` are present|
Text sections inserted in side B as a
link:#diff-intraline-info[DiffIntralineInfo] entity.
|`due_to_rebase`|not set if `false`|Indicates whether this entry was introduced by a
@@ -6312,11 +6371,12 @@ link:rest-api-changes.html#diff-web-link-info[DiffWebLinkInfo] entries.
The `DiffIntralineInfo` entity contains information about intraline edits in a
file.
-The information consists of a list of `<skip length, mark length>` pairs, where
+The information consists of a list of `<skip length, edit length>` pairs, where
the skip length is the number of characters between the end of the previous edit
-and the start of this edit, and the mark length is the number of edited characters
+and the start of this edit, and the edit length is the number of edited characters
following the skip. The start of the edits is from the beginning of the related
-diff content lines.
+diff content lines. If the list is empty, the entire DiffContent should be considered
+as unedited.
Note that the implied newline character at the end of each line is included in
the length calculation, and thus it is possible for the edits to span newlines.
@@ -6505,7 +6565,7 @@ and/or remove from, a change.
|=======================
|Field Name||Description
|`add` |optional|The list of hashtags to be added to the change.
-|`remove |optional|The list of hashtags to be removed from the change.
+|`remove` |optional|The list of hashtags to be removed from the change.
|=======================
[[included-in-info]]
@@ -6863,6 +6923,9 @@ If not set, the default is `ALL`.
|`notify_details`|optional|
Additional information about whom to notify about the revert as a map
of recipient type to link:#notify-info[NotifyInfo] entity.
+|`topic` |optional|
+Name of the topic for the revert change. If not set, the default is the topic
+of the change being reverted.
|=============================
[[review-info]]
@@ -7071,7 +7134,7 @@ entities.
|`reviewed` |optional|
Indicates whether the caller is authenticated and has commented on the
current revision. Only set if link:#reviewed[REVIEWED] option is requested.
-|`messageWithFooter` |optional|
+|`commit_with_footers` |optional|
If the link:#commit-footers[COMMIT_FOOTERS] option is requested and
this is the current patch set, contains the full commit message with
Gerrit-specific commit footers, as if this revision were submitted
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 93591120e7..063e54d915 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1054,14 +1054,11 @@ PreferencesInfo] is returned.
)]}'
{
"changes_per_page": 25,
- "show_site_header": true,
- "use_flash_clipboard": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "NONE",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"my": [
@@ -1133,14 +1130,11 @@ PreferencesInfo] is returned.
)]}'
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "NONE",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"my": [
@@ -1567,11 +1561,15 @@ button].
|`update_delay` ||
link:config-gerrit.html#change.updateDelay[How often in seconds the web
interface should poll for updates to the currently open change].
-|`submit_whole_topic` ||
+|`submit_whole_topic` |not set if `false`|
link:config-gerrit.html#change.submitWholeTopic[A configuration if
the whole topic is submitted].
|`disable_private_changes` |not set if `false`|
Returns true if private changes are disabled.
+|`exclude_mergeable_in_change_info` |not set if `false`|
+Value of the link:config-gerrit.html#change.api.excludeMergeableInChangeInfo[
+configuration parameter] that controls whether the mergeability bit in
+link:rest-api-changes.html#change-info[ChangeInfo] will never be set.
|=============================
[[check-account-external-ids-input]]
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 85f73295aa..c4ba97347b 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -508,8 +508,9 @@ describes the created group.
}
----
-If the group creation fails because the name is already in use the
-response is "`409 Conflict`".
+If the group creation fails because the name is already in use, or the
+UUID was specified and the UUID is already in use, the response is
+"`409 Conflict`".
[[get-group-detail]]
=== Get Group Detail
@@ -1596,6 +1597,7 @@ a new internal group.
|Field Name ||Description
|`name` |optional|The name of the group (not encoded). +
If set, must match the group name in the URL.
+|`uuid` |optional|The UUID of the group.
|`description` |optional|The description of the group.
|`visible_to_all`|optional|
Whether the group is visible to all registered users. +
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index 92b692fa33..ce262808ff 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -391,6 +391,24 @@ describes the plugin.
}
----
+Disabling of a link:config-gerrit.html#plugins.mandatory[mandatory plugin]
+is rejected:
+
+.Request
+----
+ DELETE /plugins/replication HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 405 Method Not Allowed
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ Plugin replication is mandatory
+----
+
[[reload-plugin]]
=== Reload Plugin
--
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 76151b40e0..63c3279410 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2504,6 +2504,9 @@ is returned that describes the commit.
Retrieves the branches and tags in which a change is included. As result
an link:rest-api-changes.html#included-in-info[IncludedInInfo] entity is returned.
+Branches that are not visible to the calling user according to the project's
+read permissions are filtered out from the result.
+
.Request
----
GET /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/in HTTP/1.0
@@ -2787,17 +2790,18 @@ dashboard-id.
}
----
-[[set-dashboard]]
-=== Set Dashboard
+[[create-dashboard]]
+=== Create Dashboard
--
'PUT /projects/link:#project-name[\{project-name\}]/dashboards/link:#dashboard-id[\{dashboard-id\}]'
--
-Updates/Creates a project dashboard.
+Creates a project dashboard, if a project dashboard with the given
+dashboard ID doesn't exist yet.
Currently only supported for the `default` dashboard.
-The creation/update information for the dashboard must be provided in
+The creation information for the dashboard must be provided in
the request body as a link:#dashboard-input[DashboardInput] entity.
.Request
@@ -2811,7 +2815,63 @@ the request body as a link:#dashboard-input[DashboardInput] entity.
}
----
-As response the new/updated dashboard is returned as a
+As response the new dashboard is returned as a link:#dashboard-info[
+DashboardInfo] entity.
+
+.Response
+----
+ HTTP/1.1 201 Created
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "main:closed",
+ "ref": "main",
+ "path": "closed",
+ "description": "Merged and abandoned changes in last 7 weeks",
+ "url": "/dashboard/?title\u003dClosed+changes\u0026Merged\u003dstatus:merged+age:7w\u0026Abandoned\u003dstatus:abandoned+age:7w",
+ "is_default": true,
+ "title": "Closed changes",
+ "sections": [
+ {
+ "name": "Merged",
+ "query": "status:merged age:7w"
+ },
+ {
+ "name": "Abandoned",
+ "query": "status:abandoned age:7w"
+ }
+ ]
+ }
+----
+
+[[update-dashboard]]
+=== Update Dashboard
+--
+'PUT /projects/link:#project-name[\{project-name\}]/dashboards/link:#dashboard-id[\{dashboard-id\}]'
+--
+
+Updates a project dashboard, if a project dashboard with the given
+dashboard ID already exists.
+
+Currently only supported for the `default` dashboard.
+
+The update information for the dashboard must be provided in
+the request body as a link:#dashboard-input[DashboardInput] entity.
+
+.Request
+----
+ PUT /projects/work%2Fmy-project/dashboards/default HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "id": "main:closed",
+ "commit_message": "Update the default dashboard"
+ }
+----
+
+As response the updated dashboard is returned as a
link:#dashboard-info[DashboardInfo] entity.
.Response
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index 28f01e98aa..05765ee51d 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -72,7 +72,7 @@ base.
To add a file to the change:
-. In the top left corner of the change, click Edit.
+. In the top right corner of the change, click Edit.
. Next to Files, click Open:
+
diff --git a/Documentation/user-request-tracing.txt b/Documentation/user-request-tracing.txt
index 8430e97ad1..b26f4c1e7c 100644
--- a/Documentation/user-request-tracing.txt
+++ b/Documentation/user-request-tracing.txt
@@ -1,5 +1,8 @@
= Request Tracing
+[[on-demand]]
+== On-demand Request Tracing
+
Gerrit supports on-demand tracing of single requests that results in
additional logs with debug information that are written to the
`error_log`. The logs that correspond to a traced request are
@@ -19,17 +22,24 @@ request type:
`--trace` option. More information about this can be found in
the link:cmd-index.html#trace[Trace] section of the
link:cmd-index.html[SSH command documentation].
-* Git: For Git pushes tracing can be enabled by setting the
- `trace` push option, the trace ID is returned in the command output.
- More information about this can be found in
- the link:user-upload.html#trace[Trace] section of the
- link:user-upload.html[upload documentation]. Tracing for Git requests
- other than Git push is not supported.
+* Git Push (requires usage of git protocol v2): For Git pushes tracing
+ can be enabled by setting the `trace` push option, the trace ID is
+ returned in the command output. More information about this can be
+ found in the link:user-upload.html#trace[Trace] section of the
+ link:user-upload.html[upload documentation].
+* Git Clone/Fetch/Ls-Remote (requires usage of git protocol v2): For
+ Git clone/fetch/ls-remote tracing can be enabled by setting the
+ `trace` server option. Use '-o trace=<TRACE-ID>' for `git fetch` and
+ `git ls-remote`, and '--server-option trace=<TRACE-ID>' for
+ `git clone`. If the `trace` server option is set without a value
+ (without trace ID) a trace ID is generated but the generated trace ID
+ is not returned to the client (hence a trace ID should always be
+ set).
When request tracing is enabled it is possible to provide an ID that
should be used as trace ID. If a trace ID is not provided a trace ID is
automatically generated. The trace ID must be provided to the support
-team so that they can find the trace.
+team (administrator of the server) so that they can find the trace.
When doing traces it is recommended to specify the ID of the issue
that is being investigated as trace ID so that the traces of the issue
@@ -41,6 +51,71 @@ be enabled for single requests if there is a concrete need for
debugging. In particular bots should never enable tracing for all their
requests by default.
+[[auto-retry]]
+== Automatic Request Tracing
+
+Gerrit can be link:config-gerrit.html#retry.retryWithTraceOnFailure[
+configured] to automatically retry requests on non-recoverable failures
+with tracing enabled. This allows to automatically captures traces of
+these failures for further analysis by the Gerrit administrators.
+
+The auto-retry on failure behaves the same way as if the calling user
+would retry the failed operation with tracing enabled.
+
+It is expected that the auto-retry fails with the same exception that
+triggered the auto-retry, however this is not guaranteed:
+
+* Not all Gerrit operations are fully atomic and it can happen that
+ some parts of the operation have been successfully performed before
+ the failure happened. In this case the auto-retry may fail with a
+ different exception.
+* Some exceptions may mistakenly be considered as non-recoverable and
+ the auto-retry actually succeeds.
+
+[[auto-retry-succeeded]]
+If an auto-retry succeeds you may consider filing this as
+link:https://bugs.chromium.org/p/gerrit/issues/entry?template=GoogleSource+Issue[
+Gerrit issue] so that the Gerrit developers can fix this and treat this
+exception as recoverable.
+
+The trace IDs for auto-retries are generated and start with
+`retry-on-failure-`. For REST requests they are returned to the client
+as `X-Gerrit-Trace` header.
+
+The best way to search for auto-retries in logs is to do a grep by
+`AutoRetry`. For each auto-retry that happened this should match 1 or 2
+log entries:
+
+* one `ERROR` log entry with the exception that triggered the
+ auto-retry
+* one `FINE` log entry with the exception that happened on auto-retry
+ (if this log entry is not present the operation succeeded on
+ auto-retry)
+
+To inspect single auto-retry occurrences in detail you can do a
+link:#find-trace[grep by the trace ID]. The trace ID is part of the log
+entries which have been found by the previous grep (watch out for
+something like: `retry-on-failure-1534166888910-3985dfba`).
+
+[TIP]
+Auto-retrying on failures is only supported by some of the REST
+endpoints (change REST endpoints that perform updates).
+
+[[auto-retry-metrics]]
+=== Metrics
+
+If auto-retry is link:config-gerrit.html#retry.retryWithTraceOnFailure[
+enabled] the following metrics are reported:
+
+* `action/auto_retry_count`: Number of automatic retries with tracing
+* `action/failures_on_auto_retry_count`: Number of failures on auto retry
+
+By comparing the values of these counters one can see how often the
+auto-retry succeeds. As explained link:#auto-retry-succeeded[above] if
+auto-retries succeed that's an issue with Gerrit that you may want to
+report.
+
+[[find-trace]]
== Find log entries for a trace ID
If tracing is enabled all log messages that correspond to the traced
@@ -55,6 +130,9 @@ request have a `TRACE_ID` tag set, e.g.:
By doing a grep with the trace ID over the error log the log entries
that correspond to the request can be found.
+[TIP]
+Usually only server administrators have access to the logs.
+
== Which information is captured in a trace?
* request details
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 6c4b78bfa8..0845956929 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -53,7 +53,7 @@ age:'AGE'::
+
Amount of time that has expired since the change was last updated
with a review comment or new patch set. The age must be specified
-to include a unit suffix, for example `age:2d`:
+to include a unit suffix, for example `-age:2d`:
+
* s, sec, second, seconds
* m, min, minute, minutes
@@ -63,6 +63,10 @@ to include a unit suffix, for example `age:2d`:
* mon, month, months (`1 month` is treated as `30 days`)
* y, year, years (`1 year` is treated as `365 days`)
+`age` can be used both forward and backward looking: `age:2d`
+means 'everything older than 2 days' while `-age:2d` means
+'everything with an age of at most 2 days'.
+
[[assignee]]
assignee:'USER'::
+
@@ -330,7 +334,7 @@ If 'DIR' starts with `^` it matches directories and directory segments by
regular expression. The link:http://www.brics.dk/automaton/[dk.brics.automaton
library] is used for evaluation of such patterns.
-[[footer]]
+[[footer-operator]]
footer:'FOOTER'::
+
Matches any change that has 'FOOTER' as footer in the commit message of the
@@ -412,7 +416,7 @@ is:cc::
True on any change where the current user is in CC.
Same as `cc:self`.
-is:open, is:pending::
+is:open, is:pending, is:new::
+
True if the change is open.
@@ -464,7 +468,7 @@ is:wip::
True if the change is Work In Progress.
[[status]]
-status:open, status:pending::
+status:open, status:pending, status:new::
+
True if the change state is 'review in progress'.
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index c702c767ab..d55cb48c00 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -28,6 +28,13 @@ credentials are validated using:
* Both, the HTTP and the LDAP passwords (in this order) if `gitBasicAuthPolicy`
is `HTTP_LDAP`.
+When gitBasicAuthPolicy is set to `LDAP` or `HTTP_LDAP` and the user
+is authenticating with the LDAP username/password, the Git client config
+needs to have `http.cookieFile` set to a local file, otherwise every
+single call would trigger a full LDAP authentication and groups resolution
+which could introduce a noticeable latency on the overall execution
+and produce unwanted load to the LDAP server.
+
When gitBasicAuthPolicy is not `LDAP`, the user's HTTP credentials can
be regenerated by going to `Settings`, and then accessing the `HTTP
Password` tab. Revocation can effectively be done by regenerating the
@@ -465,74 +472,8 @@ If Change-Id lines are not present in the commit messages, consider
amending the message and copying the line from the change's page
on the web, and then using `git push` as described above.
-If Change-Id lines are not available, then the user must use the
-manual mapping technique described below.
-
For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
-[[manual_replacement_mapping]]
-==== Manual Replacement Mapping
-
-[NOTE]
---
-The remainder of this section describes a manual method of replacing
-changes by matching each commit name to an existing change number.
-End-users should instead prefer to use Change-Id lines in their
-commit messages, as the process is then fully automated by Gerrit
-during normal uploads.
-
-See above for the preferred technique of replacing changes.
-
-Pushing directly to `refs/changes/` is deprecated. If you see the error
-message 'upload to refs/changes not allowed', it means that pushing directly
-to `refs/changes` is disabled on the Gerrit server and the below section does
-not apply to you.
---
-
-To add an additional patch set to a change, replacing it with an
-updated version of the same logical modification, send the new
-commit to the change's ref. For example, to add the commit whose
-SHA-1 starts with `c0ffee` as a new patch set for change number
-`1979`, use the push refspec `c0ffee:refs/changes/1979` as below:
-
-----
- git push ssh://sshusername@hostname:29418/projectname c0ffee:refs/changes/1979
-----
-
-This form can be combined together with `refs/for/'branchname'`
-(above) to simultaneously create new changes and replace changes
-during one network transaction.
-
-For example, consider the following sequence of events:
-
-----
- $ git commit -m A ; # create 3 commits
- $ git commit -m B
- $ git commit -m C
-
- $ git push ... HEAD:refs/for/master ; # upload for review
- ... A is 1500 ...
- ... B is 1501 ...
- ... C is 1502 ...
-
- $ git rebase -i HEAD~3 ; # edit "A", insert D before B
- ; # now series is A'-D-B'-C'
- $ git push ...
- HEAD:refs/for/master
- HEAD~3:refs/changes/1500
- HEAD~1:refs/changes/1501
- HEAD~0:refs/changes/1502 ; # upload replacements
-----
-
-At the final step during the push Gerrit will attach A' as a new
-patch set on change 1500; B' as a new patch set on change 1501; C'
-as a new patch set on 1502; and D will be created as a new change.
-
-Ensuring D is created as a new change requires passing the refspec
-`HEAD:refs/for/branchname`, otherwise Gerrit will ignore D and
-won't do anything with it. For this reason it is a good idea to
-always include the create change refspec when uploading replacements.
-
[[bypass_review]]
=== Bypass Review
@@ -716,10 +657,6 @@ If Change-Id lines are not present in the commit messages, consider
amending the message and copying the line from the change's page
on the web.
-If Change-Id lines are not available, then the user must use the much
-more <<manual_replacement_mapping,manual mapping technique>> offered
-by using `git push` to a specific `refs/changes/change#` reference.
-
For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
diff --git a/WORKSPACE b/WORKSPACE
index 454423a161..54c0f8fe14 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -8,11 +8,11 @@ load("//tools:nongoogle.bzl", "declare_nongoogle_deps")
http_archive(
name = "bazel_toolchains",
- sha256 = "726b5423e1c7a3866a3a6d68e7123b4a955e9fcbe912a51e0f737e6dab1d0af2",
- strip_prefix = "bazel-toolchains-3.1.0",
+ sha256 = "1adf7a8e9901287c644dcf9ca08dd8d67a69df94bedbd57a841490a84dc1e9ed",
+ strip_prefix = "bazel-toolchains-5.0.0",
urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/3.1.0/bazel-toolchains-3.1.0.tar.gz",
- "https://github.com/bazelbuild/bazel-toolchains/releases/download/3.1.0/bazel-toolchains-3.1.0.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/v5.0.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-toolchains/archive/v5.0.0.tar.gz",
],
)
@@ -92,6 +92,12 @@ go_repository(
importpath = "github.com/howeyc/fsnotify",
)
+# JGit external repository consumed from git submodule
+local_repository(
+ name = "jgit",
+ path = "modules/jgit",
+)
+
ANTLR_VERS = "3.5.2"
maven_jar(
@@ -161,9 +167,18 @@ maven_jar(
sha1 = "021a212688ec94fe77aff74ab34cc74f6f940e60",
)
-load("//lib/jgit:jgit.bzl", "jgit_repos")
+# JGit's transitive dependencies
+maven_jar(
+ name = "hamcrest-library",
+ artifact = "org.hamcrest:hamcrest-library:1.3",
+ sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
+)
-jgit_repos()
+maven_jar(
+ name = "jzlib",
+ artifact = "com.jcraft:jzlib:1.1.1",
+ sha1 = "a1551373315ffc2f96130a0e5704f74e151777ba",
+)
maven_jar(
name = "javaewah",
@@ -173,6 +188,12 @@ maven_jar(
)
maven_jar(
+ name = "error-prone-annotations",
+ artifact = "com.google.errorprone:error_prone_annotations:2.3.3",
+ sha1 = "42aa5155a54a87d70af32d4b0d06bf43779de0e2",
+)
+
+maven_jar(
name = "gson",
artifact = "com.google.code.gson:gson:2.8.5",
sha1 = "f645ed69d595b24d4cf8b3fbb64cc505bede8829",
@@ -291,8 +312,8 @@ maven_jar(
# When upgrading commons-compress, also upgrade tukaani-xz
maven_jar(
name = "commons-compress",
- artifact = "org.apache.commons:commons-compress:1.15",
- sha1 = "b686cd04abaef1ea7bc5e143c080563668eec17e",
+ artifact = "org.apache.commons:commons-compress:1.18",
+ sha1 = "1191f9f2bc0c47a8cce69193feb1ff0a8bcb37d5",
)
maven_jar(
@@ -599,18 +620,18 @@ maven_jar(
sha1 = "a3ae34e57fa8a4040e28247291d0cc3d6b8c7bcf",
)
-AUTO_VALUE_VERSION = "1.6.5"
+AUTO_VALUE_VERSION = "1.7"
maven_jar(
name = "auto-value",
artifact = "com.google.auto.value:auto-value:" + AUTO_VALUE_VERSION,
- sha1 = "816872c85048f36a67a276ef7a49cc2e4595711c",
+ sha1 = "fe8387764ed19460eda4f106849c664f51c07121",
)
maven_jar(
name = "auto-value-annotations",
artifact = "com.google.auto.value:auto-value-annotations:" + AUTO_VALUE_VERSION,
- sha1 = "c3dad10377f0e2242c9a4b88e9704eaf79103679",
+ sha1 = "5be124948ebdc7807df68207f35a0f23ce427f29",
)
declare_nongoogle_deps()
@@ -702,7 +723,7 @@ maven_jar(
sha1 = "f7be08ec23c21485b9b5a1cf1654c2ec8c58168d",
)
-GITILES_VERS = "0.2-12"
+GITILES_VERS = "0.3-7"
GITILES_REPO = GERRIT
@@ -711,14 +732,14 @@ maven_jar(
artifact = "com.google.gitiles:blame-cache:" + GITILES_VERS,
attach_source = False,
repository = GITILES_REPO,
- sha1 = "e175e4366d83f20378905ca58a505ba8adac291d",
+ sha1 = "af6212a62363906c63d367f8276ae1645f83bf93",
)
maven_jar(
name = "gitiles-servlet",
artifact = "com.google.gitiles:gitiles-servlet:" + GITILES_VERS,
repository = GITILES_REPO,
- sha1 = "53f654f79ec65b9af7fbe645c99bf7888cd1ac48",
+ sha1 = "6a53f722f8572a2f1bcb7d86e5692168844bab76",
)
# prettify must match the version used in Gitiles
@@ -731,8 +752,8 @@ maven_jar(
# Keep this version of Soy synchronized with the version used in Gitiles.
maven_jar(
name = "soy",
- artifact = "com.google.template:soy:2019-03-11",
- sha1 = "119ac4b3eb0e2c638526ca99374013965c727097",
+ artifact = "com.google.template:soy:2019-10-08",
+ sha1 = "4518bf8bac2dbbed684849bc209c39c4cb546237",
)
maven_jar(
@@ -748,24 +769,24 @@ maven_jar(
)
# When updating Bouncy Castle, also update it in bazlets.
-BC_VERS = "1.60"
+BC_VERS = "1.61"
maven_jar(
name = "bcprov",
artifact = "org.bouncycastle:bcprov-jdk15on:" + BC_VERS,
- sha1 = "bd47ad3bd14b8e82595c7adaa143501e60842a84",
+ sha1 = "00df4b474e71be02c1349c3292d98886f888d1f7",
)
maven_jar(
name = "bcpg",
artifact = "org.bouncycastle:bcpg-jdk15on:" + BC_VERS,
- sha1 = "13c7a199c484127daad298996e95818478431a2c",
+ sha1 = "422656435514ab8a28752b117d5d2646660a0ace",
)
maven_jar(
name = "bcpkix",
artifact = "org.bouncycastle:bcpkix-jdk15on:" + BC_VERS,
- sha1 = "d0c46320fbc07be3a24eb13a56cee4e3d38e0c75",
+ sha1 = "89bb3aa5b98b48e584eee2a7401b7682a46779b4",
)
maven_jar(
@@ -774,27 +795,30 @@ maven_jar(
sha1 = "fd369423346b2f1525c413e33f8cf95b09c92cbd",
)
-# Note that all of the following org.apache.httpcomponents have newer versions,
-# but 4.4.1 is the only version that is available for all of them.
-# TODO: Check what combination of new versions are compatible.
-HTTPCOMP_VERS = "4.4.1"
+# Base the following org.apache.httpcomponents versions on what
+# elasticsearch-rest-client explicitly depends on, except for
+# commons-codec (non-http) which is not necessary yet. Note that
+# below httpcore version(s) differs from the HTTPCOMP_VERS range,
+# upstream: that specific dependency has no HTTPCOMP_VERS version
+# equivalent currently.
+HTTPCOMP_VERS = "4.5.2"
maven_jar(
name = "fluent-hc",
artifact = "org.apache.httpcomponents:fluent-hc:" + HTTPCOMP_VERS,
- sha1 = "96fb842b68a44cc640c661186828b60590c71261",
+ sha1 = "7bfdfa49de6d720ad3c8cedb6a5238eec564dfed",
)
maven_jar(
name = "httpclient",
artifact = "org.apache.httpcomponents:httpclient:" + HTTPCOMP_VERS,
- sha1 = "016d0bc512222f1253ee6b64d389c84e22f697f0",
+ sha1 = "733db77aa8d9b2d68015189df76ab06304406e50",
)
maven_jar(
name = "httpcore",
- artifact = "org.apache.httpcomponents:httpcore:" + HTTPCOMP_VERS,
- sha1 = "f5aa318bda4c6c8d688c9d00b90681dcd82ce636",
+ artifact = "org.apache.httpcomponents:httpcore:4.4.4",
+ sha1 = "b31526a230871fbe285fbcbe2813f9c0839ae9b0",
)
# Test-only dependencies below.
@@ -817,30 +841,30 @@ maven_jar(
sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
)
-TRUTH_VERS = "0.44"
+TRUTH_VERS = "1.0"
maven_jar(
name = "truth",
artifact = "com.google.truth:truth:" + TRUTH_VERS,
- sha1 = "11eff954c0c14da7d43276d7b3bcf71463105368",
+ sha1 = "998e5fb3fa31df716574b4c9e8d374855e800451",
)
maven_jar(
name = "truth-java8-extension",
artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
- sha1 = "2081a0721d3101e1cf559f013e59c6129b4b10b0",
+ sha1 = "d85fbc1daf0510821f552f2aa71d9605e97aa438",
)
maven_jar(
name = "truth-liteproto-extension",
artifact = "com.google.truth.extensions:truth-liteproto-extension:" + TRUTH_VERS,
- sha1 = "64f47e4e3f79b0a582573098b9c3c6b73599f7c6",
+ sha1 = "7a279c50a0f93da15533cef4993b45606cf67d72",
)
maven_jar(
name = "truth-proto-extension",
artifact = "com.google.truth.extensions:truth-proto-extension:" + TRUTH_VERS,
- sha1 = "c03fbc16087d8cb3bf0f3265a04566d4beb88a6d",
+ sha1 = "8c0c2ea61750f02d0d5ce9c653106b6a5dc82d12",
)
maven_jar(
@@ -849,61 +873,61 @@ maven_jar(
sha1 = "7e060dd5b19431e6d198e91ff670644372f60fbd",
)
-# When bumping the easymock version number, make sure to also move powermock to a compatible version
-maven_jar(
- name = "easymock",
- artifact = "org.easymock:easymock:3.1",
- sha1 = "3e127311a86fc2e8f550ef8ee4abe094bbcf7e7e",
-)
-
-JETTY_VERS = "9.4.33.v20201020"
+JETTY_VERS = "9.4.35.v20201120"
maven_jar(
name = "jetty-servlet",
artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VERS,
- sha1 = "101609e8e5365c4406e4448099459eb605ac551f",
+ sha1 = "3e61bcb471e1bfc545ce866cbbe33c3aedeec9b1",
)
maven_jar(
name = "jetty-security",
artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VERS,
- sha1 = "c150bf2aca6cb1636e7195f844a2bb156546e50e",
+ sha1 = "80dc2f422789c78315de76d289b7a5b36c3232d5",
)
maven_jar(
name = "jetty-server",
artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VERS,
- sha1 = "f586ff2ee048ad2575866c1833d854288f402307",
+ sha1 = "513502352fd689d4730b2935421b990ada8cc818",
)
maven_jar(
name = "jetty-jmx",
artifact = "org.eclipse.jetty:jetty-jmx:" + JETTY_VERS,
- sha1 = "56b723070eeafc51b943cd9bf1a064a037e806a7",
+ sha1 = "38812031940a466d626ab5d9bbbd9d5d39e9f735",
)
maven_jar(
name = "jetty-continuation",
artifact = "org.eclipse.jetty:jetty-continuation:" + JETTY_VERS,
- sha1 = "f672e58d528fc83060558ab4fc6a797c8137dfcb",
+ sha1 = "09f021e5895471f622ec8f95e28f5815ea7ee192",
)
maven_jar(
name = "jetty-http",
artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VERS,
- sha1 = "ad28940f89ffde6ec1bd1656fe3f8493b01ba3c2",
+ sha1 = "45d35131a35a1e76991682174421e8cdf765fb9f",
)
maven_jar(
name = "jetty-io",
artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VERS,
- sha1 = "9e4b0048285b71f4769908780f957a470eca11da",
+ sha1 = "eb9460700b99b71ecd82a53697f5ff99f69b9e1c",
)
maven_jar(
name = "jetty-util",
artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VERS,
- sha1 = "c88807f210ab216aa831b48569ef50bd797384bc",
+ sha1 = "ef61b83f9715c3b5355b633d9f01d2834f908ece",
+)
+
+maven_jar(
+ name = "jetty-util-ajax",
+ artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VERS,
+ sha1 = "ebbb43912c6423bedb3458e44aee28eeb4d66f27",
+ src_sha1 = "b3acea974a17493afb125a9dfbe783870ce1d2f9",
)
maven_jar(
@@ -925,6 +949,12 @@ maven_jar(
)
maven_jar(
+ name = "javax-annotation",
+ artifact = "javax.annotation:javax.annotation-api:1.3.2",
+ sha1 = "934c04d3cfef185a8008e7bf34331b79730a9d43",
+)
+
+maven_jar(
name = "mockito",
artifact = "org.mockito:mockito-core:2.24.0",
sha1 = "969a7bcb6f16e076904336ebc7ca171d412cc1f9",
@@ -933,13 +963,13 @@ maven_jar(
BYTE_BUDDY_VERSION = "1.9.7"
maven_jar(
- name = "byte-buddy",
+ name = "bytebuddy",
artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
sha1 = "8fea78fea6449e1738b675cb155ce8422661e237",
)
maven_jar(
- name = "byte-buddy-agent",
+ name = "bytebuddy-agent",
artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
sha1 = "8e7d1b599f4943851ffea125fd9780e572727fc0",
)
@@ -974,8 +1004,8 @@ npm_binary(
bower_archive(
name = "iron-autogrow-textarea",
package = "polymerelements/iron-autogrow-textarea",
- sha1 = "68f0ece9b1e56ac26f8ce31d9938c504f6951bca",
- version = "2.1.0",
+ sha1 = "2f04c7e2a72d462de36093ab2b4889db20f699f6",
+ version = "2.2.0",
)
bower_archive(
@@ -995,64 +1025,64 @@ bower_archive(
bower_archive(
name = "iron-dropdown",
package = "polymerelements/iron-dropdown",
- sha1 = "ac96fe31cdf203a63426fa75131b43c98c0597d3",
- version = "1.5.5",
+ sha1 = "3902ba164552b1bfc59e6fa692efa4a1fd8dd4ea",
+ version = "2.2.1",
)
bower_archive(
name = "iron-input",
package = "polymerelements/iron-input",
- sha1 = "9bc0c8e81de2527125383cbcf74dd9f27e7fa9ac",
- version = "1.0.10",
+ sha1 = "f79952ff4f6f103c0a2cbd3dacf25935257ff392",
+ version = "2.1.3",
)
bower_archive(
name = "iron-overlay-behavior",
package = "polymerelements/iron-overlay-behavior",
- sha1 = "74cda9d7bf98e7a5e5004bc7ebdb6d208d49e11e",
- version = "2.0.0",
+ sha1 = "c2d2eac1b162420d9475ade2f16d5db8959b93fc",
+ version = "2.3.4",
)
bower_archive(
name = "iron-selector",
package = "polymerelements/iron-selector",
- sha1 = "e0ee46c28523bf17730318c3b481a8ed4331c3b2",
- version = "2.0.0",
+ sha1 = "3f3fcb55f6bd606ea493f99eab9daae21f7a6139",
+ version = "2.1.0",
)
bower_archive(
name = "paper-button",
package = "polymerelements/paper-button",
- sha1 = "3b01774f58a8085d3c903fc5a32944b26ab7be72",
- version = "2.0.0",
+ sha1 = "bcb783d74e1177c1d0836340e7c0280699d1438c",
+ version = "2.1.3",
)
bower_archive(
name = "paper-input",
package = "polymerelements/paper-input",
- sha1 = "6c934805e80ab201e143406edc73ea0ef35abf80",
- version = "1.1.18",
+ sha1 = "c1a81a4173d22e72e8ab609eb3715a75273396b3",
+ version = "2.2.3",
)
bower_archive(
name = "paper-tabs",
package = "polymerelements/paper-tabs",
- sha1 = "b6dd2fbd7ee887534334057a29eb545b940fc5cf",
- version = "2.0.0",
+ sha1 = "589b8e6efa0f171c93233137c8ea013dcea0ffc7",
+ version = "2.1.1",
)
bower_archive(
name = "iron-icon",
package = "polymerelements/iron-icon",
- sha1 = "7da49a0d33cd56017740e0dbcf41d2b71532023f",
- version = "2.0.0",
+ sha1 = "d21e7d4f1bdc6de881390f888e28d53155eeb551",
+ version = "2.1.0",
)
bower_archive(
name = "iron-iconset-svg",
package = "polymerelements/iron-iconset-svg",
- sha1 = "4d0c406239cad2ff2975c6dd95fa189de0fe6b50",
- version = "2.1.0",
+ sha1 = "07c0ce02ce6479856758893416a3709009db7f22",
+ version = "2.2.1",
)
bower_archive(
@@ -1065,36 +1095,36 @@ bower_archive(
bower_archive(
name = "page",
package = "visionmedia/page.js",
- sha1 = "51a05428dd4f68fae1df5f12d0e2b61ba67f7757",
- version = "1.7.1",
+ sha1 = "4a31889cd75cc5e7f68a4c7f256eecaf27102eee",
+ version = "1.11.4",
)
bower_archive(
name = "paper-item",
package = "polymerelements/paper-item",
- sha1 = "803273ceb9ffebec8ecc9373ea638af4cd34af58",
- version = "1.1.4",
+ sha1 = "c3bad022cf182d2bf1c8a44374c7fcb1409afbfa",
+ version = "2.1.1",
)
bower_archive(
name = "paper-listbox",
package = "polymerelements/paper-listbox",
- sha1 = "ccc1a90ab0a96878c7bf7c9c4cfe47c85b09c8e3",
- version = "2.0.0",
+ sha1 = "78247cc32bb776f204efef17cff3095878036a40",
+ version = "2.1.1",
)
bower_archive(
name = "paper-toggle-button",
package = "polymerelements/paper-toggle-button",
- sha1 = "4a2edbdb52c4531d39fe091f12de650bccda270f",
- version = "1.2.0",
+ sha1 = "9927960afb0062726ec1b585ef3e32764c3bbac9",
+ version = "2.1.1",
)
bower_archive(
name = "polymer",
package = "polymer/polymer",
- sha1 = "158443ab05ade5e2cdc24ebc01f1deef9aebac1b",
- version = "1.11.3",
+ sha1 = "d06e17a1d8dc6187ee5aa8c5b3501da10901c82f",
+ version = "2.7.2",
)
bower_archive(
@@ -1105,13 +1135,6 @@ bower_archive(
)
bower_archive(
- name = "promise-polyfill",
- package = "polymerlabs/promise-polyfill",
- sha1 = "a3b598c06cbd7f441402e666ff748326030905d6",
- version = "1.0.0",
-)
-
-bower_archive(
name = "resemblejs",
package = "rsmbl/Resemble.js",
sha1 = "49d5f022417c389b630d6f7ee667aa9540075c42",
@@ -1130,15 +1153,15 @@ bower_archive(
bower_archive(
name = "iron-test-helpers",
package = "polymerelements/iron-test-helpers",
- sha1 = "433b03b106f5ff32049b84150cd70938e18b67ac",
- version = "1.2.5",
+ sha1 = "882be2d4c8714b39299b5f7bf25253c4e8a40761",
+ version = "2.0.1",
)
bower_archive(
name = "test-fixture",
package = "polymerelements/test-fixture",
- sha1 = "e373bd21c069163c3a754e234d52c07c77b20d3c",
- version = "1.1.1",
+ sha1 = "7d72ddfebf555a2dd1fc60a85427d9026b509723",
+ version = "3.0.0",
)
bower_archive(
diff --git a/contrib/abandon_stale.py b/contrib/abandon_stale.py
deleted file mode 100755
index 2e01131d5c..0000000000
--- a/contrib/abandon_stale.py
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# The MIT License
-#
-# Copyright 2014 Sony Mobile Communications. All rights reserved.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-""" Script to abandon stale changes from the review server.
-
-Fetches a list of open changes that have not been updated since a given age in
-days, months or years (default 6 months), and then abandons them.
-
-Requires the user's credentials for the Gerrit server to be declared in the
-.netrc file. Supports either basic or digest authentication.
-
-Example to abandon changes that have not been updated for 3 months:
-
- ./abandon_stale --gerrit-url http://review.example.com/ --age 3months
-
-Supports dry-run mode to only list the stale changes, but not actually
-abandon them.
-
-See the --help output for more information about options.
-
-Requires pygerrit2 (https://github.com/dpursehouse/pygerrit2) to be installed
-and available for import.
-
-"""
-
-import logging
-import optparse
-import re
-import sys
-
-from pygerrit2.rest import GerritRestAPI
-from pygerrit2.rest.auth import HTTPBasicAuthFromNetrc, HTTPDigestAuthFromNetrc
-
-
-def _main():
- parser = optparse.OptionParser()
- parser.add_option('-g', '--gerrit-url', dest='gerrit_url',
- metavar='URL',
- default=None,
- help='gerrit server URL')
- parser.add_option('-b', '--basic-auth', dest='basic_auth',
- action='store_true',
- help='(deprecated) use HTTP basic authentication instead'
- ' of digest')
- parser.add_option('-d', '--digest-auth', dest='digest_auth',
- action='store_true',
- help='use HTTP digest authentication instead of basic')
- parser.add_option('-n', '--dry-run', dest='dry_run',
- action='store_true',
- help='enable dry-run mode: show stale changes but do '
- 'not abandon them')
- parser.add_option('-t', '--test', dest='testmode', action='store_true',
- help='test mode: query changes with the `test-abandon` '
- 'topic and ignore age option')
- parser.add_option('-a', '--age', dest='age',
- metavar='AGE',
- default="6months",
- help='age of change since last update in days, months'
- ' or years (default: %default)')
- parser.add_option('-m', '--message', dest='message',
- metavar='STRING', default=None,
- help='custom message to append to abandon message')
- parser.add_option('--branch', dest='branches', metavar='BRANCH_NAME',
- default=[], action='append',
- help='abandon changes only on the given branch')
- parser.add_option('--exclude-branch', dest='exclude_branches',
- metavar='BRANCH_NAME',
- default=[],
- action='append',
- help='do not abandon changes on given branch')
- parser.add_option('--project', dest='projects', metavar='PROJECT_NAME',
- default=[], action='append',
- help='abandon changes only on the given project')
- parser.add_option('--exclude-project', dest='exclude_projects',
- metavar='PROJECT_NAME',
- default=[],
- action='append',
- help='do not abandon changes on given project')
- parser.add_option('--owner', dest='owner',
- metavar='USERNAME',
- default=None,
- action='store',
- help='only abandon changes owned by the given user')
- parser.add_option('--exclude-wip', dest='exclude_wip',
- action='store_true',
- help='Exclude changes that are Work-in-Progress')
- parser.add_option('-v', '--verbose', dest='verbose',
- action='store_true',
- help='enable verbose (debug) logging')
-
- (options, _args) = parser.parse_args()
-
- level = logging.DEBUG if options.verbose else logging.INFO
- logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
- level=level)
-
- if not options.gerrit_url:
- logging.error("Gerrit URL is required")
- return 1
-
- if options.testmode:
- message = "Abandoning in test mode"
- else:
- pattern = re.compile(r"^([\d]+)(month[s]?|year[s]?|week[s]?)")
- match = pattern.match(options.age)
- if not match:
- logging.error("Invalid age: %s", options.age)
- return 1
- message = "Abandoning after %s %s or more of inactivity." % \
- (match.group(1), match.group(2))
-
- if options.digest_auth:
- auth_type = HTTPDigestAuthFromNetrc
- else:
- auth_type = HTTPBasicAuthFromNetrc
-
- try:
- auth = auth_type(url=options.gerrit_url)
- gerrit = GerritRestAPI(url=options.gerrit_url, auth=auth)
- except Exception as e:
- logging.error(e)
- return 1
-
- logging.info(message)
- try:
- stale_changes = []
- offset = 0
- step = 500
- if options.testmode:
- query_terms = ["status:new", "owner:self", "topic:test-abandon"]
- else:
- query_terms = ["status:new", "age:%s" % options.age]
- if options.exclude_wip:
- query_terms += ["-is:wip"]
- if options.branches:
- query_terms += ["branch:%s" % b for b in options.branches]
- elif options.exclude_branches:
- query_terms += ["-branch:%s" % b for b in options.exclude_branches]
- if options.projects:
- query_terms += ["project:%s" % p for p in options.projects]
- elif options.exclude_projects:
- query_terms = ["-project:%s" % p for p in options.exclude_projects]
- if options.owner and not options.testmode:
- query_terms += ["owner:%s" % options.owner]
- query = "%20".join(query_terms)
- while True:
- q = query + "&o=DETAILED_ACCOUNTS&n=%d&S=%d" % (step, offset)
- logging.debug("Query: %s", q)
- url = "/changes/?q=" + q
- result = gerrit.get(url)
- logging.debug("%d changes", len(result))
- if not result:
- break
- stale_changes += result
- last = result[-1]
- if "_more_changes" in last:
- logging.debug("More...")
- offset += step
- else:
- break
- except Exception as e:
- logging.error(e)
- return 1
-
- abandoned = 0
- errors = 0
- abandon_message = message
- if options.message:
- abandon_message += "\n\n" + options.message
- for change in stale_changes:
- number = change["_number"]
- project = ""
- if len(options.projects) != 1:
- project = "%s: " % change["project"]
- owner = ""
- if options.verbose:
- try:
- o = change["owner"]["name"]
- except KeyError:
- o = "Unknown"
- owner = " (%s)" % o
- subject = change["subject"]
- if len(subject) > 70:
- subject = subject[:65] + " [...]"
- change_id = change["id"]
- logging.info("%s%s: %s%s", number, owner, project, subject)
- if options.dry_run:
- continue
-
- try:
- gerrit.post("/changes/" + change_id + "/abandon",
- json={"message": "%s" % abandon_message})
- abandoned += 1
- except Exception as e:
- errors += 1
- logging.error(e)
- logging.info("Total %d stale open changes", len(stale_changes))
- if not options.dry_run:
- logging.info("Abandoned %d changes. %d errors.", abandoned, errors)
-
-
-if __name__ == "__main__":
- sys.exit(_main())
diff --git a/contrib/benchmark-createchange.go b/contrib/benchmark-createchange.go
new file mode 100644
index 0000000000..dc320d6ae1
--- /dev/null
+++ b/contrib/benchmark-createchange.go
@@ -0,0 +1,103 @@
+// Copyright (C) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Program to benchmark Gerrit. Creates pending changes in a loop,
+// which tests performance of BatchRefUpdate and Lucene indexing
+package main
+
+import (
+ "bytes"
+ "encoding/base64"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "sort"
+ "time"
+)
+
+func main() {
+ user := flag.String("user", "admin", "username for basic auth")
+ pw := flag.String("password", "secret", "HTTP password for basic auth")
+ project := flag.String("project", "", "project to create changes in")
+ gerritURL := flag.String("url", "http://localhost:8080/", "URL to gerrit instance")
+ numChanges := flag.Int("n", 100, "number of changes to create")
+ flag.Parse()
+ if *gerritURL == "" {
+ log.Fatal("provide --url")
+ }
+ if *project == "" {
+ log.Fatal("provide --project")
+ }
+
+ u, err := url.Parse(*gerritURL)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ basicAuth := fmt.Sprintf("%s:%s", *user, *pw)
+ authHeader := base64.StdEncoding.EncodeToString([]byte(basicAuth))
+
+ client := &http.Client{}
+
+ var dts []time.Duration
+ startAll := time.Now()
+ var lastSec int
+ for i := 0; i < *numChanges; i++ {
+ body := fmt.Sprintf(`{
+ "project" : "%s",
+ "subject" : "change %d",
+ "branch" : "master",
+ "status" : "NEW"
+ }`, *project, i)
+ start := time.Now()
+
+ thisSec := int(start.Sub(startAll) / time.Second)
+ if thisSec != lastSec {
+ log.Printf("change %d", i)
+ }
+ lastSec = thisSec
+
+ u.Path = "/a/changes/"
+ req, err := http.NewRequest("POST", u.String(), bytes.NewBufferString(body))
+ if err != nil {
+ log.Fatal(err)
+ }
+ req.Header.Add("Authorization", "Basic "+authHeader)
+ req.Header.Add("Content-Type", "application/json; charset=UTF-8")
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Fatal(err)
+ }
+ dt := time.Now().Sub(start)
+ dts = append(dts, dt)
+
+ if resp.StatusCode/100 == 2 {
+ continue
+ }
+ log.Println("code", resp.StatusCode)
+ io.Copy(os.Stdout, resp.Body)
+ }
+
+ sort.Slice(dts, func(i, j int) bool { return dts[i] < dts[j] })
+
+ var total time.Duration
+ for _, dt := range dts {
+ total += dt
+ }
+ log.Printf("min %v max %v median %v avg %v", dts[0], dts[len(dts)-1], dts[len(dts)/2], total/time.Duration(len(dts)))
+}
diff --git a/contrib/check-valid-commit.py b/contrib/check-valid-commit.py
index 763ae3e062..bb018f9bcf 100755
--- a/contrib/check-valid-commit.py
+++ b/contrib/check-valid-commit.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
from __future__ import print_function
diff --git a/contrib/find-duplicate-usernames.sh b/contrib/find-duplicate-usernames.sh
new file mode 100755
index 0000000000..b59e5beb27
--- /dev/null
+++ b/contrib/find-duplicate-usernames.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+usage() {
+ f="$(basename -- $0)"
+ cat <<EOF
+Usage:
+ cd /path/to/All-Users.git
+ "$f [username|gerrit|external]"
+
+This script finds duplicate usernames only differing in case in the given
+account schema ("username", "gerrit" or "external") and their respective accountIds.
+EOF
+ exit 1
+}
+
+if [[ "$#" -ne "1" ]] || ! [[ "$1" =~ ^(gerrit|username|external)$ ]]; then
+ usage
+fi
+
+# 1. find lines with user name and subsequent line in external-ids notes branch
+# example output of git grep -A1 "\[externalId \"username:" refs/meta/external-ids:
+# refs/meta/external-ids:00/1d/abd037e437f71d42134e6ad532a06948a2ba:[externalId "username:johndoe"]
+# refs/meta/external-ids:00/1d/abd037e437f71d42134e6ad532a06948a2ba- accountId = 1000815
+# --
+# refs/meta/external-ids:00/1f/0270fc2a6fc3a2439c454c8ab0c75323fdb0:[externalId "username:JohnDoe"]
+# refs/meta/external-ids:00/1f/0270fc2a6fc3a2439c454c8ab0c75323fdb0- accountId = 1000816
+# --
+# 2. remove group separators
+# 3. remove line break between user name and accountId lines
+# 4. unify separators to ":"
+# 5. cut on ":", select username and accountId fields
+# 6. sort case-insensitive
+# 7. flip columns
+# 8. uniq case-insensitive, only show duplicates, avoid comparing first field
+# 9. flip columns back
+git grep -A1 "\[externalId \"$1:" refs/meta/external-ids \
+ | sed -E "/$1/,/accountId/!d" \
+ | paste -d ' ' - - \
+ | tr \"= : \
+ | cut -d: --output-delimiter="" -f 5,8 \
+ | sort -f \
+ | sed -E "s/(.*) (.*)/\2 \1/" \
+ | uniq -Di -f1 \
+ | sed -E "s/(.*) (.*)/\2 \1/"
diff --git a/contrib/git-push-review b/contrib/git-push-review
index b995fc25a6..5a7f664bf3 100755
--- a/contrib/git-push-review
+++ b/contrib/git-push-review
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/contrib/hooks/post-receive-move-tmp-refs b/contrib/hooks/post-receive-move-tmp-refs
new file mode 100755
index 0000000000..fa0684f5df
--- /dev/null
+++ b/contrib/hooks/post-receive-move-tmp-refs
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# --------------------------------------------------------
+# Install this hook script as post-receive hook in replicated repositories
+# hosted by a gerrit replica which are updated by push replication from the
+# corresponding gerrit primary node.
+#
+# In the gerrit primary node configure the replication plugin to push changes from
+# refs/changes/ to refs/tmp/changes/
+# remote.NAME.push = +refs/changes/*:refs/tmp/changes/*
+# remote.NAME.push = +refs/heads/*:refs/heads/*
+# remote.NAME.push = +refs/tags/*:refs/tags/*
+# And if it's a Gerrit mirror:
+# remote.NAME.push = +refs/meta/*:refs/meta/*
+#
+# In the replicated repository in the gerrit replica configure
+# receive.hideRefs = refs/changes/
+# in order to not advertise the big number of refs in this namespace when
+# the gerrit primary's replication plugin is pushing a change
+#
+# Whenever a ref under refs/tmp/changes/ is arriving this hook will move it
+# to refs/changes/. This helps to avoid the large overhead of advertising all
+# refs/changes/ refs to the gerrit primary when it replicates changes to the
+# replica.
+#
+# Make this script executable then link to it in the repository you would like
+# to use it in.
+# cd /path/to/your/repository.git
+# ln -sf <shared hooks directory>/post-receive-move-tmp-refs hooks/post-receive
+#
+# If you want to use this by default for repositories on the Gerrit replica you
+# can set up a git template directory $TEMPLATE_DIR/hooks/post-receive and
+# configure init.templateDir in the ~/.gitconfig of the user that receives the
+# replication on the mirror host. That way when a new repository is created on
+# the primary and hence on the mirror (if configured that way) it will
+# automatically have the "tmp-refs" commit hook installed.
+# See https://git-scm.com/docs/git-init#_template_directory for details.
+
+# move new changes arriving under refs/tmp/changes/ to refs/changes/
+mv_tmp_refs()
+{
+ oldrev=$1
+ newrev=$2
+ refname=$3
+ case "$refname" in refs/tmp/changes/*)
+ short_refname=${refname##refs/tmp/changes/}
+ $(git update-ref refs/changes/$short_refname $newrev 2>/dev/null)
+ $(git update-ref -d $refname $newrev 2>/dev/null)
+ echo "moved \"$refname\" to \"refs/changes/$short_refname\""
+ ;;
+ esac
+ return 0
+}
+
+GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
+if [ -z "$GIT_DIR" ]; then
+ echo >&2 "fatal: post-receive: GIT_DIR not set"
+ exit 1
+fi
+
+# read ref updates passed to post-receive hook
+while read oldrev newrev refname
+do
+ mv_tmp_refs $oldrev $newrev $refname || continue
+done
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index 4c6769cf3e..e51e29d0eb 100755
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/contrib/refresh_plugin_in_testsite.sh b/contrib/refresh_plugin_in_testsite.sh
new file mode 100755
index 0000000000..bb42ce821a
--- /dev/null
+++ b/contrib/refresh_plugin_in_testsite.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script compiles a Gerrit plugin whose name is passed as first parameter
+# and copies it over to the plugin folder of the testsite. The path to the
+# testsite needs to be provided by the variable GERRIT_TESTSITE or as second
+# parameter.
+
+SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
+GERRIT_CODE_DIR="$SCRIPT_DIR/.."
+cd "$GERRIT_CODE_DIR"
+
+if [ "$#" -lt 1 ]
+then
+ echo "No plugin name provided as first argument. Stopping."
+ exit 1
+else
+ PLUGIN_NAME="$1"
+fi
+
+
+if [ "$#" -lt 2 ]
+then
+ if [ -z ${GERRIT_TESTSITE+x} ]
+ then
+ echo "Path to local testsite is neiter set as GERRIT_TESTSITE nor passed as second argument. Stopping."
+ exit 1
+ fi
+else
+ GERRIT_TESTSITE="$2"
+fi
+
+if [ ! -d "$GERRIT_TESTSITE" ]
+then
+ echo "Testsite directory $GERRIT_TESTSITE does not exist. Stopping."
+ exit 1
+fi
+
+bazel build //plugins/"$PLUGIN_NAME"/...
+if [ $? -ne 0 ]
+then
+ echo "Building the $PLUGIN_NAME plugin failed"
+ exit 1
+fi
+
+yes | cp -f "$GERRIT_CODE_DIR/bazel-genfiles/plugins/$PLUGIN_NAME/$PLUGIN_NAME.jar" "$GERRIT_TESTSITE/plugins/"
+if [ $? -eq 0 ]
+then
+ echo "Plugin $PLUGIN_NAME copied successfully to testsite."
+fi
diff --git a/contrib/reindex/.flake8 b/contrib/reindex/.flake8
new file mode 100644
index 0000000000..151557f247
--- /dev/null
+++ b/contrib/reindex/.flake8
@@ -0,0 +1,9 @@
+[flake8]
+max-line-length=100
+ignore=
+ # E203 whitespace before ':'
+ E203,
+ # W503: Line break before binary operator
+ W503,
+ # W504: Line break after binary operator
+ W504
diff --git a/contrib/reindex/.gitignore b/contrib/reindex/.gitignore
new file mode 100644
index 0000000000..fd8c78f5e9
--- /dev/null
+++ b/contrib/reindex/.gitignore
@@ -0,0 +1 @@
+changes-to-reindex.list
diff --git a/contrib/reindex/Pipfile b/contrib/reindex/Pipfile
new file mode 100644
index 0000000000..21ffd90226
--- /dev/null
+++ b/contrib/reindex/Pipfile
@@ -0,0 +1,19 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+pygerrit2 = "*"
+requests = "*"
+tqdm = "*"
+
+[dev-packages]
+flake8 = "*"
+black = "*"
+
+[requires]
+python_version = "3.9"
+
+[pipenv]
+allow_prereleases = true
diff --git a/contrib/reindex/Pipfile.lock b/contrib/reindex/Pipfile.lock
new file mode 100644
index 0000000000..bb7cc2dc02
--- /dev/null
+++ b/contrib/reindex/Pipfile.lock
@@ -0,0 +1,248 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "37be5a74a22d0e084ebfe168bfdcd7bcaa87ad7b42be66b1d9fbff5e936ebe72"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.9"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "certifi": {
+ "hashes": [
+ "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
+ "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
+ ],
+ "version": "==2020.12.5"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+ "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==4.0.0"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
+ },
+ "pbr": {
+ "hashes": [
+ "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
+ "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
+ ],
+ "markers": "python_version >= '2.6'",
+ "version": "==5.5.1"
+ },
+ "pygerrit2": {
+ "hashes": [
+ "sha256:d12cff5cc514dd61281d997ea86771e7f818030c3d2ef230b25bb14dae7d3f86"
+ ],
+ "index": "pypi",
+ "version": "==2.0.14"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+ "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ ],
+ "index": "pypi",
+ "version": "==2.25.1"
+ },
+ "tqdm": {
+ "hashes": [
+ "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5",
+ "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"
+ ],
+ "index": "pypi",
+ "version": "==4.54.1"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
+ "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+ "version": "==1.26.2"
+ }
+ },
+ "develop": {
+ "appdirs": {
+ "hashes": [
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
+ ],
+ "version": "==1.4.4"
+ },
+ "black": {
+ "hashes": [
+ "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
+ ],
+ "index": "pypi",
+ "version": "==20.8b1"
+ },
+ "click": {
+ "hashes": [
+ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
+ "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==7.1.2"
+ },
+ "flake8": {
+ "hashes": [
+ "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839",
+ "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"
+ ],
+ "index": "pypi",
+ "version": "==3.8.4"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
+ "mypy-extensions": {
+ "hashes": [
+ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+ "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
+ ],
+ "version": "==0.4.3"
+ },
+ "pathspec": {
+ "hashes": [
+ "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
+ "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
+ ],
+ "version": "==0.8.1"
+ },
+ "pycodestyle": {
+ "hashes": [
+ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
+ "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.6.0"
+ },
+ "pyflakes": {
+ "hashes": [
+ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
+ "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.2.0"
+ },
+ "regex": {
+ "hashes": [
+ "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
+ "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
+ "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
+ "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
+ "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
+ "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
+ "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
+ "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
+ "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
+ "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
+ "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
+ "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
+ "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
+ "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
+ "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
+ "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
+ "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
+ "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
+ "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
+ "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
+ "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
+ "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
+ "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
+ "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
+ "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
+ "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
+ "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
+ "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
+ "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
+ "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
+ "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
+ "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
+ "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
+ "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
+ "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
+ "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
+ "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
+ "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
+ "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
+ "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
+ "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
+ ],
+ "version": "==2020.11.13"
+ },
+ "toml": {
+ "hashes": [
+ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+ "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.10.2"
+ },
+ "typed-ast": {
+ "hashes": [
+ "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
+ "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
+ "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d",
+ "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
+ "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
+ "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
+ "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c",
+ "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
+ "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
+ "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
+ "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
+ "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
+ "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
+ "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d",
+ "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
+ "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
+ "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c",
+ "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
+ "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395",
+ "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
+ "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
+ "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
+ "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
+ "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
+ "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072",
+ "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298",
+ "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91",
+ "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
+ "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f",
+ "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
+ ],
+ "version": "==1.4.1"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
+ "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
+ "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
+ ],
+ "version": "==3.7.4.3"
+ }
+ }
+}
diff --git a/contrib/reindex/README.md b/contrib/reindex/README.md
new file mode 100644
index 0000000000..acb958855d
--- /dev/null
+++ b/contrib/reindex/README.md
@@ -0,0 +1,63 @@
+# Incremental reindexing during upgrade of large gerrit site
+
+In order to shorten the downtime needed to reindex changes during a
+Gerrit upgrade the following strategy can be used:
+
+- index preparation
+ - create a full consistent backup
+ - note down the timestamp when the backup was created (backup-time)
+ - create a complete copy of the production system from the backup
+ - upgrade this copy to the new Gerrit version
+ - online reindex this copy
+- upgrade of the production system
+ - make system unavailable so that users can't reach it anymore
+ e.g. by changing port numbers (downtime starts)
+ - take a full backup
+ - run
+
+ ``` bash
+ ./reindex.py -u gerrit-url -s backup-time
+ ```
+
+ to write the list of changes which have been created or modified
+ since the backup for the index preparation was created to a file
+ "changes-to-reindex.list"
+ - upgrade the production system to the new gerrit version skipping
+ reindexing
+ - copy the bulk of the new index from the copy system to the
+ production system
+ - run
+
+ ``` bash
+ ./reindex.py -u gerrit-url
+ ```
+
+ this reindexes all changes which have been created or modified after
+ the backup was taken reading these changes from the file
+ "changes-to-reindex.list"
+ - smoketest the system
+ - make the production system available to the users again
+ (downtime ends)
+
+## Online help
+
+For help on all available options run
+
+``` bash
+./reindex -h
+```
+
+## Python environment
+
+Prerequisites:
+
+- python 3.9
+- pipenv
+
+Install virtual python environment and run the script
+
+``` bash
+pipenv sync
+pipenv shell
+./reindex <options>
+```
diff --git a/contrib/reindex/reindex.py b/contrib/reindex/reindex.py
new file mode 100755
index 0000000000..266f5ecc95
--- /dev/null
+++ b/contrib/reindex/reindex.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+from argparse import ArgumentParser, RawTextHelpFormatter
+from itertools import islice
+import getpass
+import logging
+import os
+
+from pygerrit2 import GerritRestAPI, HTTPBasicAuth, HTTPBasicAuthFromNetrc
+from tqdm import tqdm
+
+EPILOG = """\
+To query the list of changes which have been created or modified since the
+given timestamp and write them to a file "changes-to-reindex.list" run
+$ ./reindex.py -u gerrit-url -s timestamp
+
+To reindex the list of changes in file "changes-to-reindex.list" run
+$ ./reindex.py -u gerrit-url
+"""
+
+
+def _parse_options():
+ parser = ArgumentParser(
+ formatter_class=RawTextHelpFormatter,
+ epilog=EPILOG,
+ )
+ parser.add_argument(
+ "-u",
+ "--url",
+ dest="url",
+ help="gerrit url",
+ )
+ parser.add_argument(
+ "-s",
+ "--since",
+ dest="time",
+ help=(
+ "changes modified after the given 'TIME', inclusive. Must be in the\n"
+ "format '2006-01-02[ 15:04:05[.890][ -0700]]', omitting the time defaults\n"
+ "to 00:00:00 and omitting the timezone defaults to UTC."
+ ),
+ )
+ parser.add_argument(
+ "-f",
+ "--file",
+ default="changes-to-reindex.list",
+ dest="file",
+ help=(
+ "file path to store list of changes if --since is given,\n"
+ "otherwise file path to read list of changes from"
+ ),
+ )
+ parser.add_argument(
+ "-c",
+ "--chunk",
+ default=100,
+ dest="chunksize",
+ help="chunk size defining how many changes are reindexed per request",
+ type=int,
+ )
+ parser.add_argument(
+ "--cert",
+ dest="cert",
+ type=str,
+ help="path to file containing custom ca certificates to trust",
+ )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ dest="verbose",
+ action="store_true",
+ help="verbose debugging output",
+ )
+ parser.add_argument(
+ "-n",
+ "--netrc",
+ default=True,
+ dest="netrc",
+ action="store_true",
+ help=(
+ "read credentials from .netrc, default to environment variables\n"
+ "USERNAME and PASSWORD, otherwise prompt for credentials interactively"
+ ),
+ )
+ return parser.parse_args()
+
+
+def _chunker(iterable, chunksize):
+ it = map(lambda s: s.strip(), iterable)
+ while True:
+ chunk = list(islice(it, chunksize))
+ if not chunk:
+ return
+ yield chunk
+
+
+class Reindexer:
+ """Class for reindexing Gerrit changes"""
+
+ def __init__(self):
+ self.options = _parse_options()
+ self._init_logger()
+ credentials = self._authenticate()
+ if self.options.cert:
+ certs = os.path.expanduser(self.options.cert)
+ self.api = GerritRestAPI(
+ url=self.options.url, auth=credentials, verify=certs
+ )
+ else:
+ self.api = GerritRestAPI(url=self.options.url, auth=credentials)
+
+ def _init_logger(self):
+ self.logger = logging.getLogger("Reindexer")
+ self.logger.setLevel(logging.DEBUG)
+ h = logging.StreamHandler()
+ if self.options.verbose:
+ h.setLevel(logging.DEBUG)
+ else:
+ h.setLevel(logging.INFO)
+ formatter = logging.Formatter("%(message)s")
+ h.setFormatter(formatter)
+ self.logger.addHandler(h)
+
+ def _authenticate(self):
+ username = password = None
+ if self.options.netrc:
+ auth = HTTPBasicAuthFromNetrc(url=self.options.url)
+ username = auth.username
+ password = auth.password
+ if not username:
+ username = os.environ.get("USERNAME")
+ if not password:
+ password = os.environ.get("PASSWORD")
+ while not username:
+ username = input("user: ")
+ while not password:
+ password = getpass.getpass("password: ")
+ auth = HTTPBasicAuth(username, password)
+ return auth
+
+ def _query(self):
+ start = 0
+ more_changes = True
+ while more_changes:
+ query = f"since:{self.options.time}&start={start}&skip-visibility"
+ for change in self.api.get(f"changes/?q={query}"):
+ more_changes = change.get("_more_changes") is not None
+ start += 1
+ yield change.get("_number")
+ break
+
+ def _query_to_file(self):
+ self.logger.debug(
+ f"writing changes since {self.options.time} to file {self.options.file}:"
+ )
+ with open(self.options.file, "w") as output:
+ for id in self._query():
+ self.logger.debug(id)
+ output.write(f"{id}\n")
+
+ def _reindex_chunk(self, chunk):
+ self.logger.debug(f"indexing {chunk}")
+ response = self.api.post(
+ "/config/server/index.changes",
+ chunk,
+ )
+ self.logger.debug(f"response: {response}")
+
+ def _reindex(self):
+ self.logger.debug(f"indexing changes from file {self.options.file}")
+ with open(self.options.file, "r") as f:
+ with tqdm(unit="changes", desc="Indexed") as pbar:
+ for chunk in _chunker(f, self.options.chunksize):
+ self._reindex_chunk(chunk)
+ pbar.update(len(chunk))
+
+ def execute(self):
+ if self.options.time:
+ self._query_to_file()
+ else:
+ self._reindex()
+
+
+def main():
+ reindexer = Reindexer()
+ reindexer.execute()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/contrib/show_new_gerrit_doc_in_chrome.sh b/contrib/show_new_gerrit_doc_in_chrome.sh
new file mode 100755
index 0000000000..d57bc8af31
--- /dev/null
+++ b/contrib/show_new_gerrit_doc_in_chrome.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script builds Gerrit's documentation and shows the current state in
+# Chrome. Specific pages (e.g. rest-api-changes.txt) including anchors can be
+# passed as parameter to jump directly to them.
+
+SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
+GERRIT_CODE_DIR="$SCRIPT_DIR/.."
+cd "$GERRIT_CODE_DIR"
+
+bazel build Documentation:searchfree
+if [ $? -ne 0 ]
+then
+ echo "Building the documentation failed. Stopping."
+ exit 1
+fi
+
+TMP_DOCS_DIR=/tmp/gerrit_docs
+rm -rf "$TMP_DOCS_DIR"
+unzip bazel-bin/Documentation/searchfree.zip -d "$TMP_DOCS_DIR" </dev/null >/dev/null 2>&1 & disown
+if [ $? -ne 0 ]
+then
+ echo "Unzipping the documentation to $TMP_DOCS_DIR failed. Stopping."
+ exit 1
+fi
+
+if [ "$#" -lt 1 ]
+then
+ FILE_NAME="index.html"
+else
+ FILE_NAME="$1"
+fi
+DOC_FILE_NAME="${FILE_NAME/.txt/.html}"
+google-chrome "file:///$TMP_DOCS_DIR/Documentation/$DOC_FILE_NAME" </dev/null >/dev/null 2>&1 & disown
diff --git a/contrib/start_testsite.sh b/contrib/start_testsite.sh
new file mode 100755
index 0000000000..014eba9575
--- /dev/null
+++ b/contrib/start_testsite.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script starts the local testsite in debug mode. If the flag "-u" is
+# passed, Gerrit is built from the current state of the repository and the
+# testsite is refreshed. The path to the testsite needs to be provided by
+# the variable GERRIT_TESTSITE or as parameter (after any used flags).
+# The testsite can be stopped by interrupting this script.
+
+SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
+GERRIT_CODE_DIR="$SCRIPT_DIR/.."
+cd "$GERRIT_CODE_DIR"
+
+UPDATE=false
+while getopts ':u' flag; do
+ case "${flag}" in
+ u) UPDATE=true ;;
+ esac
+done
+shift $(($OPTIND-1))
+
+if [ "$#" -lt 1 ]
+then
+ if [ -z ${GERRIT_TESTSITE+x} ]
+ then
+ echo "Path to local testsite is neither set as GERRIT_TESTSITE nor passed as first argument. Stopping."
+ exit 1
+ fi
+else
+ GERRIT_TESTSITE="$1"
+fi
+
+if [ "$UPDATE" = true ]
+then
+ echo "Refreshing testsite"
+ bazel build gerrit
+ if [ $? -ne 0 ]
+ then
+ echo "Build failed. Stopping."
+ exit 1
+ fi
+ $(bazel info output_base)/external/local_jdk/bin/java -jar bazel-bin/gerrit.war init --batch -d "$GERRIT_TESTSITE"
+ if [ $? -ne 0 ]
+ then
+ echo "Patching the testsite failed. Stopping."
+ exit 1
+ fi
+fi
+
+$(bazel info output_base)/external/local_jdk/bin/java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 bazel-bin/gerrit.war daemon -d "$GERRIT_TESTSITE" --console-log
diff --git a/e2e-tests/README b/e2e-tests/README
new file mode 100755
index 0000000000..59e0fba319
--- /dev/null
+++ b/e2e-tests/README
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# Example usage only-
+# 1. Optional: replace test@mail.com below with your own, reachable locally.
+# 2. Use the '>>' operator below instead to not overwrite your known_hosts; keep '>' otherwise.
+# 3. Note that appending as proposed above may potentially repeat the same line multiple times.
+# 4. Init your local Gerrit test site then start it; you may refer to [1] below.
+# 5. Set GIT_HTTP_PASSWORD below to yours, from [2].
+# 6. Change to this directory to execute ./README (this executable file) in its own terminal.
+# 7. Install sbt if missing, based on your operating system; re-run to compile.
+# 8. Optional: add the below generated (displayed) key to your local admin user [3].
+# 9. Otherwise keep the lines below that use your existing user ssh keys for admin testing.
+# 10. This script assumes the google-sourced version of the example json file [4].
+# 11. If running that scenario locally as below reports authentication failures, [4] may be a fork.
+# 12. Uncomment any one of the below sbt commands at will; you may add some locally.
+# 13. See [5] for how to start using JAVA_OPTS below; you may leave it empty for these sbt commands.
+# 14. You can initialize an IDE sbt (Scala) project from/in this root folder; see [6].
+#
+# [1] https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init
+# [2] http://localhost:8080/settings/#HTTPCredentials
+# [3] http://localhost:8080/settings/#SSHKeys
+# [4] ./src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
+# [5] https://gerrit-review.googlesource.com/Documentation/dev-e2e-tests.html#_environment_properties
+# [6] https://gerrit-review.googlesource.com/Documentation/dev-e2e-tests.html#_ide_intellij
+
+# DO NOT change this (assumed) directory; force-removed *recursively* below!
+gatlingGitKeys=/tmp/ssh-keys
+
+userSshDir=$HOME/.ssh
+
+# Comment this group of lines out if willing to generate other keys as below.
+rm -f $gatlingGitKeys
+ln -s "$userSshDir" $gatlingGitKeys
+
+# Comment this group of lines out if keys already generated, as either below or above.
+#rm -fr $gatlingGitKeys
+#mkdir $gatlingGitKeys
+#ssh-keygen -m PEM -t rsa -C "test@mail.com" -f $gatlingGitKeys/id_rsa
+
+ssh-keyscan -t rsa -p 29418 localhost > "$userSshDir"/known_hosts
+cat $gatlingGitKeys/id_rsa.pub
+
+export GIT_HTTP_USERNAME="admin"
+export GIT_HTTP_PASSWORD="TODO"
+export JAVA_OPTS="\
+"
+#-Dx=y \
+
+#sbt clean
+#sbt update
+sbt compile
+#sbt "gatling:testOnly com.google.gerrit.scenarios.CloneUsingBothProtocols"
+#sbt "gatling:lastReport"
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/AbandonChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/AbandonChange.json
new file mode 100644
index 0000000000..665cc4d0b4
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/AbandonChange.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/",
+ "number": "NUMBER"
+ }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json
new file mode 100644
index 0000000000..f69e5750ee
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch-body.json
@@ -0,0 +1,3 @@
+{
+ "revision": "master"
+}
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json
new file mode 100644
index 0000000000..5459f11b3f
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateBranch.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/PROJECT/branches/",
+ "project": "PROJECT"
+ }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange-body.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange-body.json
index 23bf26c591..8babac8184 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange-body.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange-body.json
@@ -1,5 +1,5 @@
{
"project": "${project}",
- "branch": "master",
+ "branch": "${branch}",
"subject": "Change"
}
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject-body.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject-body.json
new file mode 100644
index 0000000000..488de6de53
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject-body.json
@@ -0,0 +1,3 @@
+{
+ "force": "${force_project_deletion}"
+}
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/RestoreChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/RestoreChange.json
new file mode 100644
index 0000000000..665cc4d0b4
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/RestoreChange.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/",
+ "number": "NUMBER"
+ }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChangeInBranch.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChangeInBranch.json
new file mode 100644
index 0000000000..301c65b0d3
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChangeInBranch.json
@@ -0,0 +1,5 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/"
+ }
+]
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/AbandonChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/AbandonChange.scala
new file mode 100644
index 0000000000..d387a3e3b9
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/AbandonChange.scala
@@ -0,0 +1,71 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef.{atOnceUsers, _}
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef.http
+
+import scala.collection.mutable
+import scala.concurrent.duration.DurationInt
+
+class AbandonChange extends GerritSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private val projectName = className
+ private var numbersCopy: mutable.Queue[Int] = mutable.Queue[Int]()
+ private var createChange: Option[CreateChange] = Some(new CreateChange(projectName))
+
+ override def relativeRuntimeWeight = 10
+
+ def this(createChange: CreateChange) {
+ this()
+ this.createChange = Some(createChange)
+ }
+
+ val test: ScenarioBuilder = scenario(uniqueName)
+ .feed(data)
+ .exec(session => {
+ if (createChange.nonEmpty) {
+ if (numbersCopy.isEmpty) {
+ numbersCopy = createChange.get.numbers.clone()
+ }
+ }
+ session.set(numberKey, numbersCopy.dequeue())
+ })
+ .exec(http(uniqueName).post("${url}${" + numberKey + "}/abandon"))
+
+ private val createProject = new CreateProject(projectName)
+ private val deleteProject = new DeleteProject(projectName)
+
+ setUp(
+ createProject.test.inject(
+ nothingFor(stepWaitTime(createProject) seconds),
+ atOnceUsers(single)
+ ),
+ createChange.get.test.inject(
+ nothingFor(stepWaitTime(createChange.get) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteProject.test.inject(
+ nothingFor(stepWaitTime(deleteProject) seconds),
+ atOnceUsers(single)
+ ),
+ ).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
index 8ae69d70d7..9a91153c2d 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
@@ -19,10 +19,15 @@ import io.gatling.core.feeder.FeederBuilder
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef.http
+import scala.collection.mutable
+
class ApproveChange extends GerritSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private var numbersCopy: mutable.Queue[Int] = mutable.Queue[Int]()
private var createChange: Option[CreateChange] = None
+ override def relativeRuntimeWeight = 10
+
def this(createChange: CreateChange) {
this()
this.createChange = Some(createChange)
@@ -32,13 +37,16 @@ class ApproveChange extends GerritSimulation {
.feed(data)
.exec(session => {
if (createChange.nonEmpty) {
- session.set("number", createChange.get.number)
+ if (numbersCopy.isEmpty) {
+ numbersCopy = createChange.get.numbers.clone()
+ }
+ session.set(numberKey, numbersCopy.dequeue())
} else {
session
}
})
.exec(http(uniqueName)
- .post("${url}${number}/revisions/current/review")
+ .post("${url}${" + numberKey + "}/revisions/current/review")
.body(ElFileBody(body)).asJson)
setUp(
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
index 96943ce1b4..900702a6b0 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
@@ -37,7 +37,7 @@ class CheckProjectsCacheFlushEntries extends CacheFlushSimulation {
}
})
.exec(http(uniqueName).get("${url}")
- .check(regex("\"" + memKey + "\": (\\d+)")
+ .check(regex("\"" + memKey + "\":(\\d+)")
.is(session => session(entriesKey).as[String])))
setUp(
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
index 08966a89d3..c28386113f 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -21,9 +21,9 @@ import io.gatling.core.structure.ScenarioBuilder
import scala.concurrent.duration._
class CloneUsingBothProtocols extends GitSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
private val projectName = className
- private val duration = 2
+ private val duration = 2 * numberOfUsers
override def replaceOverride(in: String): String = {
replaceKeyWith("_project", projectName, in)
@@ -43,7 +43,7 @@ class CloneUsingBothProtocols extends GitSimulation {
),
test.inject(
nothingFor(stepWaitTime(this) seconds),
- constantUsersPerSec(single) during (duration seconds)
+ constantUsersPerSec(numberOfUsers) during (duration seconds)
).protocols(gitProtocol),
deleteProject.test.inject(
nothingFor(stepWaitTime(deleteProject) + duration seconds),
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala
new file mode 100644
index 0000000000..3630a7a7a7
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateBranch.scala
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef.{atOnceUsers, _}
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef._
+
+import scala.collection.mutable
+import scala.concurrent.duration._
+
+class CreateBranch extends ProjectSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private val branchIdKey = "branchId"
+ private var counter = 0
+ var branches: mutable.Queue[String] = mutable.Queue[String]()
+
+ def this(projectName: String) {
+ this()
+ this.projectName = projectName
+ }
+
+ val test: ScenarioBuilder = scenario(uniqueName)
+ .feed(data)
+ .exec(session => {
+ counter += 1
+ val branchId = "branch-" + counter
+ branches += branchId
+ session.set(branchIdKey, branchId)
+ })
+ .exec(http(uniqueName)
+ .post("${url}${" + branchIdKey + "}")
+ .body(ElFileBody(body)).asJson)
+
+ private val createProject = new CreateProject(projectName)
+ private val deleteProject = new DeleteProject(projectName)
+
+ setUp(
+ createProject.test.inject(
+ nothingFor(stepWaitTime(createProject) seconds),
+ atOnceUsers(single)
+ ),
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteProject.test.inject(
+ nothingFor(stepWaitTime(deleteProject) seconds),
+ atOnceUsers(single)
+ ),
+ ).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
index f3692a96d9..fb410753fb 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
@@ -19,27 +19,48 @@ import io.gatling.core.feeder.FeederBuilder
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._
+import scala.collection.mutable
import scala.concurrent.duration._
class CreateChange extends ProjectSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
- private val numberKey = "_number"
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private val weightPerUser = 0.1
+ private var createBranch: Option[CreateBranch] = None
+ private var branchesCopy: mutable.Queue[String] = mutable.Queue[String]()
var number = 0
+ var numbers: mutable.Queue[Int] = mutable.Queue[Int]()
- override def relativeRuntimeWeight = 2
+ override def relativeRuntimeWeight: Int = 2 + (numberOfUsers * weightPerUser).toInt
def this(projectName: String) {
this()
this.projectName = projectName
}
+ def this(projectName: String, createBranch: CreateBranch) {
+ this()
+ this.projectName = projectName
+ this.createBranch = Some(createBranch)
+ }
+
val test: ScenarioBuilder = scenario(uniqueName)
.feed(data)
+ .exec(session => {
+ var branchId = "master"
+ if (createBranch.nonEmpty) {
+ if (branchesCopy.isEmpty) {
+ branchesCopy = createBranch.get.branches.clone()
+ }
+ branchId = branchesCopy.dequeue()
+ }
+ session.set("branch", branchId)
+ })
.exec(httpRequest
.body(ElFileBody(body)).asJson
- .check(regex("\"" + numberKey + "\":(\\d+),").saveAs(numberKey)))
+ .check(regex("\"_" + numberKey + "\":(\\d+),").saveAs(numberKey)))
.exec(session => {
number = session(numberKey).as[Int]
+ numbers += number
session
})
@@ -54,11 +75,11 @@ class CreateChange extends ProjectSimulation {
),
test.inject(
nothingFor(stepWaitTime(this) seconds),
- atOnceUsers(single)
+ atOnceUsers(numberOfUsers)
),
deleteChange.test.inject(
nothingFor(stepWaitTime(deleteChange) seconds),
- atOnceUsers(single)
+ atOnceUsers(numberOfUsers)
),
deleteProject.test.inject(
nothingFor(stepWaitTime(deleteProject) seconds),
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
index d832bde3d7..743219f4e4 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
@@ -20,7 +20,7 @@ import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef.http
class DeleteChange extends GerritSimulation {
- private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
private var createChange: Option[CreateChange] = None
override def relativeRuntimeWeight = 2
@@ -34,12 +34,12 @@ class DeleteChange extends GerritSimulation {
.feed(data)
.exec(session => {
if (createChange.nonEmpty) {
- session.set("number", createChange.get.number)
+ session.set(numberKey, createChange.get.numbers.dequeue())
} else {
session
}
})
- .exec(http(uniqueName).delete("${url}${number}"))
+ .exec(http(uniqueName).delete("${url}${" + numberKey + "}"))
setUp(
test.inject(
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
index 17526348bc..eb4df30193 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
@@ -20,6 +20,7 @@ import io.gatling.core.structure.ScenarioBuilder
class DeleteProject extends ProjectSimulation {
private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+ private val forceKey = "force_project_deletion"
def this(projectName: String) {
this()
@@ -28,7 +29,10 @@ class DeleteProject extends ProjectSimulation {
val test: ScenarioBuilder = scenario(uniqueName)
.feed(data)
- .exec(httpRequest)
+ .exec(session => {
+ session.set(forceKey, getProperty(forceKey, "false"))
+ })
+ .exec(httpRequest.body(ElFileBody(body)).asJson)
setUp(
test.inject(
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
index 7b31b3d386..c199dd91df 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -23,6 +23,8 @@ import io.gatling.http.request.builder.HttpRequestBuilder
class GerritSimulation extends Simulation {
implicit val conf: GatlingGitConfiguration = GatlingGitConfiguration()
+ protected val numberKey: String = "number"
+
private val packageName = getClass.getPackage.getName
private val path = packageName.replaceAllLiterally(".", "/")
@@ -34,6 +36,7 @@ class GerritSimulation extends Simulation {
protected val uniqueName: String = className + "-" + hashCode()
protected val single = 1
+ val numberOfUsers: Int = replaceProperty("number_of_users", single).toInt
val replicationDelay: Int = replaceProperty("replication_delay", 15).toInt
private val powerFactor = replaceProperty("power_factor", 1.0).toDouble
protected val SecondsPerWeightUnit = 2
@@ -63,9 +66,9 @@ class GerritSimulation extends Simulation {
protected val keys: PartialFunction[(String, Any), Any] = {
case ("entries", entries) =>
replaceProperty("projects_entries", "1", entries.toString)
- case ("number", number) =>
- val precedes = replaceKeyWith("_number", 0, number.toString)
- replaceProperty("number", 1, precedes)
+ case (`numberKey`, number) =>
+ val precedes = replaceKeyWith("_" + numberKey, 0, number.toString)
+ replaceProperty(numberKey, 1, precedes)
case ("parent", parent) =>
replaceProperty("parent", "All-Projects", parent.toString)
case ("project", project) =>
@@ -89,6 +92,11 @@ class GerritSimulation extends Simulation {
}
protected def replaceProperty(term: String, default: Any, in: String): String = {
+ val value = getProperty(term, default)
+ replaceKeyWith(term, value, in)
+ }
+
+ protected def getProperty(term: String, default: Any): String = {
val property = packageName + "." + term
var value = default
default match {
@@ -100,7 +108,7 @@ class GerritSimulation extends Simulation {
case _: Integer =>
value = Integer.getInteger(property, default.asInstanceOf[Integer])
}
- replaceKeyWith(term, value, in)
+ value.toString
}
protected def replaceKeyWith(term: String, value: Any, in: String): String = {
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala
index 1137ad53b9..f2236d197f 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala
@@ -23,7 +23,7 @@ class GetMasterBranchRevision extends ProjectSimulation {
private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
var revision: Option[String] = None
val revisionKey = "revision"
- val revisionPattern: String = "\"" + revisionKey + "\": \"(.+)\""
+ val revisionPattern: String = "\"" + revisionKey + "\":\"(.+)\""
val test: ScenarioBuilder = scenario(uniqueName)
.feed(data)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
index 266c0b9da3..0bb3afb9b8 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
@@ -30,7 +30,7 @@ class GetProjectsCacheEntries extends CacheFlushSimulation {
val test: ScenarioBuilder = scenario(uniqueName)
.feed(data)
.exec(http(uniqueName).get("${url}")
- .check(regex("\"" + memKey + "\": (\\d+)").saveAs(entriesKey)))
+ .check(regex("\"" + memKey + "\":(\\d+)").saveAs(entriesKey)))
.exec(session => {
if (consumer.nonEmpty) {
consumer.get.entriesBeforeFlush(session(entriesKey).as[Int])
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/RestoreChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/RestoreChange.scala
new file mode 100644
index 0000000000..81096b0c09
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/RestoreChange.scala
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef.{atOnceUsers, _}
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef.http
+
+import scala.collection.mutable
+import scala.concurrent.duration.DurationInt
+
+class RestoreChange extends GerritSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private val projectName = className
+ private var numbersCopy: mutable.Queue[Int] = mutable.Queue[Int]()
+
+ override def relativeRuntimeWeight = 10
+
+ private val test: ScenarioBuilder = scenario(uniqueName)
+ .feed(data)
+ .exec(session => {
+ if (numbersCopy.isEmpty) {
+ numbersCopy = createChange.numbers.clone()
+ }
+ session.set(numberKey, numbersCopy.dequeue())
+ }
+ ).exec(http(uniqueName).post("${url}${" + numberKey + "}/restore"))
+
+ private val createProject = new CreateProject(projectName)
+ private val createChange = new CreateChange(projectName)
+ private val abandonChange = new AbandonChange(createChange)
+ private val deleteChange = new DeleteChange(createChange)
+ private val deleteProject = new DeleteProject(projectName)
+
+ setUp(
+ createProject.test.inject(
+ nothingFor(stepWaitTime(createProject) seconds),
+ atOnceUsers(single)
+ ),
+ createChange.test.inject(
+ nothingFor(stepWaitTime(createChange) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ abandonChange.test.inject(
+ nothingFor(stepWaitTime(abandonChange) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteChange.test.inject(
+ nothingFor(stepWaitTime(deleteChange) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteProject.test.inject(
+ nothingFor(stepWaitTime(deleteProject) seconds),
+ atOnceUsers(single)
+ ),
+ ).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
index 067496ac5e..20be28aa82 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
@@ -36,9 +36,9 @@ class SubmitChange extends GerritSimulation {
val test: ScenarioBuilder = scenario(uniqueName)
.feed(data)
.exec(session => {
- session.set("number", createChange.number)
+ session.set(numberKey, createChange.number)
})
- .exec(http(uniqueName).post("${url}${number}/submit"))
+ .exec(http(uniqueName).post("${url}${" + numberKey + "}/submit"))
private val createProject = new CreateProject(projectName)
private val approveChange = new ApproveChange(createChange)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChangeInBranch.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChangeInBranch.scala
new file mode 100644
index 0000000000..9e1431b11d
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChangeInBranch.scala
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.scenarios
+
+import io.gatling.core.Predef.{atOnceUsers, _}
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef.http
+
+import scala.collection.mutable
+import scala.concurrent.duration._
+
+class SubmitChangeInBranch extends GerritSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).circular
+ private var changesCopy: mutable.Queue[Int] = mutable.Queue[Int]()
+ private val projectName = className
+
+ override def relativeRuntimeWeight = 10
+
+ private val test: ScenarioBuilder = scenario(uniqueName)
+ .feed(data)
+ .exec(session => {
+ if (changesCopy.isEmpty) {
+ changesCopy = createChange.numbers.clone()
+ }
+ session.set(numberKey, changesCopy.dequeue())
+ })
+ .exec(http(uniqueName).post("${url}${" + numberKey + "}/submit"))
+
+ private val createProject = new CreateProject(projectName)
+ private val createBranch = new CreateBranch(projectName)
+ private val createChange = new CreateChange(projectName, createBranch)
+ private val approveChange = new ApproveChange(createChange)
+ private val deleteProject = new DeleteProject(projectName)
+
+ setUp(
+ createProject.test.inject(
+ nothingFor(stepWaitTime(createProject) seconds),
+ atOnceUsers(single)
+ ),
+ createBranch.test.inject(
+ nothingFor(stepWaitTime(createBranch) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ createChange.test.inject(
+ nothingFor(stepWaitTime(createChange) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ approveChange.test.inject(
+ nothingFor(stepWaitTime(approveChange) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(numberOfUsers)
+ ),
+ deleteProject.test.inject(
+ nothingFor(stepWaitTime(deleteProject) seconds),
+ atOnceUsers(single)
+ ),
+ ).protocols(httpProtocol)
+}
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 2328697127..ede488c5a2 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -15,17 +15,20 @@
package com.google.gerrit.acceptance;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.truth.OptionalSubject.optionals;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.entities.Patch.COMMIT_MSG;
+import static com.google.gerrit.entities.Patch.MERGE_LIST;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
-import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
-import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
@@ -51,9 +54,16 @@ import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
@@ -78,14 +88,6 @@ import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.mail.Address;
import com.google.gerrit.mail.EmailHeader;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
@@ -124,21 +126,23 @@ import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.TestServerPlugin;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.restapi.change.Revisions;
import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.util.git.DelegateSystemReader;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gerrit.testing.SshMode;
+import com.google.gerrit.testing.TestTimeUtil;
import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.jcraft.jsch.KeyPair;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -148,6 +152,8 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.sql.Timestamp;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -160,8 +166,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
@@ -174,18 +178,20 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportBundleStream;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -203,8 +209,6 @@ public abstract class AbstractDaemonTest {
@ConfigSuite.Parameter public Config baseConfig;
@ConfigSuite.Name private String configName;
- @Rule public ExpectedException exception = ExpectedException.none();
-
@Rule
public TestRule testRunner =
new TestRule() {
@@ -219,7 +223,8 @@ public abstract class AbstractDaemonTest {
beforeTest(description);
ProjectResetter.Config input = requireNonNull(resetProjects());
- try (ProjectResetter resetter = projectResetter.builder().build(input)) {
+ try (ProjectResetter resetter =
+ projectResetter != null ? projectResetter.builder().build(input) : null) {
AbstractDaemonTest.this.resetter = resetter;
base.evaluate();
} finally {
@@ -291,12 +296,14 @@ public abstract class AbstractDaemonTest {
@Inject private PluginGuiceEnvironment pluginGuiceEnvironment;
@Inject private PluginUser.Factory pluginUserFactory;
@Inject private ProjectIndexCollection projectIndexes;
- @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private SitePaths sitePaths;
+ @Inject private ProjectOperations projectOperations;
private ProjectResetter resetter;
private List<Repository> toClose;
+ private String systemTimeZone;
+ private SystemReader oldSystemReader;
@BeforeClass
public static void enablePerThreadCacheStalenessCheck() {
@@ -306,12 +313,16 @@ public abstract class AbstractDaemonTest {
@Before
public void clearSender() {
- sender.clear();
+ if (sender != null) {
+ sender.clear();
+ }
}
@Before
public void startEventRecorder() {
- eventRecorder = eventRecorderFactory.create(admin);
+ if (eventRecorderFactory != null) {
+ eventRecorder = eventRecorderFactory.create(admin);
+ }
}
@Before
@@ -326,7 +337,9 @@ public abstract class AbstractDaemonTest {
@After
public void closeEventRecorder() {
- eventRecorder.close();
+ if (eventRecorder != null) {
+ eventRecorder.close();
+ }
}
@AfterClass
@@ -412,6 +425,10 @@ public abstract class AbstractDaemonTest {
}
protected void beforeTest(Description description) throws Exception {
+ // SystemReader must be overridden before creating any repos, since they read the user/system
+ // configs at initialization time, and are then stored in the RepositoryCache forever.
+ oldSystemReader = setFakeSystemReader(temporaryFolder.getRoot());
+
this.description = description;
GerritServer.Description classDesc =
GerritServer.Description.forTestClass(description, configName);
@@ -423,6 +440,9 @@ public abstract class AbstractDaemonTest {
baseConfig.setString("sshd", null, "listenAddress", "off");
}
+ baseConfig.unset("gerrit", null, "canonicalWebUrl");
+ baseConfig.unset("httpd", null, "listenUrl");
+
baseConfig.setInt("index", null, "batchThreads", -1);
baseConfig.setInt("receive", null, "changeUpdateThreads", 4);
@@ -465,10 +485,63 @@ public abstract class AbstractDaemonTest {
atrScope.set(ctx);
ProjectInput in = projectInput(description);
gApi.projects().create(in);
- project = new Project.NameKey(in.name);
+ project = Project.nameKey(in.name);
if (!classDesc.skipProjectClone()) {
testRepo = cloneProject(project, getCloneAsAccount(description));
}
+
+ // Set the clock step last, so that the test setup isn't consuming any timestamps after the
+ // clock has been set.
+ setTimeSettings(classDesc.useSystemTime(), classDesc.useClockStep(), classDesc.useTimezone());
+ setTimeSettings(
+ methodDesc.useSystemTime(), methodDesc.useClockStep(), methodDesc.useTimezone());
+ }
+
+ private static SystemReader setFakeSystemReader(File tempDir) {
+ SystemReader oldSystemReader = SystemReader.getInstance();
+ SystemReader.setInstance(
+ new DelegateSystemReader(oldSystemReader) {
+ @Override
+ public FileBasedConfig openJGitConfig(Config parent, FS fs) {
+ return new FileBasedConfig(parent, new File(tempDir, "jgit.config"), FS.detect());
+ }
+
+ @Override
+ public FileBasedConfig openUserConfig(Config parent, FS fs) {
+ return new FileBasedConfig(parent, new File(tempDir, "user.config"), FS.detect());
+ }
+
+ @Override
+ public FileBasedConfig openSystemConfig(Config parent, FS fs) {
+ return new FileBasedConfig(parent, new File(tempDir, "system.config"), FS.detect());
+ }
+ });
+ return oldSystemReader;
+ }
+
+ private void setTimeSettings(
+ boolean useSystemTime,
+ @Nullable UseClockStep useClockStep,
+ @Nullable UseTimezone useTimezone) {
+ if (useSystemTime) {
+ TestTimeUtil.useSystemTime();
+ } else if (useClockStep != null) {
+ TestTimeUtil.resetWithClockStep(useClockStep.clockStep(), useClockStep.clockStepUnit());
+ if (useClockStep.startAtEpoch()) {
+ TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
+ }
+ }
+ if (useTimezone != null) {
+ systemTimeZone = System.setProperty("user.timezone", useTimezone.timezone());
+ }
+ }
+
+ private void resetTimeSettings() {
+ TestTimeUtil.useSystemTime();
+ if (systemTimeZone != null) {
+ System.setProperty("user.timezone", systemTimeZone);
+ systemTimeZone = null;
+ }
}
/** Override to bind an additional Guice module */
@@ -569,7 +642,7 @@ public abstract class AbstractDaemonTest {
in.submitType = submitType;
in.createEmptyCommit = createEmptyCommit;
gApi.projects().create(in);
- return new Project.NameKey(in.name);
+ return Project.nameKey(in.name);
}
protected TestRepository<InMemoryRepository> cloneProject(Project.NameKey p) throws Exception {
@@ -601,10 +674,13 @@ public abstract class AbstractDaemonTest {
repo.close();
}
closeSsh();
+ resetTimeSettings();
if (server != commonServer) {
server.close();
server = null;
}
+ SystemReader.setInstance(oldSystemReader);
+ oldSystemReader = null;
}
protected void closeSsh() {
@@ -727,18 +803,18 @@ public abstract class AbstractDaemonTest {
return push.to("refs/for/" + branch + "%topic=" + name(topic));
}
- protected BranchApi createBranch(Branch.NameKey branch) throws Exception {
+ protected BranchApi createBranch(BranchNameKey branch) throws Exception {
return gApi.projects()
- .name(branch.getParentKey().get())
- .branch(branch.get())
+ .name(branch.project().get())
+ .branch(branch.branch())
.create(new BranchInput());
}
- protected BranchApi createBranchWithRevision(Branch.NameKey branch, String revision)
+ protected BranchApi createBranchWithRevision(BranchNameKey branch, String revision)
throws Exception {
BranchInput in = new BranchInput();
in.revision = revision;
- return gApi.projects().name(branch.getParentKey().get()).branch(branch.get()).create(in);
+ return gApi.projects().name(branch.project().get()).branch(branch.branch()).create(in);
}
private static final List<Character> RANDOM =
@@ -808,12 +884,15 @@ public abstract class AbstractDaemonTest {
}
protected Account getAccount(Account.Id accountId) {
- return getAccountState(accountId).getAccount();
+ return getAccountState(accountId).account();
}
protected AccountState getAccountState(Account.Id accountId) {
Optional<AccountState> accountState = accountCache.get(accountId);
- assertThat(accountState).named("account %s", accountId.get()).isPresent();
+ assertWithMessage("account %s", accountId.get())
+ .about(optionals())
+ .that(accountState)
+ .isPresent();
return accountState.get();
}
@@ -915,59 +994,6 @@ public abstract class AbstractDaemonTest {
return gApi.changes().id(r.getChangeId()).current();
}
- protected void allow(String ref, String permission, AccountGroup.UUID id) throws Exception {
- allow(project, ref, permission, id);
- }
-
- protected void allow(Project.NameKey p, String ref, String permission, AccountGroup.UUID id)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.allow(u.getConfig(), permission, id, ref);
- u.save();
- }
- }
-
- protected void allowGlobalCapabilities(
- AccountGroup.UUID id, int min, int max, String... capabilityNames) throws Exception {
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- for (String capabilityName : capabilityNames) {
- Util.allow(
- u.getConfig(), capabilityName, id, new PermissionRange(capabilityName, min, max));
- }
- u.save();
- }
- }
-
- protected void allowGlobalCapabilities(AccountGroup.UUID id, String... capabilityNames)
- throws Exception {
- allowGlobalCapabilities(id, Arrays.asList(capabilityNames));
- }
-
- protected void allowGlobalCapabilities(AccountGroup.UUID id, Iterable<String> capabilityNames)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- for (String capabilityName : capabilityNames) {
- Util.allow(u.getConfig(), capabilityName, id);
- }
- u.save();
- }
- }
-
- protected void removeGlobalCapabilities(AccountGroup.UUID id, String... capabilityNames)
- throws Exception {
- removeGlobalCapabilities(id, Arrays.asList(capabilityNames));
- }
-
- protected void removeGlobalCapabilities(AccountGroup.UUID id, Iterable<String> capabilityNames)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- for (String capabilityName : capabilityNames) {
- Util.remove(u.getConfig(), capabilityName, id);
- }
- u.save();
- }
- }
-
protected void setUseSignedOffBy(InheritableBoolean value) throws Exception {
try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
ProjectConfig config = projectConfigFactory.read(md);
@@ -986,125 +1012,14 @@ public abstract class AbstractDaemonTest {
}
}
- protected void deny(String ref, String permission, AccountGroup.UUID id) throws Exception {
- deny(project, ref, permission, id);
- }
-
- protected void deny(Project.NameKey p, String ref, String permission, AccountGroup.UUID id)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.deny(u.getConfig(), permission, id, ref);
- u.save();
- }
- }
-
- protected PermissionRule block(String ref, String permission, AccountGroup.UUID id)
- throws Exception {
- return block(project, ref, permission, id);
- }
-
- protected PermissionRule block(
- Project.NameKey project, String ref, String permission, AccountGroup.UUID id)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- PermissionRule rule = Util.block(u.getConfig(), permission, id, ref);
- u.save();
- return rule;
- }
- }
-
- protected void blockLabel(
- String label, int min, int max, AccountGroup.UUID id, String ref, Project.NameKey project)
- throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.block(u.getConfig(), Permission.LABEL + label, min, max, id, ref);
- u.save();
- }
- }
-
- protected void grant(Project.NameKey project, String ref, String permission)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- grant(project, ref, permission, false);
- }
-
- protected void grant(Project.NameKey project, String ref, String permission, boolean force)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- grant(project, ref, permission, force, adminGroupUuid());
- }
-
- protected void grant(
- Project.NameKey project,
- String ref,
- String permission,
- boolean force,
- AccountGroup.UUID groupUUID)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
- md.setMessage(String.format("Grant %s on %s", permission, ref));
- ProjectConfig config = projectConfigFactory.read(md);
- AccessSection s = config.getAccessSection(ref, true);
- Permission p = s.getPermission(permission, true);
- PermissionRule rule = Util.newRule(config, groupUUID);
- rule.setForce(force);
- p.add(rule);
- config.commit(md);
- projectCache.evict(config.getProject());
- }
- }
-
- protected void grantLabel(
- String label,
- int min,
- int max,
- Project.NameKey project,
- String ref,
- boolean force,
- AccountGroup.UUID groupUUID,
- boolean exclusive)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException {
- String permission = Permission.LABEL + label;
- try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
- md.setMessage(String.format("Grant %s on %s", permission, ref));
- ProjectConfig config = projectConfigFactory.read(md);
- AccessSection s = config.getAccessSection(ref, true);
- Permission p = s.getPermission(permission, true);
- p.setExclusiveGroup(exclusive);
- PermissionRule rule = Util.newRule(config, groupUUID);
- rule.setForce(force);
- rule.setMin(min);
- rule.setMax(max);
- p.add(rule);
- config.commit(md);
- projectCache.evict(config.getProject());
- }
- }
-
- protected void removePermission(Project.NameKey project, String ref, String permission)
- throws IOException, ConfigInvalidException {
- try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
- md.setMessage(String.format("Remove %s on %s", permission, ref));
- ProjectConfig config = projectConfigFactory.read(md);
- AccessSection s = config.getAccessSection(ref, true);
- Permission p = s.getPermission(permission, true);
- p.clearRules();
- config.commit(md);
- projectCache.evict(config.getProject());
- }
- }
-
- protected void blockRead(String ref) throws Exception {
- block(ref, Permission.READ, REGISTERED_USERS);
- }
-
protected void blockAnonymousRead() throws Exception {
- AccountGroup.UUID anonymous = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- AccountGroup.UUID registered = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
String allRefs = RefNames.REFS + "*";
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.block(u.getConfig(), Permission.READ, anonymous, allRefs);
- Util.allow(u.getConfig(), Permission.READ, registered, allRefs);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(allRefs).group(ANONYMOUS_USERS))
+ .add(allow(Permission.READ).ref(allRefs).group(REGISTERED_USERS))
+ .update();
}
protected PushOneCommit.Result pushTo(String ref) throws Exception {
@@ -1113,11 +1028,11 @@ public abstract class AbstractDaemonTest {
}
protected void approve(String id) throws Exception {
- gApi.changes().id(id).revision("current").review(ReviewInput.approve());
+ gApi.changes().id(id).current().review(ReviewInput.approve());
}
protected void recommend(String id) throws Exception {
- gApi.changes().id(id).revision("current").review(ReviewInput.recommend());
+ gApi.changes().id(id).current().review(ReviewInput.recommend());
}
protected void assertSubmittedTogether(String chId, String... expected) throws Exception {
@@ -1135,7 +1050,7 @@ public abstract class AbstractDaemonTest {
}
protected PatchSet getPatchSet(PatchSet.Id psId) {
- return changeDataFactory.create(project, psId.getParentKey()).patchSet(psId);
+ return changeDataFactory.create(project, psId.changeId()).patchSet(psId);
}
protected IdentifiedUser user(TestAccount testAccount) {
@@ -1155,7 +1070,7 @@ public abstract class AbstractDaemonTest {
protected RevisionResource parseRevisionResource(PushOneCommit.Result r) throws Exception {
PatchSet.Id psId = r.getPatchSetId();
- return parseRevisionResource(psId.getParentKey().toString(), psId.get());
+ return parseRevisionResource(psId.changeId().toString(), psId.get());
}
protected ChangeResource parseChangeResource(String changeId) throws Exception {
@@ -1171,22 +1086,13 @@ public abstract class AbstractDaemonTest {
}
}
- // TODO(hanwen): push this down.
- protected RevCommit getRemoteHead(Project.NameKey project, String branch) throws Exception {
- return projectOperations.project(project).getHead(branch);
- }
-
- protected RevCommit getRemoteHead() throws Exception {
- return getRemoteHead(project, "master");
- }
-
protected void assertMailReplyTo(Message message, String email) throws Exception {
assertThat(message.headers()).containsKey("Reply-To");
EmailHeader.String replyTo = (EmailHeader.String) message.headers().get("Reply-To");
assertThat(replyTo.getString()).contains(email);
}
- protected Map<Branch.NameKey, ObjectId> fetchFromSubmitPreview(String changeId) throws Exception {
+ protected Map<BranchNameKey, ObjectId> fetchFromSubmitPreview(String changeId) throws Exception {
try (BinaryResult result = gApi.changes().id(changeId).current().submitPreview()) {
return fetchFromBundles(result);
}
@@ -1198,7 +1104,7 @@ public abstract class AbstractDaemonTest {
*
* <p>Omits NoteDb meta refs.
*/
- protected Map<Branch.NameKey, ObjectId> fetchFromBundles(BinaryResult bundles) throws Exception {
+ protected Map<BranchNameKey, ObjectId> fetchFromBundles(BinaryResult bundles) throws Exception {
assertThat(bundles.getContentType()).isEqualTo("application/x-zip");
FileSystem fs = Jimfs.newFileSystem();
@@ -1206,8 +1112,8 @@ public abstract class AbstractDaemonTest {
try (OutputStream out = Files.newOutputStream(previewPath)) {
bundles.writeTo(out);
}
- Map<Branch.NameKey, ObjectId> ret = new HashMap<>();
- try (FileSystem zipFs = FileSystems.newFileSystem(previewPath, null);
+ Map<BranchNameKey, ObjectId> ret = new HashMap<>();
+ try (FileSystem zipFs = FileSystems.newFileSystem(previewPath, (ClassLoader) null);
DirectoryStream<Path> dirStream =
Files.newDirectoryStream(Iterables.getOnlyElement(zipFs.getRootDirectories()))) {
for (Path p : dirStream) {
@@ -1218,7 +1124,7 @@ public abstract class AbstractDaemonTest {
int len = bundleName.length();
assertThat(bundleName).endsWith(".git");
String repoName = bundleName.substring(0, len - 4);
- Project.NameKey proj = new Project.NameKey(repoName);
+ Project.NameKey proj = Project.nameKey(repoName);
TestRepository<?> localRepo = cloneProject(proj);
try (InputStream bundleStream = Files.newInputStream(p);
@@ -1235,7 +1141,7 @@ public abstract class AbstractDaemonTest {
continue;
}
RevCommit c = localRepo.getRevWalk().parseCommit(r.getObjectId());
- ret.put(new Branch.NameKey(proj, refName), c.getTree().copy());
+ ret.put(BranchNameKey.create(proj, refName), c.getTree().copy());
}
}
}
@@ -1245,18 +1151,18 @@ public abstract class AbstractDaemonTest {
}
/** Assert that the given branches have the given tree ids. */
- protected void assertTrees(Project.NameKey proj, Map<Branch.NameKey, ObjectId> trees)
+ protected void assertTrees(Project.NameKey proj, Map<BranchNameKey, ObjectId> trees)
throws Exception {
TestRepository<?> localRepo = cloneProject(proj);
GitUtil.fetch(localRepo, "refs/*:refs/*");
- Map<Branch.NameKey, RevTree> refValues = new HashMap<>();
+ Map<BranchNameKey, RevTree> refValues = new HashMap<>();
- for (Branch.NameKey b : trees.keySet()) {
- if (!b.getParentKey().equals(proj)) {
+ for (BranchNameKey b : trees.keySet()) {
+ if (!b.project().equals(proj)) {
continue;
}
- Ref r = localRepo.getRepository().exactRef(b.get());
+ Ref r = localRepo.getRepository().exactRef(b.branch());
assertThat(r).isNotNull();
RevWalk rw = localRepo.getRevWalk();
RevCommit c = rw.parseCommit(r.getObjectId());
@@ -1360,7 +1266,7 @@ public abstract class AbstractDaemonTest {
protected InternalGroup group(AccountGroup.UUID groupUuid) {
InternalGroup group = groupCache.get(groupUuid).orElse(null);
- assertThat(group).named(groupUuid.get()).isNotNull();
+ assertWithMessage(groupUuid.get()).that(group).isNotNull();
return group;
}
@@ -1370,13 +1276,13 @@ public abstract class AbstractDaemonTest {
}
protected InternalGroup group(String groupName) {
- InternalGroup group = groupCache.get(new AccountGroup.NameKey(groupName)).orElse(null);
- assertThat(group).named(groupName).isNotNull();
+ InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
+ assertWithMessage(groupName).that(group).isNotNull();
return group;
}
protected GroupReference groupRef(String groupName) {
- InternalGroup group = groupCache.get(new AccountGroup.NameKey(groupName)).orElse(null);
+ InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
assertThat(group).isNotNull();
return new GroupReference(group.getGroupUUID(), group.getName());
}
@@ -1398,8 +1304,8 @@ public abstract class AbstractDaemonTest {
}
protected void assertGroupDoesNotExist(String groupName) {
- InternalGroup group = groupCache.get(new AccountGroup.NameKey(groupName)).orElse(null);
- assertThat(group).named(groupName).isNull();
+ InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
+ assertWithMessage(groupName).that(group).isNull();
}
protected void assertNotifyTo(TestAccount expected) {
@@ -1551,7 +1457,7 @@ public abstract class AbstractDaemonTest {
LabelValue... value)
throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType labelType = category(label, value);
+ LabelType labelType = label(label, value);
labelType.setFunction(func);
labelType.setRefPatterns(refPatterns);
u.getConfig().getLabelSections().put(labelType.getName(), labelType);
@@ -1559,10 +1465,6 @@ public abstract class AbstractDaemonTest {
}
}
- protected void fail(@Nullable String format, Object... args) {
- assert_().fail(format, args);
- }
-
protected void enableCreateNewChangeForAllNotInTarget() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig()
diff --git a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
index fa501e6200..a372089444 100644
--- a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
@@ -73,7 +73,11 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
}
protected static FakeEmailSenderSubject assertThat(FakeEmailSender sender) {
- return assertAbout(FakeEmailSenderSubject::new).that(sender);
+ return assertAbout(fakeEmailSenders()).that(sender);
+ }
+
+ protected static Subject.Factory<FakeEmailSenderSubject, FakeEmailSender> fakeEmailSenders() {
+ return FakeEmailSenderSubject::new;
}
protected void setEmailStrategy(TestAccount account, EmailStrategy strategy) throws Exception {
@@ -91,8 +95,8 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
gApi.accounts().self().setPreferences(prefs);
}
- protected static class FakeEmailSenderSubject
- extends Subject<FakeEmailSenderSubject, FakeEmailSender> {
+ protected static class FakeEmailSenderSubject extends Subject {
+ private final FakeEmailSender fakeEmailSender;
private Message message;
private StagedUsers users;
private Map<RecipientType, List<String>> recipients = new HashMap<>();
@@ -100,10 +104,11 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
FakeEmailSenderSubject(FailureMetadata failureMetadata, FakeEmailSender target) {
super(failureMetadata, target);
+ fakeEmailSender = target;
}
public FakeEmailSenderSubject didNotSend() {
- Message message = actual().peekMessage();
+ Message message = fakeEmailSender.peekMessage();
if (message != null) {
failWithoutActual(fact("expected no message", message));
}
@@ -111,7 +116,7 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
}
public FakeEmailSenderSubject sent(String messageType, StagedUsers users) {
- message = actual().nextMessage();
+ message = fakeEmailSender.nextMessage();
if (message == null) {
failWithoutActual(fact("expected message", "not sent"));
}
@@ -140,9 +145,7 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
: header));
}
- // Return a named subject that displays a human-readable table of
- // recipients.
- return named(recipientMapToString(recipients, users::emailToName));
+ return this;
}
private static String recipientMapToString(
@@ -203,8 +206,9 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
if (recipients.get(type).contains(email) != expected) {
failWithoutActual(
fact(
- expected ? "should notify" : "shouldn't notify",
- type + ": " + users.emailToName(email)));
+ expected ? "expected to notify" : "expected not to notify",
+ type + ": " + users.emailToName(email)),
+ fact("but notified", recipientMapToString(recipients, users::emailToName)));
}
if (expected) {
accountedFor.add(email);
@@ -429,7 +433,7 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
.reviewer(REVIEWER_BY_EMAIL)
.reviewer(ccer.email(), ReviewerState.CC, false)
.reviewer(CC_BY_EMAIL, ReviewerState.CC, false);
- ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ ReviewResult result = gApi.changes().id(r.getChangeId()).current().review(in);
supportReviewersByEmail = true;
if (result.reviewers.values().stream().anyMatch(v -> v.error != null)) {
supportReviewersByEmail = false;
@@ -437,7 +441,7 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
ReviewInput.noScore()
.reviewer(reviewer.email())
.reviewer(ccer.email(), ReviewerState.CC, false);
- result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ result = gApi.changes().id(r.getChangeId()).current().review(in);
}
Truth.assertThat(result.reviewers.values().stream().allMatch(v -> v.error == null)).isTrue();
}
diff --git a/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java b/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
index ccd30ab5a4..020602b227 100644
--- a/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractPluginFieldsTest.java
@@ -22,11 +22,11 @@ import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableListMultimap;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.DynamicOptions.DynamicBean;
import com.google.gerrit.server.change.ChangeAttributeFactory;
import com.google.gerrit.server.restapi.change.GetChange;
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index aeae2c2cfd..75d0d2f867 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -20,9 +20,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.GroupCache;
@@ -76,7 +76,7 @@ public class AccountCreator {
if (account != null) {
return account;
}
- Account.Id id = new Account.Id(sequences.nextAccountId());
+ Account.Id id = Account.id(sequences.nextAccountId());
List<ExternalId> extIds = new ArrayList<>(2);
String httpPass = null;
@@ -98,7 +98,7 @@ public class AccountCreator {
if (groupNames != null) {
for (String n : groupNames) {
- AccountGroup.NameKey k = new AccountGroup.NameKey(n);
+ AccountGroup.NameKey k = AccountGroup.nameKey(n);
Optional<InternalGroup> group = groupCache.get(k);
if (!group.isPresent()) {
throw new NoSuchGroupException(n);
diff --git a/java/com/google/gerrit/acceptance/AccountIndexedCounter.java b/java/com/google/gerrit/acceptance/AccountIndexedCounter.java
new file mode 100644
index 0000000000..88b97c70a5
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/AccountIndexedCounter.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.util.concurrent.AtomicLongMap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.events.AccountIndexedListener;
+
+/** Checks if an account is indexed the correct number of times. */
+public class AccountIndexedCounter implements AccountIndexedListener {
+ private final AtomicLongMap<Integer> countsByAccount = AtomicLongMap.create();
+
+ @Override
+ public void onAccountIndexed(int id) {
+ countsByAccount.incrementAndGet(id);
+ }
+
+ public void clear() {
+ countsByAccount.clear();
+ }
+
+ public void assertReindexOf(TestAccount testAccount) {
+ assertReindexOf(testAccount, 1);
+ }
+
+ public void assertReindexOf(AccountInfo accountInfo) {
+ assertReindexOf(Account.id(accountInfo._accountId), 1);
+ }
+
+ public void assertReindexOf(TestAccount testAccount, long expectedCount) {
+ assertThat(countsByAccount.asMap()).containsExactly(testAccount.id().get(), expectedCount);
+ clear();
+ }
+
+ public void assertReindexOf(Account.Id accountId, long expectedCount) {
+ assertThat(countsByAccount.asMap()).containsEntry(accountId.get(), expectedCount);
+ countsByAccount.remove(accountId.get());
+ }
+
+ public void assertNoReindex() {
+ assertThat(countsByAccount.asMap()).isEmpty();
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 25d8df1ae7..646d8f0765 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -2,6 +2,82 @@ load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//tools/bzl:java.bzl", "java_library2")
load("//tools/bzl:javadoc.bzl", "java_doc")
+FUNCTION_SRCS = [
+ "testsuite/ThrowingConsumer.java",
+ "testsuite/ThrowingFunction.java",
+]
+
+DEPLOY_ENV = [
+ "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/gpg",
+ "//java/com/google/gerrit/git",
+ "//java/com/google/gerrit/index:query_exception",
+ "//java/com/google/gerrit/launcher",
+ "//java/com/google/gerrit/lifecycle",
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/httpd",
+ "//java/com/google/gerrit/index",
+ "//java/com/google/gerrit/index/project",
+ "//java/com/google/gerrit/json",
+ "//java/com/google/gerrit/lucene",
+ "//java/com/google/gerrit/mail",
+ "//java/com/google/gerrit/metrics",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/audit",
+ "//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/logging",
+ "//java/com/google/gerrit/server/restapi",
+ "//java/com/google/gerrit/server/schema",
+ "//java/com/google/gerrit/server/util/git",
+ "//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/sshd",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib:args4j",
+ "//lib:gson",
+ "//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jsch",
+ "//lib/commons:compress",
+ "//lib/commons:lang",
+ "//lib/flogger:api",
+ "//lib/guice",
+ "//lib/guice:guice-assistedinject",
+ "//lib/guice:guice-servlet",
+ "//lib/mail",
+ "//lib/mina:sshd",
+ "//lib:guava",
+ "//lib/bouncycastle:bcpg",
+ "//lib/bouncycastle:bcprov",
+ "//prolog:gerrit-prolog-common",
+]
+
+TEST_DEPS = [
+ "//java/com/google/gerrit/httpd/auth/openid",
+ "//java/com/google/gerrit/pgm",
+ "//java/com/google/gerrit/pgm/http/jetty",
+ "//java/com/google/gerrit/pgm/util",
+ "//java/com/google/gerrit/truth",
+ "//java/com/google/gerrit/acceptance/testsuite/project",
+ "//java/com/google/gerrit/server/group/testing",
+ "//java/com/google/gerrit/server/project/testing:project-test-util",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//java/com/google/gerrit/extensions/common/testing:common-test-util",
+ "//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
+ "//java/com/google/gerrit/gpg/testing:gpg-test-util",
+ "//java/com/google/gerrit/git/testing",
+]
+
+PGM_DEPLOY_ENV = [
+ "//lib:caffeine",
+ "//lib:caffeine-guava",
+ "//lib/jackson:jackson-core",
+ "//lib/prolog:cafeteria",
+]
+
java_library(
name = "lib",
testonly = True,
@@ -10,125 +86,53 @@ java_library(
visibility = ["//visibility:public"],
exports = [
":framework-lib",
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/extensions/common/testing:common-test-util",
- "//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
- "//java/com/google/gerrit/git/testing",
- "//java/com/google/gerrit/gpg/testing:gpg-test-util",
- "//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/index",
- "//java/com/google/gerrit/json",
- "//java/com/google/gerrit/launcher",
- "//java/com/google/gerrit/lucene",
- "//java/com/google/gerrit/mail",
- "//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/pgm",
- "//java/com/google/gerrit/pgm/init",
- "//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/reviewdb:server",
- "//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/audit",
- "//java/com/google/gerrit/server/git/receive",
- "//java/com/google/gerrit/server/project/testing:project-test-util",
- "//java/com/google/gerrit/server/restapi",
- "//java/com/google/gerrit/sshd",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//lib:args4j",
- "//lib:gson",
- "//lib:guava-retrying",
- "//lib:h2",
- "//lib:jimfs",
- "//lib:jsch",
- "//lib:servlet-api-without-neverlink",
- "//lib/bouncycastle:bcpg",
- "//lib/bouncycastle:bcprov",
- "//lib/commons:compress",
- "//lib/flogger:api",
- "//lib/guice",
- "//lib/guice:guice-assistedinject",
- "//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/mina:sshd",
- "//prolog:gerrit-prolog-common",
- ],
+ ] + DEPLOY_ENV + TEST_DEPS,
)
java_binary(
name = "framework",
testonly = True,
+ deploy_env = [":framework-deploy-env"],
main_class = "Dummy",
visibility = ["//visibility:public"],
runtime_deps = [":framework-lib"],
)
+java_binary(
+ name = "framework-deploy-env",
+ testonly = True,
+ main_class = "Dummy",
+ runtime_deps = DEPLOY_ENV + PGM_DEPLOY_ENV,
+)
+
java_library2(
name = "framework-lib",
testonly = True,
- srcs = glob(["**/*.java"]),
+ srcs = glob(
+ ["**/*.java"],
+ exclude = FUNCTION_SRCS,
+ ),
exported_deps = [
- "//java/com/google/gerrit/exceptions",
- "//java/com/google/gerrit/gpg",
- "//java/com/google/gerrit/httpd/auth/openid",
- "//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/launcher",
- "//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/pgm:daemon",
- "//java/com/google/gerrit/pgm/http/jetty",
- "//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/server/group/testing",
- "//java/com/google/gerrit/server/project/testing:project-test-util",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//lib:guava",
+ ":function",
+ "//lib:jgit-junit",
"//lib:jimfs",
- "//lib/auto:auto-value",
- "//lib/auto:auto-value-annotations",
- "//lib/flogger:api",
+ "//lib:servlet-api",
"//lib/httpcomponents:fluent-hc",
"//lib/httpcomponents:httpclient",
"//lib/httpcomponents:httpcore",
- "//lib/jetty:servlet",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/mockito",
"//lib/truth",
"//lib/truth:truth-java8-extension",
- "//prolog:gerrit-prolog-common",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/index",
- "//java/com/google/gerrit/index/project",
- "//java/com/google/gerrit/json",
- "//java/com/google/gerrit/lucene",
- "//java/com/google/gerrit/mail",
- "//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
- "//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/audit",
- "//java/com/google/gerrit/server/git/receive",
- "//java/com/google/gerrit/server/restapi",
- "//java/com/google/gerrit/server/schema",
- "//java/com/google/gerrit/server/util/time",
- "//java/com/google/gerrit/sshd",
- "//lib:args4j",
- "//lib:gson",
- "//lib:guava-retrying",
- "//lib:jsch",
- "//lib:servlet-api",
- "//lib/commons:lang",
"//lib/greenmail",
- "//lib/guice",
- "//lib/guice:guice-assistedinject",
- "//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/mail",
- "//lib/mina:sshd",
- ],
+ ] + TEST_DEPS,
+ visibility = ["//visibility:public"],
+ deps = DEPLOY_ENV,
+)
+
+java_library(
+ name = "function",
+ srcs = FUNCTION_SRCS,
+ visibility = ["//visibility:public"],
)
java_doc(
diff --git a/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java b/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java
index 27ed603cae..1ff7d0eb8a 100644
--- a/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java
+++ b/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java
@@ -45,9 +45,8 @@ public class ChangeIndexedCounter implements ChangeIndexedListener {
assertReindexOf(info, 1);
}
- public void assertReindexOf(ChangeInfo info, int expectedCount) {
- assertThat(getCount(info)).isEqualTo(expectedCount);
- assertThat(countsByChange).hasSize(1);
+ public void assertReindexOf(ChangeInfo info, long expectedCount) {
+ assertThat(countsByChange.asMap()).containsExactly(info._number, expectedCount);
clear();
}
}
diff --git a/java/com/google/gerrit/acceptance/DisabledAccountIndex.java b/java/com/google/gerrit/acceptance/DisabledAccountIndex.java
index 91baafbc83..271d15c176 100644
--- a/java/com/google/gerrit/acceptance/DisabledAccountIndex.java
+++ b/java/com/google/gerrit/acceptance/DisabledAccountIndex.java
@@ -14,11 +14,11 @@
package com.google.gerrit.acceptance;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.index.account.AccountIndex;
diff --git a/java/com/google/gerrit/acceptance/DisabledChangeIndex.java b/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
index a32c6d13d4..34f72f5ccf 100644
--- a/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
+++ b/java/com/google/gerrit/acceptance/DisabledChangeIndex.java
@@ -14,12 +14,12 @@
package com.google.gerrit.acceptance;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.query.change.ChangeData;
import java.util.Optional;
diff --git a/java/com/google/gerrit/acceptance/DisabledProjectIndex.java b/java/com/google/gerrit/acceptance/DisabledProjectIndex.java
index 2524a76181..ed119ff3bc 100644
--- a/java/com/google/gerrit/acceptance/DisabledProjectIndex.java
+++ b/java/com/google/gerrit/acceptance/DisabledProjectIndex.java
@@ -14,13 +14,13 @@
package com.google.gerrit.acceptance;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Project;
/**
* This class wraps an index and assumes the search index can't handle any queries. However, it does
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
new file mode 100644
index 0000000000..eaf03b332c
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -0,0 +1,253 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.gerrit.extensions.api.changes.ActionVisitor;
+import com.google.gerrit.extensions.config.DownloadScheme;
+import com.google.gerrit.extensions.events.AccountActivationListener;
+import com.google.gerrit.extensions.events.AccountIndexedListener;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
+import com.google.gerrit.extensions.events.CommentAddedListener;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.GroupIndexedListener;
+import com.google.gerrit.extensions.events.ProjectIndexedListener;
+import com.google.gerrit.extensions.events.RevisionCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.extensions.webui.FileHistoryWebLink;
+import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.change.ChangeETagComputation;
+import com.google.gerrit.server.git.ChangeMessageModifier;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.OnSubmitValidationListener;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
+import com.google.gerrit.server.logging.PerformanceLogger;
+import com.google.gerrit.server.rules.SubmitRule;
+import com.google.gerrit.server.validators.AccountActivationValidationListener;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.inject.Inject;
+import com.google.inject.util.Providers;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExtensionRegistry {
+ private final DynamicSet<AccountIndexedListener> accountIndexedListeners;
+ private final DynamicSet<ChangeIndexedListener> changeIndexedListeners;
+ private final DynamicSet<GroupIndexedListener> groupIndexedListeners;
+ private final DynamicSet<ProjectIndexedListener> projectIndexedListeners;
+ private final DynamicSet<CommitValidationListener> commitValidationListeners;
+ private final DynamicSet<ExceptionHook> exceptionHooks;
+ private final DynamicSet<PerformanceLogger> performanceLoggers;
+ private final DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
+ private final DynamicSet<SubmitRule> submitRules;
+ private final DynamicSet<ChangeMessageModifier> changeMessageModifiers;
+ private final DynamicSet<ChangeETagComputation> changeETagComputations;
+ private final DynamicSet<ActionVisitor> actionVisitors;
+ private final DynamicMap<DownloadScheme> downloadSchemes;
+ private final DynamicSet<RefOperationValidationListener> refOperationValidationListeners;
+ private final DynamicSet<CommentAddedListener> commentAddedListeners;
+ private final DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners;
+ private final DynamicSet<FileHistoryWebLink> fileHistoryWebLinks;
+ private final DynamicSet<PatchSetWebLink> patchSetWebLinks;
+ private final DynamicSet<RevisionCreatedListener> revisionCreatedListeners;
+ private final DynamicSet<GroupBackend> groupBackends;
+ private final DynamicSet<AccountActivationValidationListener>
+ accountActivationValidationListeners;
+ private final DynamicSet<AccountActivationListener> accountActivationListeners;
+ private final DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
+
+ @Inject
+ ExtensionRegistry(
+ DynamicSet<AccountIndexedListener> accountIndexedListeners,
+ DynamicSet<ChangeIndexedListener> changeIndexedListeners,
+ DynamicSet<GroupIndexedListener> groupIndexedListeners,
+ DynamicSet<ProjectIndexedListener> projectIndexedListeners,
+ DynamicSet<CommitValidationListener> commitValidationListeners,
+ DynamicSet<ExceptionHook> exceptionHooks,
+ DynamicSet<PerformanceLogger> performanceLoggers,
+ DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners,
+ DynamicSet<SubmitRule> submitRules,
+ DynamicSet<ChangeMessageModifier> changeMessageModifiers,
+ DynamicSet<ChangeETagComputation> changeETagComputations,
+ DynamicSet<ActionVisitor> actionVisitors,
+ DynamicMap<DownloadScheme> downloadSchemes,
+ DynamicSet<RefOperationValidationListener> refOperationValidationListeners,
+ DynamicSet<CommentAddedListener> commentAddedListeners,
+ DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners,
+ DynamicSet<FileHistoryWebLink> fileHistoryWebLinks,
+ DynamicSet<PatchSetWebLink> patchSetWebLinks,
+ DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
+ DynamicSet<GroupBackend> groupBackends,
+ DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
+ DynamicSet<AccountActivationListener> accountActivationListeners,
+ DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners) {
+ this.accountIndexedListeners = accountIndexedListeners;
+ this.changeIndexedListeners = changeIndexedListeners;
+ this.groupIndexedListeners = groupIndexedListeners;
+ this.projectIndexedListeners = projectIndexedListeners;
+ this.commitValidationListeners = commitValidationListeners;
+ this.exceptionHooks = exceptionHooks;
+ this.performanceLoggers = performanceLoggers;
+ this.projectCreationValidationListeners = projectCreationValidationListeners;
+ this.submitRules = submitRules;
+ this.changeMessageModifiers = changeMessageModifiers;
+ this.changeETagComputations = changeETagComputations;
+ this.actionVisitors = actionVisitors;
+ this.downloadSchemes = downloadSchemes;
+ this.refOperationValidationListeners = refOperationValidationListeners;
+ this.commentAddedListeners = commentAddedListeners;
+ this.refUpdatedListeners = refUpdatedListeners;
+ this.fileHistoryWebLinks = fileHistoryWebLinks;
+ this.patchSetWebLinks = patchSetWebLinks;
+ this.revisionCreatedListeners = revisionCreatedListeners;
+ this.groupBackends = groupBackends;
+ this.accountActivationValidationListeners = accountActivationValidationListeners;
+ this.accountActivationListeners = accountActivationListeners;
+ this.onSubmitValidationListeners = onSubmitValidationListeners;
+ }
+
+ public Registration newRegistration() {
+ return new Registration();
+ }
+
+ @SuppressWarnings("FunctionalInterfaceClash")
+ public class Registration implements AutoCloseable {
+ private final List<RegistrationHandle> registrationHandles = new ArrayList<>();
+
+ public Registration add(AccountIndexedListener accountIndexedListener) {
+ return add(accountIndexedListeners, accountIndexedListener);
+ }
+
+ public Registration add(ChangeIndexedListener changeIndexedListener) {
+ return add(changeIndexedListeners, changeIndexedListener);
+ }
+
+ public Registration add(GroupIndexedListener groupIndexedListener) {
+ return add(groupIndexedListeners, groupIndexedListener);
+ }
+
+ public Registration add(ProjectIndexedListener projectIndexedListener) {
+ return add(projectIndexedListeners, projectIndexedListener);
+ }
+
+ public Registration add(CommitValidationListener commitValidationListener) {
+ return add(commitValidationListeners, commitValidationListener);
+ }
+
+ public Registration add(ExceptionHook exceptionHook) {
+ return add(exceptionHooks, exceptionHook);
+ }
+
+ public Registration add(PerformanceLogger performanceLogger) {
+ return add(performanceLoggers, performanceLogger);
+ }
+
+ public Registration add(ProjectCreationValidationListener projectCreationListener) {
+ return add(projectCreationValidationListeners, projectCreationListener);
+ }
+
+ public Registration add(SubmitRule submitRule) {
+ return add(submitRules, submitRule);
+ }
+
+ public Registration add(ChangeMessageModifier changeMessageModifier) {
+ return add(changeMessageModifiers, changeMessageModifier);
+ }
+
+ public Registration add(ChangeMessageModifier changeMessageModifier, String exportName) {
+ return add(changeMessageModifiers, changeMessageModifier, exportName);
+ }
+
+ public Registration add(ChangeETagComputation changeETagComputation) {
+ return add(changeETagComputations, changeETagComputation);
+ }
+
+ public Registration add(ActionVisitor actionVisitor) {
+ return add(actionVisitors, actionVisitor);
+ }
+
+ public Registration add(DownloadScheme downloadScheme, String exportName) {
+ return add(downloadSchemes, downloadScheme, exportName);
+ }
+
+ public Registration add(RefOperationValidationListener refOperationValidationListener) {
+ return add(refOperationValidationListeners, refOperationValidationListener);
+ }
+
+ public Registration add(CommentAddedListener commentAddedListener) {
+ return add(commentAddedListeners, commentAddedListener);
+ }
+
+ public Registration add(GitReferenceUpdatedListener refUpdatedListener) {
+ return add(refUpdatedListeners, refUpdatedListener);
+ }
+
+ public Registration add(FileHistoryWebLink fileHistoryWebLink) {
+ return add(fileHistoryWebLinks, fileHistoryWebLink);
+ }
+
+ public Registration add(PatchSetWebLink patchSetWebLink) {
+ return add(patchSetWebLinks, patchSetWebLink);
+ }
+
+ public Registration add(RevisionCreatedListener revisionCreatedListener) {
+ return add(revisionCreatedListeners, revisionCreatedListener);
+ }
+
+ public Registration add(GroupBackend groupBackend) {
+ return add(groupBackends, groupBackend);
+ }
+
+ public Registration add(
+ AccountActivationValidationListener accountActivationValidationListener) {
+ return add(accountActivationValidationListeners, accountActivationValidationListener);
+ }
+
+ public Registration add(AccountActivationListener accountDeactivatedListener) {
+ return add(accountActivationListeners, accountDeactivatedListener);
+ }
+
+ public Registration add(OnSubmitValidationListener onSubmitValidationListener) {
+ return add(onSubmitValidationListeners, onSubmitValidationListener);
+ }
+
+ private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
+ return add(dynamicSet, extension, "gerrit");
+ }
+
+ private <T> Registration add(DynamicSet<T> dynamicSet, T extension, String exportname) {
+ RegistrationHandle registrationHandle = dynamicSet.add(exportname, extension);
+ registrationHandles.add(registrationHandle);
+ return this;
+ }
+
+ private <T> Registration add(DynamicMap<T> dynamicMap, T extension, String exportName) {
+ RegistrationHandle registrationHandle =
+ ((PrivateInternals_DynamicMapImpl<T>) dynamicMap)
+ .put("myPlugin", exportName, Providers.of(extension));
+ registrationHandles.add(registrationHandle);
+ return this;
+ }
+
+ @Override
+ public void close() {
+ registrationHandles.forEach(h -> h.remove());
+ }
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/GcAssert.java b/java/com/google/gerrit/acceptance/GcAssert.java
index b9ef629df1..bef33238ca 100644
--- a/java/com/google/gerrit/acceptance/GcAssert.java
+++ b/java/com/google/gerrit/acceptance/GcAssert.java
@@ -16,7 +16,7 @@ package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertWithMessage;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import java.io.File;
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 63ea4e3ce3..805afc22c7 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -43,6 +43,7 @@ import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.index.AutoFlush;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
import com.google.gerrit.server.ssh.NoSshModule;
+import com.google.gerrit.server.util.ReplicaUtil;
import com.google.gerrit.server.util.SocketUtil;
import com.google.gerrit.server.util.SystemLog;
import com.google.gerrit.testing.FakeEmailSender;
@@ -105,6 +106,9 @@ public class GerritServer implements AutoCloseable {
has(Sandboxed.class, testDesc.getTestClass()),
has(SkipProjectClone.class, testDesc.getTestClass()),
has(UseSsh.class, testDesc.getTestClass()),
+ false, // @UseSystemTime is only valid on methods.
+ get(UseClockStep.class, testDesc.getTestClass()),
+ get(UseTimezone.class, testDesc.getTestClass()),
null, // @GerritConfig is only valid on methods.
null, // @GerritConfigs is only valid on methods.
null, // @GlobalPluginConfig is only valid on methods.
@@ -113,6 +117,15 @@ public class GerritServer implements AutoCloseable {
public static Description forTestMethod(
org.junit.runner.Description testDesc, String configName) {
+ UseClockStep useClockStep = testDesc.getAnnotation(UseClockStep.class);
+ if (testDesc.getAnnotation(UseSystemTime.class) == null && useClockStep == null) {
+ // Only read the UseClockStep from the class if on method level neither @UseSystemTime nor
+ // @UseClockStep have been used.
+ // If the method defines @UseSystemTime or @UseClockStep it should overwrite @UseClockStep
+ // on class level.
+ useClockStep = get(UseClockStep.class, testDesc.getTestClass());
+ }
+
return new AutoValue_GerritServer_Description(
testDesc,
configName,
@@ -127,6 +140,11 @@ public class GerritServer implements AutoCloseable {
|| has(SkipProjectClone.class, testDesc.getTestClass()),
testDesc.getAnnotation(UseSsh.class) != null
|| has(UseSsh.class, testDesc.getTestClass()),
+ testDesc.getAnnotation(UseSystemTime.class) != null,
+ useClockStep,
+ testDesc.getAnnotation(UseTimezone.class) != null
+ ? testDesc.getAnnotation(UseTimezone.class)
+ : get(UseTimezone.class, testDesc.getTestClass()),
testDesc.getAnnotation(GerritConfig.class),
testDesc.getAnnotation(GerritConfigs.class),
testDesc.getAnnotation(GlobalPluginConfig.class),
@@ -142,6 +160,16 @@ public class GerritServer implements AutoCloseable {
return false;
}
+ @Nullable
+ private static <T extends Annotation> T get(Class<T> annotation, Class<?> clazz) {
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+ if (clazz.getAnnotation(annotation) != null) {
+ return clazz.getAnnotation(annotation);
+ }
+ }
+ return null;
+ }
+
abstract org.junit.runner.Description testDescription();
@Nullable
@@ -161,6 +189,14 @@ public class GerritServer implements AutoCloseable {
return useSshAnnotation() && SshMode.useSsh();
}
+ abstract boolean useSystemTime();
+
+ @Nullable
+ abstract UseClockStep useClockStep();
+
+ @Nullable
+ abstract UseTimezone useTimezone();
+
@Nullable
abstract GerritConfig config();
@@ -174,12 +210,15 @@ public class GerritServer implements AutoCloseable {
abstract GlobalPluginConfigs pluginConfigs();
private void checkValidAnnotations() {
+ if (useClockStep() != null && useSystemTime()) {
+ throw new IllegalStateException("Use either @UseClockStep or @UseSystemTime, not both");
+ }
if (configs() != null && config() != null) {
- throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
+ throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig, not both");
}
if (pluginConfigs() != null && pluginConfig() != null) {
throw new IllegalStateException(
- "Use either @GlobalPluginConfig or @GlobalPluginConfigs not both");
+ "Use either @GlobalPluginConfig or @GlobalPluginConfigs, not both");
}
if ((pluginConfigs() != null || pluginConfig() != null) && memory()) {
throw new IllegalStateException("Must use @UseLocalDisk with @GlobalPluginConfig(s)");
@@ -362,7 +401,7 @@ public class GerritServer implements AutoCloseable {
@Nullable InMemoryRepositoryManager inMemoryRepoManager)
throws Exception {
Config cfg = desc.buildConfig(baseConfig);
- daemon.setSlave(isSlave(baseConfig) || cfg.getBoolean("container", "slave", false));
+ daemon.setReplica(ReplicaUtil.isReplica(baseConfig) || ReplicaUtil.isReplica(cfg));
mergeTestConfig(cfg);
// Set the log4j configuration to an invalid one to prevent system logs
// from getting configured and creating log files.
@@ -376,7 +415,8 @@ public class GerritServer implements AutoCloseable {
"accountPatchReviewDb", null, "url", JdbcAccountPatchReviewStore.TEST_IN_MEMORY_URL);
daemon.setEnableHttpd(desc.httpd());
daemon.setLuceneModule(
- LuceneIndexModule.singleVersionAllLatest(0, isSlave(baseConfig), AutoFlush.ENABLED));
+ LuceneIndexModule.singleVersionAllLatest(
+ 0, ReplicaUtil.isReplica(baseConfig), AutoFlush.ENABLED));
daemon.setDatabaseForTesting(
ImmutableList.of(
new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
@@ -392,10 +432,6 @@ public class GerritServer implements AutoCloseable {
return new GerritServer(desc, null, createTestInjector(daemon), daemon, null);
}
- private static boolean isSlave(Config baseConfig) {
- return baseConfig.getBoolean("container", "slave", false);
- }
-
private static GerritServer startOnDisk(
Description desc,
Path site,
@@ -436,9 +472,13 @@ public class GerritServer implements AutoCloseable {
private static void mergeTestConfig(Config cfg) {
String forceEphemeralPort = String.format("%s:0", getLocalHost().getHostName());
String url = "http://" + forceEphemeralPort + "/";
- cfg.setString("gerrit", null, "canonicalWebUrl", url);
- cfg.setString("httpd", null, "listenUrl", url);
+ if (cfg.getString("gerrit", null, "canonicalWebUrl") == null) {
+ cfg.setString("gerrit", null, "canonicalWebUrl", url);
+ }
+ if (cfg.getString("httpd", null, "listenUrl") == null) {
+ cfg.setString("httpd", null, "listenUrl", url);
+ }
if (cfg.getString("sshd", null, "listenAddress") == null) {
cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
}
@@ -454,11 +494,13 @@ public class GerritServer implements AutoCloseable {
cfg.setInt("sshd", null, "commandStartThreads", 1);
cfg.setInt("receive", null, "threadPoolSize", 1);
cfg.setInt("index", null, "threads", 1);
- cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
+ if (cfg.getString("index", null, "reindexAfterRefUpdate") == null) {
+ cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
+ }
}
private static Injector createTestInjector(Daemon daemon) throws Exception {
- Injector sysInjector = get(daemon, "sysInjector");
+ Injector sysInjector = getInjector(daemon, "sysInjector");
Module module =
new FactoryModule() {
@Override
@@ -491,13 +533,14 @@ public class GerritServer implements AutoCloseable {
return sysInjector.createChildInjector(module);
}
- @SuppressWarnings("unchecked")
- private static <T> T get(Object obj, String field)
+ private static Injector getInjector(Object obj, String field)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
- return (T) f.get(obj);
+ Object v = f.get(obj);
+ checkArgument(v instanceof Injector, "not an Injector: %s", v);
+ return (Injector) f.get(obj);
}
private static InetAddress getLocalHost() {
@@ -557,7 +600,7 @@ public class GerritServer implements AutoCloseable {
Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
- cfg.setBoolean("container", null, "slave", true);
+ cfg.setBoolean("container", null, "replica", true);
InMemoryRepositoryManager inMemoryRepoManager = null;
if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
diff --git a/java/com/google/gerrit/acceptance/GitClientVersion.java b/java/com/google/gerrit/acceptance/GitClientVersion.java
new file mode 100644
index 0000000000..4c9a32dc72
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/GitClientVersion.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.stream.IntStream;
+
+/** Class to parse and represent version of git-core client */
+public class GitClientVersion implements Comparable<GitClientVersion> {
+ private final int v[];
+
+ /**
+ * Constructor to represent instance for minimum supported git-core version
+ *
+ * @param parts version passed as single digits
+ */
+ public GitClientVersion(int... parts) {
+ this.v = parts;
+ }
+
+ /**
+ * Parse the git-core version as returned by git version command
+ *
+ * @param version String returned by git version command
+ */
+ public GitClientVersion(String version) {
+ // "git version x.y.z", at Google "git version x.y.z.gXXXXXXXXXX-goog"
+ String parts[] = version.split(" ")[2].split("\\.");
+ int numParts = Math.min(parts.length, 3); // ignore Google-specific part of the version
+ v = new int[numParts];
+ for (int i = 0; i < numParts; i++) {
+ v[i] = Integer.valueOf(parts[i]);
+ }
+ }
+
+ @Override
+ public int compareTo(GitClientVersion o) {
+ int m = Math.max(v.length, o.v.length);
+ for (int i = 0; i < m; i++) {
+ int l = i < v.length ? v[i] : 0;
+ int r = i < o.v.length ? o.v[i] : 0;
+ if (l != r) {
+ return l < r ? -1 : 1;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return IntStream.of(v).mapToObj(String::valueOf).collect(joining("."));
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/GitUtil.java b/java/com/google/gerrit/acceptance/GitUtil.java
index cdfdae7682..ae72793397 100644
--- a/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/java/com/google/gerrit/acceptance/GitUtil.java
@@ -15,13 +15,14 @@
package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
import com.google.gerrit.common.FooterConstants;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
@@ -109,19 +110,14 @@ public class GitUtil {
throws Exception {
DfsRepositoryDescription desc = new DfsRepositoryDescription("clone of " + project.get());
- FS fs = FS.detect();
-
- // Avoid leaking user state into our tests.
- fs.setUserHome(null);
-
- InMemoryRepository dest =
- new InMemoryRepository.Builder()
- .setRepositoryDescription(desc)
- // SshTransport depends on a real FS to read ~/.ssh/config, but
- // InMemoryRepository by default uses a null FS.
- // TODO(dborowitz): Remove when we no longer depend on SSH.
- .setFS(fs)
- .build();
+ InMemoryRepository.Builder b = new InMemoryRepository.Builder().setRepositoryDescription(desc);
+ if (uri.startsWith("ssh://")) {
+ // SshTransport depends on a real FS to read ~/.ssh/config, but InMemoryRepository by default
+ // uses a null FS.
+ // Avoid leaking user state into our tests.
+ b.setFS(FS.detect().setUserHome(null));
+ }
+ InMemoryRepository dest = b.build();
Config cfg = dest.getConfig();
cfg.setString("remote", "origin", "url", uri);
cfg.setString("remote", "origin", "fetch", "+refs/heads/*:refs/remotes/origin/*");
@@ -134,11 +130,6 @@ public class GitUtil {
return testRepo;
}
- public static TestRepository<InMemoryRepository> cloneProject(
- Project.NameKey project, SshSession sshSession) throws Exception {
- return cloneProject(project, sshSession.getUrl() + "/" + project.get());
- }
-
public static Ref createAnnotatedTag(TestRepository<?> testRepo, String name, PersonIdent tagger)
throws GitAPIException {
TagCommand cmd =
@@ -209,13 +200,13 @@ public class GitUtil {
public static void assertPushOk(PushResult result, String ref) {
RemoteRefUpdate rru = result.getRemoteUpdate(ref);
- assertThat(rru.getStatus()).named(rru.toString()).isEqualTo(RemoteRefUpdate.Status.OK);
+ assertWithMessage(rru.toString()).that(rru.getStatus()).isEqualTo(RemoteRefUpdate.Status.OK);
}
public static void assertPushRejected(PushResult result, String ref, String expectedMessage) {
RemoteRefUpdate rru = result.getRemoteUpdate(ref);
- assertThat(rru.getStatus())
- .named(rru.toString())
+ assertWithMessage(rru.toString())
+ .that(rru.getStatus())
.isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
assertThat(rru.getMessage()).isEqualTo(expectedMessage);
}
diff --git a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index a704d2fc8b..a3207e28f1 100644
--- a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -55,8 +55,6 @@ class InMemoryTestingDatabaseModule extends LifecycleModule {
@Override
protected void configure() {
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
-
- // TODO(dborowitz): Use jimfs.
bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
if (repoManager != null) {
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
index 83ed2020ee..75e5a2ee9e 100644
--- a/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -21,24 +21,24 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.InProcessProtocol.Context;
import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.config.GerritRequestModule;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
+import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
import com.google.gerrit.server.git.ReceivePackInitializer;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackInitializer;
+import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.plugincontext.PluginSetContext;
@@ -90,8 +90,6 @@ class InProcessProtocol extends TestProtocol<Context> {
@Provides
@RemotePeer
SocketAddress getSocketAddress() {
- // TODO(dborowitz): Could potentially fake this with thread ID or
- // something.
throw new OutOfScopeException("No remote peer in acceptance tests");
}
};
@@ -203,6 +201,7 @@ class InProcessProtocol extends TestProtocol<Context> {
private final ThreadLocalRequestContext threadContext;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
+ private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
@Inject
Upload(
@@ -212,7 +211,8 @@ class InProcessProtocol extends TestProtocol<Context> {
UploadValidators.Factory uploadValidatorsFactory,
ThreadLocalRequestContext threadContext,
ProjectCache projectCache,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook) {
this.transferConfig = transferConfig;
this.uploadPackInitializers = uploadPackInitializers;
this.preUploadHooks = preUploadHooks;
@@ -220,6 +220,7 @@ class InProcessProtocol extends TestProtocol<Context> {
this.threadContext = threadContext;
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
+ this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
}
@Override
@@ -249,12 +250,17 @@ class InProcessProtocol extends TestProtocol<Context> {
if (projectState == null) {
throw new RuntimeException("can't load project state for " + req.project.get());
}
- UploadPack up = new UploadPack(repo);
+ Repository permissionAwareRepository = PermissionAwareRepositoryManager.wrap(repo, perm);
+ UploadPack up = new UploadPack(permissionAwareRepository);
up.setPackConfig(transferConfig.getPackConfig());
up.setTimeout(transferConfig.getTimeout());
- up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
+ if (projectState.isAllUsers()) {
+ up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
+ }
List<PreUploadHook> hooks = Lists.newArrayList(preUploadHooks);
- hooks.add(uploadValidatorsFactory.create(projectState.getProject(), repo, "localhost-test"));
+ hooks.add(
+ uploadValidatorsFactory.create(
+ projectState.getProject(), permissionAwareRepository, "localhost-test"));
up.setPreUploadHook(PreUploadHookChain.newChain(hooks));
uploadPackInitializers.runEach(initializer -> initializer.init(req.project, up));
return up;
@@ -343,7 +349,7 @@ class InProcessProtocol extends TestProtocol<Context> {
ImmutableList.<PostReceiveHook>builder()
.add(
(pack, commands) -> {
- if (affectsSize(pack, commands)) {
+ if (affectsSize(pack)) {
try {
quotaBackend
.user(identifiedUser)
diff --git a/java/com/google/gerrit/acceptance/ProjectResetter.java b/java/com/google/gerrit/acceptance/ProjectResetter.java
index ea958f661b..a528974478 100644
--- a/java/com/google/gerrit/acceptance/ProjectResetter.java
+++ b/java/com/google/gerrit/acceptance/ProjectResetter.java
@@ -15,7 +15,7 @@
package com.google.gerrit.acceptance;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS;
+import static com.google.gerrit.entities.RefNames.REFS_USERS;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableList;
@@ -23,11 +23,11 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.RefState;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupIncludeCache;
diff --git a/java/com/google/gerrit/acceptance/PushOneCommit.java b/java/com/google/gerrit/acceptance/PushOneCommit.java
index e15dd40d57..3ccbe4d85b 100644
--- a/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static org.junit.Assert.assertEquals;
@@ -24,9 +25,9 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -395,15 +396,15 @@ public class PushOneCommit {
public void assertErrorStatus() {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
- assertThat(refUpdate.getStatus())
- .named(message(refUpdate))
+ assertWithMessage(message(refUpdate))
+ .that(refUpdate.getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
}
private void assertStatus(Status expectedStatus, String expectedMessage) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
- assertThat(refUpdate.getStatus()).named(message(refUpdate)).isEqualTo(expectedStatus);
+ assertWithMessage(message(refUpdate)).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
if (expectedMessage == null) {
assertThat(refUpdate.getMessage()).isNull();
} else {
diff --git a/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java b/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
index 19910db042..e943519d19 100644
--- a/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
+++ b/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
@@ -14,12 +14,12 @@
package com.google.gerrit.acceptance;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.query.change.ChangeData;
diff --git a/java/com/google/gerrit/acceptance/ReindexGroupsAtStartup.java b/java/com/google/gerrit/acceptance/ReindexGroupsAtStartup.java
index bd8a926bf8..b985e409f0 100644
--- a/java/com/google/gerrit/acceptance/ReindexGroupsAtStartup.java
+++ b/java/com/google/gerrit/acceptance/ReindexGroupsAtStartup.java
@@ -20,6 +20,7 @@ import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.db.Groups;
import com.google.gerrit.server.index.group.GroupIndexer;
+import com.google.gerrit.server.util.ReplicaUtil;
import com.google.inject.Inject;
import com.google.inject.Scopes;
import java.io.IOException;
@@ -50,8 +51,8 @@ public class ReindexGroupsAtStartup implements LifecycleListener {
@Override
public void start() {
- // Gerrit slaves without a reindex
- if (cfg.getBoolean("container", "slave", false)
+ // Gerrit replicas without a reindex
+ if (ReplicaUtil.isReplica(cfg)
&& !cfg.getBoolean("index", "scheduledIndexer", "runOnStartup", true)) {
return;
}
diff --git a/java/com/google/gerrit/acceptance/RestResponse.java b/java/com/google/gerrit/acceptance/RestResponse.java
index e8de5c6220..a045d803db 100644
--- a/java/com/google/gerrit/acceptance/RestResponse.java
+++ b/java/com/google/gerrit/acceptance/RestResponse.java
@@ -17,13 +17,23 @@ package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
+import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_UNPROCESSABLE_ENTITY;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
+import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
-import org.apache.http.HttpStatus;
public class RestResponse extends HttpResponse {
@@ -47,47 +57,47 @@ public class RestResponse extends HttpResponse {
}
public void assertOK() throws Exception {
- assertStatus(HttpStatus.SC_OK);
+ assertStatus(SC_OK);
}
public void assertNotFound() throws Exception {
- assertStatus(HttpStatus.SC_NOT_FOUND);
+ assertStatus(SC_NOT_FOUND);
}
public void assertConflict() throws Exception {
- assertStatus(HttpStatus.SC_CONFLICT);
+ assertStatus(SC_CONFLICT);
}
public void assertForbidden() throws Exception {
- assertStatus(HttpStatus.SC_FORBIDDEN);
+ assertStatus(SC_FORBIDDEN);
}
public void assertNoContent() throws Exception {
- assertStatus(HttpStatus.SC_NO_CONTENT);
+ assertStatus(SC_NO_CONTENT);
}
public void assertBadRequest() throws Exception {
- assertStatus(HttpStatus.SC_BAD_REQUEST);
+ assertStatus(SC_BAD_REQUEST);
}
public void assertUnprocessableEntity() throws Exception {
- assertStatus(HttpStatus.SC_UNPROCESSABLE_ENTITY);
+ assertStatus(SC_UNPROCESSABLE_ENTITY);
}
public void assertMethodNotAllowed() throws Exception {
- assertStatus(HttpStatus.SC_METHOD_NOT_ALLOWED);
+ assertStatus(SC_METHOD_NOT_ALLOWED);
}
public void assertCreated() throws Exception {
- assertStatus(HttpStatus.SC_CREATED);
+ assertStatus(SC_CREATED);
}
public void assertPreconditionFailed() throws Exception {
- assertStatus(HttpStatus.SC_PRECONDITION_FAILED);
+ assertStatus(SC_PRECONDITION_FAILED);
}
public void assertTemporaryRedirect(String path) throws Exception {
- assertStatus(HttpStatus.SC_MOVED_TEMPORARILY);
+ assertStatus(SC_MOVED_TEMPORARILY);
assertThat(URI.create(getHeader("Location")).getPath()).isEqualTo(path);
}
}
diff --git a/java/com/google/gerrit/acceptance/SshdModule.java b/java/com/google/gerrit/acceptance/SshdModule.java
index 185d6e2a3d..873ba17707 100644
--- a/java/com/google/gerrit/acceptance/SshdModule.java
+++ b/java/com/google/gerrit/acceptance/SshdModule.java
@@ -34,7 +34,7 @@ public class SshdModule extends AbstractModule {
if (keys == null) {
keys = new SimpleGeneratorHostKeyProvider();
keys.setAlgorithm("RSA");
- keys.loadKeys();
+ keys.loadKeys(null);
}
return keys;
}
diff --git a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
index d20124ae7b..43fe4ebfa0 100644
--- a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
+++ b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import static org.junit.Assert.fail;
@@ -29,12 +29,12 @@ import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.git.DelegateSystemReader;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Injector;
import com.google.inject.Module;
@@ -67,10 +67,10 @@ public abstract class StandaloneSiteTest {
private ServerContext(GerritServer server) throws Exception {
this.server = server;
Injector i = server.getTestInjector();
- if (adminId == null) {
- adminId = i.getInstance(AccountCreator.class).admin().id();
+ if (admin == null) {
+ admin = i.getInstance(AccountCreator.class).admin();
}
- ctx = i.getInstance(OneOffRequestContext.class).openAs(adminId);
+ ctx = i.getInstance(OneOffRequestContext.class).openAs(admin.id());
GerritApi gApi = i.getInstance(GerritApi.class);
try {
@@ -125,7 +125,7 @@ public abstract class StandaloneSiteTest {
@Rule public RuleChain ruleChain = RuleChain.outerRule(tempSiteDir).around(testRunner);
protected SitePaths sitePaths;
- protected Account.Id adminId;
+ protected TestAccount admin;
private GerritServer.Description serverDesc;
private SystemReader oldSystemReader;
@@ -143,20 +143,10 @@ public abstract class StandaloneSiteTest {
private static SystemReader setFakeSystemReader(File tempDir) {
SystemReader oldSystemReader = SystemReader.getInstance();
SystemReader.setInstance(
- new SystemReader() {
+ new DelegateSystemReader(oldSystemReader) {
@Override
- public String getHostname() {
- return oldSystemReader.getHostname();
- }
-
- @Override
- public String getenv(String variable) {
- return oldSystemReader.getenv(variable);
- }
-
- @Override
- public String getProperty(String key) {
- return oldSystemReader.getProperty(key);
+ public FileBasedConfig openJGitConfig(Config parent, FS fs) {
+ return new FileBasedConfig(parent, new File(tempDir, "jgit.config"), FS.detect());
}
@Override
@@ -168,16 +158,6 @@ public abstract class StandaloneSiteTest {
public FileBasedConfig openSystemConfig(Config parent, FS fs) {
return new FileBasedConfig(parent, new File(tempDir, "system.config"), FS.detect());
}
-
- @Override
- public long getCurrentTime() {
- return oldSystemReader.getCurrentTime();
- }
-
- @Override
- public int getTimezone(long when) {
- return oldSystemReader.getTimezone(when);
- }
});
return oldSystemReader;
}
@@ -214,8 +194,8 @@ public abstract class StandaloneSiteTest {
// Use invokeProgram with the current classloader, rather than mainImpl, which would create a
// new classloader. This is necessary so that static state, particularly the SystemReader, is
// shared with the test method.
- assertThat(GerritLauncher.invokeProgram(StandaloneSiteTest.class.getClassLoader(), args))
- .named("gerrit.war " + Arrays.stream(args).collect(joining(" ")))
+ assertWithMessage("gerrit.war " + Arrays.stream(args).collect(joining(" ")))
+ .that(GerritLauncher.invokeProgram(StandaloneSiteTest.class.getClassLoader(), args))
.isEqualTo(0);
}
diff --git a/java/com/google/gerrit/acceptance/TestAccount.java b/java/com/google/gerrit/acceptance/TestAccount.java
index c937aed558..07bb739bf1 100644
--- a/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/java/com/google/gerrit/acceptance/TestAccount.java
@@ -21,8 +21,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.common.net.InetAddresses;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import java.net.InetSocketAddress;
import java.util.Arrays;
import org.apache.http.client.utils.URIBuilder;
diff --git a/java/com/google/gerrit/acceptance/UseClockStep.java b/java/com/google/gerrit/acceptance/UseClockStep.java
new file mode 100644
index 0000000000..10a93fed53
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/UseClockStep.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Annotation to use a clock step for the execution of acceptance tests (the test class must inherit
+ * from {@link AbstractDaemonTest}).
+ *
+ * <p>Annotations on method level override annotations on class level.
+ */
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+public @interface UseClockStep {
+ /** Amount to increment clock by on each lookup. */
+ long clockStep() default 1L;
+
+ /** Time unit for {@link #clockStep()}. */
+ TimeUnit clockStepUnit() default TimeUnit.SECONDS;
+
+ /** Whether the clock should initially be set to {@link java.time.Instant#EPOCH}. */
+ boolean startAtEpoch() default false;
+}
diff --git a/java/com/google/gerrit/acceptance/UseSystemTime.java b/java/com/google/gerrit/acceptance/UseSystemTime.java
new file mode 100644
index 0000000000..e9cbd47369
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/UseSystemTime.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to use the system time for the execution of acceptance tests (the test class must
+ * inherit from {@link AbstractDaemonTest}).
+ *
+ * <p>Can only be applied on method level, since using system time on class level is the default if
+ * {@link UseClockStep} is not used.
+ *
+ * <p>Intended to be used to use system time for single tests when the test class is annotated with
+ * {@link UseClockStep}.
+ */
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface UseSystemTime {}
diff --git a/java/com/google/gerrit/acceptance/UseTimezone.java b/java/com/google/gerrit/acceptance/UseTimezone.java
new file mode 100644
index 0000000000..7412030b83
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/UseTimezone.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to set a timezone for the execution of acceptance tests (the test class must inherit
+ * from {@link AbstractDaemonTest}).
+ *
+ * <p>Annotations on method level override annotations on class level.
+ */
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+public @interface UseTimezone {
+ /** The timezone that should be used for the test, e.g. "US/Eastern". */
+ String timezone();
+}
diff --git a/java/com/google/gerrit/acceptance/rest/CreateTestPlugin.java b/java/com/google/gerrit/acceptance/rest/CreateTestPlugin.java
index 85a2d7b012..2a77d31c3a 100644
--- a/java/com/google/gerrit/acceptance/rest/CreateTestPlugin.java
+++ b/java/com/google/gerrit/acceptance/rest/CreateTestPlugin.java
@@ -33,7 +33,8 @@ public class CreateTestPlugin
}
@Override
- public Object apply(ConfigResource parentResource, IdString id, Input input) throws Exception {
+ public Response<?> apply(ConfigResource parentResource, IdString id, Input input)
+ throws Exception {
return Response.created(input);
}
}
diff --git a/java/com/google/gerrit/acceptance/rest/GetTestPlugin.java b/java/com/google/gerrit/acceptance/rest/GetTestPlugin.java
index a31cc151dd..3b6da85337 100644
--- a/java/com/google/gerrit/acceptance/rest/GetTestPlugin.java
+++ b/java/com/google/gerrit/acceptance/rest/GetTestPlugin.java
@@ -27,7 +27,7 @@ import com.google.inject.Singleton;
public class GetTestPlugin implements RestReadView<PluginResource> {
@Override
- public Object apply(PluginResource resource)
+ public Response<?> apply(PluginResource resource)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
return Response.ok("Foo");
}
diff --git a/java/com/google/gerrit/acceptance/rest/ListTestPlugin.java b/java/com/google/gerrit/acceptance/rest/ListTestPlugin.java
index 00e071ccb5..ce037a73c9 100644
--- a/java/com/google/gerrit/acceptance/rest/ListTestPlugin.java
+++ b/java/com/google/gerrit/acceptance/rest/ListTestPlugin.java
@@ -18,14 +18,15 @@ import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.ConfigResource;
public class ListTestPlugin implements RestReadView<ConfigResource> {
@Override
- public Object apply(ConfigResource resource)
+ public Response<?> apply(ConfigResource resource)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
- return ImmutableList.of();
+ return Response.ok(ImmutableList.of());
}
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
index 61b828ee36..efae223749 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.testsuite.account;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
/**
* An aggregation of operations on accounts for test purposes.
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index 7641e4784f..f1b840a466 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -16,7 +16,7 @@ package com.google.gerrit.acceptance.testsuite.account;
import static com.google.common.base.Preconditions.checkState;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
@@ -61,14 +61,14 @@ public class AccountOperationsImpl implements AccountOperations {
private Account.Id createAccount(TestAccountCreation accountCreation) throws Exception {
AccountsUpdate.AccountUpdater accountUpdater =
(account, updateBuilder) ->
- fillBuilder(updateBuilder, accountCreation, account.getAccount().getId());
+ fillBuilder(updateBuilder, accountCreation, account.account().id());
AccountState createdAccount = createAccount(accountUpdater);
- return createdAccount.getAccount().getId();
+ return createdAccount.account().id();
}
private AccountState createAccount(AccountsUpdate.AccountUpdater accountUpdater)
throws IOException, ConfigInvalidException {
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
return accountsUpdate.insert("Create Test Account", accountId, accountUpdater);
}
@@ -129,13 +129,13 @@ public class AccountOperationsImpl implements AccountOperations {
}
private TestAccount toTestAccount(AccountState accountState) {
- Account account = accountState.getAccount();
+ Account account = accountState.account();
return TestAccount.builder()
- .accountId(account.getId())
- .preferredEmail(Optional.ofNullable(account.getPreferredEmail()))
- .fullname(Optional.ofNullable(account.getFullName()))
- .username(accountState.getUserName())
- .active(accountState.getAccount().isActive())
+ .accountId(account.id())
+ .preferredEmail(Optional.ofNullable(account.preferredEmail()))
+ .fullname(Optional.ofNullable(account.fullName()))
+ .username(accountState.userName())
+ .active(accountState.account().isActive())
.build();
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
index e7ffeeccbc..2574d55a1b 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
@@ -15,7 +15,7 @@
package com.google.gerrit.acceptance.testsuite.account;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.Optional;
@AutoValue
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
index f2414e029e..983fec02da 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
@@ -16,7 +16,7 @@ package com.google.gerrit.acceptance.testsuite.account;
import com.google.auto.value.AutoValue;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.Optional;
@AutoValue
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java b/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java
index 4847fdb5bc..6c95360a05 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java
@@ -19,7 +19,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.gerrit.acceptance.SshEnabled;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
index 533d06b0fc..b9414e1a2b 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.testsuite.group;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
/**
* An aggregation of operations on groups for test purposes.
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
index e0ddee56fd..fd5c003e48 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
@@ -16,9 +16,9 @@ package com.google.gerrit.acceptance.testsuite.group;
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.NoSuchGroupException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.GroupUUID;
@@ -78,10 +78,10 @@ public class GroupOperationsImpl implements GroupOperations {
}
private InternalGroupCreation toInternalGroupCreation(TestGroupCreation groupCreation) {
- AccountGroup.Id groupId = new AccountGroup.Id(seq.nextGroupId());
+ AccountGroup.Id groupId = AccountGroup.id(seq.nextGroupId());
String groupName = groupCreation.name().orElse("group-with-id-" + groupId.get());
AccountGroup.UUID groupUuid = GroupUUID.make(groupName, serverIdent);
- AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
+ AccountGroup.NameKey nameKey = AccountGroup.nameKey(groupName);
return InternalGroupCreation.builder()
.setId(groupId)
.setGroupUUID(groupUuid)
@@ -153,7 +153,7 @@ public class GroupOperationsImpl implements GroupOperations {
private InternalGroupUpdate toInternalGroupUpdate(TestGroupUpdate groupUpdate) {
InternalGroupUpdate.Builder builder = InternalGroupUpdate.builder();
- groupUpdate.name().map(AccountGroup.NameKey::new).ifPresent(builder::setName);
+ groupUpdate.name().map(AccountGroup::nameKey).ifPresent(builder::setName);
groupUpdate.description().ifPresent(builder::setDescription);
groupUpdate.ownerGroupUuid().ifPresent(builder::setOwnerGroupUUID);
groupUpdate.visibleToAll().ifPresent(builder::setVisibleToAll);
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java
index b450304361..c885353320 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroup.java
@@ -16,8 +16,8 @@ package com.google.gerrit.acceptance.testsuite.group;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
import java.util.Optional;
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
index 612ce2a47f..8bb7b23b9a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
@@ -18,8 +18,8 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Optional;
import java.util.Set;
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
index bc9d569aa7..47c7117efc 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
@@ -18,8 +18,8 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/BUILD b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
new file mode 100644
index 0000000000..8a3a23a5b6
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
@@ -0,0 +1,22 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(default_testonly = 1)
+
+java_library(
+ name = "project",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/acceptance:function",
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/server",
+ "//lib:guava",
+ "//lib:jgit",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib/commons:lang",
+ "//lib/guice",
+ ],
+)
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java
index 029d161f11..2db611baeb 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java
@@ -14,7 +14,9 @@
package com.google.gerrit.acceptance.testsuite.project;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.project.ProjectConfig;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
/**
@@ -28,6 +30,9 @@ public interface ProjectOperations {
PerProjectOperations project(Project.NameKey key);
+ /** Starts a fluent chain for updating All-Projects. */
+ TestProjectUpdate.Builder allProjectsForUpdate();
+
interface PerProjectOperations {
/**
* Returns the commit for this project. branchName can either be shortened ("HEAD", "master") or
@@ -40,5 +45,33 @@ public interface ProjectOperations {
* fully qualified refname ("refs/heads/master").
*/
boolean hasHead(String branchName);
+
+ /** Returns a fresh {@link ProjectConfig} read from the tip of {@code refs/meta/config}. */
+ ProjectConfig getProjectConfig();
+
+ /**
+ * Returns a fresh JGit {@link Config} instance read from {@code project.config} at the tip of
+ * {@code refs/meta/config}. Does not have a base config, i.e. does not respect {@code
+ * $site_path/etc/project.config}.
+ */
+ Config getConfig();
+
+ /**
+ * Starts the fluent chain to update a project. The returned builder can be used to specify how
+ * the attributes of the project should be modified. To update the project for real, the {@link
+ * TestProjectUpdate.Builder#update()} must be called.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * projectOperations
+ * .forUpdate()
+ * .add(allow(ABANDON).ref("refs/*").group(REGISTERED_USERS))
+ * .update();
+ * </pre>
+ *
+ * @return a builder to update the check.
+ */
+ TestProjectUpdate.Builder forUpdate();
}
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index 28be3f3a7e..62a560b7a8 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -14,30 +14,68 @@
package com.google.gerrit.acceptance.testsuite.project;
-import com.google.common.base.Preconditions;
+import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
+import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.testsuite.project.TestProjectCreation.Builder;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestCapability;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestLabelPermission;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestPermission;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectCreator;
import com.google.inject.Inject;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import org.apache.commons.lang.RandomStringUtils;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
public class ProjectOperationsImpl implements ProjectOperations {
- private final ProjectCreator projectCreator;
+ private final AllProjectsName allProjectsName;
private final GitRepositoryManager repoManager;
+ private final MetaDataUpdate.Server metaDataUpdateFactory;
+ private final ProjectCache projectCache;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectCreator projectCreator;
@Inject
- ProjectOperationsImpl(GitRepositoryManager repoManager, ProjectCreator projectCreator) {
+ ProjectOperationsImpl(
+ AllProjectsName allProjectsName,
+ GitRepositoryManager repoManager,
+ MetaDataUpdate.Server metaDataUpdateFactory,
+ ProjectCache projectCache,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectCreator projectCreator) {
+ this.allProjectsName = allProjectsName;
this.repoManager = repoManager;
+ this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.projectCache = projectCache;
+ this.projectConfigFactory = projectConfigFactory;
this.projectCreator = projectCreator;
}
@@ -58,7 +96,7 @@ public class ProjectOperationsImpl implements ProjectOperations {
args.ownerIds = new ArrayList<>();
projectCreation.submitType().ifPresent(st -> args.submitType = st);
projectCreator.createProject(args);
- return new Project.NameKey(name);
+ return Project.nameKey(name);
}
@Override
@@ -66,8 +104,12 @@ public class ProjectOperationsImpl implements ProjectOperations {
return new PerProjectOperations(key);
}
- private class PerProjectOperations implements ProjectOperations.PerProjectOperations {
+ @Override
+ public TestProjectUpdate.Builder allProjectsForUpdate() {
+ return project(allProjectsName).forUpdate();
+ }
+ private class PerProjectOperations implements ProjectOperations.PerProjectOperations {
Project.NameKey nameKey;
PerProjectOperations(Project.NameKey nameKey) {
@@ -76,7 +118,7 @@ public class ProjectOperationsImpl implements ProjectOperations {
@Override
public RevCommit getHead(String branch) {
- return Preconditions.checkNotNull(headOrNull(branch));
+ return requireNonNull(headOrNull(branch));
}
@Override
@@ -84,6 +126,91 @@ public class ProjectOperationsImpl implements ProjectOperations {
return headOrNull(branch) != null;
}
+ @Override
+ public TestProjectUpdate.Builder forUpdate() {
+ return TestProjectUpdate.builder(nameKey, allProjectsName, this::updateProject);
+ }
+
+ private void updateProject(TestProjectUpdate projectUpdate)
+ throws IOException, ConfigInvalidException {
+ try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create(nameKey)) {
+ ProjectConfig projectConfig = projectConfigFactory.read(metaDataUpdate);
+ if (projectUpdate.removeAllAccessSections()) {
+ projectConfig.getAccessSections().forEach(as -> projectConfig.remove(as));
+ }
+ removePermissions(projectConfig, projectUpdate.removedPermissions());
+ addCapabilities(projectConfig, projectUpdate.addedCapabilities());
+ addPermissions(projectConfig, projectUpdate.addedPermissions());
+ addLabelPermissions(projectConfig, projectUpdate.addedLabelPermissions());
+ setExclusiveGroupPermissions(projectConfig, projectUpdate.exclusiveGroupPermissions());
+ projectConfig.commit(metaDataUpdate);
+ }
+ projectCache.evict(nameKey);
+ }
+
+ private void removePermissions(
+ ProjectConfig projectConfig,
+ ImmutableList<TestProjectUpdate.TestPermissionKey> removedPermissions) {
+ for (TestProjectUpdate.TestPermissionKey p : removedPermissions) {
+ Permission permission =
+ projectConfig.getAccessSection(p.section(), true).getPermission(p.name(), true);
+ if (p.group().isPresent()) {
+ GroupReference group = new GroupReference(p.group().get(), p.group().get().get());
+ group = projectConfig.resolve(group);
+ permission.removeRule(group);
+ } else {
+ permission.clearRules();
+ }
+ }
+ }
+
+ private void addCapabilities(
+ ProjectConfig projectConfig, ImmutableList<TestCapability> addedCapabilities) {
+ for (TestCapability c : addedCapabilities) {
+ PermissionRule rule = newRule(projectConfig, c.group());
+ rule.setRange(c.min(), c.max());
+ projectConfig
+ .getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
+ .getPermission(c.name(), true)
+ .add(rule);
+ }
+ }
+
+ private void addPermissions(
+ ProjectConfig projectConfig, ImmutableList<TestPermission> addedPermissions) {
+ for (TestPermission p : addedPermissions) {
+ PermissionRule rule = newRule(projectConfig, p.group());
+ rule.setAction(p.action());
+ rule.setForce(p.force());
+ projectConfig.getAccessSection(p.ref(), true).getPermission(p.name(), true).add(rule);
+ }
+ }
+
+ private void addLabelPermissions(
+ ProjectConfig projectConfig, ImmutableList<TestLabelPermission> addedLabelPermissions) {
+ for (TestLabelPermission p : addedLabelPermissions) {
+ PermissionRule rule = newRule(projectConfig, p.group());
+ rule.setAction(p.action());
+ rule.setRange(p.min(), p.max());
+ String permissionName =
+ p.impersonation() ? Permission.forLabelAs(p.name()) : Permission.forLabel(p.name());
+ Permission permission =
+ projectConfig.getAccessSection(p.ref(), true).getPermission(permissionName, true);
+ permission.add(rule);
+ }
+ }
+
+ private void setExclusiveGroupPermissions(
+ ProjectConfig projectConfig,
+ ImmutableMap<TestProjectUpdate.TestPermissionKey, Boolean> exclusiveGroupPermissions) {
+ exclusiveGroupPermissions.forEach(
+ (key, exclusive) ->
+ projectConfig
+ .getAccessSection(key.section(), true)
+ .getPermission(key.name(), true)
+ .setExclusiveGroup(exclusive));
+ }
+
private RevCommit headOrNull(String branch) {
if (!branch.startsWith(Constants.R_REFS)) {
branch = RefNames.REFS_HEADS + branch;
@@ -97,5 +224,45 @@ public class ProjectOperationsImpl implements ProjectOperations {
throw new IllegalStateException(e);
}
}
+
+ @Override
+ public ProjectConfig getProjectConfig() {
+ try (Repository repo = repoManager.openRepository(nameKey)) {
+ ProjectConfig projectConfig = projectConfigFactory.create(nameKey);
+ projectConfig.load(nameKey, repo);
+ return projectConfig;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public Config getConfig() {
+ try (Repository repo = repoManager.openRepository(nameKey);
+ RevWalk rw = new RevWalk(repo)) {
+ Ref ref = repo.exactRef(REFS_CONFIG);
+ if (ref == null) {
+ return new Config();
+ }
+ RevTree tree = rw.parseTree(ref.getObjectId());
+ TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), PROJECT_CONFIG, tree);
+ if (tw == null) {
+ return new Config();
+ }
+ ObjectLoader loader = rw.getObjectReader().open(tw.getObjectId(0));
+ String text = new String(loader.getCachedBytes(), UTF_8);
+ Config config = new Config();
+ config.fromText(text);
+ return config;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private static PermissionRule newRule(ProjectConfig project, AccountGroup.UUID groupUUID) {
+ GroupReference group = new GroupReference(groupUUID, groupUUID.get());
+ group = project.resolve(group);
+ return new PermissionRule(group);
}
}
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
index 31af1d2251..99e045c693 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
@@ -16,8 +16,8 @@ package com.google.gerrit.acceptance.testsuite.project;
import com.google.auto.value.AutoValue;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.Optional;
@AutoValue
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
new file mode 100644
index 0000000000..739ed19bf5
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
@@ -0,0 +1,449 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.testsuite.project;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.common.data.AccessSection.GLOBAL_CAPABILITIES;
+import static java.util.Objects.requireNonNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import java.util.Optional;
+import org.eclipse.jgit.lib.Constants;
+
+@AutoValue
+public abstract class TestProjectUpdate {
+ /** Starts a builder for allowing a capability. */
+ public static TestCapability.Builder allowCapability(String name) {
+ return TestCapability.builder().name(name);
+ }
+
+ /** Records a global capability to be updated. */
+ @AutoValue
+ public abstract static class TestCapability {
+ private static Builder builder() {
+ return new AutoValue_TestProjectUpdate_TestCapability.Builder();
+ }
+
+ abstract String name();
+
+ abstract AccountGroup.UUID group();
+
+ abstract int min();
+
+ abstract int max();
+
+ /** Builder for {@link TestCapability}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Sets the name of the capability. */
+ public abstract Builder name(String name);
+
+ abstract String name();
+
+ /** Sets the group to which the capability applies. */
+ public abstract Builder group(AccountGroup.UUID group);
+
+ abstract Builder min(int min);
+
+ abstract Optional<Integer> min();
+
+ abstract Builder max(int max);
+
+ abstract Optional<Integer> max();
+
+ /** Sets the minimum and maximum values for the capability. */
+ public Builder range(int min, int max) {
+ checkNonInvertedRange(min, max);
+ return min(min).max(max);
+ }
+
+ /** Builds the {@link TestCapability}. */
+ abstract TestCapability autoBuild();
+
+ public TestCapability build() {
+ PermissionRange.WithDefaults withDefaults = GlobalCapability.getRange(name());
+ if (withDefaults != null) {
+ int min = min().orElse(withDefaults.getDefaultMin());
+ int max = max().orElse(withDefaults.getDefaultMax());
+ range(min, max);
+ // Don't enforce range is nonempty; this is allowed for e.g. batchChangesLimit.
+ } else {
+ checkArgument(
+ !min().isPresent() && !max().isPresent(),
+ "capability %s does not support ranges",
+ name());
+ range(0, 0);
+ }
+
+ return autoBuild();
+ }
+ }
+ }
+
+ /** Starts a builder for allowing a permission. */
+ public static TestPermission.Builder allow(String name) {
+ return TestPermission.builder().name(name).action(PermissionRule.Action.ALLOW);
+ }
+
+ /** Starts a builder for denying a permission. */
+ public static TestPermission.Builder deny(String name) {
+ return TestPermission.builder().name(name).action(PermissionRule.Action.DENY);
+ }
+
+ /** Starts a builder for blocking a permission. */
+ public static TestPermission.Builder block(String name) {
+ return TestPermission.builder().name(name).action(PermissionRule.Action.BLOCK);
+ }
+
+ /**
+ * Records a permission to be updated.
+ *
+ * <p>Not used for permissions that have ranges (label permissions) or global capabilities.
+ */
+ @AutoValue
+ public abstract static class TestPermission {
+ private static Builder builder() {
+ return new AutoValue_TestProjectUpdate_TestPermission.Builder().force(false);
+ }
+
+ abstract String name();
+
+ abstract String ref();
+
+ abstract AccountGroup.UUID group();
+
+ abstract PermissionRule.Action action();
+
+ abstract boolean force();
+
+ /** Builder for {@link TestPermission}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder name(String name);
+
+ /** Sets the ref pattern used on the permission. */
+ public abstract Builder ref(String ref);
+
+ /** Sets the group to which the permission applies. */
+ public abstract Builder group(AccountGroup.UUID groupUuid);
+
+ abstract Builder action(PermissionRule.Action action);
+
+ /** Sets whether the permission is a force permission. */
+ public abstract Builder force(boolean force);
+
+ /** Builds the {@link TestPermission}. */
+ public abstract TestPermission build();
+ }
+ }
+
+ /** Starts a builder for allowing a label permission. */
+ public static TestLabelPermission.Builder allowLabel(String name) {
+ return TestLabelPermission.builder().name(name).action(PermissionRule.Action.ALLOW);
+ }
+
+ /** Starts a builder for denying a label permission. */
+ public static TestLabelPermission.Builder blockLabel(String name) {
+ return TestLabelPermission.builder().name(name).action(PermissionRule.Action.BLOCK);
+ }
+
+ /** Records a label permission to be updated. */
+ @AutoValue
+ public abstract static class TestLabelPermission {
+ private static Builder builder() {
+ return new AutoValue_TestProjectUpdate_TestLabelPermission.Builder().impersonation(false);
+ }
+
+ abstract String name();
+
+ abstract String ref();
+
+ abstract AccountGroup.UUID group();
+
+ abstract PermissionRule.Action action();
+
+ abstract int min();
+
+ abstract int max();
+
+ abstract boolean impersonation();
+
+ /** Builder for {@link TestLabelPermission}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder name(String name);
+
+ /** Sets the ref pattern used on the permission. */
+ public abstract Builder ref(String ref);
+
+ /** Sets the group to which the permission applies. */
+ public abstract Builder group(AccountGroup.UUID group);
+
+ abstract Builder action(PermissionRule.Action action);
+
+ abstract Builder min(int min);
+
+ abstract Builder max(int max);
+
+ /** Sets the minimum and maximum values for the permission. */
+ public Builder range(int min, int max) {
+ checkArgument(min != 0 || max != 0, "empty range");
+ checkNonInvertedRange(min, max);
+ return min(min).max(max);
+ }
+
+ /** Sets whether this permission should be for impersonating another user's votes. */
+ public abstract Builder impersonation(boolean impersonation);
+
+ abstract TestLabelPermission autoBuild();
+
+ /** Builds the {@link TestPermission}. */
+ public TestLabelPermission build() {
+ TestLabelPermission result = autoBuild();
+ checkLabelName(result.name());
+ return result;
+ }
+ }
+ }
+
+ /**
+ * Starts a builder for describing a permission key for deletion. Not for label permissions or
+ * global capabilities.
+ */
+ public static TestPermissionKey.Builder permissionKey(String name) {
+ return TestPermissionKey.builder().name(name);
+ }
+
+ /** Starts a builder for describing a label permission key for deletion. */
+ public static TestPermissionKey.Builder labelPermissionKey(String name) {
+ checkLabelName(name);
+ return TestPermissionKey.builder().name(Permission.forLabel(name));
+ }
+
+ /** Starts a builder for describing a capability key for deletion. */
+ public static TestPermissionKey.Builder capabilityKey(String name) {
+ return TestPermissionKey.builder().name(name).section(GLOBAL_CAPABILITIES);
+ }
+
+ /** Records the key of a permission (of any type) for deletion. */
+ @AutoValue
+ public abstract static class TestPermissionKey {
+ private static Builder builder() {
+ return new AutoValue_TestProjectUpdate_TestPermissionKey.Builder();
+ }
+
+ abstract String section();
+
+ abstract String name();
+
+ abstract Optional<AccountGroup.UUID> group();
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder section(String section);
+
+ abstract Optional<String> section();
+
+ /** Sets the ref pattern used on the permission. Not for global capabilities. */
+ public Builder ref(String ref) {
+ requireNonNull(ref);
+ checkArgument(ref.startsWith(Constants.R_REFS), "must be a ref: %s", ref);
+ checkArgument(
+ !section().isPresent() || !section().get().equals(GLOBAL_CAPABILITIES),
+ "can't set ref on global capability");
+ return section(ref);
+ }
+
+ abstract Builder name(String name);
+
+ /** Sets the group to which the permission applies. */
+ public abstract Builder group(AccountGroup.UUID group);
+
+ /** Builds the {@link TestPermissionKey}. */
+ public abstract TestPermissionKey build();
+ }
+ }
+
+ static Builder builder(
+ Project.NameKey nameKey,
+ AllProjectsName allProjectsName,
+ ThrowingConsumer<TestProjectUpdate> projectUpdater) {
+ return new AutoValue_TestProjectUpdate.Builder()
+ .nameKey(nameKey)
+ .allProjectsName(allProjectsName)
+ .projectUpdater(projectUpdater)
+ .removeAllAccessSections(false);
+ }
+
+ /** Builder for {@link TestProjectUpdate}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder nameKey(Project.NameKey project);
+
+ abstract Builder allProjectsName(AllProjectsName allProjects);
+
+ abstract ImmutableList.Builder<TestPermission> addedPermissionsBuilder();
+
+ abstract ImmutableList.Builder<TestLabelPermission> addedLabelPermissionsBuilder();
+
+ abstract ImmutableList.Builder<TestCapability> addedCapabilitiesBuilder();
+
+ abstract ImmutableList.Builder<TestPermissionKey> removedPermissionsBuilder();
+
+ abstract ImmutableMap.Builder<TestPermissionKey, Boolean> exclusiveGroupPermissionsBuilder();
+
+ abstract Builder removeAllAccessSections(boolean value);
+
+ /**
+ * Removes all access sections. Useful when testing against a specific set of access sections or
+ * permissions.
+ */
+ public Builder removeAllAccessSections() {
+ return removeAllAccessSections(true);
+ }
+
+ /** Adds a permission to be included in this update. */
+ public Builder add(TestPermission testPermission) {
+ addedPermissionsBuilder().add(testPermission);
+ return this;
+ }
+
+ /** Adds a permission to be included in this update. */
+ public Builder add(TestPermission.Builder testPermissionBuilder) {
+ return add(testPermissionBuilder.build());
+ }
+
+ /** Adds a label permission to be included in this update. */
+ public Builder add(TestLabelPermission testLabelPermission) {
+ addedLabelPermissionsBuilder().add(testLabelPermission);
+ return this;
+ }
+
+ /** Adds a label permission to be included in this update. */
+ public Builder add(TestLabelPermission.Builder testLabelPermissionBuilder) {
+ return add(testLabelPermissionBuilder.build());
+ }
+
+ /** Adds a capability to be included in this update. */
+ public Builder add(TestCapability testCapability) {
+ addedCapabilitiesBuilder().add(testCapability);
+ return this;
+ }
+
+ /** Adds a capability to be included in this update. */
+ public Builder add(TestCapability.Builder testCapabilityBuilder) {
+ return add(testCapabilityBuilder.build());
+ }
+
+ /** Removes a permission, label permission, or capability as part of this update. */
+ public Builder remove(TestPermissionKey testPermissionKey) {
+ removedPermissionsBuilder().add(testPermissionKey);
+ return this;
+ }
+
+ /** Removes a permission, label permission, or capability as part of this update. */
+ public Builder remove(TestPermissionKey.Builder testPermissionKeyBuilder) {
+ return remove(testPermissionKeyBuilder.build());
+ }
+
+ /** Sets the exclusive bit bit for the given permission key. */
+ public Builder setExclusiveGroup(
+ TestPermissionKey.Builder testPermissionKeyBuilder, boolean exclusive) {
+ return setExclusiveGroup(testPermissionKeyBuilder.build(), exclusive);
+ }
+
+ /** Sets the exclusive bit bit for the given permission key. */
+ public Builder setExclusiveGroup(TestPermissionKey testPermissionKey, boolean exclusive) {
+ checkArgument(
+ !testPermissionKey.group().isPresent(),
+ "do not specify group for setExclusiveGroup: %s",
+ testPermissionKey);
+ checkArgument(
+ !testPermissionKey.section().equals(GLOBAL_CAPABILITIES),
+ "setExclusiveGroup not valid for global capabilities: %s",
+ testPermissionKey);
+ exclusiveGroupPermissionsBuilder().put(testPermissionKey, exclusive);
+ return this;
+ }
+
+ abstract Builder projectUpdater(ThrowingConsumer<TestProjectUpdate> projectUpdater);
+
+ abstract TestProjectUpdate autoBuild();
+
+ TestProjectUpdate build() {
+ TestProjectUpdate projectUpdate = autoBuild();
+ if (projectUpdate.hasCapabilityUpdates()) {
+ checkArgument(
+ projectUpdate.nameKey().equals(projectUpdate.allProjectsName()),
+ "cannot update global capabilities on %s, only %s: %s",
+ projectUpdate.nameKey(),
+ projectUpdate.allProjectsName(),
+ projectUpdate);
+ }
+ return projectUpdate;
+ }
+
+ /** Executes the update, updating the underlying project. */
+ public void update() {
+ TestProjectUpdate projectUpdate = build();
+ projectUpdate.projectUpdater().acceptAndThrowSilently(projectUpdate);
+ }
+ }
+
+ abstract Project.NameKey nameKey();
+
+ abstract AllProjectsName allProjectsName();
+
+ abstract ImmutableList<TestPermission> addedPermissions();
+
+ abstract ImmutableList<TestLabelPermission> addedLabelPermissions();
+
+ abstract ImmutableList<TestCapability> addedCapabilities();
+
+ abstract ImmutableList<TestPermissionKey> removedPermissions();
+
+ abstract ImmutableMap<TestPermissionKey, Boolean> exclusiveGroupPermissions();
+
+ abstract ThrowingConsumer<TestProjectUpdate> projectUpdater();
+
+ abstract boolean removeAllAccessSections();
+
+ boolean hasCapabilityUpdates() {
+ return !addedCapabilities().isEmpty()
+ || removedPermissions().stream().anyMatch(k -> k.section().equals(GLOBAL_CAPABILITIES));
+ }
+
+ private static void checkLabelName(String name) {
+ // "label-Code-Review" is technically a valid label name, and we don't prevent users from
+ // using it in production, but specifying it in a test is programmer error.
+ checkArgument(!Permission.isLabel(name), "expected label name, got permission name: %s", name);
+ LabelType.checkName(name);
+ }
+
+ private static void checkNonInvertedRange(int min, int max) {
+ checkArgument(min <= max, "inverted range: %s > %s", min, max);
+ }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
index 17d9294583..a9914b3d74 100644
--- a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
@@ -16,7 +16,7 @@ package com.google.gerrit.acceptance.testsuite.request;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.testsuite.account.TestAccount;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
/**
* An aggregation of operations on Guice request scopes for test purposes.
diff --git a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
index 554642266a..db730a6c3a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
@@ -24,7 +24,7 @@ import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.TestAccount;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
diff --git a/java/com/google/gerrit/common/BUILD b/java/com/google/gerrit/common/BUILD
index 3b3d9c65a2..1099919898 100644
--- a/java/com/google/gerrit/common/BUILD
+++ b/java/com/google/gerrit/common/BUILD
@@ -21,16 +21,15 @@ java_library(
visibility = ["//visibility:public"],
deps = [
":annotations",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/prettify:server",
- "//java/com/google/gerrit/reviewdb:server",
- "//java/com/google/gwtorm",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/flogger:api",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/common/FileUtil.java b/java/com/google/gerrit/common/FileUtil.java
index 04288bc71b..5b0925e70c 100644
--- a/java/com/google/gerrit/common/FileUtil.java
+++ b/java/com/google/gerrit/common/FileUtil.java
@@ -44,7 +44,6 @@ public class FileUtil {
}
public static void chmod(int mode, Path path) {
- // TODO(dborowitz): Is there a portable way to do this with NIO?
chmod(mode, path.toFile());
}
diff --git a/java/com/google/gerrit/common/PageLinks.java b/java/com/google/gerrit/common/PageLinks.java
index 2243eab1bb..38de5b15a1 100644
--- a/java/com/google/gerrit/common/PageLinks.java
+++ b/java/com/google/gerrit/common/PageLinks.java
@@ -14,12 +14,12 @@
package com.google.gerrit.common;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gwtorm.client.KeyUtil;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Change.Status;
+import com.google.gerrit.entities.KeyUtil;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
public class PageLinks {
public static final String PROJECT_CHANGE_DELIMITER = "/+/";
@@ -83,7 +83,7 @@ public class PageLinks {
}
public static String toChange(@Nullable Project.NameKey project, PatchSet.Id ps) {
- return toChange(project, ps.getParentKey()) + ps.getId();
+ return toChange(project, ps.changeId()) + ps.getId();
}
public static String toProject(Project.NameKey p) {
diff --git a/java/com/google/gerrit/common/UsedAt.java b/java/com/google/gerrit/common/UsedAt.java
index 44ed92bc47..9f8b2551bd 100644
--- a/java/com/google/gerrit/common/UsedAt.java
+++ b/java/com/google/gerrit/common/UsedAt.java
@@ -14,6 +14,7 @@
package com.google.gerrit.common;
+import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -22,13 +23,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
- * A marker for a method that is public solely because it is called from inside a project or an
- * organisation using Gerrit.
+ * A marker to say a method/type/field is added or is increased to public solely because it is
+ * called from inside a project or an organisation using Gerrit.
*/
-@Target({METHOD, TYPE})
+@Target({METHOD, TYPE, FIELD})
@Retention(RUNTIME)
public @interface UsedAt {
- /** Enumeration of projects that call a method that would otherwise be private. */
+ /** Enumeration of projects that call a method/type/field. */
enum Project {
GOOGLE,
COLLABNET,
diff --git a/java/com/google/gerrit/common/auth/openid/OpenIdUrls.java b/java/com/google/gerrit/common/auth/openid/OpenIdUrls.java
index 713fd4dc39..16dfb9b5e1 100644
--- a/java/com/google/gerrit/common/auth/openid/OpenIdUrls.java
+++ b/java/com/google/gerrit/common/auth/openid/OpenIdUrls.java
@@ -18,5 +18,4 @@ public class OpenIdUrls {
public static final String LASTID_COOKIE = "gerrit.last_openid";
public static final String URL_LAUNCHPAD = "https://login.launchpad.net/+openid";
- public static final String URL_YAHOO = "https://me.yahoo.com";
}
diff --git a/java/com/google/gerrit/common/data/AccessSection.java b/java/com/google/gerrit/common/data/AccessSection.java
index 3670e961a6..0c9663b90c 100644
--- a/java/com/google/gerrit/common/data/AccessSection.java
+++ b/java/com/google/gerrit/common/data/AccessSection.java
@@ -18,7 +18,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
diff --git a/java/com/google/gerrit/common/data/CommentDetail.java b/java/com/google/gerrit/common/data/CommentDetail.java
index 1ae246f94c..d69f0bba50 100644
--- a/java/com/google/gerrit/common/data/CommentDetail.java
+++ b/java/com/google/gerrit/common/data/CommentDetail.java
@@ -14,9 +14,9 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -42,7 +42,7 @@ public class CommentDetail {
protected CommentDetail() {}
public void include(Change.Id changeId, Comment p) {
- PatchSet.Id psId = new PatchSet.Id(changeId, p.key.patchSetId);
+ PatchSet.Id psId = PatchSet.id(changeId, p.key.patchSetId);
if (p.side == 0) {
if (idA == null && idB.equals(psId)) {
a.add(p);
diff --git a/java/com/google/gerrit/common/data/ContributorAgreement.java b/java/com/google/gerrit/common/data/ContributorAgreement.java
index a6e8cdd33f..bc106f0229 100644
--- a/java/com/google/gerrit/common/data/ContributorAgreement.java
+++ b/java/com/google/gerrit/common/data/ContributorAgreement.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.ArrayList;
import java.util.List;
diff --git a/java/com/google/gerrit/common/data/FilenameComparator.java b/java/com/google/gerrit/common/data/FilenameComparator.java
index e0a6569897..ebf423c583 100644
--- a/java/com/google/gerrit/common/data/FilenameComparator.java
+++ b/java/com/google/gerrit/common/data/FilenameComparator.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.entities.Patch;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
diff --git a/java/com/google/gerrit/common/data/GarbageCollectionResult.java b/java/com/google/gerrit/common/data/GarbageCollectionResult.java
index a6c534c431..5ed01585a4 100644
--- a/java/com/google/gerrit/common/data/GarbageCollectionResult.java
+++ b/java/com/google/gerrit/common/data/GarbageCollectionResult.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.ArrayList;
import java.util.List;
diff --git a/java/com/google/gerrit/common/data/GroupDescription.java b/java/com/google/gerrit/common/data/GroupDescription.java
index d22b94b09b..ed8b39d0a2 100644
--- a/java/com/google/gerrit/common/data/GroupDescription.java
+++ b/java/com/google/gerrit/common/data/GroupDescription.java
@@ -15,8 +15,8 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
import java.util.Set;
diff --git a/java/com/google/gerrit/common/data/GroupReference.java b/java/com/google/gerrit/common/data/GroupReference.java
index e5b0965293..0af088ede9 100644
--- a/java/com/google/gerrit/common/data/GroupReference.java
+++ b/java/com/google/gerrit/common/data/GroupReference.java
@@ -14,8 +14,10 @@
package com.google.gerrit.common.data;
+import static java.util.Objects.requireNonNull;
+
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
/** Describes a group within a projects {@link AccessSection}s. */
public class GroupReference implements Comparable<GroupReference> {
@@ -46,17 +48,27 @@ public class GroupReference implements Comparable<GroupReference> {
/**
* Create a group reference.
*
- * @param uuid UUID of the group, may be {@code null} if the group name couldn't be resolved
+ * @param uuid UUID of the group, must not be {@code null}
+ * @param name the group name, must not be {@code null}
+ */
+ public GroupReference(AccountGroup.UUID uuid, String name) {
+ setUUID(requireNonNull(uuid));
+ setName(name);
+ }
+
+ /**
+ * Create a group reference where the group's name couldn't be resolved.
+ *
* @param name the group name, must not be {@code null}
*/
- public GroupReference(@Nullable AccountGroup.UUID uuid, String name) {
- setUUID(uuid);
+ public GroupReference(String name) {
+ setUUID(null);
setName(name);
}
@Nullable
public AccountGroup.UUID getUUID() {
- return uuid != null ? new AccountGroup.UUID(uuid) : null;
+ return uuid != null ? AccountGroup.uuid(uuid) : null;
}
public void setUUID(@Nullable AccountGroup.UUID newUUID) {
diff --git a/java/com/google/gerrit/common/data/LabelFunction.java b/java/com/google/gerrit/common/data/LabelFunction.java
index 7d13c7086f..6af675bd35 100644
--- a/java/com/google/gerrit/common/data/LabelFunction.java
+++ b/java/com/google/gerrit/common/data/LabelFunction.java
@@ -15,7 +15,7 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.entities.PatchSetApproval;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -98,18 +98,18 @@ public enum LabelFunction {
}
for (PatchSetApproval a : approvals) {
- if (a.getValue() == 0) {
+ if (a.value() == 0) {
continue;
}
if (isBlock && labelType.isMaxNegative(a)) {
- submitRecordLabel.appliedBy = a.getAccountId();
+ submitRecordLabel.appliedBy = a.accountId();
submitRecordLabel.status = SubmitRecord.Label.Status.REJECT;
return submitRecordLabel;
}
if (labelType.isMaxPositive(a) || !requiresMaxValue) {
- submitRecordLabel.appliedBy = a.getAccountId();
+ submitRecordLabel.appliedBy = a.accountId();
submitRecordLabel.status = SubmitRecord.Label.Status.MAY;
if (isRequired) {
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index 42945c452d..90b0930386 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -19,8 +19,8 @@ import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSetApproval;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -34,6 +34,7 @@ public class LabelType {
public static final boolean DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE = false;
public static final boolean DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE = false;
public static final boolean DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE = false;
+ public static final boolean DEF_COPY_ANY_SCORE = false;
public static final boolean DEF_COPY_MAX_SCORE = false;
public static final boolean DEF_COPY_MIN_SCORE = false;
public static final boolean DEF_IGNORE_SELF_APPROVAL = false;
@@ -96,6 +97,7 @@ public class LabelType {
protected LabelFunction function;
+ protected boolean copyAnyScore;
protected boolean copyMinScore;
protected boolean copyMaxScore;
protected boolean copyAllScoresOnMergeFirstParentUpdate;
@@ -139,6 +141,7 @@ public class LabelType {
setCopyAllScoresIfNoCodeChange(DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
setCopyAllScoresOnTrivialRebase(DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
setCopyAllScoresOnMergeFirstParentUpdate(DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
+ setCopyAnyScore(DEF_COPY_ANY_SCORE);
setCopyMaxScore(DEF_COPY_MAX_SCORE);
setCopyMinScore(DEF_COPY_MIN_SCORE);
setAllowPostSubmit(DEF_ALLOW_POST_SUBMIT);
@@ -155,7 +158,7 @@ public class LabelType {
}
public boolean matches(PatchSetApproval psa) {
- return psa.getLabelId().get().equalsIgnoreCase(name);
+ return psa.labelId().get().equalsIgnoreCase(name);
}
public LabelFunction getFunction() {
@@ -229,6 +232,14 @@ public class LabelType {
this.defaultValue = defaultValue;
}
+ public boolean isCopyAnyScore() {
+ return copyAnyScore;
+ }
+
+ public void setCopyAnyScore(boolean copyAnyScore) {
+ this.copyAnyScore = copyAnyScore;
+ }
+
public boolean isCopyMinScore() {
return copyMinScore;
}
@@ -279,11 +290,11 @@ public class LabelType {
}
public boolean isMaxNegative(PatchSetApproval ca) {
- return maxNegative == ca.getValue();
+ return maxNegative == ca.value();
}
public boolean isMaxPositive(PatchSetApproval ca) {
- return maxPositive == ca.getValue();
+ return maxPositive == ca.value();
}
public LabelValue getValue(short value) {
@@ -291,11 +302,11 @@ public class LabelType {
}
public LabelValue getValue(PatchSetApproval ca) {
- return byValue.get(ca.getValue());
+ return byValue.get(ca.value());
}
public LabelId getLabelId() {
- return new LabelId(name);
+ return LabelId.create(name);
}
@Override
diff --git a/java/com/google/gerrit/common/data/LabelTypes.java b/java/com/google/gerrit/common/data/LabelTypes.java
index d5891d1575..1647658aad 100644
--- a/java/com/google/gerrit/common/data/LabelTypes.java
+++ b/java/com/google/gerrit/common/data/LabelTypes.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.LabelId;
+import com.google.gerrit.entities.LabelId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
diff --git a/java/com/google/gerrit/common/data/PatchScript.java b/java/com/google/gerrit/common/data/PatchScript.java
index 3428580a55..c177e356d6 100644
--- a/java/com/google/gerrit/common/data/PatchScript.java
+++ b/java/com/google/gerrit/common/data/PatchScript.java
@@ -14,12 +14,12 @@
package com.google.gerrit.common.data;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.prettify.common.SparseFileContent;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.diff.Edit;
@@ -56,7 +56,6 @@ public class PatchScript {
private CommentDetail comments;
private List<Patch> history;
private boolean hugeFile;
- private boolean intralineDifference;
private boolean intralineFailure;
private boolean intralineTimeout;
private boolean binary;
@@ -83,7 +82,6 @@ public class PatchScript {
CommentDetail cd,
List<Patch> hist,
boolean hf,
- boolean id,
boolean idf,
boolean idt,
boolean bin,
@@ -108,7 +106,6 @@ public class PatchScript {
comments = cd;
history = hist;
hugeFile = hf;
- intralineDifference = id;
intralineFailure = idf;
intralineTimeout = idt;
binary = bin;
@@ -178,10 +175,6 @@ public class PatchScript {
return diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE;
}
- public boolean hasIntralineDifference() {
- return intralineDifference;
- }
-
public boolean hasIntralineFailure() {
return intralineFailure;
}
diff --git a/java/com/google/gerrit/common/data/PermissionRange.java b/java/com/google/gerrit/common/data/PermissionRange.java
index 2f05854ba1..97c37315d1 100644
--- a/java/com/google/gerrit/common/data/PermissionRange.java
+++ b/java/com/google/gerrit/common/data/PermissionRange.java
@@ -17,6 +17,10 @@ package com.google.gerrit.common.data;
import java.util.ArrayList;
import java.util.List;
+/**
+ * Represents a closed interval [min, max] with a name. The special value [0, 0] is understood to be
+ * the empty range.
+ */
public class PermissionRange implements Comparable<PermissionRange> {
public static class WithDefaults extends PermissionRange {
protected int defaultMin;
diff --git a/java/com/google/gerrit/common/data/SubmitRecord.java b/java/com/google/gerrit/common/data/SubmitRecord.java
index 22861b24b1..fe5843ad41 100644
--- a/java/com/google/gerrit/common/data/SubmitRecord.java
+++ b/java/com/google/gerrit/common/data/SubmitRecord.java
@@ -14,7 +14,7 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
diff --git a/java/com/google/gerrit/common/data/SubscribeSection.java b/java/com/google/gerrit/common/data/SubscribeSection.java
index aaf0798428..6ac4695bce 100644
--- a/java/com/google/gerrit/common/data/SubscribeSection.java
+++ b/java/com/google/gerrit/common/data/SubscribeSection.java
@@ -14,8 +14,8 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -60,14 +60,14 @@ public class SubscribeSection {
* @param branch the branch to check
* @return if the branch could trigger a superproject update
*/
- public boolean appliesTo(Branch.NameKey branch) {
+ public boolean appliesTo(BranchNameKey branch) {
for (RefSpec r : matchingRefSpecs) {
- if (r.matchSource(branch.get())) {
+ if (r.matchSource(branch.branch())) {
return true;
}
}
for (RefSpec r : multiMatchRefSpecs) {
- if (r.matchSource(branch.get())) {
+ if (r.matchSource(branch.branch())) {
return true;
}
}
diff --git a/java/com/google/gerrit/common/data/testing/BUILD b/java/com/google/gerrit/common/data/testing/BUILD
index 8ab01dead8..b9ec30b8de 100644
--- a/java/com/google/gerrit/common/data/testing/BUILD
+++ b/java/com/google/gerrit/common/data/testing/BUILD
@@ -7,7 +7,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java b/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java
index 265d5901d5..d841aa6389 100644
--- a/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java
+++ b/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java
@@ -21,9 +21,9 @@ import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
-public class GroupReferenceSubject extends Subject<GroupReferenceSubject, GroupReference> {
+public class GroupReferenceSubject extends Subject {
public static GroupReferenceSubject assertThat(GroupReference group) {
return assertAbout(groupReferences()).that(group);
@@ -33,19 +33,20 @@ public class GroupReferenceSubject extends Subject<GroupReferenceSubject, GroupR
return GroupReferenceSubject::new;
}
+ private final GroupReference group;
+
private GroupReferenceSubject(FailureMetadata metadata, GroupReference group) {
super(metadata, group);
+ this.group = group;
}
- public ComparableSubject<?, AccountGroup.UUID> groupUuid() {
+ public ComparableSubject<AccountGroup.UUID> groupUuid() {
isNotNull();
- GroupReference group = actual();
- return check("groupUuid()").that(group.getUUID());
+ return check("getUUID()").that(group.getUUID());
}
public StringSubject name() {
isNotNull();
- GroupReference group = actual();
- return check("name()").that(group.getName());
+ return check("getName()").that(group.getName());
}
}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 8ed9729ec7..ed32ce5b28 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -32,6 +32,7 @@ import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.elasticsearch.builders.QueryBuilder;
import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
+import com.google.gerrit.entities.converter.ProtoConverter;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.FieldType;
@@ -45,7 +46,6 @@ import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.converter.ProtoConverter;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gson.Gson;
@@ -127,7 +127,6 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
private final SitePaths sitePaths;
private final String indexNameRaw;
- protected final String type;
protected final ElasticRestClientProvider client;
protected final String indexName;
protected final Gson gson;
@@ -147,7 +146,6 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
this.indexName = config.getIndexName(indexName, schema.getVersion());
this.indexNameRaw = indexName;
this.client = client;
- this.type = client.adapter().getType();
}
@Override
@@ -167,7 +165,7 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
@Override
public void delete(K id) {
- String uri = getURI(type, BULK);
+ String uri = getURI(BULK);
Response response = postRequest(uri, getDeleteActions(id), getRefreshParam());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
@@ -192,10 +190,8 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
}
// Recreate the index.
- String indexCreationFields = concatJsonString(getSettings(client.adapter()), getMappings());
- response =
- performRequest(
- "PUT", indexName + client.adapter().includeTypeNameParam(), indexCreationFields);
+ String indexCreationFields = concatJsonString(getSettings(), getMappings());
+ response = performRequest("PUT", indexName, indexCreationFields);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
String error = String.format("Failed to create index %s: %s", indexName, statusCode);
@@ -207,26 +203,20 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
protected abstract String getMappings();
- private String getSettings(ElasticQueryAdapter adapter) {
- return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting(config, adapter)));
+ private String getSettings() {
+ return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting(config)));
}
protected abstract String getId(V v);
protected String getMappingsForSingleType(MappingProperties properties) {
- return getMappingsFor(client.adapter().getType(), properties);
+ return getMappingsFor(properties);
}
- protected String getMappingsFor(String type, MappingProperties properties) {
+ protected String getMappingsFor(MappingProperties properties) {
JsonObject mappings = new JsonObject();
- if (client.adapter().omitType()) {
- mappings.add(MAPPINGS, gson.toJsonTree(properties));
- } else {
- JsonObject mappingType = new JsonObject();
- mappingType.add(type, gson.toJsonTree(properties));
- mappings.add(MAPPINGS, gson.toJsonTree(mappingType));
- }
+ mappings.add(MAPPINGS, gson.toJsonTree(properties));
return gson.toJson(mappings);
}
@@ -305,15 +295,9 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
return sortArray;
}
- protected String getURI(String type, String request) {
+ protected String getURI(String request) {
try {
- String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
- if (SEARCH.equals(request) && client.adapter().omitType()) {
- return encodedIndexName + "/" + request;
- }
- String encodedTypeIfAny =
- client.adapter().omitType() ? "" : "/" + URLEncoder.encode(type, UTF_8.toString());
- return encodedIndexName + encodedTypeIfAny + "/" + request;
+ return URLEncoder.encode(indexName, UTF_8.toString()) + "/" + request;
} catch (UnsupportedEncodingException e) {
throw new StorageException(e);
}
@@ -359,12 +343,10 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
protected class ElasticQuerySource implements DataSource<V> {
private final QueryOptions opts;
private final String search;
- private final String index;
- ElasticQuerySource(Predicate<V> p, QueryOptions opts, String index, JsonArray sortArray)
+ ElasticQuerySource(Predicate<V> p, QueryOptions opts, JsonArray sortArray)
throws QueryParseException {
this.opts = opts;
- this.index = index;
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
SearchSourceBuilder searchSource =
new SearchSourceBuilder(client.adapter())
@@ -392,7 +374,7 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) {
try {
- String uri = getURI(index, SEARCH);
+ String uri = getURI(SEARCH);
Response response =
performRequest(HttpPost.METHOD_NAME, uri, search, Collections.emptyMap());
StatusLine statusLine = response.getStatusLine();
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
index a9b145bfbc..edbd82c15b 100644
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -6,6 +6,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
@@ -13,10 +14,10 @@ java_library(
"//java/com/google/gerrit/index/project",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/proto",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
"//lib/commons:codec",
"//lib/commons:lang",
@@ -29,6 +30,5 @@ java_library(
"//lib/httpcomponents:httpcore",
"//lib/httpcomponents:httpcore-nio",
"//lib/jackson:jackson-core",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index f646efcb8a..bde3ad50b6 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -14,19 +14,17 @@
package com.google.gerrit.elasticsearch;
-import static com.google.gerrit.server.index.account.AccountField.ID;
-
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.elasticsearch.bulk.BulkRequest;
import com.google.gerrit.elasticsearch.bulk.IndexRequest;
import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.SitePaths;
@@ -76,22 +74,29 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
public void replace(AccountState as) {
BulkRequest bulk = new IndexRequest(getId(as), indexName).add(new UpdateRequest<>(schema, as));
- String uri = getURI(type, BULK);
+ String uri = getURI(BULK);
Response response = postRequest(uri, bulk, getRefreshParam());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new StorageException(
String.format(
"Failed to replace account %s in index %s: %s",
- as.getAccount().getId(), indexName, statusCode));
+ as.account().id(), indexName, statusCode));
}
}
@Override
public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts)
throws QueryParseException {
- JsonArray sortArray = getSortArray(AccountField.ID.getName());
- return new ElasticQuerySource(p, opts.filterFields(IndexUtils::accountFields), type, sortArray);
+ JsonArray sortArray =
+ getSortArray(
+ schema.useLegacyNumericFields()
+ ? AccountField.ID.getName()
+ : AccountField.ID_STR.getName());
+ return new ElasticQuerySource(
+ p,
+ opts.filterFields(o -> IndexUtils.accountFields(o, schema.useLegacyNumericFields())),
+ sortArray);
}
@Override
@@ -106,7 +111,7 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
@Override
protected String getId(AccountState as) {
- return as.getAccount().getId().toString();
+ return as.account().id().toString();
}
@Override
@@ -116,7 +121,15 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
source = json.getAsJsonObject().get("fields");
}
- Account.Id id = new Account.Id(source.getAsJsonObject().get(ID.getName()).getAsInt());
+ Account.Id id =
+ Account.id(
+ source
+ .getAsJsonObject()
+ .get(
+ schema.useLegacyNumericFields()
+ ? AccountField.ID.getName()
+ : AccountField.ID_STR.getName())
+ .getAsInt());
// Use the AccountCache rather than depending on any stored fields in the document (of which
// there shouldn't be any). The most expensive part to compute anyway is the effective group
// IDs, and we don't have a good way to reindex when those change.
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index ca2b1a8203..2be1585f06 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -14,8 +14,6 @@
package com.google.gerrit.elasticsearch;
-import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
-import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
@@ -23,25 +21,25 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.elasticsearch.bulk.BulkRequest;
import com.google.gerrit.elasticsearch.bulk.IndexRequest;
import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.converter.ChangeProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetProtoConverter;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
@@ -49,7 +47,6 @@ import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gson.JsonArray;
@@ -58,7 +55,6 @@ import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.http.HttpStatus;
@@ -81,12 +77,11 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
}
private static final String CHANGES = "changes";
- private static final String OPEN_CHANGES = "open_" + CHANGES;
- private static final String CLOSED_CHANGES = "closed_" + CHANGES;
private final ChangeMapping mapping;
private final ChangeData.Factory changeDataFactory;
private final Schema<ChangeData> schema;
+ private final FieldDef<ChangeData, ?> idField;
@Inject
ElasticChangeIndex(
@@ -98,14 +93,16 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
super(cfg, sitePaths, schema, clientBuilder, CHANGES);
this.changeDataFactory = changeDataFactory;
this.schema = schema;
- mapping = new ChangeMapping(schema, client.adapter());
+ this.mapping = new ChangeMapping(schema, client.adapter());
+ this.idField =
+ this.schema.useLegacyNumericFields() ? ChangeField.LEGACY_ID : ChangeField.LEGACY_ID_STR;
}
@Override
public void replace(ChangeData cd) {
BulkRequest bulk = new IndexRequest(getId(cd), indexName).add(new UpdateRequest<>(schema, cd));
- String uri = getURI(type, BULK);
+ String uri = getURI(BULK);
Response response = postRequest(uri, bulk, getRefreshParam());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
@@ -118,26 +115,9 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
@Override
public DataSource<ChangeData> getSource(Predicate<ChangeData> p, QueryOptions opts)
throws QueryParseException {
- Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
- List<String> indexes = Lists.newArrayListWithCapacity(2);
- if (!client.adapter().omitType()) {
- if (client.adapter().useV6Type()) {
- if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()
- || !Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
- indexes.add(ElasticQueryAdapter.V6_TYPE);
- }
- } else {
- if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
- indexes.add(OPEN_CHANGES);
- }
- if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
- indexes.add(CLOSED_CHANGES);
- }
- }
- }
-
- QueryOptions filteredOpts = opts.filterFields(IndexUtils::changeFields);
- return new ElasticQuerySource(p, filteredOpts, getURI(indexes), getSortArray());
+ QueryOptions filteredOpts =
+ opts.filterFields(o -> IndexUtils.changeFields(o, schema.useLegacyNumericFields()));
+ return new ElasticQuerySource(p, filteredOpts, getSortArray());
}
private JsonArray getSortArray() {
@@ -146,14 +126,10 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
JsonArray sortArray = new JsonArray();
addNamedElement(ChangeField.UPDATED.getName(), properties, sortArray);
- addNamedElement(ChangeField.LEGACY_ID.getName(), properties, sortArray);
+ addNamedElement(idField.getName(), properties, sortArray);
return sortArray;
}
- private String getURI(List<String> types) {
- return String.join(",", types);
- }
-
@Override
protected String getDeleteActions(Change.Id c) {
return getDeleteRequest(c);
@@ -161,7 +137,7 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
@Override
protected String getMappings() {
- return getMappingsFor(client.adapter().getType(), mapping.changes);
+ return getMappingsFor(mapping.changes);
}
@Override
@@ -179,10 +155,10 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
JsonElement c = source.get(ChangeField.CHANGE.getName());
if (c == null) {
- int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
+ int id = source.get(idField.getName()).getAsInt();
// IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
String projectName = requireNonNull(source.get(ChangeField.PROJECT.getName()).getAsString());
- return changeDataFactory.create(new Project.NameKey(projectName), new Change.Id(id));
+ return changeDataFactory.create(Project.nameKey(projectName), Change.id(id));
}
ChangeData cd =
@@ -252,7 +228,7 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
break;
}
- accounts.add(new Account.Id(aId));
+ accounts.add(Account.id(aId));
}
cd.setReviewedBy(accounts);
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index 35c33cb9b4..06b128c102 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -44,7 +44,7 @@ class ElasticConfiguration {
static final String DEFAULT_PORT = "9200";
static final String DEFAULT_USERNAME = "elastic";
- static final int DEFAULT_NUMBER_OF_SHARDS = 0;
+ static final int DEFAULT_NUMBER_OF_SHARDS = 1;
static final int DEFAULT_NUMBER_OF_REPLICAS = 1;
static final int DEFAULT_MAX_RESULT_WINDOW = 10000;
@@ -107,10 +107,7 @@ class ElasticConfiguration {
return String.format("%s%s_%04d", prefix, name, schemaVersion);
}
- int getNumberOfShards(ElasticQueryAdapter adapter) {
- if (numberOfShards == DEFAULT_NUMBER_OF_SHARDS) {
- return adapter.getDefaultNumberOfShards();
- }
+ int getNumberOfShards() {
return numberOfShards;
}
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index eff3d52de9..241e7fddd9 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -18,13 +18,13 @@ import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.elasticsearch.bulk.BulkRequest;
import com.google.gerrit.elasticsearch.bulk.IndexRequest;
import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.group.InternalGroup;
@@ -75,7 +75,7 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
BulkRequest bulk =
new IndexRequest(getId(group), indexName).add(new UpdateRequest<>(schema, group));
- String uri = getURI(type, BULK);
+ String uri = getURI(BULK);
Response response = postRequest(uri, bulk, getRefreshParam());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
@@ -90,7 +90,7 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
public DataSource<InternalGroup> getSource(Predicate<InternalGroup> p, QueryOptions opts)
throws QueryParseException {
JsonArray sortArray = getSortArray(GroupField.UUID.getName());
- return new ElasticQuerySource(p, opts.filterFields(IndexUtils::groupFields), type, sortArray);
+ return new ElasticQuerySource(p, opts.filterFields(IndexUtils::groupFields), sortArray);
}
@Override
@@ -116,8 +116,7 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
}
AccountGroup.UUID uuid =
- new AccountGroup.UUID(
- source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
+ AccountGroup.uuid(source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
// Use the GroupCache rather than depending on any stored fields in the
// document (of which there shouldn't be any).
return groupCache.get().get(uuid).orElse(null);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index a0ebb07ba3..36eeca1083 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -18,6 +18,7 @@ import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.elasticsearch.bulk.BulkRequest;
import com.google.gerrit.elasticsearch.bulk.IndexRequest;
import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
@@ -27,7 +28,6 @@ import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.project.ProjectCache;
@@ -76,7 +76,7 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
new IndexRequest(projectState.getProject().getName(), indexName)
.add(new UpdateRequest<>(schema, projectState));
- String uri = getURI(type, BULK);
+ String uri = getURI(BULK);
Response response = postRequest(uri, bulk, getRefreshParam());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
@@ -91,7 +91,7 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
throws QueryParseException {
JsonArray sortArray = getSortArray(ProjectField.NAME.getName());
- return new ElasticQuerySource(p, opts.filterFields(IndexUtils::projectFields), type, sortArray);
+ return new ElasticQuerySource(p, opts.filterFields(IndexUtils::projectFields), sortArray);
}
@Override
@@ -117,8 +117,7 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
}
Project.NameKey nameKey =
- new Project.NameKey(
- source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
+ Project.nameKey(source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
return projectCache.get().get(nameKey).toProjectData();
}
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 779d433615..19d990181d 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -14,42 +14,23 @@
package com.google.gerrit.elasticsearch;
-import static com.google.gerrit.elasticsearch.ElasticVersion.V6_8;
-
public class ElasticQueryAdapter {
- static final String V6_TYPE = "_doc";
-
- private static final String INCLUDE_TYPE = "include_type_name=true";
private static final String INDICES = "?allow_no_indices=false";
- private final boolean useV6Type;
- private final boolean omitType;
- private final int defaultNumberOfShards;
-
private final String searchFilteringName;
- private final String indicesExistParams;
private final String exactFieldType;
private final String stringFieldType;
private final String indexProperty;
private final String rawFieldsKey;
private final String versionDiscoveryUrl;
- private final String includeTypeNameParam;
- ElasticQueryAdapter(ElasticVersion version) {
- this.useV6Type = version.isV6();
- this.omitType = version.isV7OrLater();
- this.defaultNumberOfShards = version.isV7OrLater() ? 1 : 5;
- this.versionDiscoveryUrl = version.isV6OrLater() ? "/%s*" : "/%s*/_aliases";
+ ElasticQueryAdapter() {
+ this.versionDiscoveryUrl = "/%s*";
this.searchFilteringName = "_source";
this.exactFieldType = "keyword";
this.stringFieldType = "text";
this.indexProperty = "true";
this.rawFieldsKey = "_source";
-
- // Since v6.7 (end-of-life), in fact, for these two parameters:
- this.indicesExistParams =
- version.isAtLeastMinorVersion(V6_8) ? INDICES + "&" + INCLUDE_TYPE : INDICES;
- this.includeTypeNameParam = version.isAtLeastMinorVersion(V6_8) ? "?" + INCLUDE_TYPE : "";
}
public String searchFilteringName() {
@@ -57,7 +38,7 @@ public class ElasticQueryAdapter {
}
String indicesExistParams() {
- return indicesExistParams;
+ return INDICES;
}
String exactFieldType() {
@@ -76,27 +57,7 @@ public class ElasticQueryAdapter {
return rawFieldsKey;
}
- boolean useV6Type() {
- return useV6Type;
- }
-
- boolean omitType() {
- return omitType;
- }
-
- int getDefaultNumberOfShards() {
- return defaultNumberOfShards;
- }
-
- String getType() {
- return useV6Type() ? V6_TYPE : "";
- }
-
String getVersionDiscoveryUrl(String name) {
return String.format(versionDiscoveryUrl, name);
}
-
- String includeTypeNameParam() {
- return includeTypeNameParam;
- }
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
index 394158dbc2..d05e91cbac 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
@@ -145,7 +145,7 @@ public class ElasticQueryBuilder {
String name = p.getField().getName();
String value = p.getValue();
- if (value.isEmpty()) {
+ if (!p.getField().isRepeatable() && value.isEmpty()) {
return new BoolQueryBuilder().mustNot(QueryBuilders.existsQuery(name));
} else if (p instanceof RegexPredicate) {
if (value.startsWith("^")) {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index a67de44d0d..f635b23c53 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -65,7 +65,7 @@ class ElasticRestClientProvider implements Provider<RestClient>, LifecycleListen
client = build();
ElasticVersion version = getVersion();
logger.atInfo().log("Elasticsearch integration version %s", version);
- adapter = new ElasticQueryAdapter(version);
+ adapter = new ElasticQueryAdapter();
}
}
}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticSetting.java b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
index e016efb94e..7ec0566a88 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticSetting.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
@@ -22,18 +22,18 @@ class ElasticSetting {
private static final ImmutableMap<String, String> CUSTOM_CHAR_MAPPING =
ImmutableMap.of("\\u002E", "\\u0020", "\\u005F", "\\u0020");
- static SettingProperties createSetting(ElasticConfiguration config, ElasticQueryAdapter adapter) {
- return new ElasticSetting.Builder().addCharFilter().addAnalyzer().build(config, adapter);
+ static SettingProperties createSetting(ElasticConfiguration config) {
+ return new ElasticSetting.Builder().addCharFilter().addAnalyzer().build(config);
}
static class Builder {
private final ImmutableMap.Builder<String, FieldProperties> fields =
new ImmutableMap.Builder<>();
- SettingProperties build(ElasticConfiguration config, ElasticQueryAdapter adapter) {
+ SettingProperties build(ElasticConfiguration config) {
SettingProperties properties = new SettingProperties();
properties.analysis = fields.build();
- properties.numberOfShards = config.getNumberOfShards(adapter);
+ properties.numberOfShards = config.getNumberOfShards();
properties.numberOfReplicas = config.numberOfReplicas;
properties.maxResultWindow = config.maxResultWindow;
return properties;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index b3f1471517..5e7278074d 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -18,11 +18,6 @@ import com.google.common.base.Joiner;
import java.util.regex.Pattern;
public enum ElasticVersion {
- V6_8("6.8.*"),
- V7_0("7.0.*"),
- V7_1("7.1.*"),
- V7_2("7.2.*"),
- V7_3("7.3.*"),
V7_4("7.4.*"),
V7_5("7.5.*"),
V7_6("7.6.*"),
@@ -67,34 +62,6 @@ public enum ElasticVersion {
return Joiner.on(", ").join(ElasticVersion.values());
}
- public boolean isV6() {
- return getMajor() == 6;
- }
-
- public boolean isV6OrLater() {
- return isAtLeastVersion(6);
- }
-
- public boolean isV7OrLater() {
- return isAtLeastVersion(7);
- }
-
- private boolean isAtLeastVersion(int major) {
- return getMajor() >= major;
- }
-
- public boolean isAtLeastMinorVersion(ElasticVersion version) {
- return getMajor().equals(version.getMajor()) && getMinor() >= version.getMinor();
- }
-
- private Integer getMajor() {
- return Integer.valueOf(version.split("\\.")[0]);
- }
-
- private Integer getMinor() {
- return Integer.valueOf(version.split("\\.")[1]);
- }
-
@Override
public String toString() {
return version;
diff --git a/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java b/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
index a693f6db4c..2f0bd0118e 100644
--- a/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
+++ b/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
@@ -40,11 +40,7 @@ public class UpdateRequest<V> extends BulkRequest {
for (Values<V> values : schema.buildFields(v)) {
String name = values.getField().getName();
if (values.getField().isRepeatable()) {
- builder.field(
- name,
- Streams.stream(values.getValues())
- .filter(e -> shouldAddElement(e))
- .collect(toList()));
+ builder.field(name, Streams.stream(values.getValues()).collect(toList()));
} else {
Object element = Iterables.getOnlyElement(values.getValues(), "");
if (shouldAddElement(element)) {
diff --git a/java/com/google/gerrit/reviewdb/client/Account.java b/java/com/google/gerrit/entities/Account.java
index fa3abedca1..809db1e429 100644
--- a/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/java/com/google/gerrit/entities/Account.java
@@ -12,15 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_DRAFT_COMMENTS;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_STARRED_CHANGES;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS;
+import static com.google.gerrit.entities.RefNames.REFS_DRAFT_COMMENTS;
+import static com.google.gerrit.entities.RefNames.REFS_STARRED_CHANGES;
+import static com.google.gerrit.entities.RefNames.REFS_USERS;
+import com.google.auto.value.AutoValue;
import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
-import com.google.gwtorm.client.IntKey;
import java.sql.Timestamp;
import java.util.Optional;
@@ -37,44 +38,24 @@ import java.util.Optional;
* <li>ExternalId: OpenID identities and email addresses known to be registered to this user.
* Multiple records can exist when the user has more than one public identity, such as a work
* and a personal email address.
- * <li>{@link AccountGroupMember}: membership of the user in a specific human managed {@link
- * AccountGroup}. Multiple records can exist when the user is a member of more than one group.
* <li>AccountSshKey: user's public SSH keys, for authentication through the internal SSH daemon.
* One record per SSH key uploaded by the user, keys are checked in random order until a match
* is found.
* <li>{@link DiffPreferencesInfo}: user's preferences for rendering side-to-side and unified diff
* </ul>
*/
-public final class Account {
+@AutoValue
+public abstract class Account {
public static Id id(int id) {
- return new Id(id);
+ return new AutoValue_Account_Id(id);
}
/** Key local to Gerrit to identify a user. */
- public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- protected int id;
-
- protected Id() {}
-
- public Id(int id) {
- this.id = id;
- }
-
- @Override
- public int get() {
- return id;
- }
-
- @Override
- protected void set(int newValue) {
- id = newValue;
- }
-
+ @AutoValue
+ public abstract static class Id implements Comparable<Id> {
/** Parse an Account.Id out of a string representation. */
public static Optional<Id> tryParse(String str) {
- return Optional.ofNullable(Ints.tryParse(str)).map(Id::new);
+ return Optional.ofNullable(Ints.tryParse(str)).map(Account::id);
}
public static Id fromRef(String name) {
@@ -99,12 +80,12 @@ public final class Account {
*/
public static Id fromRefPart(String name) {
Integer id = RefNames.parseShardedRefPart(name);
- return id != null ? new Account.Id(id) : null;
+ return id != null ? Account.id(id) : null;
}
public static Id parseAfterShardedRefPart(String name) {
Integer id = RefNames.parseAfterShardedRefPart(name);
- return id != null ? new Account.Id(id) : null;
+ return id != null ? Account.id(id) : null;
}
/**
@@ -119,34 +100,52 @@ public final class Account {
*/
public static Id fromRefSuffix(String name) {
Integer id = RefNames.parseRefSuffix(name);
- return id != null ? new Account.Id(id) : null;
+ return id != null ? Account.id(id) : null;
+ }
+
+ abstract int id();
+
+ public int get() {
+ return id();
+ }
+
+ @Override
+ public final int compareTo(Id o) {
+ return Integer.compare(id(), o.id());
+ }
+
+ @Override
+ public final String toString() {
+ return Integer.toString(get());
}
}
- private Id accountId;
+ public abstract Id id();
/** Date and time the user registered with the review server. */
- private Timestamp registeredOn;
+ public abstract Timestamp registeredOn();
/** Full name of the user ("Given-name Surname" style). */
- private String fullName;
+ @Nullable
+ public abstract String fullName();
/** Email address the user prefers to be contacted through. */
- private String preferredEmail;
+ @Nullable
+ public abstract String preferredEmail();
/**
* Is this user inactive? This is used to avoid showing some users (eg. former employees) in
* auto-suggest.
*/
- private boolean inactive;
+ public abstract boolean inactive();
/** The user-settable status of this account (e.g. busy, OOO, available) */
- private String status;
+ @Nullable
+ public abstract String status();
/** ID of the user branch from which the account was read. */
- private String metaId;
-
- protected Account() {}
+ @Nullable
+ public abstract String metaId();
/**
* Create a new account.
@@ -154,38 +153,11 @@ public final class Account {
* @param newId unique id, see {@link com.google.gerrit.server.notedb.Sequences#nextAccountId()}.
* @param registeredOn when the account was registered.
*/
- public Account(Account.Id newId, Timestamp registeredOn) {
- this.accountId = newId;
- this.registeredOn = registeredOn;
- }
-
- /** Get local id of this account, to link with in other entities */
- public Account.Id getId() {
- return accountId;
- }
-
- /** Get the full name of the user ("Given-name Surname" style). */
- public String getFullName() {
- return fullName;
- }
-
- /** Set the full name of the user ("Given-name Surname" style). */
- public void setFullName(String name) {
- if (name != null && !name.trim().isEmpty()) {
- fullName = name.trim();
- } else {
- fullName = null;
- }
- }
-
- /** Email address the user prefers to be contacted through. */
- public String getPreferredEmail() {
- return preferredEmail;
- }
-
- /** Set the email address the user prefers to be contacted through. */
- public void setPreferredEmail(String addr) {
- preferredEmail = addr;
+ public static Account.Builder builder(Account.Id newId, Timestamp registeredOn) {
+ return new AutoValue_Account.Builder()
+ .setInactive(false)
+ .setId(newId)
+ .setRegisteredOn(registeredOn);
}
/**
@@ -201,13 +173,13 @@ public final class Account {
* generic string containing the accountId.
*/
public String getName() {
- if (fullName != null) {
- return fullName;
+ if (fullName() != null) {
+ return fullName();
}
- if (preferredEmail != null) {
- return preferredEmail;
+ if (preferredEmail() != null) {
+ return preferredEmail();
}
- return getName(accountId);
+ return getName(id());
}
public static String getName(Account.Id accountId) {
@@ -227,62 +199,70 @@ public final class Account {
* </ul>
*/
public String getNameEmail(String anonymousCowardName) {
- String name = fullName != null ? fullName : anonymousCowardName;
+ String name = fullName() != null ? fullName() : anonymousCowardName;
StringBuilder b = new StringBuilder();
b.append(name);
- if (preferredEmail != null) {
+ if (preferredEmail() != null) {
b.append(" <");
- b.append(preferredEmail);
+ b.append(preferredEmail());
b.append(">");
} else {
b.append(" (");
- b.append(accountId.get());
+ b.append(id().get());
b.append(")");
}
return b.toString();
}
- /** Get the date and time the user first registered. */
- public Timestamp getRegisteredOn() {
- return registeredOn;
+ public boolean isActive() {
+ return !inactive();
}
- public String getMetaId() {
- return metaId;
- }
+ public abstract Builder toBuilder();
- public void setMetaId(String metaId) {
- this.metaId = metaId;
- }
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Id id();
- public boolean isActive() {
- return !inactive;
- }
+ abstract Builder setId(Id id);
- public void setActive(boolean active) {
- inactive = !active;
- }
+ public abstract Timestamp registeredOn();
- public String getStatus() {
- return status;
- }
+ abstract Builder setRegisteredOn(Timestamp registeredOn);
- public void setStatus(String status) {
- this.status = status;
- }
+ @Nullable
+ public abstract String fullName();
- @Override
- public boolean equals(Object o) {
- return o instanceof Account && ((Account) o).getId().equals(getId());
- }
+ public abstract Builder setFullName(String fullName);
- @Override
- public int hashCode() {
- return getId().get();
+ @Nullable
+ public abstract String preferredEmail();
+
+ public abstract Builder setPreferredEmail(String preferredEmail);
+
+ public abstract boolean inactive();
+
+ public abstract Builder setInactive(boolean inactive);
+
+ public Builder setActive(boolean active) {
+ return setInactive(!active);
+ }
+
+ @Nullable
+ public abstract String status();
+
+ public abstract Builder setStatus(String status);
+
+ @Nullable
+ public abstract String metaId();
+
+ public abstract Builder setMetaId(@Nullable String metaId);
+
+ public abstract Account build();
}
@Override
- public String toString() {
+ public final String toString() {
return getName();
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/java/com/google/gerrit/entities/AccountGroup.java
index 0db7bbd83f..c10edc24a0 100644
--- a/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/java/com/google/gerrit/entities/AccountGroup.java
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
+import com.google.auto.value.AutoValue;
import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.IntKey;
-import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Objects;
@@ -34,63 +33,45 @@ public final class AccountGroup {
}
public static NameKey nameKey(String n) {
- return new NameKey(n);
+ return new AutoValue_AccountGroup_NameKey(n);
}
/** Group name key */
- public static class NameKey extends StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
+ @AutoValue
+ public abstract static class NameKey implements Comparable<NameKey> {
+ abstract String name();
- protected String name;
-
- protected NameKey() {}
-
- public NameKey(String n) {
- name = n;
+ public String get() {
+ return name();
}
@Override
- public String get() {
- return name;
+ public final int compareTo(NameKey o) {
+ return name().compareTo(o.name());
}
@Override
- protected void set(String newValue) {
- name = newValue;
+ public final String toString() {
+ return KeyUtil.encode(get());
}
}
public static UUID uuid(String n) {
- return new UUID(n);
+ return new AutoValue_AccountGroup_UUID(n);
}
/** Globally unique identifier. */
- public static class UUID extends StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- protected String uuid;
-
- protected UUID() {}
-
- public UUID(String n) {
- uuid = n;
- }
+ @AutoValue
+ public abstract static class UUID implements Comparable<UUID> {
+ abstract String uuid();
- @Override
public String get() {
- return uuid;
- }
-
- @Override
- protected void set(String newValue) {
- uuid = newValue;
+ return uuid();
}
/** Parse an {@link AccountGroup.UUID} out of a string representation. */
public static UUID parse(String str) {
- final UUID r = new UUID();
- r.fromString(str);
- return r;
+ return AccountGroup.uuid(KeyUtil.decode(str));
}
/** Parse an {@link AccountGroup.UUID} out of a ref-name. */
@@ -112,7 +93,17 @@ public final class AccountGroup {
*/
public static UUID fromRefPart(String refPart) {
String uuid = RefNames.parseShardedUuidFromRefPart(refPart);
- return uuid != null ? new AccountGroup.UUID(uuid) : null;
+ return uuid != null ? AccountGroup.uuid(uuid) : null;
+ }
+
+ @Override
+ public final int compareTo(UUID o) {
+ return uuid().compareTo(o.uuid());
+ }
+
+ @Override
+ public final String toString() {
+ return KeyUtil.encode(get());
}
}
@@ -122,36 +113,26 @@ public final class AccountGroup {
}
public static Id id(int id) {
- return new Id(id);
+ return new AutoValue_AccountGroup_Id(id);
}
/** Synthetic key to link to within the database */
- public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- protected int id;
-
- protected Id() {}
-
- public Id(int id) {
- this.id = id;
- }
+ @AutoValue
+ public abstract static class Id {
+ abstract int id();
- @Override
public int get() {
- return id;
- }
-
- @Override
- protected void set(int newValue) {
- id = newValue;
+ return id();
}
/** Parse an AccountGroup.Id out of a string representation. */
public static Id parse(String str) {
- final Id r = new Id();
- r.fromString(str);
- return r;
+ return AccountGroup.id(Integer.parseInt(str));
+ }
+
+ @Override
+ public final String toString() {
+ return Integer.toString(get());
}
}
diff --git a/java/com/google/gerrit/entities/AccountGroupByIdAudit.java b/java/com/google/gerrit/entities/AccountGroupByIdAudit.java
new file mode 100644
index 0000000000..17ddf518d6
--- /dev/null
+++ b/java/com/google/gerrit/entities/AccountGroupByIdAudit.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import com.google.auto.value.AutoValue;
+import java.sql.Timestamp;
+import java.util.Optional;
+
+/** Inclusion of an {@link AccountGroup} in another {@link AccountGroup}. */
+@AutoValue
+public abstract class AccountGroupByIdAudit {
+ public static Builder builder() {
+ return new AutoValue_AccountGroupByIdAudit.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder groupId(AccountGroup.Id groupId);
+
+ public abstract Builder includeUuid(AccountGroup.UUID includeUuid);
+
+ public abstract Builder addedBy(Account.Id addedBy);
+
+ public abstract Builder addedOn(Timestamp addedOn);
+
+ abstract Builder removedBy(Account.Id removedBy);
+
+ abstract Builder removedOn(Timestamp removedOn);
+
+ public Builder removed(Account.Id removedBy, Timestamp removedOn) {
+ return removedBy(removedBy).removedOn(removedOn);
+ }
+
+ public abstract AccountGroupByIdAudit build();
+ }
+
+ public abstract AccountGroup.Id groupId();
+
+ public abstract AccountGroup.UUID includeUuid();
+
+ public abstract Account.Id addedBy();
+
+ public abstract Timestamp addedOn();
+
+ public abstract Optional<Account.Id> removedBy();
+
+ public abstract Optional<Timestamp> removedOn();
+
+ public abstract Builder toBuilder();
+
+ public boolean isActive() {
+ return !removedOn().isPresent();
+ }
+}
diff --git a/java/com/google/gerrit/entities/AccountGroupMemberAudit.java b/java/com/google/gerrit/entities/AccountGroupMemberAudit.java
new file mode 100644
index 0000000000..4d191b8c36
--- /dev/null
+++ b/java/com/google/gerrit/entities/AccountGroupMemberAudit.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import com.google.auto.value.AutoValue;
+import java.sql.Timestamp;
+import java.util.Optional;
+
+/** Membership of an {@link Account} in an {@link AccountGroup}. */
+@AutoValue
+public abstract class AccountGroupMemberAudit {
+ public static Builder builder() {
+ return new AutoValue_AccountGroupMemberAudit.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder groupId(AccountGroup.Id groupId);
+
+ public abstract Builder memberId(Account.Id accountId);
+
+ public abstract Builder addedBy(Account.Id addedBy);
+
+ abstract Account.Id addedBy();
+
+ public abstract Builder addedOn(Timestamp addedOn);
+
+ abstract Timestamp addedOn();
+
+ abstract Builder removedBy(Account.Id removedBy);
+
+ abstract Builder removedOn(Timestamp removedOn);
+
+ public Builder removed(Account.Id removedBy, Timestamp removedOn) {
+ return removedBy(removedBy).removedOn(removedOn);
+ }
+
+ public Builder removedLegacy() {
+ return removed(addedBy(), addedOn());
+ }
+
+ public abstract AccountGroupMemberAudit build();
+ }
+
+ public abstract AccountGroup.Id groupId();
+
+ public abstract Account.Id memberId();
+
+ public abstract Account.Id addedBy();
+
+ public abstract Timestamp addedOn();
+
+ public abstract Optional<Account.Id> removedBy();
+
+ public abstract Optional<Timestamp> removedOn();
+
+ public abstract Builder toBuilder();
+
+ public boolean isActive() {
+ return !removedOn().isPresent();
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/BUILD b/java/com/google/gerrit/entities/BUILD
index 3bc6528d5d..8784bd8f30 100644
--- a/java/com/google/gerrit/reviewdb/BUILD
+++ b/java/com/google/gerrit/entities/BUILD
@@ -5,14 +5,17 @@ package(
)
java_library(
- name = "server",
+ name = "entities",
srcs = glob(["**/*.java"]),
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gwtorm",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib/errorprone:annotations",
"//proto:entities_java_proto",
],
)
diff --git a/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java b/java/com/google/gerrit/entities/BooleanProjectConfig.java
index a70d254607..5201f6dc26 100644
--- a/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java
+++ b/java/com/google/gerrit/entities/BooleanProjectConfig.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
/**
* Contains all inheritable boolean project configs and maps internal representations to API
diff --git a/java/com/google/gerrit/entities/BranchNameKey.java b/java/com/google/gerrit/entities/BranchNameKey.java
new file mode 100644
index 0000000000..cbb2e25adb
--- /dev/null
+++ b/java/com/google/gerrit/entities/BranchNameKey.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import com.google.auto.value.AutoValue;
+
+/** Branch name key */
+@AutoValue
+public abstract class BranchNameKey implements Comparable<BranchNameKey> {
+ public static BranchNameKey create(Project.NameKey projectName, String branchName) {
+ return new AutoValue_BranchNameKey(projectName, RefNames.fullName(branchName));
+ }
+
+ public static BranchNameKey create(String projectName, String branchName) {
+ return create(Project.nameKey(projectName), branchName);
+ }
+
+ public abstract Project.NameKey project();
+
+ public abstract String branch();
+
+ public String shortName() {
+ return RefNames.shortName(branch());
+ }
+
+ @Override
+ public final int compareTo(BranchNameKey o) {
+ // TODO(dborowitz): Only compares branch name in order to match old StringKey behavior.
+ // Consider comparing project name first.
+ return branch().compareTo(o.branch());
+ }
+
+ @Override
+ public final String toString() {
+ return project() + "," + KeyUtil.encode(branch());
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/client/Change.java b/java/com/google/gerrit/entities/Change.java
index 79739dc948..739bd38097 100644
--- a/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -12,19 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.auto.value.AutoValue;
+import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gwtorm.client.IntKey;
-import com.google.gwtorm.client.StringKey;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.sql.Timestamp;
import java.util.Arrays;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
/**
- * A change proposed to be merged into a {@link Branch}.
+ * A change proposed to be merged into a branch.
*
* <p>The data graph rooted below a Change can be quite complex:
*
@@ -37,7 +44,7 @@ import java.util.Arrays;
* |
* +- {@link PatchSetApproval}: a +/- vote on the change's current state.
* |
- * +- {@link PatchLineComment}: comment about a specific line
+ * +- {@link Comment}: comment about a specific line
* </pre>
*
* <p>
@@ -53,8 +60,8 @@ import java.util.Arrays;
* commit can contain zero patches, if the merge has no conflicts, or has no impact other than to
* cut off a line of development.
*
- * <p>Each PatchLineComment is a draft or a published comment about a single line of the associated
- * file. These are the inline comment entities created by users as they perform a review.
+ * <p>Each Comment is a draft or a published comment about a single line of the associated file.
+ * These are the inline comment entities created by users as they perform a review.
*
* <p>When additional PatchSets appear under a change, these PatchSets reference <i>replacement</i>
* commits; alternative commits that could be made to the project instead of the original commit
@@ -69,10 +76,10 @@ import java.util.Arrays;
* <h5>ChangeMessage</h5>
*
* <p>The ChangeMessage entity is a general free-form comment about the whole change, rather than
- * PatchLineComment's file and line specific context. The ChangeMessage appears at the start of any
- * email generated by Gerrit, and is shown on the change overview page, rather than in a
- * file-specific context. Users often use this entity to describe general remarks about the overall
- * concept proposed by the change.
+ * Comment's file and line specific context. The ChangeMessage appears at the start of any email
+ * generated by Gerrit, and is shown on the change overview page, rather than in a file-specific
+ * context. Users often use this entity to describe general remarks about the overall concept
+ * proposed by the change.
*
* <p>
*
@@ -93,49 +100,27 @@ import java.util.Arrays;
* notice of a replacement patch set is sent, or when notice of the change submission occurs.
*/
public final class Change {
- public static Id id(int id) {
- return new Id(id);
- }
-
- public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- public int id;
-
- protected Id() {}
-
- public Id(int id) {
- this.id = id;
- }
-
- @Override
- public int get() {
- return id;
- }
-
- @Override
- protected void set(int newValue) {
- id = newValue;
- }
+ private static final SecureRandom rng;
- public String toRefPrefix() {
- return refPrefixBuilder().toString();
+ static {
+ try {
+ rng = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Cannot create RNG for Change-Id generator", e);
}
+ }
- StringBuilder refPrefixBuilder() {
- StringBuilder r = new StringBuilder(32).append(REFS_CHANGES);
- int m = id % 100;
- if (m < 10) {
- r.append('0');
- }
- return r.append(m).append('/').append(id).append('/');
- }
+ public static Id id(int id) {
+ return new AutoValue_Change_Id(id);
+ }
+ @AutoValue
+ public abstract static class Id {
/** Parse a Change.Id out of a string representation. */
public static Id parse(String str) {
- final Id r = new Id();
- r.fromString(str);
- return r;
+ Integer id = Ints.tryParse(str);
+ checkArgument(id != null, "invalid change ID: %s", str);
+ return Change.id(id);
}
public static Id fromRef(String ref) {
@@ -150,7 +135,7 @@ public final class Change {
if (ref.substring(ce).equals(RefNames.META_SUFFIX)
|| ref.substring(ce).equals(RefNames.ROBOT_COMMENTS_SUFFIX)
|| PatchSet.Id.fromRef(ref, ce) >= 0) {
- return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
+ return Change.id(Integer.parseInt(ref.substring(cs, ce)));
}
return null;
}
@@ -173,7 +158,7 @@ public final class Change {
}
int ce = nextNonDigit(ref, cs);
if (ce < ref.length() && ref.charAt(ce) == '/' && isNumeric(ref, ce + 1)) {
- return new Change.Id(Integer.parseInt(ref.substring(cs, ce)));
+ return Change.id(Integer.parseInt(ref.substring(cs, ce)));
}
return null;
}
@@ -195,14 +180,14 @@ public final class Change {
int endChangeId = nextNonDigit(ref, startChangeId);
String id = ref.substring(startChangeId, endChangeId);
if (id != null && !id.isEmpty()) {
- return new Change.Id(Integer.parseInt(id));
+ return Change.id(Integer.parseInt(id));
}
return null;
}
public static Id fromRefPart(String ref) {
Integer id = RefNames.parseShardedRefPart(ref);
- return id != null ? new Change.Id(id) : null;
+ return id != null ? Change.id(id) : null;
}
static int startIndex(String ref) {
@@ -253,35 +238,66 @@ public final class Change {
}
return i;
}
+
+ abstract int id();
+
+ public int get() {
+ return id();
+ }
+
+ public String toRefPrefix() {
+ return refPrefixBuilder().toString();
+ }
+
+ StringBuilder refPrefixBuilder() {
+ StringBuilder r = new StringBuilder(32).append(REFS_CHANGES);
+ int m = get() % 100;
+ if (m < 10) {
+ r.append('0');
+ }
+ return r.append(m).append('/').append(get()).append('/');
+ }
+
+ @Override
+ public final String toString() {
+ return Integer.toString(get());
+ }
+ }
+
+ public static ObjectId generateChangeId() {
+ byte[] rand = new byte[Constants.OBJECT_ID_STRING_LENGTH];
+ rng.nextBytes(rand);
+ String randomString = new String(rand, UTF_8);
+
+ try (ObjectInserter f = new ObjectInserter.Formatter()) {
+ return f.idFor(Constants.OBJ_COMMIT, Constants.encode(randomString));
+ }
+ }
+
+ public static Key generateKey() {
+ return key("I" + generateChangeId().name());
}
public static Key key(String key) {
- return new Key(key);
+ return new AutoValue_Change_Key(key);
}
/**
* Globally unique identification of this change. This generally takes the form of a string
* "Ixxxxxx...", and is stored in the Change-Id footer of a commit.
*/
- public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
-
- protected String id;
-
- protected Key() {}
-
- public Key(String id) {
- this.id = id;
+ @AutoValue
+ public abstract static class Key {
+ // TODO(dborowitz): This hardly seems worth it: why would someone pass a URL-encoded change key?
+ // Ideally the standard key() factory method would enforce the format and throw IAE.
+ public static Key parse(String str) {
+ return Change.key(KeyUtil.decode(str));
}
- @Override
- public String get() {
- return id;
- }
+ abstract String key();
- @Override
- protected void set(String newValue) {
- id = newValue;
+ public String get() {
+ return key();
}
/** Construct a key that is after all keys prefixed by this key. */
@@ -289,7 +305,7 @@ public final class Change {
final StringBuilder revEnd = new StringBuilder(get().length() + 1);
revEnd.append(get());
revEnd.append('\u9fa5');
- return new Key(revEnd.toString());
+ return Change.key(revEnd.toString());
}
/** Obtain a shorter version of this key string, using a leading prefix. */
@@ -298,11 +314,9 @@ public final class Change {
return s.substring(0, Math.min(s.length(), 9));
}
- /** Parse a Change.Key out of a string representation. */
- public static Key parse(String str) {
- final Key r = new Key();
- r.fromString(str);
- return r;
+ @Override
+ public final String toString() {
+ return get();
}
}
@@ -412,13 +426,6 @@ public final class Change {
}
}
- // TODO(davido): Remove in 3.0, after all sites upgraded to version,
- // where DRAFT status was removed. This code path is still needed,
- // when changes are deserialized from the secondary index, during
- // the online migration to the new schema version wasn't completed.
- if (c == 'd') {
- return Status.NEW;
- }
return null;
}
@@ -456,7 +463,7 @@ public final class Change {
protected Account.Id owner;
/** The branch (and project) this change merges into. */
- protected Branch.NameKey dest;
+ protected BranchNameKey dest;
// DELETED: id = 9 (open)
@@ -512,7 +519,7 @@ public final class Change {
Change.Key newKey,
Change.Id newId,
Account.Id ownedBy,
- Branch.NameKey forBranch,
+ BranchNameKey forBranch,
Timestamp ts) {
changeKey = newKey;
changeId = newId;
@@ -599,16 +606,16 @@ public final class Change {
this.owner = owner;
}
- public Branch.NameKey getDest() {
+ public BranchNameKey getDest() {
return dest;
}
- public void setDest(Branch.NameKey dest) {
+ public void setDest(BranchNameKey dest) {
this.dest = dest;
}
public Project.NameKey getProject() {
- return dest.getParentKey();
+ return dest.project();
}
public String getSubject() {
@@ -626,7 +633,7 @@ public final class Change {
/** Get the id of the most current {@link PatchSet} in this change. */
public PatchSet.Id currentPatchSetId() {
if (currentPatchSetId > 0) {
- return new PatchSet.Id(changeId, currentPatchSetId);
+ return PatchSet.id(changeId, currentPatchSetId);
}
return null;
}
@@ -649,7 +656,7 @@ public final class Change {
}
public void setCurrentPatchSet(PatchSet.Id psId, String subject, String originalSubject) {
- if (!psId.getParentKey().equals(changeId)) {
+ if (!psId.changeId().equals(changeId)) {
throw new IllegalArgumentException("patch set ID " + psId + " is not for change " + changeId);
}
currentPatchSetId = psId.get();
diff --git a/java/com/google/gerrit/reviewdb/client/ChangeMessage.java b/java/com/google/gerrit/entities/ChangeMessage.java
index de318bd271..f34cc7d850 100644
--- a/java/com/google/gerrit/reviewdb/client/ChangeMessage.java
+++ b/java/com/google/gerrit/entities/ChangeMessage.java
@@ -12,57 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
+import com.google.auto.value.AutoValue;
import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
import java.util.Objects;
/** A message attached to a {@link Change}. */
public final class ChangeMessage {
public static Key key(Change.Id changeId, String uuid) {
- return new Key(changeId, uuid);
+ return new AutoValue_ChangeMessage_Key(changeId, uuid);
}
- public static class Key extends StringKey<Change.Id> {
- private static final long serialVersionUID = 1L;
+ @AutoValue
+ public abstract static class Key {
+ public abstract Change.Id changeId();
- protected Change.Id changeId;
-
- protected String uuid;
-
- protected Key() {
- changeId = new Change.Id();
- }
-
- public Key(Change.Id change, String uuid) {
- this.changeId = change;
- this.uuid = uuid;
- }
-
- @Override
- public Change.Id getParentKey() {
- return changeId;
- }
-
- public Change.Id changeId() {
- return getParentKey();
- }
-
- @Override
- public String get() {
- return uuid;
- }
-
- public String uuid() {
- return get();
- }
-
- @Override
- public void set(String newValue) {
- uuid = newValue;
- }
+ public abstract String uuid();
}
protected Key key;
diff --git a/java/com/google/gerrit/reviewdb/client/CodedEnum.java b/java/com/google/gerrit/entities/CodedEnum.java
index 11e7efaad4..90629a0543 100644
--- a/java/com/google/gerrit/reviewdb/client/CodedEnum.java
+++ b/java/com/google/gerrit/entities/CodedEnum.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
/** Extension of Enum which provides distinct character code values. */
public interface CodedEnum {
diff --git a/java/com/google/gerrit/reviewdb/client/Comment.java b/java/com/google/gerrit/entities/Comment.java
index e03d0fa24e..55d739a8b2 100644
--- a/java/com/google/gerrit/reviewdb/client/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -12,11 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.gerrit.common.Nullable;
import java.sql.Timestamp;
import java.util.Comparator;
import java.util.Objects;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
/**
* This class represents inline comments in NoteDb. This means it determines the JSON format for
@@ -24,30 +29,33 @@ import java.util.Objects;
*
* <p>Changing fields in this class changes the storage format of inline comments in NoteDb and may
* require a corresponding data migration (adding new optional fields is generally okay).
- *
- * <p>{@link PatchLineComment} historically represented comments in ReviewDb. There are a few
- * notable differences:
- *
- * <ul>
- * <li>PatchLineComment knows the comment status (published or draft). For comments in NoteDb the
- * status is determined by the branch in which they are stored (published comments are stored
- * in the change meta ref; draft comments are store in refs/draft-comments branches in
- * All-Users). Hence Comment doesn't need to contain the status, but the status is implicitly
- * known by where the comments are read from.
- * <li>PatchLineComment knows the change ID. For comments in NoteDb, the change ID is determined
- * by the branch in which they are stored (the ref name contains the change ID). Hence Comment
- * doesn't need to contain the change ID, but the change ID is implicitly known by where the
- * comments are read from.
- * </ul>
- *
- * <p>For all utility classes and middle layer functionality using Comment over PatchLineComment is
- * preferred, as ReviewDb is gone so PatchLineComment is slated for deletion as well. This means
- * Comment should be used everywhere and only for storing inline comment in ReviewDb a conversion to
- * PatchLineComment is done. Converting Comments to PatchLineComments and vice verse is done by
- * CommentsUtil#toPatchLineComments(Change.Id, PatchLineComment.Status, Iterable) and
- * CommentsUtil#toComments(String, Iterable).
*/
public class Comment {
+ public enum Status {
+ DRAFT('d'),
+
+ PUBLISHED('P');
+
+ private final char code;
+
+ Status(char c) {
+ code = c;
+ }
+
+ public char getCode() {
+ return code;
+ }
+
+ public static Status forCode(char c) {
+ for (Status s : Status.values()) {
+ if (s.code == c) {
+ return s;
+ }
+ }
+ return null;
+ }
+ }
+
public static class Key {
public String uuid;
public String filename;
@@ -65,17 +73,10 @@ public class Comment {
@Override
public String toString() {
- return new StringBuilder()
- .append("Comment.Key{")
- .append("uuid=")
- .append(uuid)
- .append(',')
- .append("filename=")
- .append(filename)
- .append(',')
- .append("patchSetId=")
- .append(patchSetId)
- .append('}')
+ return MoreObjects.toStringHelper(this)
+ .add("uuid", uuid)
+ .add("filename", filename)
+ .add("patchSetId", patchSetId)
.toString();
}
@@ -104,7 +105,7 @@ public class Comment {
}
public Account.Id getId() {
- return new Account.Id(id);
+ return Account.id(id);
}
@Override
@@ -122,15 +123,30 @@ public class Comment {
@Override
public String toString() {
- return new StringBuilder()
- .append("Comment.Identity{")
- .append("id=")
- .append(id)
- .append('}')
- .toString();
+ return MoreObjects.toStringHelper(this).add("id", id).toString();
}
}
+ /**
+ * The Range class defines continuous range of character.
+ *
+ * <p>The pair (startLine, startChar) defines the first character in the range. The pair (endLine,
+ * endChar) defines the first character AFTER the range (i.e. it doesn't belong the range).
+ * (endLine, endChar) must be a valid character inside text, except EOF case.
+ *
+ * <p>Special cases:
+ *
+ * <ul>
+ * <li>Zero length range: (startLine, startChar) = (endLine, endChar). Range defines insert
+ * position right before the (startLine, startChar) character (for {@link FixReplacement})
+ * <li>EOF case - range includes the last character in the file:
+ * <ul>
+ * <li>if a file ends with EOL mark, then (endLine, endChar) = (num_of_lines + 1, 0)
+ * <li>if a file doesn't end with EOL mark, then (endLine, endChar) = (num_of_lines,
+ * num_of_chars_in_last_line)
+ * </ul>
+ * </ul>
+ */
public static class Range implements Comparable<Range> {
private static final Comparator<Range> RANGE_COMPARATOR =
Comparator.<Range>comparingInt(range -> range.startLine)
@@ -138,10 +154,10 @@ public class Comment {
.thenComparingInt(range -> range.endLine)
.thenComparingInt(range -> range.endChar);
- public int startLine; // 1-based, inclusive
- public int startChar; // 0-based, inclusive
- public int endLine; // 1-based, exclusive
- public int endChar; // 0-based, exclusive
+ public int startLine; // 1-based
+ public int startChar; // 0-based
+ public int endLine; // 1-based
+ public int endChar; // 0-based
public Range(Range r) {
this(r.startLine, r.startChar, r.endLine, r.endChar);
@@ -177,20 +193,11 @@ public class Comment {
@Override
public String toString() {
- return new StringBuilder()
- .append("Comment.Range{")
- .append("startLine=")
- .append(startLine)
- .append(',')
- .append("startChar=")
- .append(startChar)
- .append(',')
- .append("endLine=")
- .append(endLine)
- .append(',')
- .append("endChar=")
- .append(endChar)
- .append('}')
+ return MoreObjects.toStringHelper(this)
+ .add("startLine", startLine)
+ .add("startChar", startChar)
+ .add("endLine", endLine)
+ .add("endChar", endChar)
.toString();
}
@@ -201,7 +208,9 @@ public class Comment {
}
public Key key;
+ /** The line number (1-based) to which the comment refers, or 0 for a file comment. */
public int lineNbr;
+
public Identity author;
protected Identity realAuthor;
public Timestamp writtenOn;
@@ -211,17 +220,15 @@ public class Comment {
public Range range;
public String tag;
- // Hex commit SHA1 of the commit of the patchset to which this comment applies.
- public String revId;
+ // Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
+ // this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
+ // serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
+ // directly, and instead use the public getter/setter that wraps an ObjectId.
+ private String revId;
+
public String serverId;
public boolean unresolved;
- /**
- * Whether the comment was parsed from a JSON representation (false) or the legacy custom notes
- * format (true).
- */
- public transient boolean legacyFormat;
-
public Comment(Comment c) {
this(
new Key(c.key),
@@ -269,8 +276,13 @@ public class Comment {
this.range = range != null ? range.asCommentRange() : null;
}
- public void setRevId(RevId revId) {
- this.revId = revId != null ? revId.get() : null;
+ @Nullable
+ public ObjectId getCommitId() {
+ return revId != null ? ObjectId.fromString(revId) : null;
+ }
+
+ public void setCommitId(@Nullable AnyObjectId commitId) {
+ this.revId = commitId != null ? commitId.name() : null;
}
public void setRealAuthor(Account.Id id) {
@@ -322,44 +334,22 @@ public class Comment {
@Override
public String toString() {
- return new StringBuilder()
- .append("Comment{")
- .append("key=")
- .append(key)
- .append(',')
- .append("lineNbr=")
- .append(lineNbr)
- .append(',')
- .append("author=")
- .append(author.getId().get())
- .append(',')
- .append("realAuthor=")
- .append(realAuthor != null ? realAuthor.getId().get() : "")
- .append(',')
- .append("writtenOn=")
- .append(writtenOn.toString())
- .append(',')
- .append("side=")
- .append(side)
- .append(',')
- .append("message=")
- .append(Objects.toString(message, ""))
- .append(',')
- .append("parentUuid=")
- .append(Objects.toString(parentUuid, ""))
- .append(',')
- .append("range=")
- .append(Objects.toString(range, ""))
- .append(',')
- .append("revId=")
- .append(revId != null ? revId : "")
- .append(',')
- .append("tag=")
- .append(Objects.toString(tag, ""))
- .append(',')
- .append("unresolved=")
- .append(unresolved)
- .append('}')
- .toString();
+ return toStringHelper().toString();
+ }
+
+ protected ToStringHelper toStringHelper() {
+ return MoreObjects.toStringHelper(this)
+ .add("key", key)
+ .add("lineNbr", lineNbr)
+ .add("author", author.getId())
+ .add("realAuthor", realAuthor != null ? realAuthor.getId() : "")
+ .add("writtenOn", writtenOn)
+ .add("side", side)
+ .add("message", Objects.toString(message, ""))
+ .add("parentUuid", Objects.toString(parentUuid, ""))
+ .add("range", Objects.toString(range, ""))
+ .add("revId", Objects.toString(revId, ""))
+ .add("tag", Objects.toString(tag, ""))
+ .add("unresolved", unresolved);
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/CommentRange.java b/java/com/google/gerrit/entities/CommentRange.java
index e6c5078218..f58780fd3b 100644
--- a/java/com/google/gerrit/reviewdb/client/CommentRange.java
+++ b/java/com/google/gerrit/entities/CommentRange.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
public class CommentRange {
diff --git a/java/com/google/gerrit/reviewdb/client/CoreDownloadSchemes.java b/java/com/google/gerrit/entities/CoreDownloadSchemes.java
index 2ca89c8bb4..37c10f1a49 100644
--- a/java/com/google/gerrit/reviewdb/client/CoreDownloadSchemes.java
+++ b/java/com/google/gerrit/entities/CoreDownloadSchemes.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
/** Download scheme string constants supported by the download-commands core plugin. */
public class CoreDownloadSchemes {
diff --git a/java/com/google/gerrit/reviewdb/client/FixReplacement.java b/java/com/google/gerrit/entities/FixReplacement.java
index 66630e43c3..046300e5a6 100644
--- a/java/com/google/gerrit/reviewdb/client/FixReplacement.java
+++ b/java/com/google/gerrit/entities/FixReplacement.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
public class FixReplacement {
public String path;
diff --git a/java/com/google/gerrit/reviewdb/client/FixSuggestion.java b/java/com/google/gerrit/entities/FixSuggestion.java
index d766a3a659..ac4e720fc9 100644
--- a/java/com/google/gerrit/reviewdb/client/FixSuggestion.java
+++ b/java/com/google/gerrit/entities/FixSuggestion.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import java.util.List;
diff --git a/java/com/google/gwtorm/client/StandardKeyEncoder.java b/java/com/google/gerrit/entities/KeyUtil.java
index 1ceb0dcecd..40fb757efc 100644
--- a/java/com/google/gwtorm/client/StandardKeyEncoder.java
+++ b/java/com/google/gerrit/entities/KeyUtil.java
@@ -1,4 +1,4 @@
-// Copyright 2008 Google Inc.
+// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gwtorm.client;
+package com.google.gerrit.entities;
-import com.google.gwtorm.client.KeyUtil.Encoder;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
-public class StandardKeyEncoder extends Encoder {
+public class KeyUtil {
private static final char[] hexc = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
@@ -49,8 +48,7 @@ public class StandardKeyEncoder extends Encoder {
for (char i = 'a'; i <= 'f'; i++) hexb[i] = (byte) ((i - 'a') + 10);
}
- @Override
- public String encode(final String e) {
+ public static String encode(final String e) {
final byte[] b;
try {
b = e.getBytes("UTF-8");
@@ -73,8 +71,7 @@ public class StandardKeyEncoder extends Encoder {
return r.toString();
}
- @Override
- public String decode(final String e) {
+ public static String decode(final String e) {
if (e.indexOf('%') < 0) {
return e.replace('+', ' ');
}
diff --git a/java/com/google/gerrit/reviewdb/client/LabelId.java b/java/com/google/gerrit/entities/LabelId.java
index abf131b09c..1cc45c8c42 100644
--- a/java/com/google/gerrit/reviewdb/client/LabelId.java
+++ b/java/com/google/gerrit/entities/LabelId.java
@@ -12,38 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
-import com.google.gwtorm.client.StringKey;
-
-public class LabelId extends StringKey<com.google.gwtorm.client.Key<?>> {
- private static final long serialVersionUID = 1L;
+import com.google.auto.value.AutoValue;
+@AutoValue
+public abstract class LabelId {
static final String LEGACY_SUBMIT_NAME = "SUBM";
public static LabelId create(String n) {
- return new LabelId(n);
+ return new AutoValue_LabelId(n);
}
public static LabelId legacySubmit() {
- return new LabelId(LEGACY_SUBMIT_NAME);
+ return create(LEGACY_SUBMIT_NAME);
}
- public String id;
-
- public LabelId() {}
-
- public LabelId(String n) {
- id = n;
- }
+ abstract String id();
- @Override
public String get() {
- return id;
- }
-
- @Override
- protected void set(String newValue) {
- id = newValue;
+ return id();
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/Patch.java b/java/com/google/gerrit/entities/Patch.java
index d192df0d47..cc38bdad2e 100644
--- a/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/java/com/google/gerrit/entities/Patch.java
@@ -12,9 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
-import com.google.gwtorm.client.StringKey;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.primitives.Ints;
+import java.util.List;
/** A single modified file in a {@link PatchSet}. */
public final class Patch {
@@ -36,58 +41,29 @@ public final class Patch {
}
public static Key key(PatchSet.Id patchSetId, String fileName) {
- return new Key(patchSetId, fileName);
+ return new AutoValue_Patch_Key(patchSetId, fileName);
}
- public static class Key extends StringKey<PatchSet.Id> {
- private static final long serialVersionUID = 1L;
-
- protected PatchSet.Id patchSetId;
-
- protected String fileName;
-
- protected Key() {
- patchSetId = new PatchSet.Id();
- }
-
- public Key(PatchSet.Id ps, String name) {
- this.patchSetId = ps;
- this.fileName = name;
- }
-
- @Override
- public PatchSet.Id getParentKey() {
- return patchSetId;
- }
-
- public PatchSet.Id patchSetId() {
- return getParentKey();
- }
-
- @Override
- public String get() {
- return fileName;
- }
-
- public String fileName() {
- return get();
+ @AutoValue
+ public abstract static class Key {
+ /** Parse a Patch.Key out of a string representation. */
+ public static Key parse(String str) {
+ List<String> parts = Splitter.on(',').limit(3).splitToList(str);
+ checkKeyFormat(parts.size() == 3, str);
+ Integer changeId = Ints.tryParse(parts.get(0));
+ checkKeyFormat(changeId != null, str);
+ Integer patchSetNum = Ints.tryParse(parts.get(1));
+ checkKeyFormat(patchSetNum != null, str);
+ return key(PatchSet.id(Change.id(changeId), patchSetNum), parts.get(2));
}
- @Override
- protected void set(String newValue) {
- fileName = newValue;
+ private static void checkKeyFormat(boolean test, String input) {
+ checkArgument(test, "invalid patch key: %s", input);
}
- /** Parse a Patch.Id out of a string representation. */
- public static Key parse(String str) {
- final Key r = new Key();
- r.fromString(str);
- return r;
- }
+ public abstract PatchSet.Id patchSetId();
- public String getFileName() {
- return get();
- }
+ public abstract String fileName();
}
/** Type of modification made to the file path. */
@@ -271,7 +247,7 @@ public final class Patch {
}
public String getFileName() {
- return key.fileName;
+ return key.fileName();
}
public String getSourceFileName() {
diff --git a/java/com/google/gerrit/entities/PatchSet.java b/java/com/google/gerrit/entities/PatchSet.java
new file mode 100644
index 0000000000..8b93dbc92f
--- /dev/null
+++ b/java/com/google/gerrit/entities/PatchSet.java
@@ -0,0 +1,233 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import com.google.common.primitives.Ints;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** A single revision of a {@link Change}. */
+@AutoValue
+public abstract class PatchSet {
+ /** Is the reference name a change reference? */
+ public static boolean isChangeRef(String name) {
+ return Id.fromRef(name) != null;
+ }
+
+ /**
+ * Is the reference name a change reference?
+ *
+ * @deprecated use isChangeRef instead.
+ */
+ @Deprecated
+ public static boolean isRef(String name) {
+ return isChangeRef(name);
+ }
+
+ public static String joinGroups(List<String> groups) {
+ requireNonNull(groups);
+ for (String group : groups) {
+ checkArgument(!group.contains(","), "group may not contain ',': %s", group);
+ }
+ return String.join(",", groups);
+ }
+
+ public static ImmutableList<String> splitGroups(String joinedGroups) {
+ return Streams.stream(Splitter.on(',').split(joinedGroups)).collect(toImmutableList());
+ }
+
+ public static Id id(Change.Id changeId, int id) {
+ return new AutoValue_PatchSet_Id(changeId, id);
+ }
+
+ @AutoValue
+ public abstract static class Id {
+ /** Parse a PatchSet.Id out of a string representation. */
+ public static Id parse(String str) {
+ List<String> parts = Splitter.on(',').splitToList(str);
+ checkIdFormat(parts.size() == 2, str);
+ Integer changeId = Ints.tryParse(parts.get(0));
+ checkIdFormat(changeId != null, str);
+ Integer id = Ints.tryParse(parts.get(1));
+ checkIdFormat(id != null, str);
+ return PatchSet.id(Change.id(changeId), id);
+ }
+
+ private static void checkIdFormat(boolean test, String input) {
+ checkArgument(test, "invalid patch set ID: %s", input);
+ }
+
+ /** Parse a PatchSet.Id from a {@link #refName()} result. */
+ public static Id fromRef(String ref) {
+ int cs = Change.Id.startIndex(ref);
+ if (cs < 0) {
+ return null;
+ }
+ int ce = Change.Id.nextNonDigit(ref, cs);
+ int patchSetId = fromRef(ref, ce);
+ if (patchSetId < 0) {
+ return null;
+ }
+ int changeId = Integer.parseInt(ref.substring(cs, ce));
+ return PatchSet.id(Change.id(changeId), patchSetId);
+ }
+
+ static int fromRef(String ref, int changeIdEnd) {
+ // Patch set ID.
+ int ps = changeIdEnd + 1;
+ if (ps >= ref.length() || ref.charAt(ps) == '0') {
+ return -1;
+ }
+ for (int i = ps; i < ref.length(); i++) {
+ if (ref.charAt(i) < '0' || ref.charAt(i) > '9') {
+ return -1;
+ }
+ }
+ return Integer.parseInt(ref.substring(ps));
+ }
+
+ public static String toId(int number) {
+ return number == 0 ? "edit" : String.valueOf(number);
+ }
+
+ public String getId() {
+ return toId(id());
+ }
+
+ public abstract Change.Id changeId();
+
+ abstract int id();
+
+ public int get() {
+ return id();
+ }
+
+ public String toRefName() {
+ return changeId().refPrefixBuilder().append(id()).toString();
+ }
+
+ @Override
+ public final String toString() {
+ return changeId().toString() + ',' + id();
+ }
+ }
+
+ public static Builder builder() {
+ return new AutoValue_PatchSet.Builder().groups(ImmutableList.of());
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder id(Id id);
+
+ public abstract Id id();
+
+ public abstract Builder commitId(ObjectId commitId);
+
+ public abstract Optional<ObjectId> commitId();
+
+ public abstract Builder uploader(Account.Id uploader);
+
+ public abstract Builder createdOn(Timestamp createdOn);
+
+ public abstract Builder groups(Iterable<String> groups);
+
+ public abstract ImmutableList<String> groups();
+
+ public abstract Builder pushCertificate(Optional<String> pushCertificate);
+
+ public abstract Builder pushCertificate(String pushCertificate);
+
+ public abstract Builder description(Optional<String> description);
+
+ public abstract Builder description(String description);
+
+ public abstract Optional<String> description();
+
+ public abstract PatchSet build();
+ }
+
+ /** ID of the patch set. */
+ public abstract Id id();
+
+ /**
+ * Commit ID of the patch set, also known as the revision.
+ *
+ * <p>If this is a deserialized instance that was originally serialized by an older version of
+ * Gerrit, and the old data erroneously did not include a {@code commitId}, then this method will
+ * return {@link ObjectId#zeroId()}.
+ */
+ public abstract ObjectId commitId();
+
+ /**
+ * Account that uploaded the patch set.
+ *
+ * <p>If this is a deserialized instance that was originally serialized by an older version of
+ * Gerrit, and the old data erroneously did not include an {@code uploader}, then this method will
+ * return an account ID of 0.
+ */
+ public abstract Account.Id uploader();
+
+ /**
+ * When this patch set was first introduced onto the change.
+ *
+ * <p>If this is a deserialized instance that was originally serialized by an older version of
+ * Gerrit, and the old data erroneously did not include a {@code createdOn}, then this method will
+ * return a timestamp of 0.
+ */
+ public abstract Timestamp createdOn();
+
+ /**
+ * Opaque group identifier, usually assigned during creation.
+ *
+ * <p>This field is actually a comma-separated list of values, as in rare cases involving merge
+ * commits a patch set may belong to multiple groups.
+ *
+ * <p>Changes on the same branch having patch sets with intersecting groups are considered
+ * related, as in the "Related Changes" tab.
+ */
+ public abstract ImmutableList<String> groups();
+
+ /** Certificate sent with a push that created this patch set. */
+ public abstract Optional<String> pushCertificate();
+
+ /**
+ * Optional user-supplied description for this patch set.
+ *
+ * <p>When this field is an empty {@code Optional}, the description was never set on the patch
+ * set. When this field is present but an empty string, the description was set and later cleared.
+ */
+ public abstract Optional<String> description();
+
+ /** Patch set number. */
+ public int number() {
+ return id().get();
+ }
+
+ /** Name of the corresponding patch set ref. */
+ public String refName() {
+ return id().toRefName();
+ }
+}
diff --git a/java/com/google/gerrit/entities/PatchSetApproval.java b/java/com/google/gerrit/entities/PatchSetApproval.java
new file mode 100644
index 0000000000..a4bb2518a0
--- /dev/null
+++ b/java/com/google/gerrit/entities/PatchSetApproval.java
@@ -0,0 +1,139 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.primitives.Shorts;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.Optional;
+
+/** An approval (or negative approval) on a patch set. */
+@AutoValue
+public abstract class PatchSetApproval {
+ public static Key key(PatchSet.Id patchSetId, Account.Id accountId, LabelId labelId) {
+ return new AutoValue_PatchSetApproval_Key(patchSetId, accountId, labelId);
+ }
+
+ @AutoValue
+ public abstract static class Key {
+ public abstract PatchSet.Id patchSetId();
+
+ public abstract Account.Id accountId();
+
+ public abstract LabelId labelId();
+
+ public boolean isLegacySubmit() {
+ return LabelId.LEGACY_SUBMIT_NAME.equals(labelId().get());
+ }
+ }
+
+ public static Builder builder() {
+ return new AutoValue_PatchSetApproval.Builder().postSubmit(false);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder key(Key key);
+
+ public abstract Key key();
+
+ public abstract Builder value(short value);
+
+ public Builder value(int value) {
+ return value(Shorts.checkedCast(value));
+ }
+
+ public abstract Builder granted(Timestamp granted);
+
+ public Builder granted(Date granted) {
+ return granted(new Timestamp(granted.getTime()));
+ }
+
+ public abstract Builder tag(String tag);
+
+ public abstract Builder tag(Optional<String> tag);
+
+ public abstract Builder realAccountId(Account.Id realAccountId);
+
+ abstract Optional<Account.Id> realAccountId();
+
+ public abstract Builder postSubmit(boolean isPostSubmit);
+
+ abstract PatchSetApproval autoBuild();
+
+ public PatchSetApproval build() {
+ if (!realAccountId().isPresent()) {
+ realAccountId(key().accountId());
+ }
+ return autoBuild();
+ }
+ }
+
+ public abstract Key key();
+
+ /**
+ * Value assigned by the user.
+ *
+ * <p>The precise meaning of "value" is up to each category.
+ *
+ * <p>In general:
+ *
+ * <ul>
+ * <li><b>&lt; 0:</b> The approval is rejected/revoked.
+ * <li><b>= 0:</b> No indication either way is provided.
+ * <li><b>&gt; 0:</b> The approval is approved/positive.
+ * </ul>
+ *
+ * and in the negative and positive direction a magnitude can be assumed.The further from 0 the
+ * more assertive the approval.
+ */
+ public abstract short value();
+
+ public abstract Timestamp granted();
+
+ public abstract Optional<String> tag();
+
+ /** Real user that made this approval on behalf of the user recorded in {@link Key#accountId}. */
+ public abstract Account.Id realAccountId();
+
+ public abstract boolean postSubmit();
+
+ public abstract Builder toBuilder();
+
+ public PatchSetApproval copyWithPatchSet(PatchSet.Id psId) {
+ return toBuilder().key(key(psId, key().accountId(), key().labelId())).build();
+ }
+
+ public PatchSet.Id patchSetId() {
+ return key().patchSetId();
+ }
+
+ public Account.Id accountId() {
+ return key().accountId();
+ }
+
+ public LabelId labelId() {
+ return key().labelId();
+ }
+
+ public String label() {
+ return labelId().get();
+ }
+
+ public boolean isLegacySubmit() {
+ return key().isLegacySubmit();
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java b/java/com/google/gerrit/entities/PatchSetInfo.java
index f949013be6..e3c661380f 100644
--- a/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java
+++ b/java/com/google/gerrit/entities/PatchSetInfo.java
@@ -12,19 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
+
+import static java.util.Objects.requireNonNull;
import java.util.List;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
/** Additional data about a {@link PatchSet} not normally loaded. */
public final class PatchSetInfo {
public static class ParentInfo {
- public RevId id;
+ public ObjectId commitId;
public String shortMessage;
- public ParentInfo(RevId id, String shortMessage) {
- this.id = id;
- this.shortMessage = shortMessage;
+ public ParentInfo(AnyObjectId commitId, String shortMessage) {
+ this.commitId = commitId.copy();
+ this.shortMessage = requireNonNull(shortMessage);
}
protected ParentInfo() {}
@@ -47,8 +51,8 @@ public final class PatchSetInfo {
/** List of parents of the patch set. */
protected List<ParentInfo> parents;
- /** SHA-1 of commit */
- protected String revId;
+ /** ID of commit. */
+ protected ObjectId commitId;
/** Optional user-supplied description for the patch set. */
protected String description;
@@ -107,12 +111,12 @@ public final class PatchSetInfo {
return parents;
}
- public void setRevId(String s) {
- revId = s;
+ public void setCommitId(AnyObjectId commitId) {
+ this.commitId = commitId.copy();
}
- public String getRevId() {
- return revId;
+ public ObjectId getCommitId() {
+ return commitId;
}
public void setDescription(String description) {
diff --git a/java/com/google/gerrit/reviewdb/client/Project.java b/java/com/google/gerrit/entities/Project.java
index e025c0f875..867b14db79 100644
--- a/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/java/com/google/gerrit/entities/Project.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
+
+import static java.util.Objects.requireNonNull;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gwtorm.client.StringKey;
+import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -35,50 +37,60 @@ public final class Project {
return new NameKey(name);
}
- /** Project name key */
- public static class NameKey extends StringKey<com.google.gwtorm.client.Key<?>> {
+ /**
+ * Project name key.
+ *
+ * <p>This class has subclasses such as {@code AllProjectsName}, which make Guice injection more
+ * convenient. Subclasses must compare equal if they have the same name, regardless of the
+ * specific class. This implies that subclasses may not add additional fields.
+ *
+ * <p>Because of this unusual subclassing behavior, this class is not an {@code @AutoValue},
+ * unlike other key types in this package. However, this is strictly an implementation detail; its
+ * interface and semantics are otherwise analogous to the {@code @AutoValue} types.
+ */
+ public static class NameKey implements Serializable, Comparable<NameKey> {
private static final long serialVersionUID = 1L;
- protected String name;
+ /** Parse a Project.NameKey out of a string representation. */
+ public static NameKey parse(String str) {
+ return nameKey(KeyUtil.decode(str));
+ }
+
+ public static String asStringOrNull(NameKey key) {
+ return key == null ? null : key.get();
+ }
- protected NameKey() {}
+ private final String name;
- public NameKey(String n) {
- name = n;
+ protected NameKey(String name) {
+ this.name = requireNonNull(name);
}
- @Override
public String get() {
return name;
}
@Override
- protected void set(String newValue) {
- name = newValue;
- }
-
- @Override
- public int hashCode() {
+ public final int hashCode() {
return get().hashCode();
}
@Override
- public boolean equals(Object b) {
+ public final boolean equals(Object b) {
if (b instanceof NameKey) {
return get().equals(((NameKey) b).get());
}
return false;
}
- /** Parse a Project.NameKey out of a string representation. */
- public static NameKey parse(String str) {
- final NameKey r = new NameKey();
- r.fromString(str);
- return r;
+ @Override
+ public final int compareTo(NameKey o) {
+ return get().compareTo(o.get());
}
- public static String asStringOrNull(NameKey key) {
- return key == null ? null : key.get();
+ @Override
+ public final String toString() {
+ return KeyUtil.encode(get());
}
}
@@ -220,7 +232,7 @@ public final class Project {
}
public void setParentName(String n) {
- parent = n != null ? new NameKey(n) : null;
+ parent = n != null ? nameKey(n) : null;
}
public void setParentName(NameKey n) {
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/entities/RefNames.java
index 984e43e925..5595bc75e3 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/entities/RefNames.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import com.google.gerrit.common.UsedAt;
@@ -128,6 +128,11 @@ public class RefNames {
return shard(id.changeId().get(), r).append('/').append(id.get()).toString();
}
+ public static String changeRefPrefix(Change.Id id) {
+ StringBuilder r = newStringBuilder().append(REFS_CHANGES);
+ return shard(id.get(), r).append('/').toString();
+ }
+
public static String robotCommentsRef(Change.Id id) {
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
return shard(id.get(), r).append(ROBOT_COMMENTS_SUFFIX).toString();
diff --git a/java/com/google/gerrit/entities/RobotComment.java b/java/com/google/gerrit/entities/RobotComment.java
new file mode 100644
index 0000000000..a7951add84
--- /dev/null
+++ b/java/com/google/gerrit/entities/RobotComment.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class RobotComment extends Comment {
+ public String robotId;
+ public String robotRunId;
+ public String url;
+ public Map<String, String> properties;
+ public List<FixSuggestion> fixSuggestions;
+
+ public RobotComment(
+ Key key,
+ Account.Id author,
+ Timestamp writtenOn,
+ short side,
+ String message,
+ String serverId,
+ String robotId,
+ String robotRunId) {
+ super(key, author, writtenOn, side, message, serverId, false);
+ this.robotId = robotId;
+ this.robotRunId = robotRunId;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper()
+ .add("robotId", robotId)
+ .add("robotRunId", robotRunId)
+ .add("url", url)
+ .add("properties", Objects.toString(properties, ""))
+ .add("fixSuggestions", Objects.toString(fixSuggestions, ""))
+ .toString();
+ }
+}
diff --git a/java/com/google/gerrit/entities/SubmoduleSubscription.java b/java/com/google/gerrit/entities/SubmoduleSubscription.java
new file mode 100644
index 0000000000..5ea1b1e355
--- /dev/null
+++ b/java/com/google/gerrit/entities/SubmoduleSubscription.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import java.util.Objects;
+
+/**
+ * Defining a project/branch subscription to a project/branch project.
+ *
+ * <p>This means a class instance represents a repo/branch subscription to a project/branch (the
+ * subscriber).
+ *
+ * <p>A subscriber operates a submodule in defined path.
+ */
+public final class SubmoduleSubscription {
+ protected BranchNameKey superProject;
+
+ protected String submodulePath;
+
+ protected BranchNameKey submodule;
+
+ public SubmoduleSubscription(BranchNameKey superProject, BranchNameKey submodule, String path) {
+ this.superProject = superProject;
+ this.submodule = submodule;
+ this.submodulePath = path;
+ }
+
+ /**
+ * Indicates the super project, aka subscriber: the project owner of the gitlinks to the
+ * submodules.
+ */
+ public BranchNameKey getSuperProject() {
+ return superProject;
+ }
+
+ public String getPath() {
+ return submodulePath;
+ }
+
+ public BranchNameKey getSubmodule() {
+ return submodule;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SubmoduleSubscription) {
+ SubmoduleSubscription s = (SubmoduleSubscription) o;
+ return superProject.equals(s.superProject)
+ && submodulePath.equals(s.submodulePath)
+ && submodule.equals(s.submodule);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(superProject, submodulePath, submodule);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getSuperProject()).append(':').append(getPath());
+ sb.append(" follows ");
+ sb.append(getSubmodule());
+ return sb.toString();
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/client/UserIdentity.java b/java/com/google/gerrit/entities/UserIdentity.java
index 0b7aee3846..e07d21aac8 100644
--- a/java/com/google/gerrit/reviewdb/client/UserIdentity.java
+++ b/java/com/google/gerrit/entities/UserIdentity.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/reviewdb/converter/AccountIdProtoConverter.java b/java/com/google/gerrit/entities/converter/AccountIdProtoConverter.java
index 8dd179467b..1e846fbca0 100644
--- a/java/com/google/gerrit/reviewdb/converter/AccountIdProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/AccountIdProtoConverter.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.protobuf.Parser;
+@Immutable
public enum AccountIdProtoConverter implements ProtoConverter<Entities.Account_Id, Account.Id> {
INSTANCE;
@@ -28,7 +30,7 @@ public enum AccountIdProtoConverter implements ProtoConverter<Entities.Account_I
@Override
public Account.Id fromProto(Entities.Account_Id proto) {
- return new Account.Id(proto.getId());
+ return Account.id(proto.getId());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverter.java b/java/com/google/gerrit/entities/converter/BranchNameKeyProtoConverter.java
index 4558f9bae9..4d0e306956 100644
--- a/java/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/BranchNameKeyProtoConverter.java
@@ -12,32 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
+@Immutable
public enum BranchNameKeyProtoConverter
- implements ProtoConverter<Entities.Branch_NameKey, Branch.NameKey> {
+ implements ProtoConverter<Entities.Branch_NameKey, BranchNameKey> {
INSTANCE;
private final ProtoConverter<Entities.Project_NameKey, Project.NameKey> projectNameConverter =
ProjectNameKeyProtoConverter.INSTANCE;
@Override
- public Entities.Branch_NameKey toProto(Branch.NameKey nameKey) {
+ public Entities.Branch_NameKey toProto(BranchNameKey nameKey) {
return Entities.Branch_NameKey.newBuilder()
- .setProjectName(projectNameConverter.toProto(nameKey.getParentKey()))
- .setBranchName(nameKey.get())
+ .setProject(projectNameConverter.toProto(nameKey.project()))
+ .setBranch(nameKey.branch())
.build();
}
@Override
- public Branch.NameKey fromProto(Entities.Branch_NameKey proto) {
- return new Branch.NameKey(
- projectNameConverter.fromProto(proto.getProjectName()), proto.getBranchName());
+ public BranchNameKey fromProto(Entities.Branch_NameKey proto) {
+ return BranchNameKey.create(
+ projectNameConverter.fromProto(proto.getProject()), proto.getBranch());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeIdProtoConverter.java
index 14ed59c43b..0d4ec70570 100644
--- a/java/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeIdProtoConverter.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.protobuf.Parser;
+@Immutable
public enum ChangeIdProtoConverter implements ProtoConverter<Entities.Change_Id, Change.Id> {
INSTANCE;
@@ -28,7 +30,7 @@ public enum ChangeIdProtoConverter implements ProtoConverter<Entities.Change_Id,
@Override
public Change.Id fromProto(Entities.Change_Id proto) {
- return new Change.Id(proto.getId());
+ return Change.id(proto.getId());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeKeyProtoConverter.java
index dccd7d9d1a..f3ccdfaf3d 100644
--- a/java/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeKeyProtoConverter.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.protobuf.Parser;
+@Immutable
public enum ChangeKeyProtoConverter implements ProtoConverter<Entities.Change_Key, Change.Key> {
INSTANCE;
@@ -28,7 +30,7 @@ public enum ChangeKeyProtoConverter implements ProtoConverter<Entities.Change_Ke
@Override
public Change.Key fromProto(Entities.Change_Key proto) {
- return new Change.Key(proto.getId());
+ return Change.key(proto.getId());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverter.java
index bb532dfaa4..3e93c5a2fe 100644
--- a/java/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverter.java
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.protobuf.Parser;
+@Immutable
public enum ChangeMessageKeyProtoConverter
implements ProtoConverter<Entities.ChangeMessage_Key, ChangeMessage.Key> {
INSTANCE;
@@ -29,14 +31,14 @@ public enum ChangeMessageKeyProtoConverter
@Override
public Entities.ChangeMessage_Key toProto(ChangeMessage.Key messageKey) {
return Entities.ChangeMessage_Key.newBuilder()
- .setChangeId(changeIdConverter.toProto(messageKey.getParentKey()))
- .setUuid(messageKey.get())
+ .setChangeId(changeIdConverter.toProto(messageKey.changeId()))
+ .setUuid(messageKey.uuid())
.build();
}
@Override
public ChangeMessage.Key fromProto(Entities.ChangeMessage_Key proto) {
- return new ChangeMessage.Key(changeIdConverter.fromProto(proto.getChangeId()), proto.getUuid());
+ return ChangeMessage.key(changeIdConverter.fromProto(proto.getChangeId()), proto.getUuid());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeMessageProtoConverter.java
index 0895d8d89b..19c1212499 100644
--- a/java/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeMessageProtoConverter.java
@@ -12,16 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
import java.sql.Timestamp;
import java.util.Objects;
+@Immutable
public enum ChangeMessageProtoConverter
implements ProtoConverter<Entities.ChangeMessage, ChangeMessage> {
INSTANCE;
diff --git a/java/com/google/gerrit/reviewdb/converter/ChangeProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
index 30b6d27b43..5b066ea87c 100644
--- a/java/com/google/gerrit/reviewdb/converter/ChangeProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
@@ -12,16 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
import java.sql.Timestamp;
+@Immutable
public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Change> {
INSTANCE;
@@ -31,7 +33,7 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
ChangeKeyProtoConverter.INSTANCE;
private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
AccountIdProtoConverter.INSTANCE;
- private final ProtoConverter<Entities.Branch_NameKey, Branch.NameKey> branchNameConverter =
+ private final ProtoConverter<Entities.Branch_NameKey, BranchNameKey> branchNameConverter =
BranchNameKeyProtoConverter.INSTANCE;
@Override
@@ -86,7 +88,7 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
proto.hasChangeKey() ? changeKeyConverter.fromProto(proto.getChangeKey()) : null;
Account.Id owner =
proto.hasOwnerAccountId() ? accountIdConverter.fromProto(proto.getOwnerAccountId()) : null;
- Branch.NameKey destination =
+ BranchNameKey destination =
proto.hasDest() ? branchNameConverter.fromProto(proto.getDest()) : null;
Change change =
new Change(key, changeId, owner, destination, new Timestamp(proto.getCreatedOn()));
@@ -100,7 +102,7 @@ public enum ChangeProtoConverter implements ProtoConverter<Entities.Change, Chan
String subject = proto.hasSubject() ? proto.getSubject() : null;
String originalSubject = proto.hasOriginalSubject() ? proto.getOriginalSubject() : null;
change.setCurrentPatchSet(
- new PatchSet.Id(changeId, proto.getCurrentPatchSetId()), subject, originalSubject);
+ PatchSet.id(changeId, proto.getCurrentPatchSetId()), subject, originalSubject);
if (proto.hasTopic()) {
change.setTopic(proto.getTopic());
}
diff --git a/java/com/google/gerrit/reviewdb/converter/LabelIdProtoConverter.java b/java/com/google/gerrit/entities/converter/LabelIdProtoConverter.java
index 274f23b26c..a1894ac0aa 100644
--- a/java/com/google/gerrit/reviewdb/converter/LabelIdProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/LabelIdProtoConverter.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.LabelId;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.LabelId;
import com.google.protobuf.Parser;
+@Immutable
public enum LabelIdProtoConverter implements ProtoConverter<Entities.LabelId, LabelId> {
INSTANCE;
@@ -28,7 +30,7 @@ public enum LabelIdProtoConverter implements ProtoConverter<Entities.LabelId, La
@Override
public LabelId fromProto(Entities.LabelId proto) {
- return new LabelId(proto.getId());
+ return LabelId.create(proto.getId());
}
@Override
diff --git a/java/com/google/gerrit/entities/converter/ObjectIdProtoConverter.java b/java/com/google/gerrit/entities/converter/ObjectIdProtoConverter.java
new file mode 100644
index 0000000000..6c805282b3
--- /dev/null
+++ b/java/com/google/gerrit/entities/converter/ObjectIdProtoConverter.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities.converter;
+
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.proto.Entities;
+import com.google.protobuf.Parser;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Proto converter for {@code ObjectId}s.
+ *
+ * <p>This converter uses the hex representation of object IDs embedded in a wrapper proto type,
+ * rather than a more parsimonious implementation (e.g. a raw byte array), for two reasons:
+ *
+ * <ul>
+ * <li>Hex strings are easier to read and work with when reading and writing protos in text
+ * formats, for example in test failure messages, or when using command-line tools.
+ * <li>This maintains backwards wire compatibility with a pre-NoteDb implementation.
+ * </ul>
+ */
+@Immutable
+public enum ObjectIdProtoConverter implements ProtoConverter<Entities.ObjectId, ObjectId> {
+ INSTANCE;
+
+ @Override
+ public Entities.ObjectId toProto(ObjectId objectId) {
+ return Entities.ObjectId.newBuilder().setName(objectId.name()).build();
+ }
+
+ @Override
+ public ObjectId fromProto(Entities.ObjectId proto) {
+ return ObjectId.fromString(proto.getName());
+ }
+
+ @Override
+ public Parser<Entities.ObjectId> getParser() {
+ return Entities.ObjectId.parser();
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverter.java b/java/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverter.java
index 353830195d..c7d1714192 100644
--- a/java/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverter.java
@@ -12,15 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.protobuf.Parser;
+@Immutable
public enum PatchSetApprovalKeyProtoConverter
implements ProtoConverter<Entities.PatchSetApproval_Key, PatchSetApproval.Key> {
INSTANCE;
@@ -35,18 +37,18 @@ public enum PatchSetApprovalKeyProtoConverter
@Override
public Entities.PatchSetApproval_Key toProto(PatchSetApproval.Key key) {
return Entities.PatchSetApproval_Key.newBuilder()
- .setPatchSetId(patchSetIdConverter.toProto(key.getParentKey()))
- .setAccountId(accountIdConverter.toProto(key.getAccountId()))
- .setCategoryId(labelIdConverter.toProto(key.getLabelId()))
+ .setPatchSetId(patchSetIdConverter.toProto(key.patchSetId()))
+ .setAccountId(accountIdConverter.toProto(key.accountId()))
+ .setLabelId(labelIdConverter.toProto(key.labelId()))
.build();
}
@Override
public PatchSetApproval.Key fromProto(Entities.PatchSetApproval_Key proto) {
- return new PatchSetApproval.Key(
+ return PatchSetApproval.key(
patchSetIdConverter.fromProto(proto.getPatchSetId()),
accountIdConverter.fromProto(proto.getAccountId()),
- labelIdConverter.fromProto(proto.getCategoryId()));
+ labelIdConverter.fromProto(proto.getLabelId()));
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverter.java b/java/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverter.java
index 418076f47f..78a35ff57a 100644
--- a/java/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverter.java
@@ -12,15 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.protobuf.Parser;
import java.sql.Timestamp;
import java.util.Objects;
+@Immutable
public enum PatchSetApprovalProtoConverter
implements ProtoConverter<Entities.PatchSetApproval, PatchSetApproval> {
INSTANCE;
@@ -34,21 +36,18 @@ public enum PatchSetApprovalProtoConverter
public Entities.PatchSetApproval toProto(PatchSetApproval patchSetApproval) {
Entities.PatchSetApproval.Builder builder =
Entities.PatchSetApproval.newBuilder()
- .setKey(patchSetApprovalKeyProtoConverter.toProto(patchSetApproval.getKey()))
- .setValue(patchSetApproval.getValue())
- .setGranted(patchSetApproval.getGranted().getTime())
- .setPostSubmit(patchSetApproval.isPostSubmit());
+ .setKey(patchSetApprovalKeyProtoConverter.toProto(patchSetApproval.key()))
+ .setValue(patchSetApproval.value())
+ .setGranted(patchSetApproval.granted().getTime())
+ .setPostSubmit(patchSetApproval.postSubmit());
- String tag = patchSetApproval.getTag();
- if (tag != null) {
- builder.setTag(tag);
- }
- Account.Id realAccountId = patchSetApproval.getRealAccountId();
+ patchSetApproval.tag().ifPresent(builder::setTag);
+ Account.Id realAccountId = patchSetApproval.realAccountId();
// PatchSetApproval#getRealAccountId automatically delegates to PatchSetApproval#getAccountId if
// the real author is not set. However, the previous protobuf representation kept
// 'realAccountId' empty if it wasn't set. To ensure binary compatibility, simulate the previous
// behavior.
- if (realAccountId != null && !Objects.equals(realAccountId, patchSetApproval.getAccountId())) {
+ if (realAccountId != null && !Objects.equals(realAccountId, patchSetApproval.accountId())) {
builder.setRealAccountId(accountIdConverter.toProto(realAccountId));
}
@@ -57,21 +56,19 @@ public enum PatchSetApprovalProtoConverter
@Override
public PatchSetApproval fromProto(Entities.PatchSetApproval proto) {
- PatchSetApproval patchSetApproval =
- new PatchSetApproval(
- patchSetApprovalKeyProtoConverter.fromProto(proto.getKey()),
- (short) proto.getValue(),
- new Timestamp(proto.getGranted()));
+ PatchSetApproval.Builder builder =
+ PatchSetApproval.builder()
+ .key(patchSetApprovalKeyProtoConverter.fromProto(proto.getKey()))
+ .value(proto.getValue())
+ .granted(new Timestamp(proto.getGranted()))
+ .postSubmit(proto.getPostSubmit());
if (proto.hasTag()) {
- patchSetApproval.setTag(proto.getTag());
+ builder.tag(proto.getTag());
}
if (proto.hasRealAccountId()) {
- patchSetApproval.setRealAccountId(accountIdConverter.fromProto(proto.getRealAccountId()));
- }
- if (proto.hasPostSubmit()) {
- patchSetApproval.setPostSubmit(proto.getPostSubmit());
+ builder.realAccountId(accountIdConverter.fromProto(proto.getRealAccountId()));
}
- return patchSetApproval;
+ return builder.build();
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverter.java b/java/com/google/gerrit/entities/converter/PatchSetIdProtoConverter.java
index a2b95bd97d..60c13f18f9 100644
--- a/java/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/PatchSetIdProtoConverter.java
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
+@Immutable
public enum PatchSetIdProtoConverter implements ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> {
INSTANCE;
@@ -28,14 +30,14 @@ public enum PatchSetIdProtoConverter implements ProtoConverter<Entities.PatchSet
@Override
public Entities.PatchSet_Id toProto(PatchSet.Id patchSetId) {
return Entities.PatchSet_Id.newBuilder()
- .setChangeId(changeIdConverter.toProto(patchSetId.getParentKey()))
- .setPatchSetId(patchSetId.get())
+ .setChangeId(changeIdConverter.toProto(patchSetId.changeId()))
+ .setId(patchSetId.get())
.build();
}
@Override
public PatchSet.Id fromProto(Entities.PatchSet_Id proto) {
- return new PatchSet.Id(changeIdConverter.fromProto(proto.getChangeId()), proto.getPatchSetId());
+ return PatchSet.id(changeIdConverter.fromProto(proto.getChangeId()), proto.getId());
}
@Override
diff --git a/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java b/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java
new file mode 100644
index 0000000000..13a6e71a46
--- /dev/null
+++ b/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities.converter;
+
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.proto.Entities;
+import com.google.protobuf.Parser;
+import java.sql.Timestamp;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectId;
+
+@Immutable
+public enum PatchSetProtoConverter implements ProtoConverter<Entities.PatchSet, PatchSet> {
+ INSTANCE;
+
+ private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
+ PatchSetIdProtoConverter.INSTANCE;
+ private final ProtoConverter<Entities.ObjectId, ObjectId> objectIdConverter =
+ ObjectIdProtoConverter.INSTANCE;
+ private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
+ AccountIdProtoConverter.INSTANCE;
+
+ @Override
+ public Entities.PatchSet toProto(PatchSet patchSet) {
+ Entities.PatchSet.Builder builder =
+ Entities.PatchSet.newBuilder()
+ .setId(patchSetIdConverter.toProto(patchSet.id()))
+ .setCommitId(objectIdConverter.toProto(patchSet.commitId()))
+ .setUploaderAccountId(accountIdConverter.toProto(patchSet.uploader()))
+ .setCreatedOn(patchSet.createdOn().getTime());
+ List<String> groups = patchSet.groups();
+ if (!groups.isEmpty()) {
+ builder.setGroups(PatchSet.joinGroups(groups));
+ }
+ patchSet.pushCertificate().ifPresent(builder::setPushCertificate);
+ patchSet.description().ifPresent(builder::setDescription);
+ return builder.build();
+ }
+
+ @Override
+ public PatchSet fromProto(Entities.PatchSet proto) {
+ PatchSet.Builder builder =
+ PatchSet.builder()
+ .id(patchSetIdConverter.fromProto(proto.getId()))
+ .groups(
+ proto.hasGroups() ? PatchSet.splitGroups(proto.getGroups()) : ImmutableList.of());
+ if (proto.hasPushCertificate()) {
+ builder.pushCertificate(proto.getPushCertificate());
+ }
+ if (proto.hasDescription()) {
+ builder.description(proto.getDescription());
+ }
+
+ // The following fields used to theoretically be nullable in PatchSet, but in practice no
+ // production codepath should have ever serialized an instance that was missing one of these
+ // fields.
+ //
+ // However, since some protos may theoretically be missing these fields, we need to support
+ // them. Populate specific sentinel values for each field as documented in the PatchSet javadoc.
+ // Callers that encounter one of these sentinels will likely fail, for example by failing to
+ // look up the zeroId. They would have also failed back when the fields were nullable, for
+ // example with NPE; the current behavior just fails slightly differently.
+ builder
+ .commitId(
+ proto.hasCommitId()
+ ? objectIdConverter.fromProto(proto.getCommitId())
+ : ObjectId.zeroId())
+ .uploader(
+ proto.hasUploaderAccountId()
+ ? accountIdConverter.fromProto(proto.getUploaderAccountId())
+ : Account.id(0))
+ .createdOn(proto.hasCreatedOn() ? new Timestamp(proto.getCreatedOn()) : new Timestamp(0));
+
+ return builder.build();
+ }
+
+ @Override
+ public Parser<Entities.PatchSet> getParser() {
+ return Entities.PatchSet.parser();
+ }
+}
diff --git a/java/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverter.java b/java/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverter.java
index f7d809ec27..6bb0f79ea2 100644
--- a/java/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverter.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
+@Immutable
public enum ProjectNameKeyProtoConverter
implements ProtoConverter<Entities.Project_NameKey, Project.NameKey> {
INSTANCE;
@@ -29,7 +31,7 @@ public enum ProjectNameKeyProtoConverter
@Override
public Project.NameKey fromProto(Entities.Project_NameKey proto) {
- return new Project.NameKey(proto.getName());
+ return Project.nameKey(proto.getName());
}
@Override
diff --git a/java/com/google/gerrit/reviewdb/converter/ProtoConverter.java b/java/com/google/gerrit/entities/converter/ProtoConverter.java
index 568759c3c8..a5c4052f47 100644
--- a/java/com/google/gerrit/reviewdb/converter/ProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ProtoConverter.java
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
+import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.MessageLite;
import com.google.protobuf.Parser;
+@Immutable
public interface ProtoConverter<P extends MessageLite, C> {
P toProto(C valueClass);
diff --git a/java/com/google/gerrit/exceptions/BUILD b/java/com/google/gerrit/exceptions/BUILD
index e08c3fd79f..ef59be1338 100644
--- a/java/com/google/gerrit/exceptions/BUILD
+++ b/java/com/google/gerrit/exceptions/BUILD
@@ -4,5 +4,5 @@ java_library(
name = "exceptions",
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
- deps = ["//java/com/google/gerrit/reviewdb:server"],
+ deps = ["//java/com/google/gerrit/entities"],
)
diff --git a/java/com/google/gerrit/exceptions/InvalidMergeStrategyException.java b/java/com/google/gerrit/exceptions/InvalidMergeStrategyException.java
new file mode 100644
index 0000000000..d9c5776407
--- /dev/null
+++ b/java/com/google/gerrit/exceptions/InvalidMergeStrategyException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.exceptions;
+
+public class InvalidMergeStrategyException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidMergeStrategyException(String strategy) {
+ super("invalid merge strategy: " + strategy);
+ }
+}
diff --git a/java/com/google/gerrit/exceptions/NoSuchGroupException.java b/java/com/google/gerrit/exceptions/NoSuchGroupException.java
index dca28cb91b..95efac397c 100644
--- a/java/com/google/gerrit/exceptions/NoSuchGroupException.java
+++ b/java/com/google/gerrit/exceptions/NoSuchGroupException.java
@@ -14,7 +14,7 @@
package com.google.gerrit.exceptions;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
/** Indicates the account group does not exist. */
public class NoSuchGroupException extends Exception {
diff --git a/java/com/google/gerrit/extensions/BUILD b/java/com/google/gerrit/extensions/BUILD
index b5e58630d7..da5dc8bdcf 100644
--- a/java/com/google/gerrit/extensions/BUILD
+++ b/java/com/google/gerrit/extensions/BUILD
@@ -1,8 +1,11 @@
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//lib:guava.bzl", "GUAVA_DOC_URL")
-load("//lib/jgit:jgit.bzl", "JGIT_DOC_URL")
load("//tools/bzl:javadoc.bzl", "java_doc")
+_DOC_VERS = "5.5.0.201909110433-r"
+
+JGIT_DOC_URL = "https://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
+
java_binary(
name = "extension-api",
main_class = "Dummy",
@@ -23,7 +26,6 @@ java_library(
],
)
-#TODO(davido): There is no provided_deps argument to java_library rule
java_library(
name = "api",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 3d5fcccde8..f0462c6ea5 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -209,6 +210,10 @@ public interface ChangeApi {
return suggestReviewers().withQuery(query);
}
+ default SuggestedReviewersRequest suggestCcs(String query) throws RestApiException {
+ return suggestReviewers().forCc().withQuery(query);
+ }
+
/**
* Retrieve reviewers ({@code ReviewerState.REVIEWER} and {@code ReviewerState.CC}) on the change.
*/
@@ -241,12 +246,16 @@ public interface ChangeApi {
* <ul>
* <li>{@code CHECK} is omitted, to skip consistency checks.
* <li>{@code SKIP_MERGEABLE} is omitted, so the {@code mergeable} bit <em>is</em> set.
+ * <li>{@code SKIP_DIFFSTAT} is omitted to ensure diffstat calculations.
* </ul>
*/
default ChangeInfo get() throws RestApiException {
return get(
EnumSet.complementOf(
- EnumSet.of(ListChangesOption.CHECK, ListChangesOption.SKIP_MERGEABLE)));
+ EnumSet.of(
+ ListChangesOption.CHECK,
+ ListChangesOption.SKIP_MERGEABLE,
+ ListChangesOption.SKIP_DIFFSTAT)));
}
/** {@link #get(ListChangesOption...)} with no options included. */
@@ -377,6 +386,8 @@ public interface ChangeApi {
abstract class SuggestedReviewersRequest {
private String query;
private int limit;
+ private boolean excludeGroups;
+ private ReviewerState reviewerState = ReviewerState.REVIEWER;
public abstract List<SuggestedReviewerInfo> get() throws RestApiException;
@@ -390,6 +401,16 @@ public interface ChangeApi {
return this;
}
+ public SuggestedReviewersRequest excludeGroups(boolean excludeGroups) {
+ this.excludeGroups = excludeGroups;
+ return this;
+ }
+
+ public SuggestedReviewersRequest forCc() {
+ this.reviewerState = ReviewerState.CC;
+ return this;
+ }
+
public String getQuery() {
return query;
}
@@ -397,6 +418,14 @@ public interface ChangeApi {
public int getLimit() {
return limit;
}
+
+ public boolean getExcludeGroups() {
+ return excludeGroups;
+ }
+
+ public ReviewerState getReviewerState() {
+ return reviewerState;
+ }
}
/**
diff --git a/java/com/google/gerrit/extensions/api/changes/NotifyInfo.java b/java/com/google/gerrit/extensions/api/changes/NotifyInfo.java
index ef49651bc0..dd29635b1b 100644
--- a/java/com/google/gerrit/extensions/api/changes/NotifyInfo.java
+++ b/java/com/google/gerrit/extensions/api/changes/NotifyInfo.java
@@ -14,13 +14,38 @@
package com.google.gerrit.extensions.api.changes;
+import com.google.common.base.MoreObjects;
import java.util.List;
+import java.util.Objects;
/** Detailed information about who should be notified about an update. */
public class NotifyInfo {
public List<String> accounts;
+ /**
+ * @param accounts may be either just a list of: account IDs, Full names, usernames, or emails.
+ * Also could be a list of those: "Full name <email@example.com>" or "Full name (<ID>)"
+ */
public NotifyInfo(List<String> accounts) {
this.accounts = accounts;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof NotifyInfo)) {
+ return false;
+ }
+ NotifyInfo other = (NotifyInfo) o;
+ return Objects.equals(other.accounts, accounts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(accounts);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("accounts", accounts).toString();
+ }
}
diff --git a/java/com/google/gerrit/extensions/api/changes/RevertInput.java b/java/com/google/gerrit/extensions/api/changes/RevertInput.java
index c1be9b04f0..c4272e493c 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevertInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevertInput.java
@@ -24,4 +24,6 @@ public class RevertInput {
public NotifyHandling notify = NotifyHandling.ALL;
public Map<RecipientType, NotifyInfo> notifyDetails;
+
+ public String topic;
}
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 68575ca6cc..f854d4aaa5 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -136,7 +136,7 @@ public interface RevisionApi {
SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException;
- List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException;
+ TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException;
MergeListRequest getMergeList() throws RestApiException;
@@ -341,7 +341,7 @@ public interface RevisionApi {
}
@Override
- public List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
+ public TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/api/groups/GroupInput.java b/java/com/google/gerrit/extensions/api/groups/GroupInput.java
index ab38434168..30af08fb5a 100644
--- a/java/com/google/gerrit/extensions/api/groups/GroupInput.java
+++ b/java/com/google/gerrit/extensions/api/groups/GroupInput.java
@@ -18,6 +18,7 @@ import java.util.List;
public class GroupInput {
public String name;
+ public String uuid;
public String description;
public Boolean visibleToAll;
public String ownerId;
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 3d70996fd3..c6d9dee3e4 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -201,6 +201,9 @@ public interface ProjectApi {
*/
void index(boolean indexChildren) throws RestApiException;
+ /** Reindexes all changes of the project. */
+ void indexChanges() throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -370,5 +373,10 @@ public interface ProjectApi {
public void index(boolean indexChildren) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public void indexChanges() throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/extensions/client/Comment.java b/java/com/google/gerrit/extensions/client/Comment.java
index 3bca4bb9ec..d5fbf8964f 100644
--- a/java/com/google/gerrit/extensions/client/Comment.java
+++ b/java/com/google/gerrit/extensions/client/Comment.java
@@ -30,7 +30,9 @@ public abstract class Comment {
public String path;
public Side side;
public Integer parent;
- public Integer line; // value 0 or null indicates a file comment, normal lines start at 1
+ /** Value 0 or null indicates a file comment, normal lines start at 1. */
+ public Integer line;
+
public Range range;
public String inReplyTo;
public Timestamp updated;
diff --git a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
index 652abcc7ab..ed01a4dd90 100644
--- a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
@@ -38,7 +38,7 @@ public class DiffPreferencesInfo {
IGNORE_NONE,
IGNORE_TRAILING,
IGNORE_LEADING_AND_TRAILING,
- IGNORE_ALL;
+ IGNORE_ALL
}
public Integer context;
@@ -73,17 +73,12 @@ public class DiffPreferencesInfo {
i.fontSize = DEFAULT_FONT_SIZE;
i.lineLength = DEFAULT_LINE_LENGTH;
i.cursorBlinkRate = 0;
- i.ignoreWhitespace = Whitespace.IGNORE_NONE;
i.expandAllComments = false;
i.intralineDifference = true;
i.manualReview = false;
- i.retainHeader = false;
i.showLineEndings = true;
i.showTabs = true;
i.showWhitespaceErrors = true;
- i.skipDeleted = false;
- i.skipUnchanged = false;
- i.skipUncommented = false;
i.syntaxHighlighting = true;
i.hideTopMenu = false;
i.autoHideDiffTableHeader = true;
@@ -92,6 +87,11 @@ public class DiffPreferencesInfo {
i.hideEmptyPane = false;
i.matchBrackets = false;
i.lineWrapping = false;
+ i.ignoreWhitespace = Whitespace.IGNORE_NONE;
+ i.retainHeader = false;
+ i.skipDeleted = false;
+ i.skipUnchanged = false;
+ i.skipUncommented = false;
return i;
}
}
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index 8eb54e17b4..212f6da7a9 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -67,14 +67,6 @@ public class GeneralPreferencesInfo {
}
}
- public enum ReviewCategoryStrategy {
- NONE,
- NAME,
- EMAIL,
- USERNAME,
- ABBREV
- }
-
public enum DiffView {
SIDE_BY_SIDE,
UNIFIED_DIFF
@@ -130,10 +122,6 @@ public class GeneralPreferencesInfo {
/** Number of changes to show in a screen. */
public Integer changesPerPage;
- /** Should the site header be displayed when logged in ? */
- public Boolean showSiteHeader;
- /** Should the Flash helper movie be used to copy text to the clipboard? */
- public Boolean useFlashClipboard;
/** Type of download URL the user prefers to use. */
public String downloadScheme;
@@ -145,20 +133,15 @@ public class GeneralPreferencesInfo {
public DiffView diffView;
public Boolean sizeBarInChangeTable;
public Boolean legacycidInChangeTable;
- public ReviewCategoryStrategy reviewCategoryStrategy;
public Boolean muteCommonPathPrefixes;
public Boolean signedOffBy;
- public List<MenuItem> my;
- public List<String> changeTable;
public EmailStrategy emailStrategy;
public EmailFormat emailFormat;
public DefaultBase defaultBaseForMerges;
public Boolean publishCommentsOnPush;
public Boolean workInProgressByDefault;
-
- public boolean isShowInfoInReviewCategory() {
- return getReviewCategoryStrategy() != ReviewCategoryStrategy.NONE;
- }
+ public List<MenuItem> my;
+ public List<String> changeTable;
public DateFormat getDateFormat() {
if (dateFormat == null) {
@@ -174,13 +157,6 @@ public class GeneralPreferencesInfo {
return timeFormat;
}
- public ReviewCategoryStrategy getReviewCategoryStrategy() {
- if (reviewCategoryStrategy == null) {
- return ReviewCategoryStrategy.NONE;
- }
- return reviewCategoryStrategy;
- }
-
public DiffView getDiffView() {
if (diffView == null) {
return DiffView.SIDE_BY_SIDE;
@@ -205,11 +181,6 @@ public class GeneralPreferencesInfo {
public static GeneralPreferencesInfo defaults() {
GeneralPreferencesInfo p = new GeneralPreferencesInfo();
p.changesPerPage = DEFAULT_PAGESIZE;
- p.showSiteHeader = true;
- p.useFlashClipboard = true;
- p.emailStrategy = EmailStrategy.ENABLED;
- p.emailFormat = EmailFormat.HTML_PLAINTEXT;
- p.reviewCategoryStrategy = ReviewCategoryStrategy.NONE;
p.downloadScheme = null;
p.dateFormat = DateFormat.STD;
p.timeFormat = TimeFormat.HHMM_12;
@@ -221,6 +192,8 @@ public class GeneralPreferencesInfo {
p.legacycidInChangeTable = false;
p.muteCommonPathPrefixes = true;
p.signedOffBy = false;
+ p.emailStrategy = EmailStrategy.ENABLED;
+ p.emailFormat = EmailFormat.HTML_PLAINTEXT;
p.defaultBaseForMerges = DefaultBase.FIRST_PARENT;
p.publishCommentsOnPush = false;
p.workInProgressByDefault = false;
diff --git a/java/com/google/gerrit/extensions/client/ListChangesOption.java b/java/com/google/gerrit/extensions/client/ListChangesOption.java
index c842adc61f..425265b3fe 100644
--- a/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -75,7 +75,13 @@ public enum ListChangesOption implements ListOption {
TRACKING_IDS(21),
/** Skip mergeability data */
- SKIP_MERGEABLE(22);
+ SKIP_MERGEABLE(22),
+
+ /**
+ * Skip diffstat computation that compute the insertions field (number of lines inserted) and
+ * deletions field (number of lines deleted)
+ */
+ SKIP_DIFFSTAT(23);
private final int value;
diff --git a/java/com/google/gerrit/extensions/common/AccountDetailInfo.java b/java/com/google/gerrit/extensions/common/AccountDetailInfo.java
index a498ab0465..3ffa97a212 100644
--- a/java/com/google/gerrit/extensions/common/AccountDetailInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountDetailInfo.java
@@ -18,7 +18,6 @@ import java.sql.Timestamp;
public class AccountDetailInfo extends AccountInfo {
public Timestamp registeredOn;
- public Boolean inactive;
public AccountDetailInfo(Integer id) {
super(id);
diff --git a/java/com/google/gerrit/extensions/common/AccountInfo.java b/java/com/google/gerrit/extensions/common/AccountInfo.java
index f20509ba6a..d1bbe88230 100644
--- a/java/com/google/gerrit/extensions/common/AccountInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -14,6 +14,7 @@
package com.google.gerrit.extensions.common;
+import com.google.common.base.MoreObjects;
import java.util.List;
import java.util.Objects;
@@ -26,6 +27,7 @@ public class AccountInfo {
public List<AvatarInfo> avatars;
public Boolean _moreAccounts;
public String status;
+ public Boolean inactive;
public AccountInfo(Integer id) {
this._accountId = id;
@@ -54,6 +56,16 @@ public class AccountInfo {
}
@Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", _accountId)
+ .add("name", name)
+ .add("email", email)
+ .add("username", username)
+ .toString();
+ }
+
+ @Override
public int hashCode() {
return Objects.hash(
_accountId, name, email, secondaryEmails, username, avatars, _moreAccounts, status);
diff --git a/java/com/google/gerrit/extensions/common/AvatarInfo.java b/java/com/google/gerrit/extensions/common/AvatarInfo.java
index 00f18191ea..de609ebf2b 100644
--- a/java/com/google/gerrit/extensions/common/AvatarInfo.java
+++ b/java/com/google/gerrit/extensions/common/AvatarInfo.java
@@ -21,7 +21,7 @@ public class AvatarInfo {
* <p>The web UI prefers avatar images to be square, both the height and width of the image should
* be this size. The height is the more important dimension to match than the width.
*/
- public static final int DEFAULT_SIZE = 26;
+ public static final int DEFAULT_SIZE = 32;
public String url;
public Integer height;
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index 1e822e37e6..e8aeb40eb8 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -23,4 +23,5 @@ public class ChangeConfigInfo {
public String replyTooltip;
public int updateDelay;
public Boolean submitWholeTopic;
+ public Boolean excludeMergeableInChangeInfo;
}
diff --git a/java/com/google/gerrit/extensions/common/GroupAuditEventInfo.java b/java/com/google/gerrit/extensions/common/GroupAuditEventInfo.java
index 3e6f7625ce..711337a342 100644
--- a/java/com/google/gerrit/extensions/common/GroupAuditEventInfo.java
+++ b/java/com/google/gerrit/extensions/common/GroupAuditEventInfo.java
@@ -15,6 +15,7 @@
package com.google.gerrit.extensions.common;
import java.sql.Timestamp;
+import java.util.Optional;
public abstract class GroupAuditEventInfo {
public enum Type {
@@ -30,35 +31,35 @@ public abstract class GroupAuditEventInfo {
public static UserMemberAuditEventInfo createAddUserEvent(
AccountInfo user, Timestamp date, AccountInfo member) {
- return new UserMemberAuditEventInfo(Type.ADD_USER, user, date, member);
+ return new UserMemberAuditEventInfo(Type.ADD_USER, user, Optional.of(date), member);
}
public static UserMemberAuditEventInfo createRemoveUserEvent(
- AccountInfo user, Timestamp date, AccountInfo member) {
+ AccountInfo user, Optional<Timestamp> date, AccountInfo member) {
return new UserMemberAuditEventInfo(Type.REMOVE_USER, user, date, member);
}
public static GroupMemberAuditEventInfo createAddGroupEvent(
AccountInfo user, Timestamp date, GroupInfo member) {
- return new GroupMemberAuditEventInfo(Type.ADD_GROUP, user, date, member);
+ return new GroupMemberAuditEventInfo(Type.ADD_GROUP, user, Optional.of(date), member);
}
public static GroupMemberAuditEventInfo createRemoveGroupEvent(
- AccountInfo user, Timestamp date, GroupInfo member) {
+ AccountInfo user, Optional<Timestamp> date, GroupInfo member) {
return new GroupMemberAuditEventInfo(Type.REMOVE_GROUP, user, date, member);
}
- protected GroupAuditEventInfo(Type type, AccountInfo user, Timestamp date) {
+ protected GroupAuditEventInfo(Type type, AccountInfo user, Optional<Timestamp> date) {
this.type = type;
this.user = user;
- this.date = date;
+ this.date = date.orElse(null);
}
public static class UserMemberAuditEventInfo extends GroupAuditEventInfo {
public AccountInfo member;
- public UserMemberAuditEventInfo(
- Type type, AccountInfo user, Timestamp date, AccountInfo member) {
+ private UserMemberAuditEventInfo(
+ Type type, AccountInfo user, Optional<Timestamp> date, AccountInfo member) {
super(type, user, date);
this.member = member;
}
@@ -67,8 +68,8 @@ public abstract class GroupAuditEventInfo {
public static class GroupMemberAuditEventInfo extends GroupAuditEventInfo {
public GroupInfo member;
- public GroupMemberAuditEventInfo(
- Type type, AccountInfo user, Timestamp date, GroupInfo member) {
+ private GroupMemberAuditEventInfo(
+ Type type, AccountInfo user, Optional<Timestamp> date, GroupInfo member) {
super(type, user, date);
this.member = member;
}
diff --git a/java/com/google/gerrit/extensions/common/testing/BUILD b/java/com/google/gerrit/extensions/common/testing/BUILD
index 9cecb66e0c..c976de0a04 100644
--- a/java/com/google/gerrit/extensions/common/testing/BUILD
+++ b/java/com/google/gerrit/extensions/common/testing/BUILD
@@ -9,7 +9,7 @@ java_library(
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/truth",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
index f0f5516a5e..d6fcb37524 100644
--- a/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/CommitInfoSubject.java
@@ -24,7 +24,7 @@ import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.truth.ListSubject;
-public class CommitInfoSubject extends Subject<CommitInfoSubject, CommitInfo> {
+public class CommitInfoSubject extends Subject {
public static CommitInfoSubject assertThat(CommitInfo commitInfo) {
return assertAbout(commits()).that(commitInfo);
@@ -34,37 +34,35 @@ public class CommitInfoSubject extends Subject<CommitInfoSubject, CommitInfo> {
return CommitInfoSubject::new;
}
+ private final CommitInfo commitInfo;
+
private CommitInfoSubject(FailureMetadata failureMetadata, CommitInfo commitInfo) {
super(failureMetadata, commitInfo);
+ this.commitInfo = commitInfo;
}
public StringSubject commit() {
isNotNull();
- CommitInfo commitInfo = actual();
- return check("commit()").that(commitInfo.commit);
+ return check("commit").that(commitInfo.commit);
}
public ListSubject<CommitInfoSubject, CommitInfo> parents() {
isNotNull();
- CommitInfo commitInfo = actual();
- return check("parents()").about(elements()).thatCustom(commitInfo.parents, commits());
+ return check("parents").about(elements()).thatCustom(commitInfo.parents, commits());
}
public GitPersonSubject committer() {
isNotNull();
- CommitInfo commitInfo = actual();
- return check("committer()").about(gitPersons()).that(commitInfo.committer);
+ return check("committer").about(gitPersons()).that(commitInfo.committer);
}
public GitPersonSubject author() {
isNotNull();
- CommitInfo commitInfo = actual();
- return check("author()").about(gitPersons()).that(commitInfo.author);
+ return check("author").about(gitPersons()).that(commitInfo.author);
}
public StringSubject message() {
isNotNull();
- CommitInfo commitInfo = actual();
return check("message").that(commitInfo.message);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
index 25750c1e09..b55f7c209a 100644
--- a/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/ContentEntrySubject.java
@@ -27,7 +27,7 @@ import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
import com.google.gerrit.truth.ListSubject;
-public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEntry> {
+public class ContentEntrySubject extends Subject {
public static ContentEntrySubject assertThat(ContentEntry contentEntry) {
return assertAbout(contentEntries()).that(contentEntry);
@@ -37,13 +37,15 @@ public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEnt
return ContentEntrySubject::new;
}
+ private final ContentEntry contentEntry;
+
private ContentEntrySubject(FailureMetadata failureMetadata, ContentEntry contentEntry) {
super(failureMetadata, contentEntry);
+ this.contentEntry = contentEntry;
}
public void isDueToRebase() {
isNotNull();
- ContentEntry contentEntry = actual();
if (contentEntry.dueToRebase == null || !contentEntry.dueToRebase) {
failWithActual(simpleFact("expected entry to be marked 'dueToRebase'"));
}
@@ -51,7 +53,6 @@ public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEnt
public void isNotDueToRebase() {
isNotNull();
- ContentEntry contentEntry = actual();
if (contentEntry.dueToRebase != null && contentEntry.dueToRebase) {
failWithActual(simpleFact("expected entry not to be marked 'dueToRebase'"));
}
@@ -59,7 +60,6 @@ public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEnt
public ListSubject<StringSubject, String> commonLines() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("commonLines()")
.about(elements())
.that(contentEntry.ab, StandardSubjectBuilder::that);
@@ -67,31 +67,26 @@ public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEnt
public ListSubject<StringSubject, String> linesOfA() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("linesOfA()").about(elements()).that(contentEntry.a, StandardSubjectBuilder::that);
}
public ListSubject<StringSubject, String> linesOfB() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("linesOfB()").about(elements()).that(contentEntry.b, StandardSubjectBuilder::that);
}
public IterableSubject intralineEditsOfA() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("intralineEditsOfA()").that(contentEntry.editA);
}
public IterableSubject intralineEditsOfB() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("intralineEditsOfB()").that(contentEntry.editB);
}
public IntegerSubject numberOfSkippedLines() {
isNotNull();
- ContentEntry contentEntry = actual();
return check("numberOfSkippedLines()").that(contentEntry.skip);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
index ee37bde8ee..8853a30b56 100644
--- a/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
@@ -26,39 +26,38 @@ import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
import com.google.gerrit.truth.ListSubject;
-public class DiffInfoSubject extends Subject<DiffInfoSubject, DiffInfo> {
+public class DiffInfoSubject extends Subject {
public static DiffInfoSubject assertThat(DiffInfo diffInfo) {
return assertAbout(DiffInfoSubject::new).that(diffInfo);
}
+ private final DiffInfo diffInfo;
+
private DiffInfoSubject(FailureMetadata failureMetadata, DiffInfo diffInfo) {
super(failureMetadata, diffInfo);
+ this.diffInfo = diffInfo;
}
public ListSubject<ContentEntrySubject, ContentEntry> content() {
isNotNull();
- DiffInfo diffInfo = actual();
- return check("content()")
+ return check("content")
.about(elements())
.thatCustom(diffInfo.content, ContentEntrySubject.contentEntries());
}
- public ComparableSubject<?, ChangeType> changeType() {
+ public ComparableSubject<ChangeType> changeType() {
isNotNull();
- DiffInfo diffInfo = actual();
- return check("changeType()").that(diffInfo.changeType);
+ return check("changeType").that(diffInfo.changeType);
}
public FileMetaSubject metaA() {
isNotNull();
- DiffInfo diffInfo = actual();
- return check("metaA()").about(fileMetas()).that(diffInfo.metaA);
+ return check("metaA").about(fileMetas()).that(diffInfo.metaA);
}
public FileMetaSubject metaB() {
isNotNull();
- DiffInfo diffInfo = actual();
- return check("metaB()").about(fileMetas()).that(diffInfo.metaB);
+ return check("metaB").about(fileMetas()).that(diffInfo.metaB);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/EditInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/EditInfoSubject.java
index 1c99141fdf..b5622e02ce 100644
--- a/java/com/google/gerrit/extensions/common/testing/EditInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/EditInfoSubject.java
@@ -24,7 +24,7 @@ import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.truth.OptionalSubject;
import java.util.Optional;
-public class EditInfoSubject extends Subject<EditInfoSubject, EditInfo> {
+public class EditInfoSubject extends Subject {
public static EditInfoSubject assertThat(EditInfo editInfo) {
return assertAbout(edits()).that(editInfo);
@@ -39,19 +39,20 @@ public class EditInfoSubject extends Subject<EditInfoSubject, EditInfo> {
return OptionalSubject.assertThat(editInfoOptional, edits());
}
+ private final EditInfo editInfo;
+
private EditInfoSubject(FailureMetadata failureMetadata, EditInfo editInfo) {
super(failureMetadata, editInfo);
+ this.editInfo = editInfo;
}
public CommitInfoSubject commit() {
isNotNull();
- EditInfo editInfo = actual();
- return check("commit()").about(commits()).that(editInfo.commit);
+ return check("commit").about(commits()).that(editInfo.commit);
}
public StringSubject baseRevision() {
isNotNull();
- EditInfo editInfo = actual();
- return check("baseRevision()").that(editInfo.baseRevision);
+ return check("baseRevision").that(editInfo.baseRevision);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/FileInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/FileInfoSubject.java
index 3ebf8388d9..d011d5d21d 100644
--- a/java/com/google/gerrit/extensions/common/testing/FileInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FileInfoSubject.java
@@ -22,31 +22,31 @@ import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.FileInfo;
-public class FileInfoSubject extends Subject<FileInfoSubject, FileInfo> {
+public class FileInfoSubject extends Subject {
public static FileInfoSubject assertThat(FileInfo fileInfo) {
return assertAbout(FileInfoSubject::new).that(fileInfo);
}
+ private final FileInfo fileInfo;
+
private FileInfoSubject(FailureMetadata failureMetadata, FileInfo fileInfo) {
super(failureMetadata, fileInfo);
+ this.fileInfo = fileInfo;
}
public IntegerSubject linesInserted() {
isNotNull();
- FileInfo fileInfo = actual();
- return check("linesInserted()").that(fileInfo.linesInserted);
+ return check("linesInserted").that(fileInfo.linesInserted);
}
public IntegerSubject linesDeleted() {
isNotNull();
- FileInfo fileInfo = actual();
- return check("linesDeleted()").that(fileInfo.linesDeleted);
+ return check("linesDeleted").that(fileInfo.linesDeleted);
}
- public ComparableSubject<?, Character> status() {
+ public ComparableSubject<Character> status() {
isNotNull();
- FileInfo fileInfo = actual();
- return check("status()").that(fileInfo.status);
+ return check("status").that(fileInfo.status);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
index d1b2031b99..fb09a1ff9b 100644
--- a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
@@ -21,7 +21,7 @@ import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
-public class FileMetaSubject extends Subject<FileMetaSubject, FileMeta> {
+public class FileMetaSubject extends Subject {
public static FileMetaSubject assertThat(FileMeta fileMeta) {
return assertAbout(fileMetas()).that(fileMeta);
@@ -31,13 +31,15 @@ public class FileMetaSubject extends Subject<FileMetaSubject, FileMeta> {
return FileMetaSubject::new;
}
+ private final FileMeta fileMeta;
+
private FileMetaSubject(FailureMetadata failureMetadata, FileMeta fileMeta) {
super(failureMetadata, fileMeta);
+ this.fileMeta = fileMeta;
}
public IntegerSubject totalLineCount() {
isNotNull();
- FileMeta fileMeta = actual();
return check("totalLineCount()").that(fileMeta.lines);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/FixReplacementInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/FixReplacementInfoSubject.java
index 6ba5f8b291..9ba69dc2d3 100644
--- a/java/com/google/gerrit/extensions/common/testing/FixReplacementInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FixReplacementInfoSubject.java
@@ -22,8 +22,7 @@ import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.common.FixReplacementInfo;
-public class FixReplacementInfoSubject
- extends Subject<FixReplacementInfoSubject, FixReplacementInfo> {
+public class FixReplacementInfoSubject extends Subject {
public static FixReplacementInfoSubject assertThat(FixReplacementInfo fixReplacementInfo) {
return assertAbout(fixReplacements()).that(fixReplacementInfo);
@@ -33,23 +32,26 @@ public class FixReplacementInfoSubject
return FixReplacementInfoSubject::new;
}
+ private final FixReplacementInfo fixReplacementInfo;
+
private FixReplacementInfoSubject(
FailureMetadata failureMetadata, FixReplacementInfo fixReplacementInfo) {
super(failureMetadata, fixReplacementInfo);
+ this.fixReplacementInfo = fixReplacementInfo;
}
public StringSubject path() {
isNotNull();
- return check("path()").that(actual().path);
+ return check("path").that(fixReplacementInfo.path);
}
public RangeSubject range() {
isNotNull();
- return check("range()").about(ranges()).that(actual().range);
+ return check("range").about(ranges()).that(fixReplacementInfo.range);
}
public StringSubject replacement() {
isNotNull();
- return check("replacement()").that(actual().replacement);
+ return check("replacement").that(fixReplacementInfo.replacement);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/FixSuggestionInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/FixSuggestionInfoSubject.java
index 98dac38aa8..4ac725adc9 100644
--- a/java/com/google/gerrit/extensions/common/testing/FixSuggestionInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FixSuggestionInfoSubject.java
@@ -21,12 +21,11 @@ import static com.google.gerrit.truth.ListSubject.elements;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.Truth;
import com.google.gerrit.extensions.common.FixReplacementInfo;
import com.google.gerrit.extensions.common.FixSuggestionInfo;
import com.google.gerrit.truth.ListSubject;
-public class FixSuggestionInfoSubject extends Subject<FixSuggestionInfoSubject, FixSuggestionInfo> {
+public class FixSuggestionInfoSubject extends Subject {
public static FixSuggestionInfoSubject assertThat(FixSuggestionInfo fixSuggestionInfo) {
return assertAbout(fixSuggestions()).that(fixSuggestionInfo);
@@ -36,20 +35,23 @@ public class FixSuggestionInfoSubject extends Subject<FixSuggestionInfoSubject,
return FixSuggestionInfoSubject::new;
}
+ private final FixSuggestionInfo fixSuggestionInfo;
+
private FixSuggestionInfoSubject(
FailureMetadata failureMetadata, FixSuggestionInfo fixSuggestionInfo) {
super(failureMetadata, fixSuggestionInfo);
+ this.fixSuggestionInfo = fixSuggestionInfo;
}
public StringSubject fixId() {
- return Truth.assertThat(actual().fixId).named("fixId");
+ return check("fixId").that(fixSuggestionInfo.fixId);
}
public ListSubject<FixReplacementInfoSubject, FixReplacementInfo> replacements() {
isNotNull();
- return check("replacements()")
+ return check("replacements")
.about(elements())
- .thatCustom(actual().replacements, fixReplacements());
+ .thatCustom(fixSuggestionInfo.replacements, fixReplacements());
}
public FixReplacementInfoSubject onlyReplacement() {
@@ -58,6 +60,6 @@ public class FixSuggestionInfoSubject extends Subject<FixSuggestionInfoSubject,
public StringSubject description() {
isNotNull();
- return check("description()").that(actual().description);
+ return check("description").that(fixSuggestionInfo.description);
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/GitPersonSubject.java b/java/com/google/gerrit/extensions/common/testing/GitPersonSubject.java
index dee0636a54..d827d5d9f9 100644
--- a/java/com/google/gerrit/extensions/common/testing/GitPersonSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/GitPersonSubject.java
@@ -14,8 +14,8 @@
package com.google.gerrit.extensions.common.testing;
-import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
import com.google.common.truth.ComparableSubject;
import com.google.common.truth.FailureMetadata;
@@ -27,7 +27,7 @@ import java.sql.Timestamp;
import java.util.Date;
import org.eclipse.jgit.lib.PersonIdent;
-public class GitPersonSubject extends Subject<GitPersonSubject, GitPerson> {
+public class GitPersonSubject extends Subject {
public static GitPersonSubject assertThat(GitPerson gitPerson) {
return assertAbout(gitPersons()).that(gitPerson);
@@ -37,36 +37,35 @@ public class GitPersonSubject extends Subject<GitPersonSubject, GitPerson> {
return GitPersonSubject::new;
}
+ private final GitPerson gitPerson;
+
private GitPersonSubject(FailureMetadata failureMetadata, GitPerson gitPerson) {
super(failureMetadata, gitPerson);
+ this.gitPerson = gitPerson;
}
public StringSubject name() {
isNotNull();
- GitPerson gitPerson = actual();
- return check("name()").that(gitPerson.name);
+ return check("name").that(gitPerson.name);
}
public StringSubject email() {
isNotNull();
- GitPerson gitPerson = actual();
- return check("email()").that(gitPerson.email);
+ return check("email").that(gitPerson.email);
}
- public ComparableSubject<?, Timestamp> date() {
+ public ComparableSubject<Timestamp> date() {
isNotNull();
- GitPerson gitPerson = actual();
- return check("date()").that(gitPerson.date);
+ return check("date").that(gitPerson.date);
}
public IntegerSubject tz() {
isNotNull();
- GitPerson gitPerson = actual();
- return check("tz()").that(gitPerson.tz);
+ return check("tz").that(gitPerson.tz);
}
public void hasSameDateAs(GitPerson other) {
- checkNotNull(other, "'other' GitPerson must not be null");
+ requireNonNull(other, "'other' GitPerson must not be null");
isNotNull();
date().isEqualTo(other.date);
tz().isEqualTo(other.tz);
@@ -76,7 +75,7 @@ public class GitPersonSubject extends Subject<GitPersonSubject, GitPerson> {
isNotNull();
name().isEqualTo(ident.getName());
email().isEqualTo(ident.getEmailAddress());
- check("roundedDate()").that(new Date(actual().date.getTime())).isEqualTo(ident.getWhen());
+ check("roundedDate()").that(new Date(gitPerson.date.getTime())).isEqualTo(ident.getWhen());
tz().isEqualTo(ident.getTimeZoneOffset());
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/RangeSubject.java b/java/com/google/gerrit/extensions/common/testing/RangeSubject.java
index 12acb8d8c7..10abca2459 100644
--- a/java/com/google/gerrit/extensions/common/testing/RangeSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/RangeSubject.java
@@ -22,7 +22,7 @@ import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
import com.google.gerrit.extensions.client.Comment;
-public class RangeSubject extends Subject<RangeSubject, Comment.Range> {
+public class RangeSubject extends Subject {
public static RangeSubject assertThat(Comment.Range range) {
return assertAbout(ranges()).that(range);
@@ -32,36 +32,39 @@ public class RangeSubject extends Subject<RangeSubject, Comment.Range> {
return RangeSubject::new;
}
+ private final Comment.Range range;
+
private RangeSubject(FailureMetadata failureMetadata, Comment.Range range) {
super(failureMetadata, range);
+ this.range = range;
}
public IntegerSubject startLine() {
- return check("startLine()").that(actual().startLine);
+ return check("startLine").that(range.startLine);
}
public IntegerSubject startCharacter() {
- return check("startCharacter()").that(actual().startCharacter);
+ return check("startCharacter").that(range.startCharacter);
}
public IntegerSubject endLine() {
- return check("endLine()").that(actual().endLine);
+ return check("endLine").that(range.endLine);
}
public IntegerSubject endCharacter() {
- return check("endCharacter()").that(actual().endCharacter);
+ return check("endCharacter").that(range.endCharacter);
}
public void isValid() {
isNotNull();
- if (!actual().isValid()) {
+ if (!range.isValid()) {
failWithActual(simpleFact("expected to be valid"));
}
}
public void isInvalid() {
isNotNull();
- if (actual().isValid()) {
+ if (range.isValid()) {
failWithActual(simpleFact("expected to be invalid"));
}
}
diff --git a/java/com/google/gerrit/extensions/common/testing/RobotCommentInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/RobotCommentInfoSubject.java
index 033f54ba59..0698735acf 100644
--- a/java/com/google/gerrit/extensions/common/testing/RobotCommentInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/RobotCommentInfoSubject.java
@@ -24,11 +24,11 @@ import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.truth.ListSubject;
import java.util.List;
-public class RobotCommentInfoSubject extends Subject<RobotCommentInfoSubject, RobotCommentInfo> {
+public class RobotCommentInfoSubject extends Subject {
public static ListSubject<RobotCommentInfoSubject, RobotCommentInfo> assertThatList(
List<RobotCommentInfo> robotCommentInfos) {
- return ListSubject.assertThat(robotCommentInfos, robotComments()).named("robotCommentInfos");
+ return ListSubject.assertThat(robotCommentInfos, robotComments());
}
public static RobotCommentInfoSubject assertThat(RobotCommentInfo robotCommentInfo) {
@@ -39,15 +39,18 @@ public class RobotCommentInfoSubject extends Subject<RobotCommentInfoSubject, Ro
return RobotCommentInfoSubject::new;
}
+ private final RobotCommentInfo robotCommentInfo;
+
private RobotCommentInfoSubject(
FailureMetadata failureMetadata, RobotCommentInfo robotCommentInfo) {
super(failureMetadata, robotCommentInfo);
+ this.robotCommentInfo = robotCommentInfo;
}
public ListSubject<FixSuggestionInfoSubject, FixSuggestionInfo> fixSuggestions() {
- return check("fixSuggestions()")
+ return check("fixSuggestions")
.about(elements())
- .thatCustom(actual().fixSuggestions, FixSuggestionInfoSubject.fixSuggestions());
+ .thatCustom(robotCommentInfo.fixSuggestions, FixSuggestionInfoSubject.fixSuggestions());
}
public FixSuggestionInfoSubject onlyFixSuggestion() {
diff --git a/java/com/google/gerrit/extensions/events/AccountActivationListener.java b/java/com/google/gerrit/extensions/events/AccountActivationListener.java
new file mode 100644
index 0000000000..b45533b8bf
--- /dev/null
+++ b/java/com/google/gerrit/extensions/events/AccountActivationListener.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/**
+ * Notified whenever an account got activated or deactivated.
+ *
+ * <p>This listener is called only after an account got (de)activated and hence cannot cancel the
+ * (de)activation. See {@link
+ * com.google.gerrit.server.validators.AccountActivationValidationListener} for a listener that can
+ * cancel a (de)activation.
+ */
+@ExtensionPoint
+public interface AccountActivationListener {
+ /**
+ * Invoked after an account got activated
+ *
+ * @param id of the account
+ */
+ default void onAccountActivated(int id) {}
+
+ /**
+ * Invoked after an account got deactivated
+ *
+ * @param id of the account
+ */
+ default void onAccountDeactivated(int id) {}
+}
diff --git a/java/com/google/gerrit/extensions/events/ChangeEvent.java b/java/com/google/gerrit/extensions/events/ChangeEvent.java
index 0b510528b3..def75b7e38 100644
--- a/java/com/google/gerrit/extensions/events/ChangeEvent.java
+++ b/java/com/google/gerrit/extensions/events/ChangeEvent.java
@@ -20,6 +20,11 @@ import java.sql.Timestamp;
/** Interface to be extended by Events with a Change. */
public interface ChangeEvent extends GerritEvent {
+ /**
+ * Information about the change. Some fields might be null.
+ *
+ * @see com.google.gerrit.server.extensions.events.EventUtil
+ */
ChangeInfo getChange();
AccountInfo getWho();
diff --git a/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java b/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java
index 8dd64edd97..64bc022353 100644
--- a/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java
+++ b/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java
@@ -20,6 +20,21 @@ import com.google.gerrit.extensions.annotations.ExtensionPoint;
@ExtensionPoint
public interface ChangeIndexedListener {
/**
+ * Invoked when a change is scheduled for indexing.
+ *
+ * @param projectName project containing the change
+ * @param id id of the change that was scheduled for indexing
+ */
+ default void onChangeScheduledForIndexing(String projectName, int id) {}
+
+ /**
+ * Invoked when a change is scheduled for deletion from indexing.
+ *
+ * @param id id of the change that was scheduled for deletion from indexing
+ */
+ default void onChangeScheduledForDeletionFromIndex(int id) {}
+
+ /**
* Invoked when a change is indexed.
*
* @param projectName project containing the change
diff --git a/java/com/google/gerrit/extensions/events/RevisionEvent.java b/java/com/google/gerrit/extensions/events/RevisionEvent.java
index f0cfa2c4b6..db7830e0bc 100644
--- a/java/com/google/gerrit/extensions/events/RevisionEvent.java
+++ b/java/com/google/gerrit/extensions/events/RevisionEvent.java
@@ -18,5 +18,11 @@ import com.google.gerrit.extensions.common.RevisionInfo;
/** Interface to be extended by Events with a Revision. */
public interface RevisionEvent extends ChangeEvent {
+
+ /**
+ * Information about the revision. Some fields might be null.
+ *
+ * @see com.google.gerrit.server.extensions.events.EventUtil
+ */
RevisionInfo getRevision();
}
diff --git a/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java b/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
index fa2288aa84..270a040c9b 100644
--- a/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
+++ b/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
@@ -38,9 +38,8 @@ public class ResourceNotFoundException extends RestApiException {
super("Not found: " + id.get(), cause);
}
- @SuppressWarnings("unchecked")
- @Override
public ResourceNotFoundException caching(CacheControl c) {
- return super.caching(c);
+ setCaching(c);
+ return this;
}
}
diff --git a/java/com/google/gerrit/extensions/restapi/Response.java b/java/com/google/gerrit/extensions/restapi/Response.java
index 8f2dd5fea1..5504cfdfec 100644
--- a/java/com/google/gerrit/extensions/restapi/Response.java
+++ b/java/com/google/gerrit/extensions/restapi/Response.java
@@ -14,6 +14,10 @@
package com.google.gerrit.extensions.restapi;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.gerrit.common.Nullable;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
/** Special return value to mean specific HTTP status codes in a REST API. */
@@ -51,24 +55,48 @@ public abstract class Response<T> {
return new Redirect(location);
}
+ /**
+ * HTTP 500 Internal Server Error: failure due to an unexpected exception.
+ *
+ * <p>Can be returned from REST endpoints, instead of throwing the exception, if additional
+ * properties (e.g. a traceId) should be set on the response.
+ *
+ * @param cause the exception that caused the request to fail, must not be a {@link
+ * RestApiException} because such an exception would result in a 4XX response code
+ */
+ public static <T> InternalServerError<T> internalServerError(Exception cause) {
+ return new InternalServerError<>(cause);
+ }
+
/** Arbitrary status code with wrapped result. */
public static <T> Response<T> withStatusCode(int statusCode, T value) {
return new Impl<>(statusCode, value);
}
@SuppressWarnings({"unchecked", "rawtypes"})
- public static <T> T unwrap(T obj) {
+ public static <T> T unwrap(T obj) throws Exception {
while (obj instanceof Response) {
obj = (T) ((Response) obj).value();
}
return obj;
}
+ private String traceId;
+
+ public Response<T> traceId(@Nullable String traceId) {
+ this.traceId = traceId;
+ return this;
+ }
+
+ public Optional<String> traceId() {
+ return Optional.ofNullable(traceId);
+ }
+
public abstract boolean isNone();
public abstract int statusCode();
- public abstract T value();
+ public abstract T value() throws Exception;
public abstract CacheControl caching();
@@ -154,13 +182,38 @@ public abstract class Response<T> {
}
/** An HTTP redirect to another location. */
- public static final class Redirect {
+ public static final class Redirect extends Response<Object> {
private final String location;
private Redirect(String url) {
this.location = url;
}
+ @Override
+ public boolean isNone() {
+ return false;
+ }
+
+ @Override
+ public int statusCode() {
+ return 302;
+ }
+
+ @Override
+ public Object value() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CacheControl caching() {
+ return CacheControl.NONE;
+ }
+
+ @Override
+ public Response<Object> caching(CacheControl c) {
+ throw new UnsupportedOperationException();
+ }
+
public String location() {
return location;
}
@@ -182,13 +235,38 @@ public abstract class Response<T> {
}
/** Accepted as task for asynchronous execution. */
- public static final class Accepted {
+ public static final class Accepted extends Response<Object> {
private final String location;
private Accepted(String url) {
this.location = url;
}
+ @Override
+ public boolean isNone() {
+ return false;
+ }
+
+ @Override
+ public int statusCode() {
+ return 202;
+ }
+
+ @Override
+ public Object value() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public CacheControl caching() {
+ return CacheControl.NONE;
+ }
+
+ @Override
+ public Response<Object> caching(CacheControl c) {
+ throw new UnsupportedOperationException();
+ }
+
public String location() {
return location;
}
@@ -208,4 +286,57 @@ public abstract class Response<T> {
return String.format("[202 Accepted] %s", location);
}
}
+
+ public static final class InternalServerError<T> extends Response<T> {
+ private final Exception cause;
+
+ private InternalServerError(Exception cause) {
+ checkArgument(!(cause instanceof RestApiException), "cause must not be a RestApiException");
+ this.cause = cause;
+ }
+
+ @Override
+ public boolean isNone() {
+ return false;
+ }
+
+ @Override
+ public int statusCode() {
+ return 500;
+ }
+
+ @Override
+ public T value() throws Exception {
+ throw cause();
+ }
+
+ @Override
+ public CacheControl caching() {
+ return CacheControl.NONE;
+ }
+
+ @Override
+ public Response<T> caching(CacheControl c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Exception cause() {
+ return cause;
+ }
+
+ @Override
+ public int hashCode() {
+ return cause.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof InternalServerError && ((InternalServerError<?>) o).cause.equals(cause);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[500 Internal Server Error] %s", cause.getClass());
+ }
+ }
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestApiException.java b/java/com/google/gerrit/extensions/restapi/RestApiException.java
index b09723e8df..f3d7decb0c 100644
--- a/java/com/google/gerrit/extensions/restapi/RestApiException.java
+++ b/java/com/google/gerrit/extensions/restapi/RestApiException.java
@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.restapi;
+import static java.util.Objects.requireNonNull;
+
/** Root exception type for REST API failures. */
public class RestApiException extends Exception {
private static final long serialVersionUID = 1L;
@@ -33,9 +35,7 @@ public class RestApiException extends Exception {
return caching;
}
- @SuppressWarnings("unchecked")
- public <T extends RestApiException> T caching(CacheControl c) {
- caching = c;
- return (T) this;
+ protected void setCaching(CacheControl caching) {
+ this.caching = requireNonNull(caching);
}
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestCollectionCreateView.java b/java/com/google/gerrit/extensions/restapi/RestCollectionCreateView.java
index 25cdb767d1..72ca74b9bf 100644
--- a/java/com/google/gerrit/extensions/restapi/RestCollectionCreateView.java
+++ b/java/com/google/gerrit/extensions/restapi/RestCollectionCreateView.java
@@ -33,13 +33,24 @@ public interface RestCollectionCreateView<P extends RestResource, C extends Rest
/**
* Process the view operation by creating the resource.
*
+ * <p>The returned response defines the status code that is returned to the client. For
+ * RestCollectionCreateViews this is usually {@code 201 Created} because a resource is created,
+ * but other 2XX or 3XX status codes are also possible (e.g. {@link Response.Redirect} can be
+ * returned for {@code 302 Found}).
+ *
+ * <p>The value of the returned response is automatically converted to JSON unless it is a {@link
+ * BinaryResult}.
+ *
+ * <p>Throwing a subclass of {@link RestApiException} results in a 4XX response to the client. For
+ * any other exception the client will get a {@code 500 Internal Server Error} response.
+ *
* @param parentResource parent resource of the resource that should be created
+ * @param id the ID of the child resource that should be created
* @param input input after parsing from request.
- * @return result to return to the client. Use {@link BinaryResult} to avoid automatic conversion
- * to JSON.
+ * @return response to return to the client
* @throws RestApiException if the resource creation is rejected
* @throws Exception the implementation of the view failed. The exception will be logged and HTTP
* 500 Internal Server Error will be returned to the client.
*/
- Object apply(P parentResource, IdString id, I input) throws Exception;
+ Response<?> apply(P parentResource, IdString id, I input) throws Exception;
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestCollectionDeleteMissingView.java b/java/com/google/gerrit/extensions/restapi/RestCollectionDeleteMissingView.java
index 7e5649c3ca..c08d06a471 100644
--- a/java/com/google/gerrit/extensions/restapi/RestCollectionDeleteMissingView.java
+++ b/java/com/google/gerrit/extensions/restapi/RestCollectionDeleteMissingView.java
@@ -37,13 +37,25 @@ public interface RestCollectionDeleteMissingView<P extends RestResource, C exten
/**
* Process the view operation by deleting the resource.
*
+ * <p>The returned response defines the status code that is returned to the client. For
+ * RestCollectionDeleteMissingViews this is usually {@code 204 No Content} because a resource is
+ * deleted, but other 2XX or 3XX status codes are also possible (e.g. {@code 200 OK}, {@code 302
+ * Found} for a redirect).
+ *
+ * <p>The returned response usually does not have any value (status code {@code 204 No Content}).
+ * If a value in the returned response is set it is automatically converted to JSON unless it is a
+ * {@link BinaryResult}.
+ *
+ * <p>Throwing a subclass of {@link RestApiException} results in a 4XX response to the client. For
+ * any other exception the client will get a {@code 500 Internal Server Error} response.
+ *
* @param parentResource parent resource of the resource that should be deleted
- * @param input input after parsing from request.
- * @return result to return to the client. Use {@link BinaryResult} to avoid automatic conversion
- * to JSON.
+ * @param id the ID of the child resource that should be deleted
+ * @param input input after parsing from request
+ * @return response to return to the client
* @throws RestApiException if the resource creation is rejected
* @throws Exception the implementation of the view failed. The exception will be logged and HTTP
* 500 Internal Server Error will be returned to the client.
*/
- Object apply(P parentResource, IdString id, I input) throws Exception;
+ Response<?> apply(P parentResource, IdString id, I input) throws Exception;
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestCollectionModifyView.java b/java/com/google/gerrit/extensions/restapi/RestCollectionModifyView.java
index acabf96323..fcaa15bb79 100644
--- a/java/com/google/gerrit/extensions/restapi/RestCollectionModifyView.java
+++ b/java/com/google/gerrit/extensions/restapi/RestCollectionModifyView.java
@@ -28,5 +28,25 @@ package com.google.gerrit.extensions.restapi;
public interface RestCollectionModifyView<P extends RestResource, C extends RestResource, I>
extends RestCollectionView<P, C, I> {
- Object apply(P parentResource, I input) throws Exception;
+ /**
+ * Process the modification on the collection resource.
+ *
+ * <p>The value of the returned response is automatically converted to JSON unless it is a {@link
+ * BinaryResult}.
+ *
+ * <p>The returned response defines the status code that is returned to the client. For
+ * RestCollectionModifyViews this is usually {@code 200 OK}, but other 2XX or 3XX status codes are
+ * also possible (e.g. {@code 201 Created} if a resource was created, {@code 202 Accepted} if a
+ * background task was scheduled, {@code 204 No Content} if no content is returned, {@code 302
+ * Found} for a redirect).
+ *
+ * <p>Throwing a subclass of {@link RestApiException} results in a 4XX response to the client. For
+ * any other exception the client will get a {@code 500 Internal Server Error} response.
+ *
+ * @param parentResource the collection resource on which the modification is done
+ * @return response to return to the client
+ * @throws Exception the implementation of the view failed. The exception will be logged and HTTP
+ * 500 Internal Server Error will be returned to the client.
+ */
+ Response<?> apply(P parentResource, I input) throws Exception;
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestModifyView.java b/java/com/google/gerrit/extensions/restapi/RestModifyView.java
index 79053dd5a1..e397bd0a78 100644
--- a/java/com/google/gerrit/extensions/restapi/RestModifyView.java
+++ b/java/com/google/gerrit/extensions/restapi/RestModifyView.java
@@ -28,11 +28,21 @@ public interface RestModifyView<R extends RestResource, I> extends RestView<R> {
/**
* Process the view operation by altering the resource.
*
- * @param resource resource to modify.
- * @param input input after parsing from request.
- * @return result to return to the client. Use {@link BinaryResult} to avoid automatic conversion
- * to JSON.
- * @throws AuthException the client is not permitted to access this view.
+ * <p>The value of the returned response is automatically converted to JSON unless it is a {@link
+ * BinaryResult}.
+ *
+ * <p>The returned response defines the status code that is returned to the client. For
+ * RestModifyViews this is usually {@code 200 OK}, but other 2XX or 3XX status codes are also
+ * possible (e.g. {@code 202 Accepted} if a background task was scheduled, {@code 204 No Content}
+ * if no content is returned, {@code 302 Found} for a redirect).
+ *
+ * <p>Throwing a subclass of {@link RestApiException} results in a 4XX response to the client. For
+ * any other exception the client will get a {@code 500 Internal Server Error} response.
+ *
+ * @param resource resource to modify
+ * @param input input after parsing from request
+ * @return response to return to the client
+ * @throws AuthException the caller is not permitted to access this view.
* @throws BadRequestException the request was incorrectly specified and cannot be handled by this
* view.
* @throws ResourceConflictException the resource state does not permit this view to make the
@@ -40,6 +50,6 @@ public interface RestModifyView<R extends RestResource, I> extends RestView<R> {
* @throws Exception the implementation of the view failed. The exception will be logged and HTTP
* 500 Internal Server Error will be returned to the client.
*/
- Object apply(R resource, I input)
+ Response<?> apply(R resource, I input)
throws AuthException, BadRequestException, ResourceConflictException, Exception;
}
diff --git a/java/com/google/gerrit/extensions/restapi/RestReadView.java b/java/com/google/gerrit/extensions/restapi/RestReadView.java
index a3c31d3b71..8991f0b552 100644
--- a/java/com/google/gerrit/extensions/restapi/RestReadView.java
+++ b/java/com/google/gerrit/extensions/restapi/RestReadView.java
@@ -17,16 +17,27 @@ package com.google.gerrit.extensions.restapi;
/**
* RestView to read a resource without modification.
*
+ * <p>RestReadViews are invoked by the HTTP GET method.
+ *
* @param <R> type of resource the view reads.
*/
public interface RestReadView<R extends RestResource> extends RestView<R> {
/**
* Process the view operation by reading from the resource.
*
- * @param resource resource to read.
- * @return result to return to the client. Use {@link BinaryResult} to avoid automatic conversion
- * to JSON.
- * @throws AuthException the client is not permitted to access this view.
+ * <p>The value of the returned response is automatically converted to JSON unless it is a {@link
+ * BinaryResult}.
+ *
+ * <p>The returned response defines the status code that is returned to the client. For
+ * RestReadViews this is usually {@code 200 OK}, but other 2XX or 3XX status codes are also
+ * possible (e.g. {@link Response.Redirect} can be returned for {@code 302 Found}).
+ *
+ * <p>Throwing a subclass of {@link RestApiException} results in a 4XX response to the client. For
+ * any other exception the client will get a {@code 500 Internal Server Error} response.
+ *
+ * @param resource resource to read
+ * @return response to return to the client
+ * @throws AuthException the caller is not permitted to access this view.
* @throws BadRequestException the request was incorrectly specified and cannot be handled by this
* view.
* @throws ResourceConflictException the resource state does not permit this view to make the
@@ -34,6 +45,6 @@ public interface RestReadView<R extends RestResource> extends RestView<R> {
* @throws Exception the implementation of the view failed. The exception will be logged and HTTP
* 500 Internal Server Error will be returned to the client.
*/
- Object apply(R resource)
+ Response<?> apply(R resource)
throws AuthException, BadRequestException, ResourceConflictException, Exception;
}
diff --git a/java/com/google/gerrit/extensions/restapi/testing/BinaryResultSubject.java b/java/com/google/gerrit/extensions/restapi/testing/BinaryResultSubject.java
index 510920519a..c5304e386e 100644
--- a/java/com/google/gerrit/extensions/restapi/testing/BinaryResultSubject.java
+++ b/java/com/google/gerrit/extensions/restapi/testing/BinaryResultSubject.java
@@ -26,7 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Optional;
-public class BinaryResultSubject extends Subject<BinaryResultSubject, BinaryResult> {
+public class BinaryResultSubject extends Subject {
public static BinaryResultSubject assertThat(BinaryResult binaryResult) {
return assertAbout(binaryResults()).that(binaryResult);
@@ -41,8 +41,11 @@ public class BinaryResultSubject extends Subject<BinaryResultSubject, BinaryResu
return OptionalSubject.assertThat(binaryResultOptional, binaryResults());
}
+ private final BinaryResult binaryResult;
+
private BinaryResultSubject(FailureMetadata failureMetadata, BinaryResult binaryResult) {
super(failureMetadata, binaryResult);
+ this.binaryResult = binaryResult;
}
public StringSubject asString() throws IOException {
@@ -50,7 +53,6 @@ public class BinaryResultSubject extends Subject<BinaryResultSubject, BinaryResu
// We shouldn't close the BinaryResult within this method as it might still
// be used afterwards. Besides, closing it doesn't have an effect for most
// implementations of a BinaryResult.
- BinaryResult binaryResult = actual();
return check("asString()").that(binaryResult.asString());
}
@@ -59,7 +61,6 @@ public class BinaryResultSubject extends Subject<BinaryResultSubject, BinaryResu
// We shouldn't close the BinaryResult within this method as it might still
// be used afterwards. Besides, closing it doesn't have an effect for most
// implementations of a BinaryResult.
- BinaryResult binaryResult = actual();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
binaryResult.writeTo(byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
diff --git a/java/com/google/gerrit/extensions/validators/CommentForValidation.java b/java/com/google/gerrit/extensions/validators/CommentForValidation.java
new file mode 100644
index 0000000000..51ae5ae975
--- /dev/null
+++ b/java/com/google/gerrit/extensions/validators/CommentForValidation.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.validators;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Holds a comment's text and {@link CommentType} in order to pass it to a validation plugin.
+ *
+ * @see CommentValidator
+ */
+@AutoValue
+public abstract class CommentForValidation {
+
+ /** The type of comment. */
+ public enum CommentType {
+ /** A regular (inline) comment. */
+ INLINE_COMMENT,
+ /** A file comment. */
+ FILE_COMMENT,
+ /** A change message. */
+ CHANGE_MESSAGE
+ }
+
+ public static CommentForValidation create(CommentType type, String text) {
+ return new AutoValue_CommentForValidation(type, text);
+ }
+
+ public abstract CommentType getType();
+
+ public abstract String getText();
+
+ public CommentValidationFailure failValidation(String message) {
+ return CommentValidationFailure.create(this, message);
+ }
+}
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidationFailure.java b/java/com/google/gerrit/extensions/validators/CommentValidationFailure.java
new file mode 100644
index 0000000000..1a832760ca
--- /dev/null
+++ b/java/com/google/gerrit/extensions/validators/CommentValidationFailure.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.validators;
+
+import com.google.auto.value.AutoValue;
+
+/** A comment or review message was rejected by a {@link CommentValidator}. */
+@AutoValue
+public abstract class CommentValidationFailure {
+ static CommentValidationFailure create(
+ CommentForValidation commentForValidation, String message) {
+ return new AutoValue_CommentValidationFailure(commentForValidation, message);
+ }
+
+ /** Returns the offending comment. */
+ public abstract CommentForValidation getComment();
+
+ /** A friendly message set by the {@link CommentValidator}. */
+ public abstract String getMessage();
+}
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidator.java b/java/com/google/gerrit/extensions/validators/CommentValidator.java
new file mode 100644
index 0000000000..cfefdefc8f
--- /dev/null
+++ b/java/com/google/gerrit/extensions/validators/CommentValidator.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.validators;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/**
+ * Validates review comments and messages. Rejecting any comment/message will prevent all comments
+ * from being published.
+ */
+@ExtensionPoint
+public interface CommentValidator {
+
+ /**
+ * Validate the specified comments.
+ *
+ * @return An empty list if all comments are valid, or else a list of validation failures.
+ */
+ ImmutableList<CommentValidationFailure> validateComments(
+ ImmutableList<CommentForValidation> comments);
+}
diff --git a/java/com/google/gerrit/git/BUILD b/java/com/google/gerrit/git/BUILD
index 4c4d5bcc89..05747165c3 100644
--- a/java/com/google/gerrit/git/BUILD
+++ b/java/com/google/gerrit/git/BUILD
@@ -5,7 +5,10 @@ java_library(
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
],
)
diff --git a/java/com/google/gerrit/git/GitUpdateFailureException.java b/java/com/google/gerrit/git/GitUpdateFailureException.java
new file mode 100644
index 0000000000..76ef2176b2
--- /dev/null
+++ b/java/com/google/gerrit/git/GitUpdateFailureException.java
@@ -0,0 +1,95 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.git;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
+import java.io.IOException;
+import java.util.Optional;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Thrown when updating a ref in Git fails. */
+public class GitUpdateFailureException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ private final ImmutableList<GitUpdateFailure> failures;
+
+ public GitUpdateFailureException(String message, RefUpdate refUpdate) {
+ super(message);
+ this.failures = ImmutableList.of(GitUpdateFailure.create(refUpdate));
+ }
+
+ public GitUpdateFailureException(String message, BatchRefUpdate batchRefUpdate) {
+ super(message);
+ this.failures =
+ batchRefUpdate.getCommands().stream()
+ .filter(c -> c.getResult() != ReceiveCommand.Result.OK)
+ .map(GitUpdateFailure::create)
+ .collect(toImmutableList());
+ }
+
+ /** @return the names of the refs for which the update failed. */
+ public ImmutableList<String> getFailedRefs() {
+ return failures.stream().map(GitUpdateFailure::ref).collect(toImmutableList());
+ }
+
+ /** @return the failures that caused this exception. */
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public ImmutableList<GitUpdateFailure> getFailures() {
+ return failures;
+ }
+
+ @AutoValue
+ public abstract static class GitUpdateFailure {
+ private static GitUpdateFailure create(RefUpdate refUpdate) {
+ return builder().ref(refUpdate.getName()).result(refUpdate.getResult().name()).build();
+ }
+
+ private static GitUpdateFailure create(ReceiveCommand receiveCommand) {
+ return builder()
+ .ref(receiveCommand.getRefName())
+ .result(receiveCommand.getResult().name())
+ .message(receiveCommand.getMessage())
+ .build();
+ }
+
+ public abstract String ref();
+
+ public abstract String result();
+
+ public abstract Optional<String> message();
+
+ public static GitUpdateFailure.Builder builder() {
+ return new AutoValue_GitUpdateFailureException_GitUpdateFailure.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder ref(String ref);
+
+ abstract Builder result(String result);
+
+ abstract Builder message(@Nullable String message);
+
+ abstract GitUpdateFailure build();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/git/LockFailureException.java b/java/com/google/gerrit/git/LockFailureException.java
index 9e67d700a6..371488da3f 100644
--- a/java/com/google/gerrit/git/LockFailureException.java
+++ b/java/com/google/gerrit/git/LockFailureException.java
@@ -14,36 +14,18 @@
package com.google.gerrit.git;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.transport.ReceiveCommand;
/** Thrown when updating a ref in Git fails with LOCK_FAILURE. */
-public class LockFailureException extends IOException {
+public class LockFailureException extends GitUpdateFailureException {
private static final long serialVersionUID = 1L;
- private final ImmutableList<String> refs;
-
public LockFailureException(String message, RefUpdate refUpdate) {
- super(message);
- refs = ImmutableList.of(refUpdate.getName());
+ super(message, refUpdate);
}
public LockFailureException(String message, BatchRefUpdate batchRefUpdate) {
- super(message);
- refs =
- batchRefUpdate.getCommands().stream()
- .filter(c -> c.getResult() == ReceiveCommand.Result.LOCK_FAILURE)
- .map(ReceiveCommand::getRefName)
- .collect(toImmutableList());
- }
-
- /** Subset of ref names that caused the lock failure. */
- public ImmutableList<String> getFailedRefs() {
- return refs;
+ super(message, batchRefUpdate);
}
}
diff --git a/java/com/google/gerrit/git/ObjectIds.java b/java/com/google/gerrit/git/ObjectIds.java
new file mode 100644
index 0000000000..4d8304662c
--- /dev/null
+++ b/java/com/google/gerrit/git/ObjectIds.java
@@ -0,0 +1,131 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.git;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.gerrit.common.Nullable;
+import java.io.IOException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+
+/** Static utilities for working with {@code ObjectId}s. */
+public class ObjectIds {
+ /** Length of a binary SHA-1 byte array. */
+ public static final int LEN = Constants.OBJECT_ID_LENGTH;
+
+ /** Length of a hex SHA-1 string. */
+ public static final int STR_LEN = Constants.OBJECT_ID_STRING_LENGTH;
+
+ /** Default abbreviated length of a hex SHA-1 string. */
+ public static final int ABBREV_STR_LEN = 7;
+
+ /**
+ * Abbreviate an ID's hex string representation to 7 chars.
+ *
+ * @param id object ID.
+ * @return abbreviated hex string representation, exactly 7 chars.
+ */
+ public static String abbreviateName(AnyObjectId id) {
+ return abbreviateName(id, ABBREV_STR_LEN);
+ }
+
+ /**
+ * Abbreviate an ID's hex string representation to {@code n} chars.
+ *
+ * @param id object ID.
+ * @param n number of hex chars, 1 to 40.
+ * @return abbreviated hex string representation, exactly {@code n} chars.
+ */
+ public static String abbreviateName(AnyObjectId id, int n) {
+ checkValidLength(n);
+ return requireNonNull(id).abbreviate(n).name();
+ }
+
+ /**
+ * Abbreviate an ID's hex string representation uniquely to at least 7 chars.
+ *
+ * @param id object ID.
+ * @param reader object reader for determining uniqueness.
+ * @return abbreviated hex string representation, unique according to {@code reader} at least 7
+ * chars.
+ * @throws IOException if an error occurs while looking for ambiguous objects.
+ */
+ public static String abbreviateName(AnyObjectId id, ObjectReader reader) throws IOException {
+ return abbreviateName(id, ABBREV_STR_LEN, reader);
+ }
+
+ /**
+ * Abbreviate an ID's hex string representation uniquely to at least {@code n} chars.
+ *
+ * @param id object ID.
+ * @param n minimum number of hex chars, 1 to 40.
+ * @param reader object reader for determining uniqueness.
+ * @return abbreviated hex string representation, unique according to {@code reader} at least
+ * {@code n} chars.
+ * @throws IOException if an error occurs while looking for ambiguous objects.
+ */
+ public static String abbreviateName(AnyObjectId id, int n, ObjectReader reader)
+ throws IOException {
+ checkValidLength(n);
+ return reader.abbreviate(id, n).name();
+ }
+
+ /**
+ * Copy a nullable ID, preserving null.
+ *
+ * @param id object ID.
+ * @return {@link AnyObjectId#copy} of {@code id}, or {@code null} if {@code id} is null.
+ */
+ @Nullable
+ public static ObjectId copyOrNull(@Nullable AnyObjectId id) {
+ return id != null ? id.copy() : null;
+ }
+
+ /**
+ * Copy a nullable ID, converting null to {@code zeroId}.
+ *
+ * @param id object ID.
+ * @return {@link AnyObjectId#copy} of {@code id}, or {@link ObjectId#zeroId} if {@code id} is
+ * null.
+ */
+ public static ObjectId copyOrZero(@Nullable AnyObjectId id) {
+ return id != null ? id.copy() : ObjectId.zeroId();
+ }
+
+ /**
+ * Return whether the given ID matches the given abbreviation.
+ *
+ * @param id object ID.
+ * @param abbreviation abbreviated hex object ID. May not be null, but may be an invalid hex SHA-1
+ * abbreviation string.
+ * @return true if {@code id} is not null and {@code abbreviation} is a valid hex SHA-1
+ * abbreviation that matches {@code id}, false otherwise.
+ */
+ public static boolean matchesAbbreviation(@Nullable AnyObjectId id, String abbreviation) {
+ requireNonNull(abbreviation);
+ return id != null && id.name().startsWith(abbreviation);
+ }
+
+ private static void checkValidLength(int n) {
+ checkArgument(n > 0);
+ checkArgument(n <= STR_LEN);
+ }
+
+ private ObjectIds() {}
+}
diff --git a/java/com/google/gerrit/git/RefUpdateUtil.java b/java/com/google/gerrit/git/RefUpdateUtil.java
index 5eaf253f1a..bd889623ee 100644
--- a/java/com/google/gerrit/git/RefUpdateUtil.java
+++ b/java/com/google/gerrit/git/RefUpdateUtil.java
@@ -99,7 +99,7 @@ public class RefUpdateUtil {
if (lockFailure + aborted == bru.getCommands().size()) {
throw new LockFailureException("Update aborted with one or more lock failures: " + bru, bru);
} else if (failure > 0) {
- throw new IOException("Update failed: " + bru);
+ throw new GitUpdateFailureException("Update failed: " + bru, bru);
}
}
@@ -130,7 +130,8 @@ public class RefUpdateUtil {
case REJECTED_CURRENT_BRANCH:
case REJECTED_MISSING_OBJECT:
case REJECTED_OTHER_REASON:
- throw new IOException("Failed to update " + ru.getName() + ": " + ru.getResult());
+ throw new GitUpdateFailureException(
+ "Failed to update " + ru.getName() + ": " + ru.getResult(), ru);
}
}
@@ -175,7 +176,8 @@ public class RefUpdateUtil {
case REJECTED_MISSING_OBJECT:
case REJECTED_OTHER_REASON:
default:
- throw new IOException("Failed to delete " + refName + ": " + ru.getResult());
+ throw new GitUpdateFailureException(
+ "Failed to delete " + refName + ": " + ru.getResult(), ru);
}
}
diff --git a/java/com/google/gerrit/git/testing/BUILD b/java/com/google/gerrit/git/testing/BUILD
index 13fddc1715..fda9aff9f0 100644
--- a/java/com/google/gerrit/git/testing/BUILD
+++ b/java/com/google/gerrit/git/testing/BUILD
@@ -9,7 +9,7 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
diff --git a/java/com/google/gerrit/git/testing/CommitSubject.java b/java/com/google/gerrit/git/testing/CommitSubject.java
index 08731072da..41eb45b75d 100644
--- a/java/com/google/gerrit/git/testing/CommitSubject.java
+++ b/java/com/google/gerrit/git/testing/CommitSubject.java
@@ -24,7 +24,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
/** Subject over JGit {@link RevCommit}s. */
-public class CommitSubject extends Subject<CommitSubject, RevCommit> {
+public class CommitSubject extends Subject {
/**
* Constructs a new subject.
@@ -56,8 +56,11 @@ public class CommitSubject extends Subject<CommitSubject, RevCommit> {
commitSubject.hasSha1(expectedSha1);
}
- private CommitSubject(FailureMetadata metadata, RevCommit actual) {
- super(metadata, actual);
+ private final RevCommit commit;
+
+ private CommitSubject(FailureMetadata metadata, RevCommit commit) {
+ super(metadata, commit);
+ this.commit = commit;
}
/**
@@ -67,8 +70,7 @@ public class CommitSubject extends Subject<CommitSubject, RevCommit> {
*/
public void hasCommitMessage(String expectedCommitMessage) {
isNotNull();
- RevCommit commit = actual();
- check("commitMessage()").that(commit.getFullMessage()).isEqualTo(expectedCommitMessage);
+ check("getFullMessage()").that(commit.getFullMessage()).isEqualTo(expectedCommitMessage);
}
/**
@@ -78,7 +80,6 @@ public class CommitSubject extends Subject<CommitSubject, RevCommit> {
*/
public void hasCommitTimestamp(Timestamp expectedCommitTimestamp) {
isNotNull();
- RevCommit commit = actual();
long timestampDiffMs =
Math.abs(commit.getCommitTime() * 1000L - expectedCommitTimestamp.getTime());
check("commitTimestampDiff()").that(timestampDiffMs).isAtMost(SECONDS.toMillis(1));
@@ -91,7 +92,6 @@ public class CommitSubject extends Subject<CommitSubject, RevCommit> {
*/
public void hasSha1(ObjectId expectedSha1) {
isNotNull();
- RevCommit commit = actual();
check("sha1()").that(commit).isEqualTo(expectedSha1);
}
}
diff --git a/java/com/google/gerrit/git/testing/ObjectIdSubject.java b/java/com/google/gerrit/git/testing/ObjectIdSubject.java
index 5fe91f950d..0cfc56396e 100644
--- a/java/com/google/gerrit/git/testing/ObjectIdSubject.java
+++ b/java/com/google/gerrit/git/testing/ObjectIdSubject.java
@@ -20,7 +20,7 @@ import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import org.eclipse.jgit.lib.ObjectId;
-public class ObjectIdSubject extends Subject<ObjectIdSubject, ObjectId> {
+public class ObjectIdSubject extends Subject {
public static ObjectIdSubject assertThat(ObjectId objectId) {
return assertAbout(objectIds()).that(objectId);
}
@@ -29,13 +29,15 @@ public class ObjectIdSubject extends Subject<ObjectIdSubject, ObjectId> {
return ObjectIdSubject::new;
}
- private ObjectIdSubject(FailureMetadata metadata, ObjectId actual) {
- super(metadata, actual);
+ private final ObjectId objectId;
+
+ private ObjectIdSubject(FailureMetadata metadata, ObjectId objectId) {
+ super(metadata, objectId);
+ this.objectId = objectId;
}
public void hasName(String expectedName) {
isNotNull();
- ObjectId objectId = actual();
- check("name()").that(objectId.getName()).isEqualTo(expectedName);
+ check("getName()").that(objectId.getName()).isEqualTo(expectedName);
}
}
diff --git a/java/com/google/gerrit/git/testing/PushResultSubject.java b/java/com/google/gerrit/git/testing/PushResultSubject.java
index f5c9810bb8..9a46632c30 100644
--- a/java/com/google/gerrit/git/testing/PushResultSubject.java
+++ b/java/com/google/gerrit/git/testing/PushResultSubject.java
@@ -15,7 +15,9 @@
package com.google.gerrit.git.testing;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.git.testing.PushResultSubject.RemoteRefUpdateSubject.refs;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
@@ -26,42 +28,44 @@ import com.google.common.collect.Maps;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StreamSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.Truth;
import com.google.gerrit.common.Nullable;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
-public class PushResultSubject extends Subject<PushResultSubject, PushResult> {
+public class PushResultSubject extends Subject {
public static PushResultSubject assertThat(PushResult actual) {
return assertAbout(PushResultSubject::new).that(actual);
}
- private PushResultSubject(FailureMetadata metadata, PushResult actual) {
- super(metadata, actual);
+ private final PushResult pushResult;
+
+ private PushResultSubject(FailureMetadata metadata, PushResult pushResult) {
+ super(metadata, pushResult);
+ this.pushResult = pushResult;
}
public void hasNoMessages() {
- check("hasNoMessages()")
- .withMessage("expected no messages")
- .that(Strings.nullToEmpty(trimMessages()))
- .isEqualTo("");
+ isNotNull();
+ check("hasNoMessages()").that(Strings.nullToEmpty(getTrimmedMessages())).isEqualTo("");
}
public void hasMessages(String... expectedLines) {
checkArgument(expectedLines.length > 0, "use hasNoMessages()");
isNotNull();
- check("messages()").that(trimMessages()).isEqualTo(String.join("\n", expectedLines));
+ check("getTrimmedMessages()")
+ .that(getTrimmedMessages())
+ .isEqualTo(String.join("\n", expectedLines));
}
public void containsMessages(String... expectedLines) {
checkArgument(expectedLines.length > 0, "use hasNoMessages()");
isNotNull();
- Iterable<String> got = Splitter.on("\n").split(trimMessages());
- check("messages()").that(got).containsAtLeastElementsIn(expectedLines).inOrder();
+ Iterable<String> got = Splitter.on("\n").split(getTrimmedMessages());
+ check("getTrimmedMessages()").that(got).containsAtLeastElementsIn(expectedLines).inOrder();
}
- private String trimMessages() {
- return trimMessages(actual().getMessages());
+ private String getTrimmedMessages() {
+ return trimMessages(pushResult.getMessages());
}
@VisibleForTesting
@@ -78,15 +82,16 @@ public class PushResultSubject extends Subject<PushResultSubject, PushResult> {
}
public void hasProcessed(ImmutableMap<String, Integer> expected) {
+ isNotNull();
ImmutableMap<String, Integer> actual;
- String messages = actual().getMessages();
+ String messages = pushResult.getMessages();
try {
actual = parseProcessed(messages);
} catch (RuntimeException e) {
- Truth.assert_()
- .fail(
- "failed to parse \"Processing changes\" line from messages: %s\n%s",
- messages, Throwables.getStackTraceAsString(e));
+ failWithActual(
+ fact(
+ "failed to parse \"Processing changes\" line from messages, reason:",
+ Throwables.getStackTraceAsString(e)));
return;
}
check("processedCommands()").that(actual).containsExactlyEntriesIn(expected).inOrder();
@@ -119,57 +124,60 @@ public class PushResultSubject extends Subject<PushResultSubject, PushResult> {
}
public RemoteRefUpdateSubject ref(String refName) {
- return assertAbout(
- (FailureMetadata m, RemoteRefUpdate a) -> new RemoteRefUpdateSubject(refName, m, a))
- .that(actual().getRemoteUpdate(refName));
+ isNotNull();
+ return check("getRemoteUpdate(%s)", refName)
+ .about(refs())
+ .that(pushResult.getRemoteUpdate(refName));
}
public RemoteRefUpdateSubject onlyRef(String refName) {
+ isNotNull();
check("setOfRefs()")
.about(StreamSubject.streams())
- .that(actual().getRemoteUpdates().stream().map(RemoteRefUpdate::getRemoteName))
- .named("set of refs")
+ .that(pushResult.getRemoteUpdates().stream().map(RemoteRefUpdate::getRemoteName))
.containsExactly(refName);
return ref(refName);
}
- public static class RemoteRefUpdateSubject
- extends Subject<RemoteRefUpdateSubject, RemoteRefUpdate> {
- private final String refName;
+ public static class RemoteRefUpdateSubject extends Subject {
+ private final RemoteRefUpdate remoteRefUpdate;
+
+ private RemoteRefUpdateSubject(FailureMetadata metadata, RemoteRefUpdate remoteRefUpdate) {
+ super(metadata, remoteRefUpdate);
+ this.remoteRefUpdate = remoteRefUpdate;
+ }
- private RemoteRefUpdateSubject(
- String refName, FailureMetadata metadata, RemoteRefUpdate actual) {
- super(metadata, actual);
- this.refName = refName;
- named("ref update for %s", refName).isNotNull();
+ static Factory<RemoteRefUpdateSubject, RemoteRefUpdate> refs() {
+ return RemoteRefUpdateSubject::new;
}
public void hasStatus(RemoteRefUpdate.Status status) {
- RemoteRefUpdate u = actual();
- Truth.assertThat(u.getStatus())
- .named(
- "status of ref update for %s%s",
- refName, u.getMessage() != null ? ": " + u.getMessage() : "")
+ isNotNull();
+ RemoteRefUpdate u = remoteRefUpdate;
+ check("getStatus()")
+ .withMessage(
+ "status message: %s", u.getMessage() != null ? ": " + u.getMessage() : "<emtpy>")
+ .that(u.getStatus())
.isEqualTo(status);
}
public void hasNoMessage() {
- Truth.assertThat(actual().getMessage())
- .named("message of ref update for %s", refName)
- .isNull();
+ isNotNull();
+ check("getMessage()").that(remoteRefUpdate.getMessage()).isNull();
}
public void hasMessage(String expected) {
- Truth.assertThat(actual().getMessage())
- .named("message of ref update for %s", refName)
- .isEqualTo(expected);
+ isNotNull();
+ check("getMessage()").that(remoteRefUpdate.getMessage()).isEqualTo(expected);
}
public void isOk() {
+ isNotNull();
hasStatus(RemoteRefUpdate.Status.OK);
}
public void isRejected(String expectedMessage) {
+ isNotNull();
hasStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
hasMessage(expectedMessage);
}
diff --git a/java/com/google/gerrit/gpg/BUILD b/java/com/google/gerrit/gpg/BUILD
index f11b9b92bd..9f804c49c5 100644
--- a/java/com/google/gerrit/gpg/BUILD
+++ b/java/com/google/gerrit/gpg/BUILD
@@ -5,18 +5,18 @@ java_library(
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/api",
"//lib:guava",
+ "//lib:jgit",
"//lib/bouncycastle:bcpg-neverlink",
"//lib/bouncycastle:bcprov-neverlink",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index 9c0885707a..9477cb63f9 100644
--- a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -201,7 +201,7 @@ public class GerritPublicKeyChecker extends PublicKeyChecker {
private Set<String> getAllowedUserIds(IdentifiedUser user) {
Set<String> result = new HashSet<>();
result.addAll(user.getEmailAddresses());
- for (ExternalId extId : user.state().getExternalIds()) {
+ for (ExternalId extId : user.state().externalIds()) {
if (extId.isScheme(SCHEME_GPGKEY)) {
continue; // Omit GPG keys.
}
diff --git a/java/com/google/gerrit/gpg/PublicKeyStore.java b/java/com/google/gerrit/gpg/PublicKeyStore.java
index 7dd01d9a12..519c400730 100644
--- a/java/com/google/gerrit/gpg/PublicKeyStore.java
+++ b/java/com/google/gerrit/gpg/PublicKeyStore.java
@@ -16,10 +16,12 @@ package com.google.gerrit.gpg;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
+import com.google.gerrit.git.ObjectIds;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -45,7 +47,6 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@@ -75,9 +76,6 @@ import org.eclipse.jgit.util.NB;
* after checking with a {@link PublicKeyChecker}.
*/
public class PublicKeyStore implements AutoCloseable {
- private static final ObjectId EMPTY_TREE =
- ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904");
-
/** Ref where GPG public keys are stored. */
public static final String REFS_GPG_KEYS = "refs/meta/gpg-keys";
@@ -360,7 +358,7 @@ public class PublicKeyStore implements AutoCloseable {
deleteFromNotes(ins, fp);
}
cb.setTreeId(notes.writeTree(ins));
- if (cb.getTreeId().equals(tip != null ? tip.getTree() : EMPTY_TREE)) {
+ if (cb.getTreeId().equals(tip != null ? tip.getTree() : EMPTY_TREE_ID)) {
return RefUpdate.Result.NO_CHANGE;
}
@@ -516,7 +514,7 @@ public class PublicKeyStore implements AutoCloseable {
}
static ObjectId keyObjectId(long keyId) {
- byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ byte[] buf = new byte[ObjectIds.LEN];
NB.encodeInt64(buf, 0, keyId);
return ObjectId.fromRaw(buf);
}
diff --git a/java/com/google/gerrit/gpg/SignedPushModule.java b/java/com/google/gerrit/gpg/SignedPushModule.java
index a05186105d..c11c4e30e4 100644
--- a/java/com/google/gerrit/gpg/SignedPushModule.java
+++ b/java/com/google/gerrit/gpg/SignedPushModule.java
@@ -16,9 +16,9 @@ package com.google.gerrit.gpg;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.EnableSignedPush;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
diff --git a/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java b/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
index b7b03db6ca..652afeab94 100644
--- a/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
+++ b/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.gpg.api;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
import com.google.gerrit.extensions.common.GpgKeyInfo;
@@ -65,9 +67,11 @@ public class GpgApiAdapterImpl implements GpgApiAdapter {
public Map<String, GpgKeyInfo> listGpgKeys(AccountResource account)
throws RestApiException, GpgException {
try {
- return gpgKeys.get().list().apply(account);
+ return gpgKeys.get().list().apply(account).value();
} catch (PGPException | IOException e) {
throw new GpgException(e);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list GPG keys", e);
}
}
@@ -79,9 +83,11 @@ public class GpgApiAdapterImpl implements GpgApiAdapter {
in.add = add;
in.delete = delete;
try {
- return postGpgKeys.get().apply(account, in);
+ return postGpgKeys.get().apply(account, in).value();
} catch (PGPException | IOException | ConfigInvalidException e) {
throw new GpgException(e);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot put GPG keys", e);
}
}
diff --git a/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java b/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
index cf09acf470..0ff12e8837 100644
--- a/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
+++ b/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.gpg.api;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.Input;
@@ -46,9 +48,9 @@ public class GpgKeyApiImpl implements GpgKeyApi {
@Override
public GpgKeyInfo get() throws RestApiException {
try {
- return get.apply(rsrc);
- } catch (IOException e) {
- throw new RestApiException("Cannot get GPG key", e);
+ return get.apply(rsrc).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get GPG key", e);
}
}
@@ -57,7 +59,7 @@ public class GpgKeyApiImpl implements GpgKeyApi {
try {
delete.apply(rsrc, new Input());
} catch (PGPException | IOException | ConfigInvalidException e) {
- throw new RestApiException("Cannot delete GPG key", e);
+ throw asRestApiException("Cannot delete GPG key", e);
}
}
}
diff --git a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index 68bd2d9b4e..1be37f54c9 100644
--- a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -105,7 +105,7 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
"Cannot send GPG key deletion message to %s",
- rsrc.getUser().getAccount().getPreferredEmail());
+ rsrc.getUser().getAccount().preferredEmail());
}
break;
case LOCK_FAILURE:
diff --git a/java/com/google/gerrit/gpg/server/GpgKeys.java b/java/com/google/gerrit/gpg/server/GpgKeys.java
index 16592f8b18..b3a2f53e3b 100644
--- a/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -27,6 +27,7 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.gpg.BouncyCastleUtil;
@@ -140,7 +141,7 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
public class ListGpgKeys implements RestReadView<AccountResource> {
@Override
- public Map<String, GpgKeyInfo> apply(AccountResource rsrc)
+ public Response<Map<String, GpgKeyInfo>> apply(AccountResource rsrc)
throws PGPException, IOException, ResourceNotFoundException {
checkVisible(self, rsrc);
Map<String, GpgKeyInfo> keys = new HashMap<>();
@@ -165,7 +166,7 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
}
}
}
- return keys;
+ return Response.ok(keys);
}
}
@@ -181,12 +182,13 @@ public class GpgKeys implements ChildCollection<AccountResource, GpgKey> {
}
@Override
- public GpgKeyInfo apply(GpgKey rsrc) throws IOException {
+ public Response<GpgKeyInfo> apply(GpgKey rsrc) throws IOException {
try (PublicKeyStore store = storeProvider.get()) {
- return toJson(
- rsrc.getKeyRing().getPublicKey(),
- checkerFactory.create().setExpectedUser(rsrc.getUser()),
- store);
+ return Response.ok(
+ toJson(
+ rsrc.getKeyRing().getPublicKey(),
+ checkerFactory.create().setExpectedUser(rsrc.getUser()),
+ store));
}
}
}
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 0bb4ff7434..5396e1c127 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -18,15 +18,18 @@ import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
@@ -34,13 +37,15 @@ import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.gpg.CheckResult;
import com.google.gerrit.gpg.Fingerprint;
import com.google.gerrit.gpg.GerritPublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
@@ -53,6 +58,8 @@ import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.mail.send.AddKeySender;
import com.google.gerrit.server.mail.send.DeleteKeySender;
import com.google.gerrit.server.query.account.InternalAccountQuery;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -87,6 +94,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
private final Provider<InternalAccountQuery> accountQueryProvider;
private final ExternalIds externalIds;
private final Provider<AccountsUpdate> accountsUpdateProvider;
+ private final RetryHelper retryHelper;
@Inject
PostGpgKeys(
@@ -98,7 +106,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
DeleteKeySender.Factory deleteKeySenderFactory,
Provider<InternalAccountQuery> accountQueryProvider,
ExternalIds externalIds,
- @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
+ @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
+ RetryHelper retryHelper) {
this.serverIdent = serverIdent;
this.self = self;
this.storeProvider = storeProvider;
@@ -108,12 +117,12 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
this.accountQueryProvider = accountQueryProvider;
this.externalIds = externalIds;
this.accountsUpdateProvider = accountsUpdateProvider;
+ this.retryHelper = retryHelper;
}
@Override
- public Map<String, GpgKeyInfo> apply(AccountResource rsrc, GpgKeysInput input)
- throws ResourceNotFoundException, BadRequestException, ResourceConflictException,
- PGPException, IOException, ConfigInvalidException {
+ public Response<Map<String, GpgKeyInfo>> apply(AccountResource rsrc, GpgKeysInput input)
+ throws RestApiException, PGPException, IOException, ConfigInvalidException {
GpgKeys.checkVisible(self, rsrc);
Collection<ExternalId> existingExtIds =
@@ -129,7 +138,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
ExternalId.Key extIdKey = toExtIdKey(key.getFingerprint());
Account account = getAccountByExternalId(extIdKey);
if (account != null) {
- if (!account.getId().equals(rsrc.getUser().getAccountId())) {
+ if (!account.id().equals(rsrc.getUser().getAccountId())) {
throw new ResourceConflictException("GPG key already associated with another account");
}
} else {
@@ -145,7 +154,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
"Update GPG Keys via API",
rsrc.getUser().getAccountId(),
u -> u.replaceExternalIds(toRemove.keySet(), newExtIds));
- return toJson(newKeys, fingerprintsToRemove, store, rsrc.getUser());
+ return Response.ok(toJson(newKeys, fingerprintsToRemove, store, rsrc.getUser()));
}
}
@@ -196,7 +205,24 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
private void storeKeys(
AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Collection<Fingerprint> toRemove)
- throws BadRequestException, PGPException, IOException {
+ throws RestApiException, PGPException, IOException {
+ try {
+ retryHelper.execute(
+ ActionType.ACCOUNT_UPDATE,
+ () -> tryStoreKeys(rsrc, keyRings, toRemove),
+ LockFailureException.class::isInstance);
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+ Throwables.throwIfInstanceOf(e, IOException.class);
+ Throwables.throwIfInstanceOf(e, PGPException.class);
+ throw new StorageException(e);
+ }
+ }
+
+ private Void tryStoreKeys(
+ AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Collection<Fingerprint> toRemove)
+ throws RestApiException, PGPException, IOException {
try (PublicKeyStore store = storeProvider.get()) {
List<String> addedKeys = new ArrayList<>();
IdentifiedUser user = rsrc.getUser();
@@ -232,7 +258,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
"Cannot send GPG key added message to %s",
- rsrc.getUser().getAccount().getPreferredEmail());
+ rsrc.getUser().getAccount().preferredEmail());
}
}
if (!toRemove.isEmpty()) {
@@ -242,8 +268,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
.send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
- "Cannot send GPG key deleted message to %s",
- user.getAccount().getPreferredEmail());
+ "Cannot send GPG key deleted message to %s", user.getAccount().preferredEmail());
}
}
break;
@@ -261,6 +286,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
throw new StorageException(String.format("Failed to save public keys: %s", saveResult));
}
}
+ return null;
}
private ExternalId.Key toExtIdKey(byte[] fp) {
@@ -275,15 +301,15 @@ public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput
}
if (accountStates.size() > 1) {
- StringBuilder msg = new StringBuilder();
- msg.append("GPG key ")
- .append(extIdKey.get())
- .append(" associated with multiple accounts: ")
- .append(Lists.transform(accountStates, AccountState.ACCOUNT_ID_FUNCTION));
- throw new IllegalStateException(msg.toString());
+ String msg = "GPG key " + extIdKey.get() + " associated with multiple accounts: [";
+ msg =
+ accountStates.stream()
+ .map(a -> a.account().id().toString())
+ .collect(joining(", ", msg, "]"));
+ throw new IllegalStateException(msg);
}
- return accountStates.get(0).getAccount();
+ return accountStates.get(0).account();
}
private Map<String, GpgKeyInfo> toJson(
diff --git a/java/com/google/gerrit/gpg/testing/BUILD b/java/com/google/gerrit/gpg/testing/BUILD
index b227dd5a79..dc390719c0 100644
--- a/java/com/google/gerrit/gpg/testing/BUILD
+++ b/java/com/google/gerrit/gpg/testing/BUILD
@@ -8,7 +8,7 @@ java_library(
deps = [
"//java/com/google/gerrit/gpg",
"//lib:guava",
+ "//lib:jgit",
"//lib/bouncycastle:bcpg-neverlink",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java b/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
index 68bbd9840d..af8234f72d 100644
--- a/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
+++ b/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
@@ -15,8 +15,8 @@
package com.google.gerrit.httpd;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
@AutoValue
abstract class AdvertisedObjectsCacheKey {
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index 1d4189f4bc..bcb2a2ac68 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -12,14 +12,13 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/git",
"//java/com/google/gerrit/json",
"//java/com/google/gerrit/launcher",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/git/receive",
@@ -32,6 +31,8 @@ java_library(
"//lib:args4j",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-servlet",
"//lib:jsch",
"//lib:servlet-api",
"//lib:soy",
@@ -43,7 +44,5 @@ java_library(
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 177be75926..3a84a296f8 100644
--- a/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -19,11 +19,11 @@ import static java.util.concurrent.TimeUnit.HOURS;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.httpd.restapi.ParameterParser;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
@@ -75,30 +75,28 @@ public abstract class CacheBasedWebSession implements WebSession {
this.identified = identified;
this.byIdCache = byIdCache;
- if (request.getRequestURI() == null || !GitSmartHttpTools.isGitClient(request)) {
- String cookie = readCookie(request);
- if (cookie != null) {
- authFromCookie(cookie);
- } else {
- String token;
- try {
- token = ParameterParser.getQueryParams(request).accessToken();
- } catch (BadRequestException e) {
- token = null;
- }
- if (token != null) {
- authFromQueryParameter(token);
- }
- }
- if (val != null && !checkAccountStatus(val.getAccountId())) {
- val = null;
- okPaths.clear();
+ String cookie = readCookie(request);
+ if (cookie != null) {
+ authFromCookie(cookie);
+ } else if (request.getRequestURI() == null || !GitSmartHttpTools.isGitClient(request)) {
+ String token;
+ try {
+ token = ParameterParser.getQueryParams(request).accessToken();
+ } catch (BadRequestException e) {
+ token = null;
}
- if (val != null && val.needsCookieRefresh()) {
- // Session is more than half old; update cache entry with new expiration date.
- val = manager.createVal(key, val);
+ if (token != null) {
+ authFromQueryParameter(token);
}
}
+ if (val != null && !checkAccountStatus(val.getAccountId())) {
+ val = null;
+ okPaths.clear();
+ }
+ if (val != null && val.needsCookieRefresh()) {
+ // Session is more than half old; update cache entry with new expiration date.
+ val = manager.createVal(key, val);
+ }
}
private void authFromCookie(String cookie) {
@@ -222,7 +220,7 @@ public abstract class CacheBasedWebSession implements WebSession {
}
private boolean checkAccountStatus(Account.Id id) {
- return byIdCache.get(id).filter(as -> as.getAccount().isActive()).isPresent();
+ return byIdCache.get(id).filter(as -> as.account().isActive()).isPresent();
}
private void saveCookie() {
diff --git a/java/com/google/gerrit/httpd/ContainerAuthFilter.java b/java/com/google/gerrit/httpd/ContainerAuthFilter.java
index d13f2f60f7..517d5dbec3 100644
--- a/java/com/google/gerrit/httpd/ContainerAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ContainerAuthFilter.java
@@ -112,13 +112,13 @@ class ContainerAuthFilter implements Filter {
username = username.toLowerCase(Locale.US);
}
Optional<AccountState> who =
- accountCache.getByUsername(username).filter(a -> a.getAccount().isActive());
+ accountCache.getByUsername(username).filter(a -> a.account().isActive());
if (!who.isPresent()) {
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
WebSession ws = session.get();
- ws.setUserAccountId(who.get().getAccount().getId());
+ ws.setUserAccountId(who.get().account().id());
ws.setAccessPathOk(AccessPath.GIT, true);
ws.setAccessPathOk(AccessPath.REST_API, true);
return true;
diff --git a/java/com/google/gerrit/httpd/DirectChangeByCommit.java b/java/com/google/gerrit/httpd/DirectChangeByCommit.java
index 152a83d1a2..a9be2aa12f 100644
--- a/java/com/google/gerrit/httpd/DirectChangeByCommit.java
+++ b/java/com/google/gerrit/httpd/DirectChangeByCommit.java
@@ -6,11 +6,11 @@ import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.Changes;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -47,7 +47,7 @@ class DirectChangeByCommit extends HttpServlet {
// If exactly one change matches, link to that change.
// TODO Link to a specific patch set, if one matched.
ChangeInfo ci = results.iterator().next();
- token = PageLinks.toChange(new Project.NameKey(ci.project), new Change.Id(ci._number));
+ token = PageLinks.toChange(Project.nameKey(ci.project), Change.id(ci._number));
} else {
// Otherwise, link to the query page.
token = PageLinks.toChangeQuery(query);
diff --git a/java/com/google/gerrit/httpd/GitOverHttpModule.java b/java/com/google/gerrit/httpd/GitOverHttpModule.java
index 8400d60d4b..cbcfb0b1db 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -16,7 +16,7 @@ package com.google.gerrit.httpd;
import static com.google.gerrit.httpd.GitOverHttpServlet.URL_REGEX;
-import com.google.gerrit.reviewdb.client.CoreDownloadSchemes;
+import com.google.gerrit.entities.CoreDownloadSchemes;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.DownloadConfig;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index bd8ba62dbb..520ce4e07c 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -20,23 +20,27 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.RequestInfo;
+import com.google.gerrit.server.RequestListener;
import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
+import com.google.gerrit.server.git.TracingHook;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackInitializer;
+import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.group.GroupAuditService;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.plugincontext.PluginSetContext;
@@ -51,6 +55,7 @@ import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import java.io.IOException;
import java.time.Duration;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
@@ -279,7 +284,7 @@ public class GitOverHttpServlet extends GitServlet {
user.setAccessPath(AccessPath.GIT);
try {
- Project.NameKey nameKey = new Project.NameKey(projectName);
+ Project.NameKey nameKey = Project.nameKey(projectName);
ProjectState state = projectCache.checkedGet(nameKey);
if (state == null || !state.statePermitsRead()) {
throw new RepositoryNotFoundException(nameKey.get());
@@ -307,27 +312,38 @@ public class GitOverHttpServlet extends GitServlet {
private final DynamicSet<PreUploadHook> preUploadHooks;
private final DynamicSet<PostUploadHook> postUploadHooks;
private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
+ private final PermissionBackend permissionBackend;
@Inject
UploadFactory(
TransferConfig tc,
DynamicSet<PreUploadHook> preUploadHooks,
DynamicSet<PostUploadHook> postUploadHooks,
- PluginSetContext<UploadPackInitializer> uploadPackInitializers) {
+ PluginSetContext<UploadPackInitializer> uploadPackInitializers,
+ PermissionBackend permissionBackend) {
this.config = tc;
this.preUploadHooks = preUploadHooks;
this.postUploadHooks = postUploadHooks;
this.uploadPackInitializers = uploadPackInitializers;
+ this.permissionBackend = permissionBackend;
}
@Override
public UploadPack create(HttpServletRequest req, Repository repo) {
- UploadPack up = new UploadPack(repo);
+ ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
+ UploadPack up =
+ new UploadPack(
+ PermissionAwareRepositoryManager.wrap(
+ repo, permissionBackend.currentUser().project(state.getNameKey())));
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks)));
up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
- ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
+ String header = req.getHeader("Git-Protocol");
+ if (header != null) {
+ String[] params = header.split(":");
+ up.setExtraParameters(Arrays.asList(params));
+ }
uploadPackInitializers.runEach(initializer -> initializer.init(state.getNameKey(), up));
return up;
}
@@ -339,6 +355,8 @@ public class GitOverHttpServlet extends GitServlet {
private final Provider<CurrentUser> userProvider;
private final GroupAuditService groupAuditService;
private final Metrics metrics;
+ private final PluginSetContext<RequestListener> requestListeners;
+ private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
private final Provider<WebSession> sessionProvider;
@Inject
@@ -346,15 +364,19 @@ public class GitOverHttpServlet extends GitServlet {
UploadValidators.Factory uploadValidatorsFactory,
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider,
- Provider<WebSession> sessionProvider,
GroupAuditService groupAuditService,
- Metrics metrics) {
+ Metrics metrics,
+ PluginSetContext<RequestListener> requestListeners,
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
+ Provider<WebSession> sessionProvider) {
this.uploadValidatorsFactory = uploadValidatorsFactory;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
- this.sessionProvider = sessionProvider;
this.groupAuditService = groupAuditService;
this.metrics = metrics;
+ this.requestListeners = requestListeners;
+ this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
+ this.sessionProvider = sessionProvider;
}
@Override
@@ -372,7 +394,14 @@ public class GitOverHttpServlet extends GitServlet {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String sessionId = getSessionIdOrNull(sessionProvider);
- try {
+ try (TraceContext traceContext = TraceContext.open()) {
+ RequestInfo requestInfo =
+ RequestInfo.builder(
+ RequestInfo.RequestType.GIT_UPLOAD, userProvider.get(), traceContext)
+ .project(state.getNameKey())
+ .build();
+ requestListeners.runEach(l -> l.onRequest(requestInfo));
+
try {
perm.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
@@ -394,8 +423,14 @@ public class GitOverHttpServlet extends GitServlet {
up.setPreUploadHook(
PreUploadHookChain.newChain(
Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
- up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
- next.doFilter(httpRequest, responseWrapper);
+ if (state.isAllUsers()) {
+ up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
+ }
+
+ try (TracingHook tracingHook = new TracingHook()) {
+ up.setProtocolV2Hook(tracingHook);
+ next.doFilter(httpRequest, responseWrapper);
+ }
} finally {
groupAuditService.dispatch(
new HttpAuditEvent(
@@ -467,15 +502,15 @@ public class GitOverHttpServlet extends GitServlet {
@Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider,
- Provider<WebSession> sessionProvider,
GroupAuditService groupAuditService,
- Metrics metrics) {
+ Metrics metrics,
+ Provider<WebSession> sessionProvider) {
this.cache = cache;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
- this.sessionProvider = sessionProvider;
this.groupAuditService = groupAuditService;
this.metrics = metrics;
+ this.sessionProvider = sessionProvider;
}
@Override
diff --git a/java/com/google/gerrit/httpd/HttpServletResponseRecorder.java b/java/com/google/gerrit/httpd/HttpServletResponseRecorder.java
index 397d093267..4ae7e5e409 100644
--- a/java/com/google/gerrit/httpd/HttpServletResponseRecorder.java
+++ b/java/com/google/gerrit/httpd/HttpServletResponseRecorder.java
@@ -67,7 +67,7 @@ public class HttpServletResponseRecorder extends HttpServletResponseWrapper {
headers.put(name, value);
}
- @SuppressWarnings("all")
+ @SuppressWarnings({"all", "MissingOverride"})
// @Override is omitted for backwards compatibility with servlet-api 2.5
// TODO: Remove @SuppressWarnings and add @Override when Google upgrades
// to servlet-api 3.1
diff --git a/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java b/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
index d53a5c52a0..89ad8785d1 100644
--- a/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
+++ b/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
@@ -15,9 +15,9 @@
package com.google.gerrit.httpd;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.change.ChangesCollection;
@@ -61,7 +61,7 @@ public class NumericChangeIdRedirectServlet extends HttpServlet {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (PermissionBackendException | RuntimeException e) {
- throw new IOException("Unable to lookup change " + id.id, e);
+ throw new IOException("Unable to lookup change " + id.get(), e);
}
String path =
PageLinks.toChange(changeResource.getProject(), changeResource.getChange().getId());
diff --git a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index eb9d1d7184..1974ba78b1 100644
--- a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -21,9 +21,10 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
@@ -32,6 +33,7 @@ import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.account.AuthenticationFailedException;
+import com.google.gerrit.server.account.externalids.PasswordVerifier;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.auth.NoSuchUserException;
import com.google.gerrit.server.config.AuthConfig;
@@ -50,6 +52,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.codec.binary.Base64;
+import org.eclipse.jgit.http.server.GitSmartHttpTools;
/**
* Authenticates the current user by HTTP basic authentication.
@@ -98,11 +101,21 @@ class ProjectBasicAuthFilter implements Filter {
HttpServletRequest req = (HttpServletRequest) request;
Response rsp = new Response((HttpServletResponse) response);
- if (verify(req, rsp)) {
+ if (isSignedInGitRequest(req) || verify(req, rsp)) {
chain.doFilter(req, rsp);
}
}
+ private boolean isSignedInGitRequest(HttpServletRequest req) {
+ boolean isGitRequest = req.getRequestURI() != null && GitSmartHttpTools.isGitClient(req);
+ boolean isAlreadySignedIn = session.get().isSignedIn();
+ boolean res = isAlreadySignedIn && isGitRequest;
+ logger.atFine().log(
+ "HTTP:%s %s signedIn=%s (isAlreadySignedIn=%s, isGitRequest=%s)",
+ req.getMethod(), req.getRequestURI(), res, isAlreadySignedIn, isGitRequest);
+ return res;
+ }
+
private boolean verify(HttpServletRequest req, Response rsp) throws IOException {
final String hdr = req.getHeader(AUTHORIZATION);
if (hdr == null || !hdr.startsWith(LIT_BASIC)) {
@@ -130,7 +143,7 @@ class ProjectBasicAuthFilter implements Filter {
}
Optional<AccountState> accountState =
- accountCache.getByUsername(username).filter(a -> a.getAccount().isActive());
+ accountCache.getByUsername(username).filter(a -> a.account().isActive());
if (!accountState.isPresent()) {
logger.atWarning().log(
"Authentication failed for %s: account inactive or not provisioned in Gerrit", username);
@@ -142,8 +155,11 @@ class ProjectBasicAuthFilter implements Filter {
GitBasicAuthPolicy gitBasicAuthPolicy = authConfig.getGitBasicAuthPolicy();
if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP
|| gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP_LDAP) {
- if (who.checkPassword(password, username)) {
- return succeedAuthentication(who);
+ if (PasswordVerifier.checkPassword(who.externalIds(), username, password)) {
+ logger.atFine().log(
+ "HTTP:%s %s username/password authentication succeeded",
+ req.getMethod(), req.getRequestURI());
+ return succeedAuthentication(who, null);
}
}
@@ -156,11 +172,13 @@ class ProjectBasicAuthFilter implements Filter {
try {
AuthResult whoAuthResult = accountManager.authenticate(whoAuth);
- setUserIdentified(whoAuthResult.getAccountId());
+ setUserIdentified(whoAuthResult.getAccountId(), whoAuthResult);
+ logger.atFine().log(
+ "HTTP:%s %s Realm authentication succeeded", req.getMethod(), req.getRequestURI());
return true;
} catch (NoSuchUserException e) {
- if (who.checkPassword(password, username)) {
- return succeedAuthentication(who);
+ if (PasswordVerifier.checkPassword(who.externalIds(), username, password)) {
+ return succeedAuthentication(who, null);
}
logger.atWarning().withCause(e).log(authenticationFailedMsg(username, req));
rsp.sendError(SC_UNAUTHORIZED);
@@ -182,8 +200,8 @@ class ProjectBasicAuthFilter implements Filter {
}
}
- private boolean succeedAuthentication(AccountState who) {
- setUserIdentified(who.getAccount().getId());
+ private boolean succeedAuthentication(AccountState who, @Nullable AuthResult whoAuthResult) {
+ setUserIdentified(who.account().id(), whoAuthResult);
return true;
}
@@ -200,11 +218,15 @@ class ProjectBasicAuthFilter implements Filter {
return String.format("Authentication from %s failed for %s", req.getRemoteAddr(), username);
}
- private void setUserIdentified(Account.Id id) {
+ private void setUserIdentified(Account.Id id, @Nullable AuthResult whoAuthResult) {
WebSession ws = session.get();
ws.setUserAccountId(id);
ws.setAccessPathOk(AccessPath.GIT, true);
ws.setAccessPathOk(AccessPath.REST_API, true);
+
+ if (whoAuthResult != null) {
+ ws.login(whoAuthResult, false);
+ }
}
private String encoding(HttpServletRequest req) {
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 4461a52fa1..8300823e7f 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -22,11 +22,11 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
@@ -152,7 +152,7 @@ class ProjectOAuthFilter implements Filter {
}
Optional<AccountState> who =
- accountCache.getByUsername(authInfo.username).filter(a -> a.getAccount().isActive());
+ accountCache.getByUsername(authInfo.username).filter(a -> a.account().isActive());
if (!who.isPresent()) {
logger.atWarning().log(
authenticationFailedMsg(authInfo.username, req)
@@ -161,10 +161,10 @@ class ProjectOAuthFilter implements Filter {
return false;
}
- Account account = who.get().getAccount();
+ Account account = who.get().account();
AuthRequest authRequest = AuthRequest.forExternalUser(authInfo.username);
- authRequest.setEmailAddress(account.getPreferredEmail());
- authRequest.setDisplayName(account.getFullName());
+ authRequest.setEmailAddress(account.preferredEmail());
+ authRequest.setDisplayName(account.fullName());
authRequest.setPassword(authInfo.tokenOrSecret);
authRequest.setAuthPlugin(authInfo.pluginName);
authRequest.setAuthProvider(authInfo.exportName);
diff --git a/java/com/google/gerrit/httpd/RequestMetrics.java b/java/com/google/gerrit/httpd/RequestMetrics.java
index cab4a922a3..e0f9b6ad7f 100644
--- a/java/com/google/gerrit/httpd/RequestMetrics.java
+++ b/java/com/google/gerrit/httpd/RequestMetrics.java
@@ -18,6 +18,7 @@ import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -28,15 +29,20 @@ public class RequestMetrics {
@Inject
public RequestMetrics(MetricMaker metricMaker) {
+ Field<Integer> statusCodeField =
+ Field.ofInteger("status", Metadata.Builder::httpStatus)
+ .description("HTTP status code")
+ .build();
+
errors =
metricMaker.newCounter(
"http/server/error_count",
new Description("Rate of REST API error responses").setRate().setUnit("errors"),
- Field.ofInteger("status", "HTTP status code"));
+ statusCodeField);
successes =
metricMaker.newCounter(
"http/server/success_count",
new Description("Rate of REST API success responses").setRate().setUnit("successes"),
- Field.ofInteger("status", "HTTP status code"));
+ statusCodeField);
}
}
diff --git a/java/com/google/gerrit/httpd/RunAsFilter.java b/java/com/google/gerrit/httpd/RunAsFilter.java
index 15dbcaba7a..135de42f53 100644
--- a/java/com/google/gerrit/httpd/RunAsFilter.java
+++ b/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -19,10 +19,10 @@ import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.config.AuthConfig;
@@ -105,7 +105,7 @@ class RunAsFilter implements Filter {
Account.Id target;
try {
- target = accountResolver.resolve(runas).asUnique().getAccount().getId();
+ target = accountResolver.resolve(runas).asUnique().account().id();
} catch (UnprocessableEntityException e) {
replyError(req, res, SC_FORBIDDEN, "no account matches " + RUN_AS, null);
return;
diff --git a/java/com/google/gerrit/httpd/UrlModule.java b/java/com/google/gerrit/httpd/UrlModule.java
index 993a0421d8..ac73d222b3 100644
--- a/java/com/google/gerrit/httpd/UrlModule.java
+++ b/java/com/google/gerrit/httpd/UrlModule.java
@@ -18,7 +18,10 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.base.Strings;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.httpd.raw.AuthorizationCheckServlet;
import com.google.gerrit.httpd.raw.CatServlet;
import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
@@ -28,8 +31,6 @@ import com.google.gerrit.httpd.restapi.ChangesRestApiServlet;
import com.google.gerrit.httpd.restapi.ConfigRestApiServlet;
import com.google.gerrit.httpd.restapi.GroupsRestApiServlet;
import com.google.gerrit.httpd.restapi.ProjectsRestApiServlet;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Key;
import com.google.inject.internal.UniqueAnnotations;
@@ -82,6 +83,9 @@ class UrlModule extends ServletModule {
serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
+ // Serve auth check. Mainly used by PolyGerrit for checking if a user is still logged in.
+ serveRegex("^/(?:a/)?auth-check$").with(AuthorizationCheckServlet.class);
+
// Bind servlets for REST root collections.
// The '/plugins/' root collection is already handled by HttpPluginServlet
// which is bound in HttpPluginModule. We cannot bind it here again although
@@ -147,7 +151,7 @@ class UrlModule extends ServletModule {
while (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
- Project.NameKey project = new Project.NameKey(name);
+ Project.NameKey project = Project.nameKey(name);
toGerrit(
PageLinks.toChangeQuery(PageLinks.projectQuery(project, Change.Status.NEW)),
req,
diff --git a/java/com/google/gerrit/httpd/WebSession.java b/java/com/google/gerrit/httpd/WebSession.java
index e476f15c01..e8b54fe7cd 100644
--- a/java/com/google/gerrit/httpd/WebSession.java
+++ b/java/com/google/gerrit/httpd/WebSession.java
@@ -15,7 +15,7 @@
package com.google.gerrit.httpd;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AuthResult;
diff --git a/java/com/google/gerrit/httpd/WebSessionManager.java b/java/com/google/gerrit/httpd/WebSessionManager.java
index d09b4dd44b..c0900ecbce 100644
--- a/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -30,7 +30,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.cache.Cache;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -286,7 +286,7 @@ public class WebSessionManager {
case 0:
break PARSE;
case 1:
- accountId = new Account.Id(readVarInt32(in));
+ accountId = Account.id(readVarInt32(in));
continue;
case 2:
refreshCookieAt = readFixInt64(in);
diff --git a/java/com/google/gerrit/httpd/XsrfCookieFilter.java b/java/com/google/gerrit/httpd/XsrfCookieFilter.java
index d15ecacd59..079efa4023 100644
--- a/java/com/google/gerrit/httpd/XsrfCookieFilter.java
+++ b/java/com/google/gerrit/httpd/XsrfCookieFilter.java
@@ -32,6 +32,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jgit.http.server.GitSmartHttpTools;
@Singleton
public class XsrfCookieFilter implements Filter {
@@ -50,8 +51,11 @@ public class XsrfCookieFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse rsp, FilterChain chain)
throws IOException, ServletException {
- WebSession s = user.get().isIdentifiedUser() ? session.get() : null;
- setXsrfTokenCookie((HttpServletRequest) req, (HttpServletResponse) rsp, s);
+ HttpServletRequest httpRequest = (HttpServletRequest) req;
+ if (!GitSmartHttpTools.isGitClient(httpRequest)) {
+ WebSession s = user.get().isIdentifiedUser() ? session.get() : null;
+ setXsrfTokenCookie(httpRequest, (HttpServletResponse) rsp, s);
+ }
chain.doFilter(req, rsp);
}
diff --git a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 552e667946..97bb44b000 100644
--- a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -18,12 +18,12 @@ import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USE
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.template.SiteHeaderFooter;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -153,20 +153,20 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
if (!accountState.isPresent()) {
continue;
}
- Account account = accountState.get().getAccount();
+ Account account = accountState.get().account();
String displayName;
- if (accountState.get().getUserName().isPresent()) {
- displayName = accountState.get().getUserName().get();
- } else if (account.getFullName() != null && !account.getFullName().isEmpty()) {
- displayName = account.getFullName();
- } else if (account.getPreferredEmail() != null) {
- displayName = account.getPreferredEmail();
+ if (accountState.get().userName().isPresent()) {
+ displayName = accountState.get().userName().get();
+ } else if (account.fullName() != null && !account.fullName().isEmpty()) {
+ displayName = account.fullName();
+ } else if (account.preferredEmail() != null) {
+ displayName = account.preferredEmail();
} else {
displayName = accountId.toString();
}
Element linkElement = doc.createElement("a");
- linkElement.setAttribute("href", "?account_id=" + account.getId().toString());
+ linkElement.setAttribute("href", "?account_id=" + account.id().toString());
linkElement.setTextContent(displayName);
userlistElement.appendChild(linkElement);
userlistElement.appendChild(doc.createElement("br"));
@@ -176,7 +176,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
}
private Optional<AuthResult> auth(Optional<AccountState> account) {
- return account.map(a -> new AuthResult(a.getAccount().getId(), null, false));
+ return account.map(a -> new AuthResult(a.account().id(), null, false));
}
private AuthResult auth(Account.Id account) {
@@ -196,7 +196,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
getServletContext().log("Multiple accounts with username " + userName + " found");
return null;
}
- return auth(accountStates.get(0).getAccount().getId());
+ return auth(accountStates.get(0).account().id());
}
private Optional<AuthResult> byPreferredEmail(String email) {
diff --git a/java/com/google/gerrit/httpd/auth/oauth/BUILD b/java/com/google/gerrit/httpd/auth/oauth/BUILD
index 2d9345ffc3..dd4549eacb 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/BUILD
+++ b/java/com/google/gerrit/httpd/auth/oauth/BUILD
@@ -8,18 +8,17 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/commons:codec",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index 0c8a1a10f3..fcaef5e833 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -19,6 +19,7 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
@@ -27,7 +28,6 @@ import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -237,7 +237,7 @@ class OAuthSession {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("No SecureRandom available for GitHub authentication", e);
+ throw new IllegalStateException("No SecureRandom available for GitHub authentication", e);
}
}
diff --git a/java/com/google/gerrit/httpd/auth/openid/BUILD b/java/com/google/gerrit/httpd/auth/openid/BUILD
index 2206397619..94f436b4d2 100644
--- a/java/com/google/gerrit/httpd/auth/openid/BUILD
+++ b/java/com/google/gerrit/httpd/auth/openid/BUILD
@@ -10,20 +10,18 @@ java_library(
# We want all these deps to be provided_deps
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/util/http",
"//java/com/google/gerrit/server",
"//lib:guava",
- "//java/com/google/gwtorm",
"//lib:servlet-api",
"//lib/commons:codec",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/openid:consumer",
],
)
diff --git a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index 283cd505f0..0b6008c1a0 100644
--- a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -59,9 +59,7 @@ class LoginForm extends HttpServlet {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final ImmutableMap<String, String> ALL_PROVIDERS =
- ImmutableMap.of(
- "launchpad", OpenIdUrls.URL_LAUNCHPAD,
- "yahoo", OpenIdUrls.URL_YAHOO);
+ ImmutableMap.of("launchpad", OpenIdUrls.URL_LAUNCHPAD);
private final ImmutableSet<String> suggestProviders;
private final Provider<String> urlProvider;
diff --git a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
index 08f2d52b57..37250b4331 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
@@ -18,6 +18,7 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
@@ -27,7 +28,6 @@ import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -222,7 +222,7 @@ class OAuthSessionOverOpenID {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("No SecureRandom available for GitHub authentication", e);
+ throw new IllegalStateException("No SecureRandom available for GitHub authentication", e);
}
}
diff --git a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 8c3dc10c9b..b685011744 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -17,12 +17,13 @@ package com.google.gerrit.httpd.auth.openid;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.KeyUtil;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.ProxyProperties;
import com.google.gerrit.httpd.WebSession;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.UrlEncoded;
import com.google.gerrit.server.account.AccountException;
@@ -32,7 +33,6 @@ import com.google.gerrit.server.auth.openid.OpenIdProviderPattern;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtorm.client.KeyUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -477,8 +477,9 @@ class OpenIdServiceImpl {
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get(req));
String nextToken = Url.decode(token);
- if (isNew && !token.startsWith(PageLinks.REGISTER + "/")) {
- rdr.append('#' + PageLinks.REGISTER);
+ String registerUri = PageLinks.REGISTER + "/";
+ if (isNew && !token.startsWith(registerUri)) {
+ rdr.append('#' + registerUri);
if (nextToken.startsWith("#")) {
// Need to strip the leading # off the token to fix registration page redirect
nextToken = nextToken.substring(1);
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index b438c007f7..4fabb18928 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -36,10 +36,10 @@ import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -174,10 +174,8 @@ class GitwebServlet extends HttpServlet {
}
Path myconf = Files.createTempFile(site.tmp_dir, "gitweb_config", ".perl");
- // To make our configuration file only readable or writable by us;
- // this reduces the chances of someone tampering with the file.
- //
- // TODO(dborowitz): Is there a portable way to do this with NIO?
+ // To make our configuration file only readable or writable by us; this reduces the chances of
+ // someone tampering with the file.
File myconfFile = myconf.toFile();
myconfFile.setWritable(false, false /* all */);
myconfFile.setReadable(false, false /* all */);
@@ -414,7 +412,7 @@ class GitwebServlet extends HttpServlet {
name = name.substring(0, name.length() - 4);
}
- Project.NameKey nameKey = new Project.NameKey(name);
+ Project.NameKey nameKey = Project.nameKey(name);
ProjectState projectState;
try {
projectState = projectCache.checkedGet(nameKey);
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 97475d1503..222041a39f 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -11,6 +11,7 @@ java_library(
"//java/com/google/gerrit/httpd",
"//java/com/google/gerrit/httpd/auth/oauth",
"//java/com/google/gerrit/httpd/auth/openid",
+ "//java/com/google/gerrit/index",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/lucene",
"//java/com/google/gerrit/metrics/dropwizard",
@@ -26,10 +27,10 @@ java_library(
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/sshd",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index c9dfc5817a..7d0b42b80e 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -38,7 +38,9 @@ import com.google.gerrit.httpd.auth.oauth.OAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.httpd.raw.StaticModule;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.pgm.util.LogFileCompressor;
@@ -72,10 +74,10 @@ import com.google.gerrit.server.events.StreamEventsApiListener;
import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.GitRepositoryManagerModule;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
+import com.google.gerrit.server.git.SystemReaderInstaller;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.AutoFlush;
import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.OnlineUpgrader;
import com.google.gerrit.server.index.VersionManager;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
@@ -262,6 +264,13 @@ public class WebAppInitializer extends GuiceServletContextListener implements Fi
Module configModule = new GerritServerConfigModule();
modules.add(configModule);
+ modules.add(
+ new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(SystemReaderInstaller.class);
+ }
+ });
modules.add(new DropWizardMetricMaker.ApiModule());
return Guice.createInjector(PRODUCTION, modules);
}
@@ -343,13 +352,12 @@ public class WebAppInitializer extends GuiceServletContextListener implements Fi
}
private Module createIndexModule() {
- switch (indexType) {
- case LUCENE:
- return LuceneIndexModule.latestVersion(false, AutoFlush.ENABLED);
- case ELASTICSEARCH:
- return ElasticIndexModule.latestVersion(false);
- default:
- throw new IllegalStateException("unsupported index.type = " + indexType);
+ if (indexType.isLucene()) {
+ return LuceneIndexModule.latestVersion(false, AutoFlush.ENABLED);
+ } else if (indexType.isElasticsearch()) {
+ return ElasticIndexModule.latestVersion(false);
+ } else {
+ throw new IllegalStateException("unsupported index.type = " + indexType);
}
}
diff --git a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
new file mode 100644
index 0000000000..e8f173c2ac
--- /dev/null
+++ b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.raw;
+
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.util.http.CacheHeaders;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Offers a dedicated endpoint for checking if a user is still logged in. Returns {@code 204
+ * NO_CONTENT} for logged-in users, {@code 403 FORBIDDEN} otherwise.
+ *
+ * <p>Mainly used by PolyGerrit to check if a user is still logged in.
+ */
+@Singleton
+public class AuthorizationCheckServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+ private final Provider<CurrentUser> user;
+
+ @Inject
+ AuthorizationCheckServlet(Provider<CurrentUser> user) {
+ this.user = user;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ CacheHeaders.setNotCacheable(res);
+ if (user.get().isIdentifiedUser()) {
+ res.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ } else {
+ res.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/httpd/raw/CatServlet.java b/java/com/google/gerrit/httpd/raw/CatServlet.java
index 1d0e7d8bb1..a29521332d 100644
--- a/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -14,12 +14,12 @@
package com.google.gerrit.httpd.raw;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
@@ -118,13 +118,13 @@ public class CatServlet extends HttpServlet {
}
}
- final Change.Id changeId = patchKey.getParentKey().getParentKey();
+ final Change.Id changeId = patchKey.patchSetId().changeId();
String revision;
try {
ChangeNotes notes = changeNotesFactory.createChecked(changeId);
permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
projectCache.checkedGet(notes.getProjectName()).checkStatePermitsRead();
- if (patchKey.getParentKey().get() == 0) {
+ if (patchKey.patchSetId().get() == 0) {
// change edit
Optional<ChangeEdit> edit = changeEditUtil.byChange(notes);
if (edit.isPresent()) {
@@ -134,12 +134,12 @@ public class CatServlet extends HttpServlet {
return;
}
} else {
- PatchSet patchSet = psUtil.get(notes, patchKey.getParentKey());
+ PatchSet patchSet = psUtil.get(notes, patchKey.patchSetId());
if (patchSet == null) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
- revision = patchSet.getRevision().get();
+ revision = patchSet.commitId().name();
}
} catch (ResourceConflictException | NoSuchChangeException | AuthException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
@@ -150,7 +150,7 @@ public class CatServlet extends HttpServlet {
return;
}
- String path = patchKey.getFileName();
+ String path = patchKey.fileName();
String restUrl =
String.format(
"%s/changes/%d/revisions/%s/files/%s/download?parent=%d",
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
new file mode 100644
index 0000000000..8d81d62464
--- /dev/null
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -0,0 +1,148 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.raw;
+
+import static com.google.template.soy.data.ordainers.GsonOrdainer.serializeObject;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.accounts.AccountApi;
+import com.google.gerrit.extensions.api.config.Server;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gson.Gson;
+import com.google.template.soy.data.SanitizedContent;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/** Helper for generating parts of {@code index.html}. */
+public class IndexHtmlUtil {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ /**
+ * Returns both static and dynamic parameters of {@code index.html}. The result is to be used when
+ * rendering the soy template.
+ */
+ public static ImmutableMap<String, Object> templateData(
+ GerritApi gerritApi,
+ String canonicalURL,
+ String cdnPath,
+ String faviconPath,
+ Map<String, String[]> urlParameterMap,
+ Function<String, SanitizedContent> urlInScriptTagOrdainer)
+ throws URISyntaxException, RestApiException {
+ return ImmutableMap.<String, Object>builder()
+ .putAll(
+ staticTemplateData(
+ canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
+ .putAll(dynamicTemplateData(gerritApi))
+ .build();
+ }
+
+ /** Returns dynamic parameters of {@code index.html}. */
+ @UsedAt(Project.GOOGLE)
+ public static Map<String, Map<String, SanitizedContent>> dynamicTemplateData(GerritApi gerritApi)
+ throws RestApiException {
+ Gson gson = OutputFormat.JSON_COMPACT.newGson();
+ Map<String, SanitizedContent> initialData = new HashMap<>();
+ Server serverApi = gerritApi.config().server();
+ initialData.put("\"/config/server/info\"", serializeObject(gson, serverApi.getInfo()));
+ initialData.put("\"/config/server/version\"", serializeObject(gson, serverApi.getVersion()));
+ initialData.put("\"/config/server/top-menus\"", serializeObject(gson, serverApi.topMenus()));
+
+ try {
+ AccountApi accountApi = gerritApi.accounts().self();
+ initialData.put("\"/accounts/self/detail\"", serializeObject(gson, accountApi.get()));
+ initialData.put(
+ "\"/accounts/self/preferences\"", serializeObject(gson, accountApi.getPreferences()));
+ initialData.put(
+ "\"/accounts/self/preferences.diff\"",
+ serializeObject(gson, accountApi.getDiffPreferences()));
+ initialData.put(
+ "\"/accounts/self/preferences.edit\"",
+ serializeObject(gson, accountApi.getEditPreferences()));
+ } catch (AuthException e) {
+ logger.atFine().log("Can't inline account-related data because user is unauthenticated");
+ // Don't render data
+ // TODO(hiesel): Tell the client that the user is not authenticated so that it doesn't have to
+ // fetch anyway. This requires more client side modifications.
+ }
+ return ImmutableMap.of("gerritInitialData", initialData);
+ }
+
+ /** Returns all static parameters of {@code index.html}. */
+ static Map<String, Object> staticTemplateData(
+ String canonicalURL,
+ String cdnPath,
+ String faviconPath,
+ Map<String, String[]> urlParameterMap,
+ Function<String, SanitizedContent> urlInScriptTagOrdainer)
+ throws URISyntaxException {
+ String canonicalPath = computeCanonicalPath(canonicalURL);
+
+ String staticPath = "";
+ if (cdnPath != null) {
+ staticPath = cdnPath;
+ } else if (canonicalPath != null) {
+ staticPath = canonicalPath;
+ }
+
+ SanitizedContent sanitizedStaticPath = urlInScriptTagOrdainer.apply(staticPath);
+ ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
+
+ if (canonicalPath != null) {
+ data.put("canonicalPath", canonicalPath);
+ }
+ if (sanitizedStaticPath != null) {
+ data.put("staticResourcePath", sanitizedStaticPath);
+ }
+ if (faviconPath != null) {
+ data.put("faviconPath", faviconPath);
+ }
+ if (urlParameterMap.containsKey("ce")) {
+ data.put("polyfillCE", "true");
+ }
+ if (urlParameterMap.containsKey("sd")) {
+ data.put("polyfillSD", "true");
+ }
+ if (urlParameterMap.containsKey("sc")) {
+ data.put("polyfillSC", "true");
+ }
+ return data.build();
+ }
+
+ private static String computeCanonicalPath(@Nullable String canonicalURL)
+ throws URISyntaxException {
+ if (Strings.isNullOrEmpty(canonicalURL)) {
+ return "";
+ }
+
+ // If we serving from a sub-directory rather than root, determine the path
+ // from the cannonical web URL.
+ URI uri = new URI(canonicalURL);
+ return uri.getPath().replaceAll("/$", "");
+ }
+
+ private IndexHtmlUtil() {}
+}
diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java
index b6594bcb10..a0b41b2155 100644
--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -17,91 +17,73 @@ package com.google.gerrit.httpd.raw;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_OK;
-import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.data.SanitizedContent;
import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
-import com.google.template.soy.tofu.SoyTofu;
+import com.google.template.soy.jbcsrc.api.SoySauce;
import java.io.IOException;
import java.io.OutputStream;
-import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
+import java.util.function.Function;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class IndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
- protected final byte[] indexSource;
+
+ @Nullable private final String canonicalUrl;
+ @Nullable private final String cdnPath;
+ @Nullable private final String faviconPath;
+ private final GerritApi gerritApi;
+ private final SoySauce soySauce;
+ private final Function<String, SanitizedContent> urlOrdainer;
IndexServlet(
- @Nullable String canonicalURL, @Nullable String cdnPath, @Nullable String faviconPath)
- throws URISyntaxException {
- String resourcePath = "com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy";
- SoyFileSet.Builder builder = SoyFileSet.builder();
- builder.add(Resources.getResource(resourcePath));
- SoyTofu.Renderer renderer =
- builder
+ @Nullable String canonicalUrl,
+ @Nullable String cdnPath,
+ @Nullable String faviconPath,
+ GerritApi gerritApi) {
+ this.canonicalUrl = canonicalUrl;
+ this.cdnPath = cdnPath;
+ this.faviconPath = faviconPath;
+ this.gerritApi = gerritApi;
+ this.soySauce =
+ SoyFileSet.builder()
+ .add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy"))
.build()
- .compileToTofu()
- .newRenderer("com.google.gerrit.httpd.raw.Index")
- .setContentKind(SanitizedContent.ContentKind.HTML)
- .setData(getTemplateData(canonicalURL, cdnPath, faviconPath));
- indexSource = renderer.render().getBytes(UTF_8);
+ .compileTemplates();
+ this.urlOrdainer =
+ (s) ->
+ UnsafeSanitizedContentOrdainer.ordainAsSafe(
+ s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
+ SoySauce.Renderer renderer;
+ try {
+ Map<String, String[]> parameterMap = req.getParameterMap();
+ // TODO(hiesel): Remove URL ordainer as parameter once Soy is consistent
+ ImmutableMap<String, Object> templateData =
+ IndexHtmlUtil.templateData(
+ gerritApi, canonicalUrl, cdnPath, faviconPath, parameterMap, urlOrdainer);
+ renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData);
+ } catch (URISyntaxException | RestApiException e) {
+ throw new IOException(e);
+ }
+
rsp.setCharacterEncoding(UTF_8.name());
rsp.setContentType("text/html");
rsp.setStatus(SC_OK);
try (OutputStream w = rsp.getOutputStream()) {
- w.write(indexSource);
- }
- }
-
- static String computeCanonicalPath(@Nullable String canonicalURL) throws URISyntaxException {
- if (Strings.isNullOrEmpty(canonicalURL)) {
- return "";
- }
-
- // If we serving from a sub-directory rather than root, determine the path
- // from the cannonical web URL.
- URI uri = new URI(canonicalURL);
- return uri.getPath().replaceAll("/$", "");
- }
-
- static Map<String, Object> getTemplateData(
- String canonicalURL, String cdnPath, String faviconPath) throws URISyntaxException {
- String canonicalPath = computeCanonicalPath(canonicalURL);
-
- String staticPath = "";
- if (cdnPath != null) {
- staticPath = cdnPath;
- } else if (canonicalPath != null) {
- staticPath = canonicalPath;
- }
-
- // The resource path must be typed as safe for use in a script src.
- // TODO(wyatta): Upgrade this to use an appropriate safe URL type.
- SanitizedContent sanitizedStaticPath =
- UnsafeSanitizedContentOrdainer.ordainAsSafe(
- staticPath, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
-
- ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
- if (canonicalPath != null) {
- data.put("canonicalPath", canonicalPath);
- }
- if (sanitizedStaticPath != null) {
- data.put("staticResourcePath", sanitizedStaticPath);
- }
- if (faviconPath != null) {
- data.put("faviconPath", faviconPath);
+ w.write(renderer.renderHtml().get().toString().getBytes(UTF_8));
}
- return data.build();
}
}
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 06ac886079..7f2161d4b2 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -22,6 +22,7 @@ import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.httpd.XsrfCookieFilter;
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
import com.google.gerrit.launcher.GerritLauncher;
@@ -41,7 +42,6 @@ import com.google.inject.servlet.ServletModule;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import javax.servlet.Filter;
@@ -54,6 +54,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jgit.http.server.GitSmartHttpTools;
import org.eclipse.jgit.lib.Config;
public class StaticModule extends ServletModule {
@@ -78,11 +79,6 @@ public class StaticModule extends ServletModule {
"/groups/self",
"/settings/*",
"/Documentation/q/*");
- // TODO(dborowitz): These fragments conflict with the REST API
- // namespace, so they will need to use a different path.
- // "/groups/*",
- // "/projects/*");
- //
/**
* Paths that should be treated as static assets when serving PolyGerrit.
@@ -224,11 +220,12 @@ public class StaticModule extends ServletModule {
@Singleton
@Named(POLYGERRIT_INDEX_SERVLET)
HttpServlet getPolyGerritUiIndexServlet(
- @CanonicalWebUrl @Nullable String canonicalUrl, @GerritServerConfig Config cfg)
- throws URISyntaxException {
+ @CanonicalWebUrl @Nullable String canonicalUrl,
+ @GerritServerConfig Config cfg,
+ GerritApi gerritApi) {
String cdnPath = cfg.getString("gerrit", null, "cdnPath");
String faviconPath = cfg.getString("gerrit", null, "faviconPath");
- return new IndexServlet(canonicalUrl, cdnPath, faviconPath);
+ return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi);
}
@Provides
@@ -409,34 +406,36 @@ public class StaticModule extends ServletModule {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
- GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(req);
- String path = pathInfo(req);
+ if (!GitSmartHttpTools.isGitClient(req)) {
+ GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(req);
+ String path = pathInfo(req);
- // Special case assets during development that are built by Bazel and not
- // served out of the source tree.
- //
- // In the war case, these are either inlined, or live under
- // /polygerrit_ui in the war file, so we can just treat them as normal
- // assets.
- if (paths.isDev()) {
- if (path.startsWith("/bower_components/")) {
- bowerComponentServlet.service(reqWrapper, res);
+ // Special case assets during development that are built by Bazel and not
+ // served out of the source tree.
+ //
+ // In the war case, these are either inlined, or live under
+ // /polygerrit_ui in the war file, so we can just treat them as normal
+ // assets.
+ if (paths.isDev()) {
+ if (path.startsWith("/bower_components/")) {
+ bowerComponentServlet.service(reqWrapper, res);
+ return;
+ } else if (path.startsWith("/fonts/")) {
+ fontServlet.service(reqWrapper, res);
+ return;
+ }
+ }
+
+ if (isPolyGerritIndex(path)) {
+ polyGerritIndex.service(reqWrapper, res);
return;
- } else if (path.startsWith("/fonts/")) {
- fontServlet.service(reqWrapper, res);
+ }
+ if (isPolyGerritAsset(path)) {
+ polygerritUI.service(reqWrapper, res);
return;
}
}
- if (isPolyGerritIndex(path)) {
- polyGerritIndex.service(reqWrapper, res);
- return;
- }
- if (isPolyGerritAsset(path)) {
- polygerritUI.service(reqWrapper, res);
- return;
- }
-
chain.doFilter(req, res);
}
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
index 562687b1d5..fc099a6b7d 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
@@ -25,6 +25,7 @@ import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -41,19 +42,24 @@ public class RestApiMetrics {
@Inject
RestApiMetrics(MetricMaker metrics) {
- Field<String> view = Field.ofString("view", "view implementation class");
+ Field<String> viewField =
+ Field.ofString("view", Metadata.Builder::className)
+ .description("view implementation class")
+ .build();
count =
metrics.newCounter(
"http/server/rest_api/count",
new Description("REST API calls by view").setRate(),
- view);
+ viewField);
errorCount =
metrics.newCounter(
"http/server/rest_api/error_count",
new Description("REST API errors by view").setRate(),
- view,
- Field.ofInteger("error_code", "HTTP status code"));
+ viewField,
+ Field.ofInteger("error_code", Metadata.Builder::httpStatus)
+ .description("HTTP status code")
+ .build());
serverLatency =
metrics.newTimer(
@@ -61,7 +67,7 @@ public class RestApiMetrics {
new Description("REST API call latency by view")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- view);
+ viewField);
responseBytes =
metrics.newHistogram(
@@ -69,7 +75,7 @@ public class RestApiMetrics {
new Description("Size of response on network (may be gzip compressed)")
.setCumulative()
.setUnit(Units.BYTES),
- view);
+ viewField);
}
String view(ViewData viewData) {
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index bb05fa85ac..1296c4913f 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -34,23 +34,19 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
-import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
-import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
@@ -67,8 +63,10 @@ import com.google.common.math.IntMath;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.PluginName;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -105,16 +103,25 @@ import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil;
+import com.google.gerrit.server.RequestInfo;
+import com.google.gerrit.server.RequestListener;
import com.google.gerrit.server.audit.ExtendedHttpAuditEvent;
import com.google.gerrit.server.cache.PerThreadCache;
+import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.GroupAuditService;
+import com.google.gerrit.server.logging.PerformanceLogContext;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.quota.QuotaException;
+import com.google.gerrit.server.restapi.change.ChangesCollection;
+import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.util.http.CacheHeaders;
@@ -126,7 +133,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
@@ -158,6 +164,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
@@ -186,9 +193,6 @@ public class RestApiServlet extends HttpServlet {
@VisibleForTesting public static final String X_GERRIT_TRACE = "X-Gerrit-Trace";
- // HTTP 422 Unprocessable Entity.
- // TODO: Remove when HttpServletResponse.SC_UNPROCESSABLE_ENTITY is available
- private static final int SC_UNPROCESSABLE_ENTITY = 422;
private static final String X_REQUESTED_WITH = "X-Requested-With";
private static final String X_GERRIT_AUTH = "X-Gerrit-Auth";
static final ImmutableSet<String> ALLOWED_CORS_METHODS =
@@ -201,6 +205,8 @@ public class RestApiServlet extends HttpServlet {
public static final String XD_AUTHORIZATION = "access_token";
public static final String XD_CONTENT_TYPE = "$ct";
public static final String XD_METHOD = "$m";
+ public static final int SC_UNPROCESSABLE_ENTITY = 422;
+ public static final int SC_TOO_MANY_REQUESTS = 429;
private static final int HEAP_EST_SIZE = 10 * 8 * 1024; // Presize 10 blocks.
private static final String PLAIN_TEXT = "text/plain";
@@ -224,30 +230,41 @@ public class RestApiServlet extends HttpServlet {
final Provider<CurrentUser> currentUser;
final DynamicItem<WebSession> webSession;
final Provider<ParameterParser> paramParser;
+ final PluginSetContext<RequestListener> requestListeners;
final PermissionBackend permissionBackend;
final GroupAuditService auditService;
final RestApiMetrics metrics;
final Pattern allowOrigin;
final RestApiQuotaEnforcer quotaChecker;
+ final Config config;
+ final DynamicSet<PerformanceLogger> performanceLoggers;
+ final ChangeFinder changeFinder;
@Inject
Globals(
Provider<CurrentUser> currentUser,
DynamicItem<WebSession> webSession,
Provider<ParameterParser> paramParser,
+ PluginSetContext<RequestListener> requestListeners,
PermissionBackend permissionBackend,
GroupAuditService auditService,
RestApiMetrics metrics,
RestApiQuotaEnforcer quotaChecker,
- @GerritServerConfig Config cfg) {
+ @GerritServerConfig Config config,
+ DynamicSet<PerformanceLogger> performanceLoggers,
+ ChangeFinder changeFinder) {
this.currentUser = currentUser;
this.webSession = webSession;
this.paramParser = paramParser;
+ this.requestListeners = requestListeners;
this.permissionBackend = permissionBackend;
this.auditService = auditService;
this.metrics = metrics;
this.quotaChecker = quotaChecker;
- allowOrigin = makeAllowOrigin(cfg);
+ this.config = config;
+ this.performanceLoggers = performanceLoggers;
+ this.changeFinder = changeFinder;
+ allowOrigin = makeAllowOrigin(config);
}
private static Pattern makeAllowOrigin(Config cfg) {
@@ -261,6 +278,7 @@ public class RestApiServlet extends HttpServlet {
private final Globals globals;
private final Provider<RestCollection<RestResource, RestResource>> members;
+ private Optional<String> traceId = Optional.empty();
public RestApiServlet(
Globals globals, RestCollection<? extends RestResource, ? extends RestResource> members) {
@@ -286,272 +304,288 @@ public class RestApiServlet extends HttpServlet {
res.setHeader("X-Content-Type-Options", "nosniff");
int status = SC_OK;
long responseBytes = -1;
- Object result = null;
+ Response<?> response = null;
QueryParams qp = null;
Object inputRequestBody = null;
RestResource rsrc = TopLevelResource.INSTANCE;
ViewData viewData = null;
try (TraceContext traceContext = enableTracing(req, res)) {
- try (PerThreadCache ignored = PerThreadCache.create(req)) {
- logger.atFinest().log(
- "Received REST request: %s %s (parameters: %s)",
- req.getMethod(), req.getRequestURI(), getParameterNames(req));
- logger.atFinest().log("Calling user: %s", globals.currentUser.get().getLoggableName());
-
- if (isCorsPreflight(req)) {
- doCorsPreflight(req, res);
- return;
- }
-
- qp = ParameterParser.getQueryParams(req);
- checkCors(req, res, qp.hasXdOverride());
- if (qp.hasXdOverride()) {
- req = applyXdOverrides(req, qp);
- }
- checkUserSession(req);
+ List<IdString> path = splitPath(req);
- List<IdString> path = splitPath(req);
- RestCollection<RestResource, RestResource> rc = members.get();
- globals
- .permissionBackend
- .currentUser()
- .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
+ RequestInfo requestInfo = createRequestInfo(traceContext, requestUri(req), path);
+ globals.requestListeners.runEach(l -> l.onRequest(requestInfo));
- viewData = new ViewData(null, null);
-
- if (path.isEmpty()) {
- globals.quotaChecker.enforce(req);
- if (rc instanceof NeedsParams) {
- ((NeedsParams) rc).setParams(qp.params());
+ try (PerThreadCache ignored = PerThreadCache.create(req)) {
+ // It's important that the PerformanceLogContext is closed before the response is sent to
+ // the client. Only this way it is ensured that the invocation of the PerformanceLogger
+ // plugins happens before the client sees the response. This is needed for being able to
+ // test performance logging from an acceptance test (see
+ // TraceIT#performanceLoggingForRestCall()).
+ try (PerformanceLogContext performanceLogContext =
+ new PerformanceLogContext(globals.config, globals.performanceLoggers)) {
+ logger.atFinest().log(
+ "Received REST request: %s %s (parameters: %s)",
+ req.getMethod(), req.getRequestURI(), getParameterNames(req));
+ logger.atFinest().log("Calling user: %s", globals.currentUser.get().getLoggableName());
+ logger.atFinest().log(
+ "Groups: %s",
+ lazy(() -> globals.currentUser.get().getEffectiveGroups().getKnownGroups()));
+
+ if (isCorsPreflight(req)) {
+ doCorsPreflight(req, res);
+ return;
}
- if (isRead(req)) {
- viewData = new ViewData(null, rc.list());
- } else if (isPost(req)) {
- RestView<RestResource> restCollectionView =
- rc.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
- if (restCollectionView != null) {
- viewData = new ViewData(null, restCollectionView);
- } else {
- throw methodNotAllowed(req);
- }
- } else {
- // DELETE on root collections is not supported
- throw methodNotAllowed(req);
+ qp = ParameterParser.getQueryParams(req);
+ checkCors(req, res, qp.hasXdOverride());
+ if (qp.hasXdOverride()) {
+ req = applyXdOverrides(req, qp);
}
- } else {
- IdString id = path.remove(0);
- try {
- rsrc = rc.parse(rsrc, id);
- globals.quotaChecker.enforce(rsrc, req);
- if (path.isEmpty()) {
- checkPreconditions(req);
- }
- } catch (ResourceNotFoundException e) {
- if (!path.isEmpty()) {
- throw e;
- }
- globals.quotaChecker.enforce(req);
+ checkUserSession(req);
- if (isPost(req) || isPut(req)) {
- RestView<RestResource> createView = rc.views().get(PluginName.GERRIT, "CREATE./");
- if (createView != null) {
- viewData = new ViewData(null, createView);
- status = SC_CREATED;
- path.add(id);
- } else {
- throw e;
- }
- } else if (isDelete(req)) {
- RestView<RestResource> deleteView =
- rc.views().get(PluginName.GERRIT, "DELETE_MISSING./");
- if (deleteView != null) {
- viewData = new ViewData(null, deleteView);
- status = SC_NO_CONTENT;
- path.add(id);
- } else {
- throw e;
- }
- } else {
- throw e;
- }
- }
- if (viewData.view == null) {
- viewData = view(rc, req.getMethod(), path);
- }
- }
- checkRequiresCapability(viewData);
+ RestCollection<RestResource, RestResource> rc = members.get();
+ globals
+ .permissionBackend
+ .currentUser()
+ .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
- while (viewData.view instanceof RestCollection<?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollection<RestResource, RestResource> c =
- (RestCollection<RestResource, RestResource>) viewData.view;
+ viewData = new ViewData(null, null);
if (path.isEmpty()) {
+ globals.quotaChecker.enforce(req);
+ if (rc instanceof NeedsParams) {
+ ((NeedsParams) rc).setParams(qp.params());
+ }
+
if (isRead(req)) {
- viewData = new ViewData(null, c.list());
+ viewData = new ViewData(null, rc.list());
} else if (isPost(req)) {
- // TODO: Here and on other collection methods: There is a bug that binds child views
- // with pluginName="gerrit" instead of the real plugin name. This has never worked
- // correctly and should be fixed where the binding gets created (DynamicMapProvider)
- // and here.
RestView<RestResource> restCollectionView =
- c.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
- if (restCollectionView != null) {
- viewData = new ViewData(null, restCollectionView);
- } else {
- throw methodNotAllowed(req);
- }
- } else if (isDelete(req)) {
- RestView<RestResource> restCollectionView =
- c.views().get(PluginName.GERRIT, "DELETE_ON_COLLECTION./");
+ rc.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
if (restCollectionView != null) {
viewData = new ViewData(null, restCollectionView);
} else {
throw methodNotAllowed(req);
}
} else {
+ // DELETE on root collections is not supported
throw methodNotAllowed(req);
}
- break;
- }
- IdString id = path.remove(0);
- try {
- rsrc = c.parse(rsrc, id);
- checkPreconditions(req);
- viewData = new ViewData(null, null);
- } catch (ResourceNotFoundException e) {
- if (!path.isEmpty()) {
- throw e;
+ } else {
+ IdString id = path.remove(0);
+ try {
+ rsrc = rc.parse(rsrc, id);
+ globals.quotaChecker.enforce(rsrc, req);
+ if (path.isEmpty()) {
+ checkPreconditions(req);
+ }
+ } catch (ResourceNotFoundException e) {
+ if (!path.isEmpty()) {
+ throw e;
+ }
+ globals.quotaChecker.enforce(req);
+
+ if (isPost(req) || isPut(req)) {
+ RestView<RestResource> createView = rc.views().get(PluginName.GERRIT, "CREATE./");
+ if (createView != null) {
+ viewData = new ViewData(null, createView);
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> deleteView =
+ rc.views().get(PluginName.GERRIT, "DELETE_MISSING./");
+ if (deleteView != null) {
+ viewData = new ViewData(null, deleteView);
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
}
+ if (viewData.view == null) {
+ viewData = view(rc, req.getMethod(), path);
+ }
+ }
+ checkRequiresCapability(viewData);
+
+ while (viewData.view instanceof RestCollection<?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollection<RestResource, RestResource> c =
+ (RestCollection<RestResource, RestResource>) viewData.view;
- if (isPost(req) || isPut(req)) {
- RestView<RestResource> createView = c.views().get(PluginName.GERRIT, "CREATE./");
- if (createView != null) {
- viewData = new ViewData(viewData.pluginName, createView);
- status = SC_CREATED;
- path.add(id);
+ if (path.isEmpty()) {
+ if (isRead(req)) {
+ viewData = new ViewData(null, c.list());
+ } else if (isPost(req)) {
+ // TODO: Here and on other collection methods: There is a bug that binds child views
+ // with pluginName="gerrit" instead of the real plugin name. This has never worked
+ // correctly and should be fixed where the binding gets created (DynamicMapProvider)
+ // and here.
+ RestView<RestResource> restCollectionView =
+ c.views().get(PluginName.GERRIT, "POST_ON_COLLECTION./");
+ if (restCollectionView != null) {
+ viewData = new ViewData(null, restCollectionView);
+ } else {
+ throw methodNotAllowed(req);
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> restCollectionView =
+ c.views().get(PluginName.GERRIT, "DELETE_ON_COLLECTION./");
+ if (restCollectionView != null) {
+ viewData = new ViewData(null, restCollectionView);
+ } else {
+ throw methodNotAllowed(req);
+ }
} else {
+ throw methodNotAllowed(req);
+ }
+ break;
+ }
+ IdString id = path.remove(0);
+ try {
+ rsrc = c.parse(rsrc, id);
+ checkPreconditions(req);
+ viewData = new ViewData(null, null);
+ } catch (ResourceNotFoundException e) {
+ if (!path.isEmpty()) {
throw e;
}
- } else if (isDelete(req)) {
- RestView<RestResource> deleteView =
- c.views().get(PluginName.GERRIT, "DELETE_MISSING./");
- if (deleteView != null) {
- viewData = new ViewData(viewData.pluginName, deleteView);
- status = SC_NO_CONTENT;
- path.add(id);
+
+ if (isPost(req) || isPut(req)) {
+ RestView<RestResource> createView = c.views().get(PluginName.GERRIT, "CREATE./");
+ if (createView != null) {
+ viewData = new ViewData(viewData.pluginName, createView);
+ path.add(id);
+ } else {
+ throw e;
+ }
+ } else if (isDelete(req)) {
+ RestView<RestResource> deleteView =
+ c.views().get(PluginName.GERRIT, "DELETE_MISSING./");
+ if (deleteView != null) {
+ viewData = new ViewData(viewData.pluginName, deleteView);
+ path.add(id);
+ } else {
+ throw e;
+ }
} else {
throw e;
}
- } else {
- throw e;
}
+ if (viewData.view == null) {
+ viewData = view(c, req.getMethod(), path);
+ }
+ checkRequiresCapability(viewData);
}
- if (viewData.view == null) {
- viewData = view(c, req.getMethod(), path);
- }
- checkRequiresCapability(viewData);
- }
- if (notModified(req, rsrc, viewData.view)) {
- logger.atFinest().log("REST call succeeded: %d", SC_NOT_MODIFIED);
- res.sendError(SC_NOT_MODIFIED);
- return;
- }
+ if (notModified(req, rsrc, viewData.view)) {
+ logger.atFinest().log("REST call succeeded: %d", SC_NOT_MODIFIED);
+ res.sendError(SC_NOT_MODIFIED);
+ return;
+ }
- if (!globals.paramParser.get().parse(viewData.view, qp.params(), req, res)) {
- return;
- }
+ if (!globals.paramParser.get().parse(viewData.view, qp.params(), req, res)) {
+ return;
+ }
- if (viewData.view instanceof RestReadView<?> && isRead(req)) {
- result = ((RestReadView<RestResource>) viewData.view).apply(rsrc);
- } else if (viewData.view instanceof RestModifyView<?, ?>) {
- @SuppressWarnings("unchecked")
- RestModifyView<RestResource, Object> m =
- (RestModifyView<RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ if (viewData.view instanceof RestReadView<?> && isRead(req)) {
+ response = ((RestReadView<RestResource>) viewData.view).apply(rsrc);
+ } else if (viewData.view instanceof RestModifyView<?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestModifyView<RestResource, Object> m =
+ (RestModifyView<RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ response = m.apply(rsrc, inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
}
- }
- } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionCreateView<RestResource, RestResource, Object> m =
- (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, path.get(0), inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionCreateView<RestResource, RestResource, Object> m =
+ (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ response = m.apply(rsrc, path.get(0), inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
}
- }
- } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
- (RestCollectionDeleteMissingView<RestResource, RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, path.get(0), inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
+ (RestCollectionDeleteMissingView<RestResource, RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ response = m.apply(rsrc, path.get(0), inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
}
- }
- } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
- @SuppressWarnings("unchecked")
- RestCollectionModifyView<RestResource, RestResource, Object> m =
- (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;
-
- Type type = inputType(m);
- inputRequestBody = parseRequest(req, type);
- result = m.apply(rsrc, inputRequestBody);
- if (inputRequestBody instanceof RawInput) {
- try (InputStream is = req.getInputStream()) {
- ServletUtils.consumeRequestBody(is);
+ } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ RestCollectionModifyView<RestResource, RestResource, Object> m =
+ (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;
+
+ Type type = inputType(m);
+ inputRequestBody = parseRequest(req, type);
+ response = m.apply(rsrc, inputRequestBody);
+ if (inputRequestBody instanceof RawInput) {
+ try (InputStream is = req.getInputStream()) {
+ ServletUtils.consumeRequestBody(is);
+ }
}
+ } else {
+ throw new ResourceNotFoundException();
}
- } else {
- throw new ResourceNotFoundException();
- }
- if (result instanceof Response) {
- @SuppressWarnings("rawtypes")
- Response<?> r = (Response) result;
- status = r.statusCode();
- configureCaching(req, res, rsrc, viewData.view, r.caching());
- } else if (result instanceof Response.Redirect) {
- CacheHeaders.setNotCacheable(res);
- String location = ((Response.Redirect) result).location();
- res.sendRedirect(location);
- logger.atFinest().log("REST call redirected to: %s", location);
- return;
- } else if (result instanceof Response.Accepted) {
- CacheHeaders.setNotCacheable(res);
- res.setStatus(SC_ACCEPTED);
- res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted) result).location());
- logger.atFinest().log("REST call succeeded: %d", SC_ACCEPTED);
- return;
- } else {
- CacheHeaders.setNotCacheable(res);
+ traceId = response.traceId();
+ traceId.ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
+
+ if (response instanceof Response.Redirect) {
+ CacheHeaders.setNotCacheable(res);
+ String location = ((Response.Redirect) response).location();
+ res.sendRedirect(location);
+ logger.atFinest().log("REST call redirected to: %s", location);
+ return;
+ } else if (response instanceof Response.Accepted) {
+ CacheHeaders.setNotCacheable(res);
+ res.setStatus(response.statusCode());
+ res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted) response).location());
+ logger.atFinest().log("REST call succeeded: %d", response.statusCode());
+ return;
+ } else if (response instanceof Response.InternalServerError) {
+ // Rethrow the exception to have exactly the same error handling as if the REST endpoint
+ // would have thrown the exception directly, instead of returning
+ // Response.InternalServerError.
+ Exception cause = ((Response.InternalServerError<?>) response).cause();
+ throw cause;
+ }
+
+ status = response.statusCode();
+ configureCaching(req, res, rsrc, viewData.view, response.caching());
+ res.setStatus(status);
+ logger.atFinest().log("REST call succeeded: %d", status);
}
- res.setStatus(status);
- logger.atFinest().log("REST call succeeded: %d", status);
- if (result != Response.none()) {
- result = Response.unwrap(result);
- if (result instanceof BinaryResult) {
- responseBytes = replyBinaryResult(req, res, (BinaryResult) result);
+ if (response != Response.none()) {
+ Object value = Response.unwrap(response);
+ if (value instanceof BinaryResult) {
+ responseBytes = replyBinaryResult(req, res, (BinaryResult) value);
} else {
- responseBytes = replyJson(req, res, false, qp.config(), result);
+ responseBytes = replyJson(req, res, false, qp.config(), value);
}
}
} catch (MalformedJsonException | JsonParseException e) {
@@ -602,21 +636,27 @@ public class RestApiServlet extends HttpServlet {
e.caching(),
e);
} catch (NotImplementedException e) {
+ logger.atSevere().withCause(e).log("Error in %s %s", req.getMethod(), uriForLogging(req));
responseBytes =
replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
} catch (UpdateException e) {
Throwable t = e.getCause();
if (t instanceof LockFailureException) {
- responseBytes =
- replyError(
- req, res, status = SC_SERVICE_UNAVAILABLE, messageOr(t, "Lock failure"), e);
+ logger.atSevere().withCause(t).log("Error in %s %s", req.getMethod(), uriForLogging(req));
+ responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
} else {
status = SC_INTERNAL_SERVER_ERROR;
responseBytes = handleException(e, req, res);
}
} catch (QuotaException e) {
responseBytes =
- replyError(req, res, status = 429, messageOr(e, "Quota limit reached"), e.caching(), e);
+ replyError(
+ req,
+ res,
+ status = SC_TOO_MANY_REQUESTS,
+ messageOr(e, "Quota limit reached"),
+ e.caching(),
+ e);
} catch (Exception e) {
status = SC_INTERNAL_SERVER_ERROR;
responseBytes = handleException(e, req, res);
@@ -641,7 +681,7 @@ public class RestApiServlet extends HttpServlet {
qp != null ? qp.params() : ImmutableListMultimap.of(),
inputRequestBody,
status,
- result,
+ response,
rsrc,
viewData == null ? null : viewData.view));
}
@@ -1029,7 +1069,7 @@ public class RestApiServlet extends HttpServlet {
TemporaryBuffer.Heap buf = heap(HEAP_EST_SIZE, Integer.MAX_VALUE);
buf.write(JSON_MAGIC);
Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
- Gson gson = newGson(config, req);
+ Gson gson = newGson(config);
if (result instanceof JsonElement) {
gson.toJson((JsonElement) result, w);
} else {
@@ -1056,25 +1096,18 @@ public class RestApiServlet extends HttpServlet {
req, res, asBinaryResult(buf).setContentType(JSON_TYPE).setCharacterEncoding(UTF_8));
}
- private static Gson newGson(
- ListMultimap<String, String> config, @Nullable HttpServletRequest req) {
+ private static Gson newGson(ListMultimap<String, String> config) {
GsonBuilder gb = OutputFormat.JSON_COMPACT.newGsonBuilder();
- enablePrettyPrint(gb, config, req);
+ enablePrettyPrint(gb, config);
enablePartialGetFields(gb, config);
return gb.create();
}
- private static void enablePrettyPrint(
- GsonBuilder gb, ListMultimap<String, String> config, @Nullable HttpServletRequest req) {
- String pp = Iterables.getFirst(config.get("pp"), null);
- if (pp == null) {
- pp = Iterables.getFirst(config.get("prettyPrint"), null);
- if (pp == null && req != null) {
- pp = acceptsJson(req) ? "0" : "1";
- }
- }
+ private static void enablePrettyPrint(GsonBuilder gb, ListMultimap<String, String> config) {
+ String pp =
+ Iterables.getFirst(config.get("pp"), Iterables.getFirst(config.get("prettyPrint"), "0"));
if ("1".equals(pp) || "true".equals(pp)) {
gb.setPrettyPrinting();
}
@@ -1271,9 +1304,11 @@ public class RestApiServlet extends HttpServlet {
// Check if we want to delegate to a child collection. Child collections are bound with
// GET.name so we have to check for this since we haven't found any other views.
- core = views.get(PluginName.GERRIT, "GET." + p.get(0));
- if (core != null) {
- return new ViewData(PluginName.GERRIT, core);
+ if (method.equals("GET")) {
+ core = views.get(PluginName.GERRIT, "GET." + p.get(0));
+ if (core != null) {
+ return new ViewData(PluginName.GERRIT, core);
+ }
}
Map<String, RestView<RestResource>> r = new TreeMap<>();
@@ -1388,6 +1423,29 @@ public class RestApiServlet extends HttpServlet {
return traceContext;
}
+ private RequestInfo createRequestInfo(
+ TraceContext traceContext, String requestUri, List<IdString> path) {
+ RequestInfo.Builder requestInfo =
+ RequestInfo.builder(RequestInfo.RequestType.REST, globals.currentUser.get(), traceContext)
+ .requestUri(requestUri);
+
+ if (path.size() < 1) {
+ return requestInfo.build();
+ }
+
+ RestCollection<?, ?> rootCollection = members.get();
+ String resourceId = path.get(0).get();
+ if (rootCollection instanceof ProjectsCollection) {
+ requestInfo.project(Project.nameKey(resourceId));
+ } else if (rootCollection instanceof ChangesCollection) {
+ ChangeNotes changeNotes = globals.changeFinder.findOne(resourceId);
+ if (changeNotes != null) {
+ requestInfo.project(changeNotes.getProjectName());
+ }
+ }
+ return requestInfo.build();
+ }
+
private boolean isDelete(HttpServletRequest req) {
return "DELETE".equals(req.getMethod());
}
@@ -1430,20 +1488,25 @@ public class RestApiServlet extends HttpServlet {
}
}
- private static long handleException(
- Throwable err, HttpServletRequest req, HttpServletResponse res) throws IOException {
- String uri = req.getRequestURI();
- if (!Strings.isNullOrEmpty(req.getQueryString())) {
- uri += "?" + LogRedactUtil.redactQueryString(req.getQueryString());
- }
- logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uri);
+ private long handleException(Throwable err, HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
if (!res.isCommitted()) {
res.reset();
+ traceId.ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
}
return 0;
}
+ private static String uriForLogging(HttpServletRequest req) {
+ String uri = req.getRequestURI();
+ if (!Strings.isNullOrEmpty(req.getQueryString())) {
+ uri += "?" + LogRedactUtil.redactQueryString(req.getQueryString());
+ }
+ return uri;
+ }
+
public static long replyError(
HttpServletRequest req,
HttpServletResponse res,
@@ -1486,10 +1549,6 @@ public class RestApiServlet extends HttpServlet {
static long replyText(
@Nullable HttpServletRequest req, HttpServletResponse res, boolean allowTracing, String text)
throws IOException {
- if ((req == null || isRead(req)) && isMaybeHTML(text)) {
- return replyJson(
- req, res, allowTracing, ImmutableListMultimap.of("pp", "0"), new JsonPrimitive(text));
- }
if (!text.endsWith("\n")) {
text += "\n";
}
@@ -1499,14 +1558,6 @@ public class RestApiServlet extends HttpServlet {
return replyBinaryResult(req, res, BinaryResult.create(text).setContentType(PLAIN_TEXT));
}
- private static boolean isMaybeHTML(String text) {
- return CharMatcher.anyOf("<&").matchesAnyOf(text);
- }
-
- private static boolean acceptsJson(HttpServletRequest req) {
- return req != null && isType(JSON_TYPE, req.getHeader(HttpHeaders.ACCEPT));
- }
-
private static boolean acceptsGzip(HttpServletRequest req) {
if (req != null) {
String accepts = req.getHeader(HttpHeaders.ACCEPT_ENCODING);
diff --git a/java/com/google/gerrit/index/BUILD b/java/com/google/gerrit/index/BUILD
index 55c7746924..95b358195a 100644
--- a/java/com/google/gerrit/index/BUILD
+++ b/java/com/google/gerrit/index/BUILD
@@ -22,17 +22,18 @@ java_library(
":query_exception",
"//antlr3:query_parser",
"//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/json",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server/logging",
"//lib:guava",
+ "//lib:jgit",
"//lib/antlr:java-runtime",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/flogger:api",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/index/IndexConfig.java b/java/com/google/gerrit/index/IndexConfig.java
index b5b36f129b..29b8ea657e 100644
--- a/java/com/google/gerrit/index/IndexConfig.java
+++ b/java/com/google/gerrit/index/IndexConfig.java
@@ -17,6 +17,7 @@ package com.google.gerrit.index;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
import org.eclipse.jgit.lib.Config;
@@ -39,6 +40,7 @@ public abstract class IndexConfig {
setIfPresent(cfg, "maxLimit", b::maxLimit);
setIfPresent(cfg, "maxPages", b::maxPages);
setIfPresent(cfg, "maxTerms", b::maxTerms);
+ setTypeOrDefault(cfg, b::type);
return b;
}
@@ -49,11 +51,17 @@ public abstract class IndexConfig {
}
}
+ private static void setTypeOrDefault(Config cfg, Consumer<String> setter) {
+ String type = cfg != null ? cfg.getString("index", null, "type") : null;
+ setter.accept(new IndexType(type).toString());
+ }
+
public static Builder builder() {
return new AutoValue_IndexConfig.Builder()
.maxLimit(Integer.MAX_VALUE)
.maxPages(Integer.MAX_VALUE)
.maxTerms(DEFAULT_MAX_TERMS)
+ .type(IndexType.getDefault())
.separateChangeSubIndexes(false);
}
@@ -71,6 +79,10 @@ public abstract class IndexConfig {
public abstract int maxTerms();
+ public abstract Builder type(String type);
+
+ public abstract String type();
+
public abstract Builder separateChangeSubIndexes(boolean separate);
abstract IndexConfig autoBuild();
@@ -105,6 +117,9 @@ public abstract class IndexConfig {
*/
public abstract int maxTerms();
+ /** @return index type. */
+ public abstract String type();
+
/**
* @return whether different subsets of changes may be stored in different physical sub-indexes.
*/
diff --git a/java/com/google/gerrit/index/IndexType.java b/java/com/google/gerrit/index/IndexType.java
new file mode 100644
index 0000000000..ee44deb465
--- /dev/null
+++ b/java/com/google/gerrit/index/IndexType.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
+
+/**
+ * Index types supported by the secondary index.
+ *
+ * <p>The explicitly known index types are Lucene (the default) and Elasticsearch.
+ *
+ * <p>The third supported index type is any other type String value, deemed as custom. This is for
+ * configuring index types that are internal or not to be disclosed. Supporting custom index types
+ * allows to not break that case upon core implementation changes.
+ */
+public class IndexType {
+ private static final String LUCENE = "lucene";
+ private static final String ELASTICSEARCH = "elasticsearch";
+
+ private final String type;
+
+ public IndexType(@Nullable String type) {
+ this.type = type == null ? getDefault() : type.toLowerCase();
+ }
+
+ public static String getDefault() {
+ return LUCENE;
+ }
+
+ public static ImmutableSet<String> getKnownTypes() {
+ return ImmutableSet.of(LUCENE, ELASTICSEARCH);
+ }
+
+ public boolean isLucene() {
+ return type.equals(LUCENE);
+ }
+
+ public boolean isElasticsearch() {
+ return type.equals(ELASTICSEARCH);
+ }
+
+ @Override
+ public String toString() {
+ return type;
+ }
+}
diff --git a/java/com/google/gerrit/index/RefState.java b/java/com/google/gerrit/index/RefState.java
index f0e465d43d..956dcabdb8 100644
--- a/java/com/google/gerrit/index/RefState.java
+++ b/java/com/google/gerrit/index/RefState.java
@@ -23,10 +23,10 @@ import com.google.common.base.Splitter;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.git.ObjectIds;
import java.io.IOException;
import java.util.List;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -42,7 +42,7 @@ public abstract class RefState {
String s = new String(b, UTF_8);
List<String> parts = Splitter.on(':').splitToList(s);
RefState.check(parts.size() == 3 && !parts.get(0).isEmpty() && !parts.get(1).isEmpty(), s);
- result.put(new Project.NameKey(parts.get(0)), RefState.create(parts.get(1), parts.get(2)));
+ result.put(Project.nameKey(parts.get(0)), RefState.create(parts.get(1), parts.get(2)));
}
return result;
}
@@ -61,7 +61,7 @@ public abstract class RefState {
public byte[] toByteArray(Project.NameKey project) {
byte[] a = (project.toString() + ':' + ref() + ':').getBytes(UTF_8);
- byte[] b = new byte[a.length + Constants.OBJECT_ID_STRING_LENGTH];
+ byte[] b = new byte[a.length + ObjectIds.STR_LEN];
System.arraycopy(a, 0, b, 0, a.length);
id().copyTo(b, a.length);
return b;
diff --git a/java/com/google/gerrit/index/Schema.java b/java/com/google/gerrit/index/Schema.java
index f65d03e438..bab2990f6e 100644
--- a/java/com/google/gerrit/index/Schema.java
+++ b/java/com/google/gerrit/index/Schema.java
@@ -35,6 +35,7 @@ public class Schema<T> {
public static class Builder<T> {
private final List<FieldDef<T, ?>> fields = new ArrayList<>();
+ private boolean useLegacyNumericFields;
public Builder<T> add(Schema<T> schema) {
this.fields.addAll(schema.getFields().values());
@@ -53,8 +54,13 @@ public class Schema<T> {
return this;
}
+ public Builder<T> legacyNumericFields(boolean useLegacyNumericFields) {
+ this.useLegacyNumericFields = useLegacyNumericFields;
+ return this;
+ }
+
public Schema<T> build() {
- return new Schema<>(ImmutableList.copyOf(fields));
+ return new Schema<>(useLegacyNumericFields, ImmutableList.copyOf(fields));
}
}
@@ -83,14 +89,15 @@ public class Schema<T> {
private final ImmutableMap<String, FieldDef<T, ?>> fields;
private final ImmutableMap<String, FieldDef<T, ?>> storedFields;
+ private final boolean useLegacyNumericFields;
private int version;
- public Schema(Iterable<FieldDef<T, ?>> fields) {
- this(0, fields);
+ public Schema(boolean useLegacyNumericFields, Iterable<FieldDef<T, ?>> fields) {
+ this(0, useLegacyNumericFields, fields);
}
- public Schema(int version, Iterable<FieldDef<T, ?>> fields) {
+ public Schema(int version, boolean useLegacyNumericFields, Iterable<FieldDef<T, ?>> fields) {
this.version = version;
ImmutableMap.Builder<String, FieldDef<T, ?>> b = ImmutableMap.builder();
ImmutableMap.Builder<String, FieldDef<T, ?>> sb = ImmutableMap.builder();
@@ -102,12 +109,17 @@ public class Schema<T> {
}
this.fields = b.build();
this.storedFields = sb.build();
+ this.useLegacyNumericFields = useLegacyNumericFields;
}
public final int getVersion() {
return version;
}
+ public final boolean useLegacyNumericFields() {
+ return useLegacyNumericFields;
+ }
+
/**
* Get all fields in this schema.
*
diff --git a/java/com/google/gerrit/index/SchemaUtil.java b/java/com/google/gerrit/index/SchemaUtil.java
index c59f251acf..9599d6a893 100644
--- a/java/com/google/gerrit/index/SchemaUtil.java
+++ b/java/com/google/gerrit/index/SchemaUtil.java
@@ -67,12 +67,19 @@ public class SchemaUtil {
}
public static <V> Schema<V> schema(Collection<FieldDef<V, ?>> fields) {
- return new Schema<>(ImmutableList.copyOf(fields));
+ return new Schema<>(true, ImmutableList.copyOf(fields));
+ }
+
+ public static <V> Schema<V> schema(Schema<V> schema, boolean useLegacyNumericFields) {
+ return new Schema<>(
+ useLegacyNumericFields,
+ new ImmutableList.Builder<FieldDef<V, ?>>().addAll(schema.getFields().values()).build());
}
@SafeVarargs
public static <V> Schema<V> schema(Schema<V> schema, FieldDef<V, ?>... moreFields) {
return new Schema<>(
+ true,
new ImmutableList.Builder<FieldDef<V, ?>>()
.addAll(schema.getFields().values())
.addAll(ImmutableList.copyOf(moreFields))
@@ -81,7 +88,7 @@ public class SchemaUtil {
@SafeVarargs
public static <V> Schema<V> schema(FieldDef<V, ?>... fields) {
- return schema(ImmutableList.copyOf(fields));
+ return new Schema<>(true, ImmutableList.copyOf(fields));
}
public static Set<String> getPersonParts(PersonIdent person) {
diff --git a/java/com/google/gerrit/index/project/BUILD b/java/com/google/gerrit/index/project/BUILD
index 2c460fdca1..b423f84f1f 100644
--- a/java/com/google/gerrit/index/project/BUILD
+++ b/java/com/google/gerrit/index/project/BUILD
@@ -5,9 +5,9 @@ java_library(
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/reviewdb:server",
"//lib:guava",
"//lib/guice",
],
diff --git a/java/com/google/gerrit/index/project/IndexedProjectQuery.java b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
index 383ba1c75d..cdfeabdab1 100644
--- a/java/com/google/gerrit/index/project/IndexedProjectQuery.java
+++ b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
@@ -14,13 +14,13 @@
package com.google.gerrit.index.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.IndexedQuery;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Project;
public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectData>
implements DataSource<ProjectData> {
diff --git a/java/com/google/gerrit/index/project/ProjectData.java b/java/com/google/gerrit/index/project/ProjectData.java
index fb029acddb..2bf9a4b537 100644
--- a/java/com/google/gerrit/index/project/ProjectData.java
+++ b/java/com/google/gerrit/index/project/ProjectData.java
@@ -18,7 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
diff --git a/java/com/google/gerrit/index/project/ProjectField.java b/java/com/google/gerrit/index/project/ProjectField.java
index 119980c104..c2c8986093 100644
--- a/java/com/google/gerrit/index/project/ProjectField.java
+++ b/java/com/google/gerrit/index/project/ProjectField.java
@@ -20,11 +20,11 @@ import static com.google.gerrit.index.FieldDef.fullText;
import static com.google.gerrit.index.FieldDef.prefix;
import static com.google.gerrit.index.FieldDef.storedOnly;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.SchemaUtil;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
/** Index schema for projects. */
public class ProjectField {
diff --git a/java/com/google/gerrit/index/project/ProjectIndex.java b/java/com/google/gerrit/index/project/ProjectIndex.java
index db7302a41a..3e99d55996 100644
--- a/java/com/google/gerrit/index/project/ProjectIndex.java
+++ b/java/com/google/gerrit/index/project/ProjectIndex.java
@@ -14,10 +14,10 @@
package com.google.gerrit.index.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Project;
public interface ProjectIndex extends Index<Project.NameKey, ProjectData> {
diff --git a/java/com/google/gerrit/index/project/ProjectIndexCollection.java b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
index 281f99214f..30227a3352 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexCollection.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
@@ -15,8 +15,8 @@
package com.google.gerrit.index.project;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.IndexCollection;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Singleton;
@Singleton
diff --git a/java/com/google/gerrit/index/project/ProjectIndexer.java b/java/com/google/gerrit/index/project/ProjectIndexer.java
index 1ca29f5580..bd5efeb224 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexer.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.index.project;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
public interface ProjectIndexer {
diff --git a/java/com/google/gerrit/index/query/AndSource.java b/java/com/google/gerrit/index/query/AndSource.java
index 7d817d2b44..538e11b783 100644
--- a/java/com/google/gerrit/index/query/AndSource.java
+++ b/java/com/google/gerrit/index/query/AndSource.java
@@ -78,48 +78,55 @@ public class AndSource<T> extends AndPredicate<T>
if (source == null) {
throw new StorageException("No DataSource: " + this);
}
- List<T> r = new ArrayList<>();
- T last = null;
- int nextStart = 0;
- boolean skipped = false;
- for (T data : buffer(source.read())) {
- if (!isMatchable() || match(data)) {
- r.add(data);
- } else {
- skipped = true;
- }
- last = data;
- nextStart++;
- }
- if (skipped && last != null && source instanceof Paginated) {
- // If our source is a paginated source and we skipped at
- // least one of its results, we may not have filled the full
- // limit the caller wants. Restart the source and continue.
- //
- @SuppressWarnings("unchecked")
- Paginated<T> p = (Paginated<T>) source;
- while (skipped && r.size() < p.getOptions().limit() + start) {
- skipped = false;
- ResultSet<T> next = p.restart(nextStart);
-
- for (T data : buffer(next)) {
- if (match(data)) {
- r.add(data);
- } else {
- skipped = true;
+ // ResultSets are lazy. Calling #read here first and then dealing with ResultSets only when
+ // requested allows the index to run asynchronous queries.
+ ResultSet<T> resultSet = source.read();
+ return new LazyResultSet<>(
+ () -> {
+ List<T> r = new ArrayList<>();
+ T last = null;
+ int nextStart = 0;
+ boolean skipped = false;
+ for (T data : buffer(resultSet)) {
+ if (!isMatchable() || match(data)) {
+ r.add(data);
+ } else {
+ skipped = true;
+ }
+ last = data;
+ nextStart++;
}
- nextStart++;
- }
- }
- }
- if (start >= r.size()) {
- r = ImmutableList.of();
- } else if (start > 0) {
- r = ImmutableList.copyOf(r.subList(start, r.size()));
- }
- return new ListResultSet<>(r);
+ if (skipped && last != null && source instanceof Paginated) {
+ // If our source is a paginated source and we skipped at
+ // least one of its results, we may not have filled the full
+ // limit the caller wants. Restart the source and continue.
+ //
+ @SuppressWarnings("unchecked")
+ Paginated<T> p = (Paginated<T>) source;
+ while (skipped && r.size() < p.getOptions().limit() + start) {
+ skipped = false;
+ ResultSet<T> next = p.restart(nextStart);
+
+ for (T data : buffer(next)) {
+ if (match(data)) {
+ r.add(data);
+ } else {
+ skipped = true;
+ }
+ nextStart++;
+ }
+ }
+ }
+
+ if (start >= r.size()) {
+ return ImmutableList.of();
+ } else if (start > 0) {
+ return ImmutableList.copyOf(r.subList(start, r.size()));
+ }
+ return ImmutableList.copyOf(r);
+ });
}
@Override
diff --git a/java/com/google/gerrit/index/query/LazyResultSet.java b/java/com/google/gerrit/index/query/LazyResultSet.java
new file mode 100644
index 0000000000..f3fab5f5b5
--- /dev/null
+++ b/java/com/google/gerrit/index/query/LazyResultSet.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.query;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.function.Supplier;
+
+/**
+ * Result set that allows for asynchronous execution of the actual query. Callers should dispatch
+ * the query and call the constructor of this class with a supplier that fetches the result and
+ * blocks on it if necessary.
+ *
+ * <p>If the execution is synchronous or the results are known a priori, consider using {@link
+ * ListResultSet}.
+ */
+public class LazyResultSet<T> implements ResultSet<T> {
+ private final Supplier<ImmutableList<T>> resultsCallback;
+
+ private boolean resultsReturned = false;
+
+ public LazyResultSet(Supplier<ImmutableList<T>> r) {
+ resultsCallback = requireNonNull(r, "results can't be null");
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return toList().iterator();
+ }
+
+ @Override
+ public ImmutableList<T> toList() {
+ if (resultsReturned) {
+ throw new IllegalStateException("Results already obtained");
+ }
+ resultsReturned = true;
+ return resultsCallback.get();
+ }
+
+ @Override
+ public void close() {}
+}
diff --git a/java/com/google/gerrit/index/query/ListResultSet.java b/java/com/google/gerrit/index/query/ListResultSet.java
index 4cf48c8f1b..9d7eadf393 100644
--- a/java/com/google/gerrit/index/query/ListResultSet.java
+++ b/java/com/google/gerrit/index/query/ListResultSet.java
@@ -14,15 +14,25 @@
package com.google.gerrit.index.query;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.List;
+/**
+ * Result set for queries that run synchronously or for cases where the result is already known and
+ * we just need to pipe it back through our interfaces.
+ *
+ * <p>If your implementation benefits from asynchronous execution (i.e. dispatching a query and
+ * awaiting results only when {@link ResultSet#toList()} is called, consider using {@link
+ * LazyResultSet}.
+ */
public class ListResultSet<T> implements ResultSet<T> {
- private ImmutableList<T> items;
+ private ImmutableList<T> results;
public ListResultSet(List<T> r) {
- items = ImmutableList.copyOf(r);
+ results = ImmutableList.copyOf(requireNonNull(r, "results can't be null"));
}
@Override
@@ -32,16 +42,16 @@ public class ListResultSet<T> implements ResultSet<T> {
@Override
public ImmutableList<T> toList() {
- if (items == null) {
+ if (results == null) {
throw new IllegalStateException("Results already obtained");
}
- ImmutableList<T> r = items;
- items = null;
+ ImmutableList<T> r = results;
+ results = null;
return r;
}
@Override
public void close() {
- items = null;
+ results = null;
}
}
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 70772455f5..9501e524e0 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -38,6 +38,7 @@ import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.server.logging.CallerFinder;
+import com.google.gerrit.server.logging.Metadata;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -60,14 +61,15 @@ public abstract class QueryProcessor<T> {
final Timer1<String> executionTime;
Metrics(MetricMaker metricMaker) {
- Field<String> index = Field.ofString("index", "index name");
executionTime =
metricMaker.newTimer(
"query/query_latency",
new Description("Successful query latency, accumulated over the life of the process")
.setCumulative()
.setUnit(Description.Units.MILLISECONDS),
- index);
+ Field.ofString("index", Metadata.Builder::indexName)
+ .description("index name")
+ .build());
}
}
@@ -216,7 +218,7 @@ public abstract class QueryProcessor<T> {
logger.atFine().log(
"Executing %d %s index queries for %s",
- cnt, schemaDef.getName(), callerFinder.findCaller());
+ cnt, schemaDef.getName(), callerFinder.findCallerLazy());
List<QueryResult<T>> out;
try {
// Parse and rewrite all queries.
diff --git a/java/com/google/gerrit/index/query/TooManyTermsInQueryException.java b/java/com/google/gerrit/index/query/TooManyTermsInQueryException.java
new file mode 100644
index 0000000000..b0a394eb1c
--- /dev/null
+++ b/java/com/google/gerrit/index/query/TooManyTermsInQueryException.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.query;
+
+public class TooManyTermsInQueryException extends QueryParseException {
+ private static final long serialVersionUID = 1L;
+
+ private static final String MESSAGE = "too many terms in query";
+
+ public TooManyTermsInQueryException() {
+ super(MESSAGE);
+ }
+
+ public TooManyTermsInQueryException(Throwable why) {
+ super(MESSAGE, why);
+ }
+}
diff --git a/java/com/google/gerrit/index/query/testing/TreeSubject.java b/java/com/google/gerrit/index/query/testing/TreeSubject.java
index c60b36394a..7d2b868bd9 100644
--- a/java/com/google/gerrit/index/query/testing/TreeSubject.java
+++ b/java/com/google/gerrit/index/query/testing/TreeSubject.java
@@ -23,43 +23,46 @@ import com.google.common.truth.Subject;
import com.google.gerrit.index.query.QueryParser;
import org.antlr.runtime.tree.Tree;
-public class TreeSubject extends Subject<TreeSubject, Tree> {
+public class TreeSubject extends Subject {
public static TreeSubject assertThat(Tree actual) {
return assertAbout(TreeSubject::new).that(actual);
}
+ private final Tree tree;
+
private TreeSubject(FailureMetadata failureMetadata, Tree tree) {
super(failureMetadata, tree);
+ this.tree = tree;
}
public void hasType(int expectedType) {
isNotNull();
- check("getType()").that(typeName(actual().getType())).isEqualTo(typeName(expectedType));
+ check("getType()").that(typeName(tree.getType())).isEqualTo(typeName(expectedType));
}
public void hasText(String expectedText) {
requireNonNull(expectedText);
isNotNull();
- check("getText()").that(actual().getText()).isEqualTo(expectedText);
+ check("getText()").that(tree.getText()).isEqualTo(expectedText);
}
public void hasNoChildren() {
isNotNull();
- check("getChildCount()").that(actual().getChildCount()).isEqualTo(0);
+ check("getChildCount()").that(tree.getChildCount()).isEqualTo(0);
}
public void hasChildCount(int expectedChildCount) {
checkArgument(
expectedChildCount > 0, "expected child count must be positive: %s", expectedChildCount);
isNotNull();
- check("getChildCount()").that(actual().getChildCount()).isEqualTo(expectedChildCount);
+ check("getChildCount()").that(tree.getChildCount()).isEqualTo(expectedChildCount);
}
public TreeSubject child(int childIndex) {
isNotNull();
return check("getChild(%s)", childIndex)
.about(TreeSubject::new)
- .that(actual().getChild(childIndex));
+ .that(tree.getChild(childIndex));
}
private static String typeName(int type) {
diff --git a/java/com/google/gerrit/jgit/BUILD b/java/com/google/gerrit/jgit/BUILD
index e67ebfe1ff..1041f1fe7d 100644
--- a/java/com/google/gerrit/jgit/BUILD
+++ b/java/com/google/gerrit/jgit/BUILD
@@ -8,6 +8,6 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//lib:gson",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
],
)
diff --git a/java/com/google/gerrit/json/BUILD b/java/com/google/gerrit/json/BUILD
index d9cec456fc..439f23f2e9 100644
--- a/java/com/google/gerrit/json/BUILD
+++ b/java/com/google/gerrit/json/BUILD
@@ -6,5 +6,6 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//lib:gson",
+ "//lib/flogger:api",
],
)
diff --git a/java/com/google/gerrit/json/EnumTypeAdapterFactory.java b/java/com/google/gerrit/json/EnumTypeAdapterFactory.java
new file mode 100644
index 0000000000..21c489125b
--- /dev/null
+++ b/java/com/google/gerrit/json/EnumTypeAdapterFactory.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.json;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.bind.TypeAdapters;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+
+/**
+ * A {@code TypeAdapterFactory} for enums.
+ *
+ * <p>This factory introduces a wrapper around Gson's own default enum handler to add the following
+ * special behavior: log when input which doesn't match any existing enum value is encountered.
+ */
+public class EnumTypeAdapterFactory implements TypeAdapterFactory {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Override
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ TypeAdapter<T> defaultEnumAdapter = TypeAdapters.ENUM_FACTORY.create(gson, typeToken);
+ if (defaultEnumAdapter == null) {
+ // Not an enum. -> Enum type adapter doesn't apply.
+ return null;
+ }
+
+ return new EnumTypeAdapter(defaultEnumAdapter, typeToken);
+ }
+
+ private static class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
+
+ private final TypeAdapter<T> defaultEnumAdapter;
+ private final TypeToken<T> typeToken;
+
+ public EnumTypeAdapter(TypeAdapter<T> defaultEnumAdapter, TypeToken<T> typeToken) {
+ this.defaultEnumAdapter = defaultEnumAdapter;
+ this.typeToken = typeToken;
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ // Still handle null values. -> Check them first.
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ T enumValue = defaultEnumAdapter.read(in);
+ if (enumValue == null) {
+ logger.atWarning().log("Expected an existing value for enum %s.", typeToken);
+ }
+ return enumValue;
+ }
+
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ defaultEnumAdapter.write(out, value);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/json/OutputFormat.java b/java/com/google/gerrit/json/OutputFormat.java
index a2d174f4cc..3e7c319aa7 100644
--- a/java/com/google/gerrit/json/OutputFormat.java
+++ b/java/com/google/gerrit/json/OutputFormat.java
@@ -55,7 +55,8 @@ public enum OutputFormat {
GsonBuilder gb =
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
- .registerTypeAdapter(Timestamp.class, new SqlTimestampDeserializer());
+ .registerTypeAdapter(Timestamp.class, new SqlTimestampDeserializer())
+ .registerTypeAdapterFactory(new EnumTypeAdapterFactory());
if (this == OutputFormat.JSON) {
gb.setPrettyPrinting();
}
diff --git a/java/com/google/gerrit/json/SqlTimestampDeserializer.java b/java/com/google/gerrit/json/SqlTimestampDeserializer.java
index 0148226284..e1cf38210a 100644
--- a/java/com/google/gerrit/json/SqlTimestampDeserializer.java
+++ b/java/com/google/gerrit/json/SqlTimestampDeserializer.java
@@ -44,7 +44,15 @@ class SqlTimestampDeserializer implements JsonDeserializer<Timestamp>, JsonSeria
throw new JsonParseException("Expected string for timestamp type");
}
- return JavaSqlTimestampHelper.parseTimestamp(p.getAsString());
+ String input = p.getAsString();
+ if (input.trim().isEmpty()) {
+ // Magic timestamp to indicate no timestamp. (-> null object)
+ // Always create a new object as timestamps are mutable. Don't use TimeUtil.never() to not
+ // introduce an undesired dependency.
+ return new Timestamp(0);
+ }
+
+ return JavaSqlTimestampHelper.parseTimestamp(input);
}
@Override
diff --git a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index 93c0fada6d..a787d6ffae 100644
--- a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -64,8 +64,10 @@ import java.util.function.Function;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LegacyIntField;
import org.apache.lucene.document.LegacyLongField;
+import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
@@ -340,15 +342,23 @@ public abstract class AbstractLuceneIndex<K, V> implements Index<K, V> {
if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
for (Object value : values.getValues()) {
- doc.add(new LegacyIntField(name, (Integer) value, store));
+ Integer intValue = (Integer) value;
+ if (schema.useLegacyNumericFields()) {
+ doc.add(new LegacyIntField(name, intValue, store));
+ } else {
+ doc.add(new IntPoint(name, intValue));
+ if (store == Store.YES) {
+ doc.add(new StoredField(name, intValue));
+ }
+ }
}
} else if (type == FieldType.LONG) {
for (Object value : values.getValues()) {
- doc.add(new LegacyLongField(name, (Long) value, store));
+ addLongField(doc, name, store, (Long) value);
}
} else if (type == FieldType.TIMESTAMP) {
for (Object value : values.getValues()) {
- doc.add(new LegacyLongField(name, ((Timestamp) value).getTime(), store));
+ addLongField(doc, name, store, ((Timestamp) value).getTime());
}
} else if (type == FieldType.EXACT || type == FieldType.PREFIX) {
for (Object value : values.getValues()) {
@@ -367,6 +377,17 @@ public abstract class AbstractLuceneIndex<K, V> implements Index<K, V> {
}
}
+ private void addLongField(Document doc, String name, Store store, Long longValue) {
+ if (schema.useLegacyNumericFields()) {
+ doc.add(new LegacyLongField(name, longValue, store));
+ } else {
+ doc.add(new LongPoint(name, longValue));
+ if (store == Store.YES) {
+ doc.add(new StoredField(name, longValue));
+ }
+ }
+ }
+
protected FieldBundle toFieldBundle(Document doc) {
Map<String, FieldDef<V, ?>> allFields = getSchema().getFields();
ListMultimap<String, Object> rawFields = ArrayListMultimap.create();
diff --git a/java/com/google/gerrit/lucene/BUILD b/java/com/google/gerrit/lucene/BUILD
index 40e3157b48..879e7068aa 100644
--- a/java/com/google/gerrit/lucene/BUILD
+++ b/java/com/google/gerrit/lucene/BUILD
@@ -24,23 +24,20 @@ java_library(
deps = [
":query_builder",
"//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
- "//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/index/project",
- "//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/proto",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/logging",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/lucene:lucene-analyzers-common",
"//lib/lucene:lucene-core-and-backward-codecs",
"//lib/lucene:lucene-misc",
diff --git a/java/com/google/gerrit/lucene/ChangeSubIndex.java b/java/com/google/gerrit/lucene/ChangeSubIndex.java
index 8fcd0b1d04..7e6fba5974 100644
--- a/java/com/google/gerrit/lucene/ChangeSubIndex.java
+++ b/java/com/google/gerrit/lucene/ChangeSubIndex.java
@@ -15,10 +15,12 @@
package com.google.gerrit.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.gerrit.lucene.LuceneChangeIndex.ID2_SORT_FIELD;
import static com.google.gerrit.lucene.LuceneChangeIndex.ID_SORT_FIELD;
import static com.google.gerrit.lucene.LuceneChangeIndex.UPDATED_SORT_FIELD;
import static com.google.gerrit.server.index.change.ChangeSchemaDefinitions.NAME;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
@@ -27,7 +29,6 @@ import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.AutoFlush;
import com.google.gerrit.server.index.change.ChangeField;
@@ -103,6 +104,9 @@ public class ChangeSubIndex extends AbstractLuceneIndex<Change.Id, ChangeData>
if (f == ChangeField.LEGACY_ID) {
int v = (Integer) getOnlyElement(values.getValues());
doc.add(new NumericDocValuesField(ID_SORT_FIELD, v));
+ } else if (f == ChangeField.LEGACY_ID_STR) {
+ String v = (String) getOnlyElement(values.getValues());
+ doc.add(new NumericDocValuesField(ID2_SORT_FIELD, Integer.valueOf(v)));
} else if (f == ChangeField.UPDATED) {
long t = ((Timestamp) getOnlyElement(values.getValues())).getTime();
doc.add(new NumericDocValuesField(UPDATED_SORT_FIELD, t));
diff --git a/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index 272c774754..315752cb34 100644
--- a/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -17,8 +17,10 @@ package com.google.gerrit.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.gerrit.server.index.account.AccountField.FULL_NAME;
import static com.google.gerrit.server.index.account.AccountField.ID;
+import static com.google.gerrit.server.index.account.AccountField.ID_STR;
import static com.google.gerrit.server.index.account.AccountField.PREFERRED_EMAIL_EXACT;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
@@ -27,7 +29,6 @@ import com.google.gerrit.index.Schema.Values;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -61,13 +62,18 @@ public class LuceneAccountIndex extends AbstractLuceneIndex<Account.Id, AccountS
private static final String FULL_NAME_SORT_FIELD = sortFieldName(FULL_NAME);
private static final String EMAIL_SORT_FIELD = sortFieldName(PREFERRED_EMAIL_EXACT);
private static final String ID_SORT_FIELD = sortFieldName(ID);
+ private static final String ID2_SORT_FIELD = sortFieldName(ID_STR);
- private static Term idTerm(AccountState as) {
- return idTerm(as.getAccount().getId());
+ private static Term idTerm(boolean useLegacyNumericFields, AccountState as) {
+ return idTerm(useLegacyNumericFields, as.account().id());
}
- private static Term idTerm(Account.Id id) {
- return QueryBuilder.intTerm(ID.getName(), id.get());
+ private static Term idTerm(boolean useLegacyNumericFields, Account.Id id) {
+ FieldDef<AccountState, ?> idField = useLegacyNumericFields ? ID : ID_STR;
+ if (useLegacyNumericFields) {
+ return QueryBuilder.intTerm(idField.getName(), id.get());
+ }
+ return QueryBuilder.stringTerm(idField.getName(), Integer.toString(id.get()));
}
private final GerritIndexWriterConfig indexWriterConfig;
@@ -113,6 +119,9 @@ public class LuceneAccountIndex extends AbstractLuceneIndex<Account.Id, AccountS
if (f == ID) {
int v = (Integer) getOnlyElement(values.getValues());
doc.add(new NumericDocValuesField(ID_SORT_FIELD, v));
+ } else if (f == ID_STR) {
+ String v = (String) getOnlyElement(values.getValues());
+ doc.add(new NumericDocValuesField(ID2_SORT_FIELD, Integer.valueOf(v)));
} else if (f == FULL_NAME) {
String value = (String) getOnlyElement(values.getValues());
doc.add(new SortedDocValuesField(FULL_NAME_SORT_FIELD, new BytesRef(value)));
@@ -126,7 +135,7 @@ public class LuceneAccountIndex extends AbstractLuceneIndex<Account.Id, AccountS
@Override
public void replace(AccountState as) {
try {
- replace(idTerm(as), toDocument(as)).get();
+ replace(idTerm(getSchema().useLegacyNumericFields(), as), toDocument(as)).get();
} catch (ExecutionException | InterruptedException e) {
throw new StorageException(e);
}
@@ -135,7 +144,7 @@ public class LuceneAccountIndex extends AbstractLuceneIndex<Account.Id, AccountS
@Override
public void delete(Account.Id key) {
try {
- delete(idTerm(key)).get();
+ delete(idTerm(getSchema().useLegacyNumericFields(), key)).get();
} catch (ExecutionException | InterruptedException e) {
throw new StorageException(e);
}
@@ -144,20 +153,29 @@ public class LuceneAccountIndex extends AbstractLuceneIndex<Account.Id, AccountS
@Override
public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts)
throws QueryParseException {
+ queryBuilder.getSchema().useLegacyNumericFields();
return new LuceneQuerySource(
- opts.filterFields(IndexUtils::accountFields), queryBuilder.toQuery(p), getSort());
+ opts.filterFields(o -> IndexUtils.accountFields(o, getSchema().useLegacyNumericFields())),
+ queryBuilder.toQuery(p),
+ getSort());
}
private Sort getSort() {
+ String idSortField = getSchema().useLegacyNumericFields() ? ID_SORT_FIELD : ID2_SORT_FIELD;
return new Sort(
new SortField(FULL_NAME_SORT_FIELD, SortField.Type.STRING, false),
new SortField(EMAIL_SORT_FIELD, SortField.Type.STRING, false),
- new SortField(ID_SORT_FIELD, SortField.Type.LONG, false));
+ new SortField(idSortField, SortField.Type.LONG, false));
}
@Override
protected AccountState fromDocument(Document doc) {
- Account.Id id = new Account.Id(doc.getField(ID.getName()).numericValue().intValue());
+ FieldDef<AccountState, ?> idField = getSchema().useLegacyNumericFields() ? ID : ID_STR;
+ Account.Id id =
+ Account.id(
+ getSchema().useLegacyNumericFields()
+ ? doc.getField(idField.getName()).numericValue().intValue()
+ : Integer.valueOf(doc.getField(idField.getName()).stringValue()));
// Use the AccountCache rather than depending on any stored fields in the document (of which
// there shouldn't be any). The most expensive part to compute anyway is the effective group
// IDs, and we don't have a good way to reindex when those change.
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index a9e431ef12..a1188da354 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -18,13 +18,13 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
+import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID_STR;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
-import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.FluentIterable;
@@ -36,7 +36,16 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.converter.ChangeProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetProtoConverter;
+import com.google.gerrit.entities.converter.ProtoConverter;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle;
@@ -44,14 +53,6 @@ import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
-import com.google.gerrit.reviewdb.converter.ProtoConverter;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
@@ -78,6 +79,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
+import java.util.function.Function;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexableField;
@@ -107,6 +109,7 @@ public class LuceneChangeIndex implements ChangeIndex {
static final String UPDATED_SORT_FIELD = sortFieldName(ChangeField.UPDATED);
static final String ID_SORT_FIELD = sortFieldName(ChangeField.LEGACY_ID);
+ static final String ID2_SORT_FIELD = sortFieldName(ChangeField.LEGACY_ID_STR);
private static final String CHANGES = "changes";
private static final String CHANGES_OPEN = "open";
@@ -135,12 +138,22 @@ public class LuceneChangeIndex implements ChangeIndex {
private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
- static Term idTerm(ChangeData cd) {
- return QueryBuilder.intTerm(LEGACY_ID.getName(), cd.getId().get());
+ @FunctionalInterface
+ static interface IdTerm {
+ Term get(String name, int id);
}
- static Term idTerm(Change.Id id) {
- return QueryBuilder.intTerm(LEGACY_ID.getName(), id.get());
+ static Term idTerm(IdTerm idTerm, FieldDef<ChangeData, ?> idField, ChangeData cd) {
+ return idTerm(idTerm, idField, cd.getId());
+ }
+
+ static Term idTerm(IdTerm idTerm, FieldDef<ChangeData, ?> idField, Change.Id id) {
+ return idTerm.get(idField.getName(), id.get());
+ }
+
+ @FunctionalInterface
+ static interface ChangeIdExtractor {
+ Change.Id extract(IndexableField f);
}
private final ListeningExecutorService executor;
@@ -150,6 +163,12 @@ public class LuceneChangeIndex implements ChangeIndex {
private final ChangeSubIndex openIndex;
private final ChangeSubIndex closedIndex;
+ // TODO(davido): Remove the below fields when support for legacy numeric fields is removed.
+ private final FieldDef<ChangeData, ?> idField;
+ private final String idSortFieldName;
+ private final IdTerm idTerm;
+ private final ChangeIdExtractor extractor;
+
@Inject
LuceneChangeIndex(
@GerritServerConfig Config cfg,
@@ -202,6 +221,20 @@ public class LuceneChangeIndex implements ChangeIndex {
searcherFactory,
autoFlush);
}
+
+ idField = this.schema.useLegacyNumericFields() ? LEGACY_ID : LEGACY_ID_STR;
+ idSortFieldName = schema.useLegacyNumericFields() ? ID_SORT_FIELD : ID2_SORT_FIELD;
+ idTerm =
+ (name, id) ->
+ this.schema.useLegacyNumericFields()
+ ? QueryBuilder.intTerm(name, id)
+ : QueryBuilder.stringTerm(name, Integer.toString(id));
+ extractor =
+ (f) ->
+ Change.id(
+ this.schema.useLegacyNumericFields()
+ ? f.numericValue().intValue()
+ : Integer.valueOf(f.stringValue()));
}
@Override
@@ -220,7 +253,7 @@ public class LuceneChangeIndex implements ChangeIndex {
@Override
public void replace(ChangeData cd) {
- Term id = LuceneChangeIndex.idTerm(cd);
+ Term id = LuceneChangeIndex.idTerm(idTerm, idField, cd);
// toDocument is essentially static and doesn't depend on the specific
// sub-index, so just pick one.
Document doc = openIndex.toDocument(cd);
@@ -236,10 +269,10 @@ public class LuceneChangeIndex implements ChangeIndex {
}
@Override
- public void delete(Change.Id id) {
- Term idTerm = LuceneChangeIndex.idTerm(id);
+ public void delete(Change.Id changeId) {
+ Term id = LuceneChangeIndex.idTerm(idTerm, idField, changeId);
try {
- Futures.allAsList(openIndex.delete(idTerm), closedIndex.delete(idTerm)).get();
+ Futures.allAsList(openIndex.delete(id), closedIndex.delete(id)).get();
} catch (ExecutionException | InterruptedException e) {
throw new StorageException(e);
}
@@ -275,11 +308,7 @@ public class LuceneChangeIndex implements ChangeIndex {
private Sort getSort() {
return new Sort(
new SortField(UPDATED_SORT_FIELD, SortField.Type.LONG, true),
- new SortField(ID_SORT_FIELD, SortField.Type.LONG, true));
- }
-
- public ChangeSubIndex getClosedChangesIndex() {
- return closedIndex;
+ new SortField(idSortFieldName, SortField.Type.LONG, true));
}
private class QuerySource implements ChangeDataSource {
@@ -327,7 +356,7 @@ public class LuceneChangeIndex implements ChangeIndex {
throw new StorageException("interrupted");
}
- final Set<String> fields = IndexUtils.changeFields(opts);
+ final Set<String> fields = IndexUtils.changeFields(opts, schema.useLegacyNumericFields());
return new ChangeDataResults(
executor.submit(
new Callable<List<Document>>() {
@@ -348,7 +377,7 @@ public class LuceneChangeIndex implements ChangeIndex {
public ResultSet<FieldBundle> readRaw() {
List<Document> documents;
try {
- documents = doRead(IndexUtils.changeFields(opts));
+ documents = doRead(IndexUtils.changeFields(opts, schema.useLegacyNumericFields()));
} catch (IOException e) {
throw new StorageException(e);
}
@@ -426,9 +455,8 @@ public class LuceneChangeIndex implements ChangeIndex {
List<Document> docs = future.get();
ImmutableList.Builder<ChangeData> result =
ImmutableList.builderWithExpectedSize(docs.size());
- String idFieldName = LEGACY_ID.getName();
for (Document doc : docs) {
- result.add(toChangeData(fields(doc, fields), fields, idFieldName));
+ result.add(toChangeData(fields(doc, fields), fields, idField.getName()));
}
return result.build();
} catch (InterruptedException e) {
@@ -469,10 +497,10 @@ public class LuceneChangeIndex implements ChangeIndex {
cd = changeDataFactory.create(parseProtoFrom(proto, ChangeProtoConverter.INSTANCE));
} else {
IndexableField f = Iterables.getFirst(doc.get(idFieldName), null);
- Change.Id id = new Change.Id(f.numericValue().intValue());
+
// IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
IndexableField project = doc.get(PROJECT.getName()).iterator().next();
- cd = changeDataFactory.create(new Project.NameKey(project.stringValue()), id);
+ cd = changeDataFactory.create(Project.nameKey(project.stringValue()), extractor.extract(f));
}
// Any decoding that is done here must also be done in {@link ElasticChangeIndex}.
@@ -575,7 +603,7 @@ public class LuceneChangeIndex implements ChangeIndex {
if (reviewedBy.size() == 1 && id == ChangeField.NOT_REVIEWED) {
break;
}
- accounts.add(new Account.Id(id));
+ accounts.add(Account.id(id));
}
cd.setReviewedBy(accounts);
}
diff --git a/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index 47a5038812..58323f01be 100644
--- a/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -17,6 +17,7 @@ package com.google.gerrit.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.gerrit.server.index.group.GroupField.UUID;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
@@ -25,7 +26,6 @@ import com.google.gerrit.index.Schema.Values;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
@@ -142,7 +142,7 @@ public class LuceneGroupIndex extends AbstractLuceneIndex<AccountGroup.UUID, Int
@Override
protected InternalGroup fromDocument(Document doc) {
- AccountGroup.UUID uuid = new AccountGroup.UUID(doc.getField(UUID.getName()).stringValue());
+ AccountGroup.UUID uuid = AccountGroup.uuid(doc.getField(UUID.getName()).stringValue());
// Use the GroupCache rather than depending on any stored fields in the
// document (of which there shouldn't be any).
return groupCache.get().get(uuid).orElse(null);
diff --git a/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index 601ed5fb3f..837ac0fb63 100644
--- a/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -17,6 +17,7 @@ package com.google.gerrit.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.gerrit.index.project.ProjectField.NAME;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
@@ -27,7 +28,6 @@ import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.AutoFlush;
@@ -142,7 +142,7 @@ public class LuceneProjectIndex extends AbstractLuceneIndex<Project.NameKey, Pro
@Override
protected ProjectData fromDocument(Document doc) {
- Project.NameKey nameKey = new Project.NameKey(doc.getField(NAME.getName()).stringValue());
+ Project.NameKey nameKey = Project.nameKey(doc.getField(NAME.getName()).stringValue());
ProjectState projectState = projectCache.get().get(nameKey);
return projectState == null ? null : projectState.toProjectData();
}
diff --git a/java/com/google/gerrit/lucene/QueryBuilder.java b/java/com/google/gerrit/lucene/QueryBuilder.java
index 8dfc12ecf4..7d82bf54b2 100644
--- a/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -36,6 +36,8 @@ import com.google.gerrit.index.query.TimestampRangePredicate;
import java.util.Date;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LongPoint;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.LegacyNumericRangeQuery;
@@ -49,6 +51,21 @@ import org.apache.lucene.util.LegacyNumericUtils;
@SuppressWarnings("deprecation")
public class QueryBuilder<V> {
+ @FunctionalInterface
+ static interface IntTermQuery {
+ Query get(String name, int value);
+ }
+
+ @FunctionalInterface
+ static interface IntRangeQuery {
+ Query get(String name, int min, int max);
+ }
+
+ @FunctionalInterface
+ static interface LongRangeQuery {
+ Query get(String name, long min, long max);
+ }
+
static Term intTerm(String name, int value) {
BytesRefBuilder builder = new BytesRefBuilder();
LegacyNumericUtils.intToPrefixCoded(value, 0, builder);
@@ -61,12 +78,36 @@ public class QueryBuilder<V> {
return new Term(name, builder.get());
}
+ static Query intPoint(String name, int value) {
+ return IntPoint.newExactQuery(name, value);
+ }
+
private final Schema<V> schema;
private final org.apache.lucene.util.QueryBuilder queryBuilder;
+ // TODO(davido): Remove the below fields when support for legacy numeric fields is removed.
+ private final IntTermQuery intTermQuery;
+ private final IntRangeQuery intRangeTermQuery;
+ private final LongRangeQuery longRangeQuery;
+
public QueryBuilder(Schema<V> schema, Analyzer analyzer) {
this.schema = schema;
queryBuilder = new org.apache.lucene.util.QueryBuilder(analyzer);
+ intTermQuery =
+ (name, value) ->
+ this.schema.useLegacyNumericFields()
+ ? new TermQuery(intTerm(name, value))
+ : intPoint(name, value);
+ intRangeTermQuery =
+ (name, min, max) ->
+ this.schema.useLegacyNumericFields()
+ ? LegacyNumericRangeQuery.newIntRange(name, min, max, true, true)
+ : IntPoint.newRangeQuery(name, min, max);
+ longRangeQuery =
+ (name, min, max) ->
+ this.schema.useLegacyNumericFields()
+ ? LegacyNumericRangeQuery.newLongRange(name, min, max, true, true)
+ : LongPoint.newRangeQuery(name, min, max);
}
public Query toQuery(Predicate<V> p) throws QueryParseException {
@@ -169,20 +210,20 @@ public class QueryBuilder<V> {
} catch (NumberFormatException e) {
throw new QueryParseException("not an integer: " + p.getValue(), e);
}
- return new TermQuery(intTerm(p.getField().getName(), value));
+ return intTermQuery.get(p.getField().getName(), value);
}
private Query intRangeQuery(IndexPredicate<V> p) throws QueryParseException {
if (p instanceof IntegerRangePredicate) {
IntegerRangePredicate<V> r = (IntegerRangePredicate<V>) p;
+ String name = r.getField().getName();
int minimum = r.getMinimumValue();
int maximum = r.getMaximumValue();
if (minimum == maximum) {
// Just fall back to a standard integer query.
- return new TermQuery(intTerm(p.getField().getName(), minimum));
+ return intTermQuery.get(name, minimum);
}
- return LegacyNumericRangeQuery.newIntRange(
- r.getField().getName(), minimum, maximum, true, true);
+ return intRangeTermQuery.get(name, minimum, maximum);
}
throw new QueryParseException("not an integer range: " + p);
}
@@ -190,20 +231,16 @@ public class QueryBuilder<V> {
private Query timestampQuery(IndexPredicate<V> p) throws QueryParseException {
if (p instanceof TimestampRangePredicate) {
TimestampRangePredicate<V> r = (TimestampRangePredicate<V>) p;
- return LegacyNumericRangeQuery.newLongRange(
- r.getField().getName(),
- r.getMinTimestamp().getTime(),
- r.getMaxTimestamp().getTime(),
- true,
- true);
+ return longRangeQuery.get(
+ r.getField().getName(), r.getMinTimestamp().getTime(), r.getMaxTimestamp().getTime());
}
throw new QueryParseException("not a timestamp: " + p);
}
private Query notTimestamp(TimestampRangePredicate<V> r) throws QueryParseException {
if (r.getMinTimestamp().getTime() == 0) {
- return LegacyNumericRangeQuery.newLongRange(
- r.getField().getName(), r.getMaxTimestamp().getTime(), null, true, true);
+ return longRangeQuery.get(
+ r.getField().getName(), r.getMaxTimestamp().getTime(), Long.MAX_VALUE);
}
throw new QueryParseException("cannot negate: " + r);
}
@@ -245,4 +282,8 @@ public class QueryBuilder<V> {
public int toIndexTimeInMinutes(Date ts) {
return (int) (ts.getTime() / 60000);
}
+
+ public Schema<V> getSchema() {
+ return schema;
+ }
}
diff --git a/java/com/google/gerrit/lucene/WrappableSearcherManager.java b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
index ba8d7dab11..4044b90d3a 100644
--- a/java/com/google/gerrit/lucene/WrappableSearcherManager.java
+++ b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
@@ -177,7 +177,7 @@ final class WrappableSearcherManager extends ReferenceManager<IndexSearcher> {
* Expert: creates a searcher from the provided {@link IndexReader} using the provided {@link
* SearcherFactory}. NOTE: this decRefs incoming reader on throwing an exception.
*/
- @SuppressWarnings("resource")
+ @SuppressWarnings({"resource", "ReferenceEquality"})
public static IndexSearcher getSearcher(SearcherFactory searcherFactory, IndexReader reader)
throws IOException {
boolean success = false;
diff --git a/java/com/google/gerrit/mail/BUILD b/java/com/google/gerrit/mail/BUILD
index 6be5f0e4e4..46bc77aa56 100644
--- a/java/com/google/gerrit/mail/BUILD
+++ b/java/com/google/gerrit/mail/BUILD
@@ -6,7 +6,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//lib:guava",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
diff --git a/java/com/google/gerrit/mail/HtmlParser.java b/java/com/google/gerrit/mail/HtmlParser.java
index 265c4122de..7905a0a2c0 100644
--- a/java/com/google/gerrit/mail/HtmlParser.java
+++ b/java/com/google/gerrit/mail/HtmlParser.java
@@ -18,7 +18,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
diff --git a/java/com/google/gerrit/mail/MailComment.java b/java/com/google/gerrit/mail/MailComment.java
index fd8198cf32..f024f17995 100644
--- a/java/com/google/gerrit/mail/MailComment.java
+++ b/java/com/google/gerrit/mail/MailComment.java
@@ -14,7 +14,7 @@
package com.google.gerrit.mail;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.Objects;
/** A comment parsed from inbound email */
diff --git a/java/com/google/gerrit/mail/ParserUtil.java b/java/com/google/gerrit/mail/ParserUtil.java
index 6a27ac4393..4b292f3e68 100644
--- a/java/com/google/gerrit/mail/ParserUtil.java
+++ b/java/com/google/gerrit/mail/ParserUtil.java
@@ -16,7 +16,7 @@ package com.google.gerrit.mail;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Pattern;
diff --git a/java/com/google/gerrit/mail/TextParser.java b/java/com/google/gerrit/mail/TextParser.java
index 1a635994b7..dac3deb8f5 100644
--- a/java/com/google/gerrit/mail/TextParser.java
+++ b/java/com/google/gerrit/mail/TextParser.java
@@ -18,7 +18,7 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
diff --git a/java/com/google/gerrit/metrics/BUILD b/java/com/google/gerrit/metrics/BUILD
index 786ef68bf5..3cc056bf5a 100644
--- a/java/com/google/gerrit/metrics/BUILD
+++ b/java/com/google/gerrit/metrics/BUILD
@@ -8,9 +8,12 @@ java_library(
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/lifecycle",
+ "//java/com/google/gerrit/server/logging",
"//lib:guava",
+ "//lib:jgit",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
"//lib/flogger:api",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
index adf6a66d5e..1fb8c57e3f 100644
--- a/java/com/google/gerrit/metrics/DisabledMetricMaker.java
+++ b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
@@ -79,7 +79,7 @@ public class DisabledMetricMaker extends MetricMaker {
@Override
public <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
- return new Timer1<F1>(name) {
+ return new Timer1<F1>(name, field1) {
@Override
protected void doRecord(F1 field1, long value, TimeUnit unit) {}
@@ -91,7 +91,7 @@ public class DisabledMetricMaker extends MetricMaker {
@Override
public <F1, F2> Timer2<F1, F2> newTimer(
String name, Description desc, Field<F1> field1, Field<F2> field2) {
- return new Timer2<F1, F2>(name) {
+ return new Timer2<F1, F2>(name, field1, field2) {
@Override
protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {}
@@ -103,7 +103,7 @@ public class DisabledMetricMaker extends MetricMaker {
@Override
public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
- return new Timer3<F1, F2, F3>(name) {
+ return new Timer3<F1, F2, F3>(name, field1, field2, field3) {
@Override
protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {}
diff --git a/java/com/google/gerrit/metrics/Field.java b/java/com/google/gerrit/metrics/Field.java
index 95eb9cff19..bdae854007 100644
--- a/java/com/google/gerrit/metrics/Field.java
+++ b/java/com/google/gerrit/metrics/Field.java
@@ -16,45 +16,36 @@ package com.google.gerrit.metrics;
import static com.google.common.base.Preconditions.checkArgument;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.server.logging.Metadata;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
/**
* Describes a bucketing field used by a metric.
*
* @param <T> type of field
*/
-public class Field<T> {
- /**
- * Break down metrics by boolean true/false.
- *
- * @param name field name
- * @return boolean field
- */
- public static Field<Boolean> ofBoolean(String name) {
- return ofBoolean(name, null);
+@AutoValue
+public abstract class Field<T> {
+ public static <T> BiConsumer<Metadata.Builder, T> ignoreMetadata() {
+ return (metadataBuilder, fieldValue) -> {};
}
/**
* Break down metrics by boolean true/false.
*
* @param name field name
- * @param description field description
- * @return boolean field
- */
- public static Field<Boolean> ofBoolean(String name, String description) {
- return new Field<>(name, Boolean.class, description);
- }
-
- /**
- * Break down metrics by cases of an enum.
- *
- * @param enumType type of enum
- * @param name field name
- * @return enum field
+ * @return builder for the boolean field
*/
- public static <E extends Enum<E>> Field<E> ofEnum(Class<E> enumType, String name) {
- return ofEnum(enumType, name, null);
+ public static Field.Builder<Boolean> ofBoolean(
+ String name, BiConsumer<Metadata.Builder, Boolean> metadataMapper) {
+ return new AutoValue_Field.Builder<Boolean>()
+ .valueType(Boolean.class)
+ .formatter(Object::toString)
+ .name(name)
+ .metadataMapper(metadataMapper);
}
/**
@@ -62,39 +53,17 @@ public class Field<T> {
*
* @param enumType type of enum
* @param name field name
- * @param description field description
- * @return enum field
- */
- public static <E extends Enum<E>> Field<E> ofEnum(
- Class<E> enumType, String name, String description) {
- return new Field<>(name, enumType, description);
- }
-
- /**
- * Break down metrics by string.
- *
- * <p>Each unique string will allocate a new submetric. <b>Do not use user content as a field
- * value</b> as field values are never reclaimed.
- *
- * @param name field name
- * @return string field
- */
- public static Field<String> ofString(String name) {
- return ofString(name, null);
- }
-
- /**
- * Break down metrics by string.
- *
- * <p>Each unique string will allocate a new submetric. <b>Do not use user content as a field
- * value</b> as field values are never reclaimed.
- *
- * @param name field name
- * @param description field description
- * @return string field
+ * @return builder for the enum field
*/
- public static Field<String> ofString(String name, String description) {
- return new Field<>(name, String.class, description);
+ public static <E extends Enum<E>> Field.Builder<E> ofEnum(
+ Class<E> enumType, String name, BiConsumer<Metadata.Builder, String> metadataMapper) {
+ return new AutoValue_Field.Builder<E>()
+ .valueType(enumType)
+ .formatter(Enum::name)
+ .name(name)
+ .metadataMapper(
+ (metadataBuilder, fieldValue) ->
+ metadataMapper.accept(metadataBuilder, fieldValue.name()));
}
/**
@@ -104,67 +73,68 @@ public class Field<T> {
* value</b> as field values are never reclaimed.
*
* @param name field name
- * @return integer field
+ * @return builder for the integer field
*/
- public static Field<Integer> ofInteger(String name) {
- return ofInteger(name, null);
+ public static Field.Builder<Integer> ofInteger(
+ String name, BiConsumer<Metadata.Builder, Integer> metadataMapper) {
+ return new AutoValue_Field.Builder<Integer>()
+ .valueType(Integer.class)
+ .formatter(Object::toString)
+ .name(name)
+ .metadataMapper(metadataMapper);
}
/**
- * Break down metrics by integer.
+ * Break down metrics by string.
*
- * <p>Each unique integer will allocate a new submetric. <b>Do not use user content as a field
+ * <p>Each unique string will allocate a new submetric. <b>Do not use user content as a field
* value</b> as field values are never reclaimed.
*
* @param name field name
- * @param description field description
- * @return integer field
+ * @return builder for the string field
*/
- public static Field<Integer> ofInteger(String name, String description) {
- return new Field<>(name, Integer.class, description);
- }
-
- private final String name;
- private final Class<T> keyType;
- private final Function<T, String> formatter;
- private final String description;
-
- private Field(String name, Class<T> keyType, String description) {
- checkArgument(name.matches("^[a-z_]+$"), "name must match [a-z_]");
- this.name = name;
- this.keyType = keyType;
- this.formatter = initFormatter(keyType);
- this.description = description;
+ public static Field.Builder<String> ofString(
+ String name, BiConsumer<Metadata.Builder, String> metadataMapper) {
+ return new AutoValue_Field.Builder<String>()
+ .valueType(String.class)
+ .formatter(s -> s)
+ .name(name)
+ .metadataMapper(metadataMapper);
}
/** @return name of this field within the metric. */
- public String getName() {
- return name;
- }
+ public abstract String name();
/** @return type of value used within the field. */
- public Class<T> getType() {
- return keyType;
- }
+ public abstract Class<T> valueType();
+
+ /** @return mapper that maps a field value to a field in the {@link Metadata} class. */
+ public abstract BiConsumer<Metadata.Builder, T> metadataMapper();
/** @return description text for the field explaining its range of values. */
- public String getDescription() {
- return description;
- }
+ public abstract Optional<String> description();
- public Function<T, String> formatter() {
- return formatter;
- }
+ /** @return formatter to format field values. */
+ public abstract Function<T, String> formatter();
+
+ @AutoValue.Builder
+ public abstract static class Builder<T> {
+ abstract Builder<T> name(String name);
+
+ abstract Builder<T> valueType(Class<T> type);
+
+ abstract Builder<T> formatter(Function<T, String> formatter);
+
+ abstract Builder<T> metadataMapper(BiConsumer<Metadata.Builder, T> metadataMapper);
+
+ public abstract Builder<T> description(String description);
+
+ abstract Field<T> autoBuild();
- @SuppressWarnings("unchecked")
- private static <T> Function<T, String> initFormatter(Class<T> keyType) {
- if (keyType == String.class) {
- return (Function<T, String>) Functions.<String>identity();
- } else if (keyType == Integer.class || keyType == Boolean.class) {
- return (Function<T, String>) Functions.toStringFunction();
- } else if (Enum.class.isAssignableFrom(keyType)) {
- return in -> ((Enum<?>) in).name();
+ public Field<T> build() {
+ Field<T> field = autoBuild();
+ checkArgument(field.name().matches("^[a-z_]+$"), "name must match [a-z_]");
+ return field;
}
- throw new IllegalStateException("unsupported type " + keyType.getName());
}
}
diff --git a/java/com/google/gerrit/metrics/Timer0.java b/java/com/google/gerrit/metrics/Timer0.java
index 21344884cf..d0033a41f4 100644
--- a/java/com/google/gerrit/metrics/Timer0.java
+++ b/java/com/google/gerrit/metrics/Timer0.java
@@ -18,6 +18,8 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.PerformanceLogRecord;
import java.util.concurrent.TimeUnit;
/**
@@ -68,7 +70,10 @@ public abstract class Timer0 implements RegistrationHandle {
* @param unit time unit of the value
*/
public final void record(long value, TimeUnit unit) {
- logger.atFinest().log("%s took %dms", name, unit.toMillis(value));
+ long durationMs = unit.toMillis(value);
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs));
+ logger.atFinest().log("%s took %dms", name, durationMs);
doRecord(value, unit);
}
diff --git a/java/com/google/gerrit/metrics/Timer1.java b/java/com/google/gerrit/metrics/Timer1.java
index 16c151ebde..a8fb1a2bb9 100644
--- a/java/com/google/gerrit/metrics/Timer1.java
+++ b/java/com/google/gerrit/metrics/Timer1.java
@@ -18,6 +18,9 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogRecord;
import java.util.concurrent.TimeUnit;
/**
@@ -35,56 +38,66 @@ import java.util.concurrent.TimeUnit;
public abstract class Timer1<F1> implements RegistrationHandle {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- public static class Context extends TimerContext {
- private final Timer1<Object> timer;
- private final Object field1;
+ public static class Context<F1> extends TimerContext {
+ private final Timer1<F1> timer;
+ private final F1 fieldValue;
- @SuppressWarnings("unchecked")
- <F1> Context(Timer1<F1> timer, F1 field1) {
- this.timer = (Timer1<Object>) timer;
- this.field1 = field1;
+ Context(Timer1<F1> timer, F1 fieldValue) {
+ this.timer = timer;
+ this.fieldValue = fieldValue;
}
@Override
public void record(long elapsed) {
- timer.record(field1, elapsed, NANOSECONDS);
+ timer.record(fieldValue, elapsed, NANOSECONDS);
}
}
protected final String name;
+ protected final Field<F1> field;
- public Timer1(String name) {
+ public Timer1(String name, Field<F1> field) {
this.name = name;
+ this.field = field;
}
/**
* Begin a timer for the current block, value will be recorded when closed.
*
- * @param field1 bucket to record the timer
+ * @param fieldValue bucket to record the timer
* @return timer context
*/
- public Context start(F1 field1) {
- return new Context(this, field1);
+ public Context<F1> start(F1 fieldValue) {
+ return new Context<>(this, fieldValue);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
+ * @param fieldValue bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- public final void record(F1 field1, long value, TimeUnit unit) {
- logger.atFinest().log("%s (%s) took %dms", name, field1, unit.toMillis(value));
- doRecord(field1, value, unit);
+ public final void record(F1 fieldValue, long value, TimeUnit unit) {
+ long durationMs = unit.toMillis(value);
+
+ Metadata.Builder metadataBuilder = Metadata.builder();
+ field.metadataMapper().accept(metadataBuilder, fieldValue);
+ Metadata metadata = metadataBuilder.build();
+
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs, metadata));
+
+ logger.atFinest().log("%s (%s = %s) took %dms", name, field.name(), fieldValue, durationMs);
+ doRecord(fieldValue, value, unit);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
+ * @param fieldValue bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- protected abstract void doRecord(F1 field1, long value, TimeUnit unit);
+ protected abstract void doRecord(F1 fieldValue, long value, TimeUnit unit);
}
diff --git a/java/com/google/gerrit/metrics/Timer2.java b/java/com/google/gerrit/metrics/Timer2.java
index bf19448d70..8a4a793af5 100644
--- a/java/com/google/gerrit/metrics/Timer2.java
+++ b/java/com/google/gerrit/metrics/Timer2.java
@@ -18,6 +18,9 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogRecord;
import java.util.concurrent.TimeUnit;
/**
@@ -36,61 +39,76 @@ import java.util.concurrent.TimeUnit;
public abstract class Timer2<F1, F2> implements RegistrationHandle {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- public static class Context extends TimerContext {
- private final Timer2<Object, Object> timer;
- private final Object field1;
- private final Object field2;
+ public static class Context<F1, F2> extends TimerContext {
+ private final Timer2<F1, F2> timer;
+ private final F1 fieldValue1;
+ private final F2 fieldValue2;
- @SuppressWarnings("unchecked")
- <F1, F2> Context(Timer2<F1, F2> timer, F1 field1, F2 field2) {
- this.timer = (Timer2<Object, Object>) timer;
- this.field1 = field1;
- this.field2 = field2;
+ Context(Timer2<F1, F2> timer, F1 fieldValue1, F2 fieldValue2) {
+ this.timer = timer;
+ this.fieldValue1 = fieldValue1;
+ this.fieldValue2 = fieldValue2;
}
@Override
public void record(long elapsed) {
- timer.record(field1, field2, elapsed, NANOSECONDS);
+ timer.record(fieldValue1, fieldValue2, elapsed, NANOSECONDS);
}
}
protected final String name;
+ protected final Field<F1> field1;
+ protected final Field<F2> field2;
- public Timer2(String name) {
+ public Timer2(String name, Field<F1> field1, Field<F2> field2) {
this.name = name;
+ this.field1 = field1;
+ this.field2 = field2;
}
/**
* Begin a timer for the current block, value will be recorded when closed.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
* @return timer context
*/
- public Context start(F1 field1, F2 field2) {
- return new Context(this, field1, field2);
+ public Context<F1, F2> start(F1 fieldValue1, F2 fieldValue2) {
+ return new Context<>(this, fieldValue1, fieldValue2);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- public final void record(F1 field1, F2 field2, long value, TimeUnit unit) {
- logger.atFinest().log("%s (%s, %s) took %dms", name, field1, field2, unit.toMillis(value));
- doRecord(field1, field2, value, unit);
+ public final void record(F1 fieldValue1, F2 fieldValue2, long value, TimeUnit unit) {
+ long durationMs = unit.toMillis(value);
+
+ Metadata.Builder metadataBuilder = Metadata.builder();
+ field1.metadataMapper().accept(metadataBuilder, fieldValue1);
+ field2.metadataMapper().accept(metadataBuilder, fieldValue2);
+ Metadata metadata = metadataBuilder.build();
+
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs, metadata));
+
+ logger.atFinest().log(
+ "%s (%s = %s, %s = %s) took %dms",
+ name, field1.name(), fieldValue1, field2.name(), fieldValue2, durationMs);
+ doRecord(fieldValue1, fieldValue2, value, unit);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- protected abstract void doRecord(F1 field1, F2 field2, long value, TimeUnit unit);
+ protected abstract void doRecord(F1 fieldValue1, F2 fieldValue2, long value, TimeUnit unit);
}
diff --git a/java/com/google/gerrit/metrics/Timer3.java b/java/com/google/gerrit/metrics/Timer3.java
index c910eb0c85..2044da6ef1 100644
--- a/java/com/google/gerrit/metrics/Timer3.java
+++ b/java/com/google/gerrit/metrics/Timer3.java
@@ -18,6 +18,9 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogRecord;
import java.util.concurrent.TimeUnit;
/**
@@ -37,67 +40,93 @@ import java.util.concurrent.TimeUnit;
public abstract class Timer3<F1, F2, F3> implements RegistrationHandle {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- public static class Context extends TimerContext {
- private final Timer3<Object, Object, Object> timer;
- private final Object field1;
- private final Object field2;
- private final Object field3;
+ public static class Context<F1, F2, F3> extends TimerContext {
+ private final Timer3<F1, F2, F3> timer;
+ private final F1 fieldValue1;
+ private final F2 fieldValue2;
+ private final F3 fieldValue3;
- @SuppressWarnings("unchecked")
- <F1, F2, F3> Context(Timer3<F1, F2, F3> timer, F1 f1, F2 f2, F3 f3) {
- this.timer = (Timer3<Object, Object, Object>) timer;
- this.field1 = f1;
- this.field2 = f2;
- this.field3 = f3;
+ Context(Timer3<F1, F2, F3> timer, F1 f1, F2 f2, F3 f3) {
+ this.timer = timer;
+ this.fieldValue1 = f1;
+ this.fieldValue2 = f2;
+ this.fieldValue3 = f3;
}
@Override
public void record(long elapsed) {
- timer.record(field1, field2, field3, elapsed, NANOSECONDS);
+ timer.record(fieldValue1, fieldValue2, fieldValue3, elapsed, NANOSECONDS);
}
}
protected final String name;
+ protected final Field<F1> field1;
+ protected final Field<F2> field2;
+ protected final Field<F3> field3;
- public Timer3(String name) {
+ public Timer3(String name, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
this.name = name;
+ this.field1 = field1;
+ this.field2 = field2;
+ this.field3 = field3;
}
/**
* Begin a timer for the current block, value will be recorded when closed.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
- * @param field3 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
+ * @param fieldValue3 bucket to record the timer
* @return timer context
*/
- public Context start(F1 field1, F2 field2, F3 field3) {
- return new Context(this, field1, field2, field3);
+ public Context<F1, F2, F3> start(F1 fieldValue1, F2 fieldValue2, F3 fieldValue3) {
+ return new Context<>(this, fieldValue1, fieldValue2, fieldValue3);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
- * @param field3 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
+ * @param fieldValue3 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- public final void record(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {
+ public final void record(
+ F1 fieldValue1, F2 fieldValue2, F3 fieldValue3, long value, TimeUnit unit) {
+ long durationMs = unit.toMillis(value);
+
+ Metadata.Builder metadataBuilder = Metadata.builder();
+ field1.metadataMapper().accept(metadataBuilder, fieldValue1);
+ field2.metadataMapper().accept(metadataBuilder, fieldValue2);
+ field3.metadataMapper().accept(metadataBuilder, fieldValue3);
+ Metadata metadata = metadataBuilder.build();
+
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(() -> PerformanceLogRecord.create(name, durationMs, metadata));
+
logger.atFinest().log(
- "%s (%s, %s, %s) took %dms", name, field1, field2, field3, unit.toMillis(value));
- doRecord(field1, field2, field3, value, unit);
+ "%s (%s = %s, %s = %s, %s = %s) took %dms",
+ name,
+ field1.name(),
+ fieldValue1,
+ field2.name(),
+ fieldValue2,
+ field3.name(),
+ fieldValue3,
+ durationMs);
+ doRecord(fieldValue1, fieldValue2, fieldValue3, value, unit);
}
/**
* Record a value in the distribution.
*
- * @param field1 bucket to record the timer
- * @param field2 bucket to record the timer
- * @param field3 bucket to record the timer
+ * @param fieldValue1 bucket to record the timer
+ * @param fieldValue2 bucket to record the timer
+ * @param fieldValue3 bucket to record the timer
* @param value value to record
* @param unit time unit of the value
*/
- protected abstract void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit);
+ protected abstract void doRecord(
+ F1 fieldValue1, F2 fieldValue2, F3 fieldValue3, long value, TimeUnit unit);
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java b/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java
index 6d1daf4bf2..d718035f40 100644
--- a/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/CallbackMetricImpl1.java
@@ -15,10 +15,10 @@
package com.google.gerrit.metrics.dropwizard;
import com.codahale.metrics.MetricRegistry;
-import com.google.common.base.Function;
import com.google.gerrit.metrics.CallbackMetric1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
+import java.util.function.Function;
/** Optimized version of {@link BucketedCallback} for single dimension. */
class CallbackMetricImpl1<F1, V> extends BucketedCallback<V> {
diff --git a/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java b/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java
index 46434ce28b..0e554a850f 100644
--- a/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/CounterImpl1.java
@@ -14,10 +14,10 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
+import java.util.function.Function;
/** Optimized version of {@link BucketedCounter} for single dimension. */
class CounterImpl1<F1> extends BucketedCounter {
diff --git a/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java b/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java
index 38c31a1b9d..07afc2a92a 100644
--- a/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/CounterImplN.java
@@ -14,13 +14,13 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
+import java.util.function.Function;
/** Generalized implementation of N-dimensional counter metrics. */
class CounterImplN extends BucketedCounter implements BucketedMetric {
diff --git a/java/com/google/gerrit/metrics/dropwizard/GetMetric.java b/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
index ae1e6ec04e..12dabfa59e 100644
--- a/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
+++ b/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
@@ -15,6 +15,7 @@
package com.google.gerrit.metrics.dropwizard;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -36,10 +37,10 @@ class GetMetric implements RestReadView<MetricResource> {
}
@Override
- public MetricJson apply(MetricResource resource)
+ public Response<MetricJson> apply(MetricResource resource)
throws AuthException, PermissionBackendException {
permissionBackend.currentUser().check(GlobalPermission.VIEW_CACHES);
- return new MetricJson(
- resource.getMetric(), metrics.getAnnotations(resource.getName()), dataOnly);
+ return Response.ok(
+ new MetricJson(resource.getMetric(), metrics.getAnnotations(resource.getName()), dataOnly));
}
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java b/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java
index 3eb12faaf1..4578db1910 100644
--- a/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/HistogramImpl1.java
@@ -14,10 +14,10 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
+import java.util.function.Function;
/** Optimized version of {@link BucketedHistogram} for single dimension. */
class HistogramImpl1<F1> extends BucketedHistogram implements BucketedMetric {
diff --git a/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java b/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java
index 3561c55a1d..446590cd90 100644
--- a/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/HistogramImplN.java
@@ -14,13 +14,13 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram2;
import com.google.gerrit.metrics.Histogram3;
+import java.util.function.Function;
/** Generalized implementation of N-dimensional Histogram metrics. */
class HistogramImplN extends BucketedHistogram implements BucketedMetric {
diff --git a/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java b/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
index 0c6945248c..7e472c9a68 100644
--- a/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
+++ b/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
@@ -16,6 +16,7 @@ package com.google.gerrit.metrics.dropwizard;
import com.codahale.metrics.Metric;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -50,7 +51,7 @@ class ListMetrics implements RestReadView<ConfigResource> {
}
@Override
- public Map<String, MetricJson> apply(ConfigResource resource)
+ public Response<Map<String, MetricJson>> apply(ConfigResource resource)
throws AuthException, PermissionBackendException {
permissionBackend.currentUser().check(GlobalPermission.VIEW_CACHES);
@@ -75,7 +76,7 @@ class ListMetrics implements RestReadView<ConfigResource> {
}
}
- return out;
+ return Response.ok(out);
}
private MetricJson toJson(String q, Metric m) {
diff --git a/java/com/google/gerrit/metrics/dropwizard/MetricJson.java b/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
index 20f4fa33a3..d59a1d93a1 100644
--- a/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
+++ b/java/com/google/gerrit/metrics/dropwizard/MetricJson.java
@@ -21,7 +21,6 @@ import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
-import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.metrics.Description;
@@ -30,6 +29,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.function.Function;
class MetricJson {
String description;
@@ -189,10 +189,10 @@ class MetricJson {
String description;
FieldJson(Field<?> field) {
- this.name = field.getName();
- this.description = field.getDescription();
+ this.name = field.name();
+ this.description = field.description().orElse(null);
this.type =
- Enum.class.isAssignableFrom(field.getType()) ? field.getType().getSimpleName() : null;
+ Enum.class.isAssignableFrom(field.valueType()) ? field.valueType().getSimpleName() : null;
}
}
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
index d97e73a961..b7d535be46 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
@@ -14,11 +14,11 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Timer1;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/** Optimized version of {@link BucketedTimer} for single dimension. */
class TimerImpl1<F1> extends BucketedTimer implements BucketedMetric {
@@ -26,8 +26,9 @@ class TimerImpl1<F1> extends BucketedTimer implements BucketedMetric {
super(metrics, name, desc, field1);
}
+ @SuppressWarnings("unchecked")
Timer1<F1> timer() {
- return new Timer1<F1>(name) {
+ return new Timer1<F1>(name, (Field<F1>) fields[0]) {
@Override
protected void doRecord(F1 field1, long value, TimeUnit unit) {
total.record(value, unit);
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
index be66009c5a..dee800e5ea 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
@@ -14,7 +14,6 @@
package com.google.gerrit.metrics.dropwizard;
-import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
@@ -22,6 +21,7 @@ import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Timer2;
import com.google.gerrit.metrics.Timer3;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/** Generalized implementation of N-dimensional timer metrics. */
class TimerImplN extends BucketedTimer implements BucketedMetric {
@@ -29,8 +29,9 @@ class TimerImplN extends BucketedTimer implements BucketedMetric {
super(metrics, name, desc, fields);
}
+ @SuppressWarnings("unchecked")
<F1, F2> Timer2<F1, F2> timer2() {
- return new Timer2<F1, F2>(name) {
+ return new Timer2<F1, F2>(name, (Field<F1>) fields[0], (Field<F2>) fields[1]) {
@Override
protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {
total.record(value, unit);
@@ -44,8 +45,10 @@ class TimerImplN extends BucketedTimer implements BucketedMetric {
};
}
+ @SuppressWarnings("unchecked")
<F1, F2, F3> Timer3<F1, F2, F3> timer3() {
- return new Timer3<F1, F2, F3>(name) {
+ return new Timer3<F1, F2, F3>(
+ name, (Field<F1>) fields[0], (Field<F2>) fields[1], (Field<F3>) fields[2]) {
@Override
protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {
total.record(value, unit);
diff --git a/java/com/google/gerrit/metrics/proc/JGitMetricModule.java b/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
index a44f907cc7..e8611b3365 100644
--- a/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
+++ b/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
@@ -20,6 +20,7 @@ import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.logging.Metadata;
import java.util.Map;
import org.eclipse.jgit.storage.file.WindowCacheStats;
@@ -183,7 +184,7 @@ public class JGitMetricModule extends MetricModule {
+ "having most data in the cache.")
.setGauge()
.setUnit("byte"),
- Field.ofString("repository_name"));
+ Field.ofString("repository_name", Metadata.Builder::projectName).build());
metrics.newTrigger(
repoEnt,
() -> {
diff --git a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
index 2c29882b65..20ac8fa908 100644
--- a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
+++ b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
@@ -23,6 +23,7 @@ import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.logging.Metadata;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
@@ -167,12 +168,17 @@ public class ProcMetricModule extends MetricModule {
}
private void procJvmGc(MetricMaker metrics) {
+ Field<String> gcNameField =
+ Field.ofString("gc_name", Metadata.Builder::garbageCollectorName)
+ .description("The name of the garbage collector")
+ .build();
+
CallbackMetric1<String, Long> gcCount =
metrics.newCallbackMetric(
"proc/jvm/gc/count",
Long.class,
new Description("Number of GCs").setCumulative(),
- Field.ofString("gc_name", "The name of the garbage collector"));
+ gcNameField);
CallbackMetric1<String, Long> gcTime =
metrics.newCallbackMetric(
@@ -181,7 +187,7 @@ public class ProcMetricModule extends MetricModule {
new Description("Approximate accumulated GC elapsed time")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- Field.ofString("gc_name", "The name of the garbage collector"));
+ gcNameField);
metrics.newTrigger(
gcCount,
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index a8690b908c..8b8f13c1df 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -1,17 +1,7 @@
load("@rules_java//java:defs.bzl", "java_library")
-# TODO(davido): This indirection doesn't avoid unwanted depdencies
-# in acceptance-framework and should be removed. Instead, provided_deps
-# should be used, once https://github.com/bazelbuild/bazel/issues/1402
-# is fixed.
-alias(
- name = "pgm",
- actual = ":daemon",
- visibility = ["//visibility:public"],
-)
-
java_library(
- name = "daemon",
+ name = "pgm",
srcs = glob(["**/*.java"]),
resource_strip_prefix = "resources",
resources = ["//resources/com/google/gerrit/pgm"],
@@ -20,6 +10,7 @@ java_library(
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/elasticsearch",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/gpg",
@@ -37,7 +28,6 @@ java_library(
"//java/com/google/gerrit/pgm/init",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server:module",
"//java/com/google/gerrit/server/api",
@@ -51,6 +41,7 @@ java_library(
"//java/com/google/gerrit/sshd",
"//lib:args4j",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
"//lib:servlet-api-without-neverlink",
"//lib/auto:auto-value",
@@ -59,7 +50,6 @@ java_library(
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:jsonevent-layout",
"//lib/log:log4j",
"//lib/prolog:cafeteria",
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 6b4a065f3f..2ca68c2281 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -42,6 +42,7 @@ import com.google.gerrit.httpd.auth.oauth.OAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.httpd.raw.StaticModule;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
@@ -84,7 +85,6 @@ import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.group.PeriodicGroupIndexer;
import com.google.gerrit.server.index.AutoFlush;
import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.OnlineUpgrader;
import com.google.gerrit.server.index.VersionManager;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
@@ -150,8 +150,11 @@ public class Daemon extends SiteProgram {
sshd = false;
}
- @Option(name = "--slave", usage = "Support fetch only")
- private boolean slave;
+ @Option(
+ name = "--replica",
+ aliases = {"--slave"},
+ usage = "Support fetch only")
+ private boolean replica;
@Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
private boolean consoleLog;
@@ -218,15 +221,15 @@ public class Daemon extends SiteProgram {
httpd = enable;
}
+ public void setReplica(boolean replica) {
+ this.replica = replica;
+ }
+
@VisibleForTesting
public Injector getHttpdInjector() {
return httpdInjector;
}
- public void setSlave(boolean slave) {
- this.slave = slave;
- }
-
@Override
public int run() throws Exception {
if (stopOnly) {
@@ -249,7 +252,7 @@ public class Daemon extends SiteProgram {
}
if (httpd == null) {
- httpd = !slave;
+ httpd = !replica;
}
if (!httpd && !sshd) {
@@ -338,7 +341,7 @@ public class Daemon extends SiteProgram {
}
cfgInjector = createCfgInjector();
config = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
- initIndexType();
+ indexType = IndexModule.getIndexType(cfgInjector);
sysInjector = createSysInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class).setDbCfgInjector(dbInjector, cfgInjector);
manager.add(dbInjector, cfgInjector, sysInjector);
@@ -380,8 +383,8 @@ public class Daemon extends SiteProgram {
private String myVersion() {
List<String> versionParts = new ArrayList<>();
- if (slave) {
- versionParts.add("[slave]");
+ if (replica) {
+ versionParts.add("[replica]");
}
if (headless) {
versionParts.add("[headless]");
@@ -417,7 +420,7 @@ public class Daemon extends SiteProgram {
modules.add(new GerritApiModule());
modules.add(new PluginApiModule());
- modules.add(new SearchingChangeCacheImpl.Module(slave));
+ modules.add(new SearchingChangeCacheImpl.Module(replica));
modules.add(new InternalAccountDirectory.Module());
modules.add(new DefaultPermissionBackendModule());
modules.add(new DefaultMemoryCacheModule());
@@ -469,7 +472,8 @@ public class Daemon extends SiteProgram {
new AbstractModule() {
@Override
protected void configure() {
- bind(GerritOptions.class).toInstance(new GerritOptions(headless, slave, polyGerritDev));
+ bind(GerritOptions.class)
+ .toInstance(new GerritOptions(headless, replica, polyGerritDev));
if (inMemoryTest) {
bind(String.class)
.annotatedWith(SecureStoreClassName.class)
@@ -479,7 +483,7 @@ public class Daemon extends SiteProgram {
}
});
modules.add(new GarbageCollectionModule());
- if (slave) {
+ if (replica) {
modules.add(new PeriodicGroupIndexer.Module());
} else {
modules.add(new AccountDeactivator.Module());
@@ -497,25 +501,13 @@ public class Daemon extends SiteProgram {
if (luceneModule != null) {
return luceneModule;
}
- switch (indexType) {
- case LUCENE:
- return LuceneIndexModule.latestVersion(slave, AutoFlush.ENABLED);
- case ELASTICSEARCH:
- return ElasticIndexModule.latestVersion(slave);
- default:
- throw new IllegalStateException("unsupported index.type = " + indexType);
+ if (indexType.isLucene()) {
+ return LuceneIndexModule.latestVersion(replica, AutoFlush.ENABLED);
}
- }
-
- private void initIndexType() {
- indexType = IndexModule.getIndexType(cfgInjector);
- switch (indexType) {
- case LUCENE:
- case ELASTICSEARCH:
- break;
- default:
- throw new IllegalStateException("unsupported index.type = " + indexType);
+ if (indexType.isElasticsearch()) {
+ return ElasticIndexModule.latestVersion(replica);
}
+ throw new IllegalStateException("unsupported index.type = " + indexType);
}
private void initSshd() {
@@ -532,12 +524,12 @@ public class Daemon extends SiteProgram {
}
modules.add(
new DefaultCommandModule(
- slave,
+ replica,
sysInjector.getInstance(DownloadConfig.class),
sysInjector.getInstance(LfsPluginAuthCommand.Module.class)));
modules.addAll(testSshModules);
- if (!slave) {
+ if (!replica) {
modules.add(new IndexCommandsModule(sysInjector));
}
return sysInjector.createChildInjector(modules);
diff --git a/java/com/google/gerrit/pgm/Init.java b/java/com/google/gerrit/pgm/Init.java
index 6d9d00318d..4e62a0f88b 100644
--- a/java/com/google/gerrit/pgm/Init.java
+++ b/java/com/google/gerrit/pgm/Init.java
@@ -37,6 +37,7 @@ import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
import com.google.gerrit.server.ioutil.HostPlatform;
import com.google.gerrit.server.securestore.SecureStoreClassName;
+import com.google.gerrit.server.util.ReplicaUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
@@ -155,7 +156,7 @@ public class Init extends BaseInit {
});
modules.add(new GerritServerConfigModule());
Guice.createInjector(modules).injectMembers(this);
- if (!run.flags.cfg.getBoolean("container", "slave", false)) {
+ if (!ReplicaUtil.isReplica(run.flags.cfg)) {
for (SchemaDefinitions<?> schemaDef : schemaDefs) {
if (!indexStatus.exists(schemaDef.getName())) {
reindex(schemaDef);
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index 544c61d47d..0a8458b122 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -23,6 +23,7 @@ import com.google.gerrit.elasticsearch.ElasticIndexModule;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.SiteIndexer;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
@@ -32,9 +33,9 @@ import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.AutoFlush;
import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
+import com.google.gerrit.server.util.ReplicaUtil;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
@@ -147,21 +148,17 @@ public class Reindex extends SiteProgram {
if (changesVersion != null) {
versions.put(ChangeSchemaDefinitions.INSTANCE.getName(), changesVersion);
}
- boolean slave = globalConfig.getBoolean("container", "slave", false);
+ boolean replica = ReplicaUtil.isReplica(globalConfig);
List<Module> modules = new ArrayList<>();
Module indexModule;
- switch (IndexModule.getIndexType(dbInjector)) {
- case LUCENE:
- indexModule =
- LuceneIndexModule.singleVersionWithExplicitVersions(
- versions, threads, slave, AutoFlush.DISABLED);
- break;
- case ELASTICSEARCH:
- indexModule =
- ElasticIndexModule.singleVersionWithExplicitVersions(versions, threads, slave);
- break;
- default:
- throw new IllegalStateException("unsupported index.type");
+ IndexType indexType = IndexModule.getIndexType(dbInjector);
+ if (indexType.isLucene()) {
+ indexModule = LuceneIndexModule.singleVersionWithExplicitVersions(versions, threads, replica, AutoFlush.DISABLED);
+ } else if (indexType.isElasticsearch()) {
+ indexModule =
+ ElasticIndexModule.singleVersionWithExplicitVersions(versions, threads, replica);
+ } else {
+ throw new IllegalStateException("unsupported index.type = " + indexType);
}
modules.add(indexModule);
modules.add(new BatchProgramModule());
@@ -178,7 +175,7 @@ public class Reindex extends SiteProgram {
private void overrideConfig() {
// Disable auto-commit for speed; committing will happen at the end of the process.
- if (IndexModule.getIndexType(dbInjector) == IndexType.LUCENE) {
+ if (IndexModule.getIndexType(dbInjector).isLucene()) {
globalConfig.setLong("index", "changes_open", "commitWithin", -1);
globalConfig.setLong("index", "changes_closed", "commitWithin", -1);
}
diff --git a/java/com/google/gerrit/pgm/Rulec.java b/java/com/google/gerrit/pgm/Rulec.java
index aa72ae00b9..d695217ec9 100644
--- a/java/com/google/gerrit/pgm/Rulec.java
+++ b/java/com/google/gerrit/pgm/Rulec.java
@@ -14,11 +14,11 @@
package com.google.gerrit.pgm;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.rules.PrologCompiler;
import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -73,7 +73,7 @@ public class Rulec extends SiteProgram {
LinkedHashSet<Project.NameKey> names = new LinkedHashSet<>();
for (String name : projectNames) {
- names.add(new Project.NameKey(name));
+ names.add(Project.nameKey(name));
}
if (all) {
names.addAll(gitManager.list());
diff --git a/java/com/google/gerrit/pgm/http/jetty/BUILD b/java/com/google/gerrit/pgm/http/jetty/BUILD
index 9d65ded896..cd188f51c3 100644
--- a/java/com/google/gerrit/pgm/http/jetty/BUILD
+++ b/java/com/google/gerrit/pgm/http/jetty/BUILD
@@ -16,6 +16,7 @@ java_library(
"//java/com/google/gerrit/util/logging",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/flogger:api",
"//lib/guice",
@@ -24,7 +25,6 @@ java_library(
"//lib/jetty:jmx",
"//lib/jetty:server",
"//lib/jetty:servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:log4j",
],
)
diff --git a/java/com/google/gerrit/pgm/init/AccountsOnInit.java b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
index ff9490527b..536ddcda2c 100644
--- a/java/com/google/gerrit/pgm/init/AccountsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
@@ -14,13 +14,13 @@
package com.google.gerrit.pgm.init;
-import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.AccountProperties;
import com.google.gerrit.server.account.Accounts;
@@ -60,63 +60,59 @@ public class AccountsOnInit {
this.allUsers = allUsers.get();
}
- public void insert(Account account) throws IOException {
+ public Account insert(Account.Builder account) throws IOException {
File path = getPath();
- if (path != null) {
- try (Repository repo = new FileRepository(path);
- ObjectInserter oi = repo.newObjectInserter()) {
- PersonIdent ident =
- new PersonIdent(
- new GerritPersonIdentProvider(flags.cfg).get(), account.getRegisteredOn());
+ try (Repository repo = new FileRepository(path);
+ ObjectInserter oi = repo.newObjectInserter()) {
+ PersonIdent ident =
+ new PersonIdent(new GerritPersonIdentProvider(flags.cfg).get(), account.registeredOn());
- Config accountConfig = new Config();
- AccountProperties.writeToAccountConfig(
- InternalAccountUpdate.builder()
- .setActive(account.isActive())
- .setFullName(account.getFullName())
- .setPreferredEmail(account.getPreferredEmail())
- .setStatus(account.getStatus())
- .build(),
- accountConfig);
+ Config accountConfig = new Config();
+ AccountProperties.writeToAccountConfig(
+ InternalAccountUpdate.builder()
+ .setActive(!account.inactive())
+ .setFullName(account.fullName())
+ .setPreferredEmail(account.preferredEmail())
+ .setStatus(account.status())
+ .build(),
+ accountConfig);
- DirCache newTree = DirCache.newInCore();
- DirCacheEditor editor = newTree.editor();
- final ObjectId blobId =
- oi.insert(Constants.OBJ_BLOB, accountConfig.toText().getBytes(UTF_8));
- editor.add(
- new PathEdit(AccountProperties.ACCOUNT_CONFIG) {
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(FileMode.REGULAR_FILE);
- ent.setObjectId(blobId);
- }
- });
- editor.finish();
+ DirCache newTree = DirCache.newInCore();
+ DirCacheEditor editor = newTree.editor();
+ final ObjectId blobId = oi.insert(Constants.OBJ_BLOB, accountConfig.toText().getBytes(UTF_8));
+ editor.add(
+ new PathEdit(AccountProperties.ACCOUNT_CONFIG) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.REGULAR_FILE);
+ ent.setObjectId(blobId);
+ }
+ });
+ editor.finish();
- ObjectId treeId = newTree.writeTree(oi);
+ ObjectId treeId = newTree.writeTree(oi);
- CommitBuilder cb = new CommitBuilder();
- cb.setTreeId(treeId);
- cb.setCommitter(ident);
- cb.setAuthor(ident);
- cb.setMessage("Create Account");
- ObjectId id = oi.insert(cb);
- oi.flush();
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(treeId);
+ cb.setCommitter(ident);
+ cb.setAuthor(ident);
+ cb.setMessage("Create Account");
+ ObjectId id = oi.insert(cb);
+ oi.flush();
- String refName = RefNames.refsUsers(account.getId());
- RefUpdate ru = repo.updateRef(refName);
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- ru.setNewObjectId(id);
- ru.setRefLogIdent(ident);
- ru.setRefLogMessage("Create Account", false);
- Result result = ru.update();
- if (result != Result.NEW) {
- throw new IOException(
- String.format("Failed to update ref %s: %s", refName, result.name()));
- }
- account.setMetaId(id.name());
+ String refName = RefNames.refsUsers(account.id());
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(id);
+ ru.setRefLogIdent(ident);
+ ru.setRefLogMessage("Create Account", false);
+ Result result = ru.update();
+ if (result != Result.NEW) {
+ throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name()));
}
+ account.setMetaId(id.name()).build();
}
+ return account.build();
}
public boolean hasAnyAccount() throws IOException {
@@ -132,7 +128,10 @@ public class AccountsOnInit {
private File getPath() {
Path basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
- checkArgument(basePath != null, "gerrit.basePath must be configured");
- return FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
+ requireNonNull(basePath, "gerrit.basePath must be configured");
+ File file = basePath.resolve(allUsers).toFile();
+ File resolvedFile = FileKey.resolve(file, FS.DETECTED);
+ requireNonNull(resolvedFile, () -> String.format("%s does not exist", file.getAbsolutePath()));
+ return resolvedFile;
}
}
diff --git a/java/com/google/gerrit/pgm/init/BUILD b/java/com/google/gerrit/pgm/init/BUILD
index eb0d49e36a..62c95268af 100644
--- a/java/com/google/gerrit/pgm/init/BUILD
+++ b/java/com/google/gerrit/pgm/init/BUILD
@@ -10,6 +10,7 @@ java_library(
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/elasticsearch",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
@@ -18,17 +19,16 @@ java_library(
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
"//lib:guava",
"//lib:h2",
+ "//lib:jgit",
"//lib/commons:validator",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 0bc1860fcd..62ff66a649 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -23,6 +23,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Die;
import com.google.gerrit.common.IoUtil;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.pgm.init.api.ConsoleUI;
@@ -412,15 +413,13 @@ public class BaseInit extends SiteProgram {
});
Injector dbInjector = createDbInjector();
- switch (IndexModule.getIndexType(dbInjector)) {
- case LUCENE:
- modules.add(new LuceneIndexModuleOnInit());
- break;
- case ELASTICSEARCH:
- modules.add(new ElasticIndexModuleOnInit());
- break;
- default:
- throw new IllegalStateException("unsupported index.type");
+ IndexType indexType = IndexModule.getIndexType(dbInjector);
+ if (indexType.isLucene()) {
+ modules.add(new LuceneIndexModuleOnInit());
+ } else if (indexType.isElasticsearch()) {
+ modules.add(new ElasticIndexModuleOnInit());
+ } else {
+ throw new IllegalStateException("unsupported index.type = " + indexType);
}
sysInjector = dbInjector.createChildInjector(modules);
}
diff --git a/java/com/google/gerrit/pgm/init/GroupsOnInit.java b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
index 273ebfb9e0..0333942e7f 100644
--- a/java/com/google/gerrit/pgm/init/GroupsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
@@ -20,11 +20,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerIdProvider;
@@ -155,7 +155,7 @@ public class GroupsOnInit {
private static InternalGroupUpdate getMemberAdditionUpdate(Account account) {
return InternalGroupUpdate.builder()
- .setMemberModification(members -> Sets.union(members, ImmutableSet.of(account.getId())))
+ .setMemberModification(members -> Sets.union(members, ImmutableSet.of(account.id())))
.build();
}
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 27e6ce90ab..cf208ae91c 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -18,18 +18,16 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.SequencesOnInit;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.account.AccountIndexCollection;
@@ -49,7 +47,6 @@ import org.apache.commons.validator.routines.EmailValidator;
public class InitAdminUser implements InitStep {
private final InitFlags flags;
private final ConsoleUI ui;
- private final AllUsersNameOnInitProvider allUsers;
private final AccountsOnInit accounts;
private final VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory;
private final ExternalIdsOnInit externalIds;
@@ -62,7 +59,6 @@ public class InitAdminUser implements InitStep {
InitAdminUser(
InitFlags flags,
ConsoleUI ui,
- AllUsersNameOnInitProvider allUsers,
AccountsOnInit accounts,
VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory,
ExternalIdsOnInit externalIds,
@@ -70,7 +66,6 @@ public class InitAdminUser implements InitStep {
GroupsOnInit groupsOnInit) {
this.flags = flags;
this.ui = ui;
- this.allUsers = allUsers;
this.accounts = accounts;
this.authorizedKeysFactory = authorizedKeysFactory;
this.externalIds = externalIds;
@@ -101,7 +96,7 @@ public class InitAdminUser implements InitStep {
if (!accounts.hasAnyAccount()) {
ui.header("Gerrit Administrator");
if (ui.yesno(true, "Create administrator user")) {
- Account.Id id = new Account.Id(sequencesOnInit.nextAccountId());
+ Account.Id id = Account.id(sequencesOnInit.nextAccountId());
String username = ui.readString("admin", "username");
String name = ui.readString("Administrator", "name");
String httpPassword = ui.readString("secret", "HTTP password");
@@ -116,11 +111,9 @@ public class InitAdminUser implements InitStep {
}
externalIds.insert("Add external IDs for initial admin user", extIds);
- Account a = new Account(id, TimeUtil.nowTs());
- a.setFullName(name);
- a.setPreferredEmail(email);
- accounts.insert(a);
-
+ Account persistedAccount =
+ accounts.insert(
+ Account.builder(id, TimeUtil.nowTs()).setFullName(name).setPreferredEmail(email));
// Only two groups should exist at this point in time and hence iterating over all of them
// is cheap.
Optional<GroupReference> adminGroupReference =
@@ -132,7 +125,7 @@ public class InitAdminUser implements InitStep {
throw new NoSuchGroupException("Administrators");
}
GroupReference adminGroup = adminGroupReference.get();
- groupsOnInit.addGroupMember(adminGroup.getUUID(), a);
+ groupsOnInit.addGroupMember(adminGroup.getUUID(), persistedAccount);
if (sshKey != null) {
VersionedAuthorizedKeysOnInit authorizedKeys = authorizedKeysFactory.create(id).load();
@@ -140,7 +133,7 @@ public class InitAdminUser implements InitStep {
authorizedKeys.save("Add SSH key for initial admin user\n");
}
- AccountState as = AccountState.forAccount(new AllUsersName(allUsers.get()), a, extIds);
+ AccountState as = AccountState.forAccount(persistedAccount, extIds);
for (AccountIndex accountIndex : accountIndexCollection.getWriteIndexes()) {
accountIndex.replace(as);
}
diff --git a/java/com/google/gerrit/pgm/init/InitIndex.java b/java/com/google/gerrit/pgm/init/InitIndex.java
index 0de08f2c40..83d9261042 100644
--- a/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -15,6 +15,7 @@
package com.google.gerrit.pgm.init;
import com.google.common.collect.Iterables;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.SchemaDefinitions;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
@@ -22,7 +23,6 @@ import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.IndexUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -53,27 +53,23 @@ class InitIndex implements InitStep {
@Override
public void run() throws IOException {
- IndexType type = IndexType.LUCENE;
- if (IndexType.values().length > 1) {
- ui.header("Index");
- type = index.select("Type", "type", type);
- }
+ ui.header("Index");
+ IndexType type =
+ new IndexType(
+ index.select("Type", "type", IndexType.getDefault(), IndexType.getKnownTypes()));
- if (type == IndexType.ELASTICSEARCH) {
+ if (type.isElasticsearch()) {
Section elasticsearch = sections.get("elasticsearch", null);
elasticsearch.string("Index Prefix", "prefix", "gerrit_");
elasticsearch.string("Server", "server", "http://localhost:9200");
index.string("Result window size", "maxLimit", "10000");
}
- if ((site.isNew || isEmptySite()) && type == IndexType.LUCENE) {
+ if ((site.isNew || isEmptySite()) && type.isLucene()) {
for (SchemaDefinitions<?> def : IndexModule.ALL_SCHEMA_DEFS) {
IndexUtils.setReady(site, def.getName(), def.getLatest().getVersion(), true);
}
} else {
- if (IndexType.values().length <= 1) {
- ui.header("Index");
- }
String message =
String.format(
"\nThe index must be %sbuilt before starting Gerrit:\n"
diff --git a/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java b/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
index a9c6cc80c6..acde91ff60 100644
--- a/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
+++ b/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
@@ -17,11 +17,11 @@ package com.google.gerrit.pgm.init;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.VersionedMetaDataOnInit;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.account.AuthorizedKeys;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
diff --git a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index 20e7ba24b4..5ca239e80a 100644
--- a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
+++ b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -16,8 +16,8 @@ package com.google.gerrit.pgm.init.api;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.project.GroupList;
@@ -43,7 +43,7 @@ public class AllProjectsConfig extends VersionedMetaDataOnInit {
super(flags, site, allProjects.get(), RefNames.REFS_CONFIG);
this.baseConfig =
ProjectConfig.Factory.getBaseConfig(
- site, new AllProjectsName(allProjects.get()), new Project.NameKey(allProjects.get()));
+ site, new AllProjectsName(allProjects.get()), Project.nameKey(allProjects.get()));
}
public Config getConfig() {
@@ -71,7 +71,7 @@ public class AllProjectsConfig extends VersionedMetaDataOnInit {
private GroupList readGroupList() throws IOException {
return GroupList.parse(
- new Project.NameKey(project),
+ Project.nameKey(project),
readUTF8(GroupList.FILE_NAME),
error ->
logger.atSevere().log(
diff --git a/java/com/google/gerrit/pgm/init/api/BUILD b/java/com/google/gerrit/pgm/init/api/BUILD
index 19203fcc48..693d319f4e 100644
--- a/java/com/google/gerrit/pgm/init/api/BUILD
+++ b/java/com/google/gerrit/pgm/init/api/BUILD
@@ -7,13 +7,12 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/exceptions",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
"//lib:guava",
+ "//lib:jgit",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
index 2f94bdbd4f..a937c4bca7 100644
--- a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
@@ -14,7 +14,7 @@
package com.google.gerrit.pgm.init.api;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/pgm/init/api/Section.java b/java/com/google/gerrit/pgm/init/api/Section.java
index 87f7aeb442..b5d35f40e6 100644
--- a/java/com/google/gerrit/pgm/init/api/Section.java
+++ b/java/com/google/gerrit/pgm/init/api/Section.java
@@ -127,8 +127,10 @@ public class Section {
public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
String title, String name, T defValue, boolean nullIfDefault) {
+ @SuppressWarnings("rawtypes")
+ Class<? extends Enum> declaringClass = defValue.getDeclaringClass();
@SuppressWarnings("unchecked")
- E allowedValues = (E) EnumSet.allOf(defValue.getClass());
+ E allowedValues = (E) EnumSet.allOf(declaringClass);
return select(title, name, defValue, allowedValues, nullIfDefault);
}
diff --git a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
index d3d22cba69..c11230cb4f 100644
--- a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
@@ -14,7 +14,7 @@
package com.google.gerrit.pgm.init.api;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.RepoSequence;
@@ -38,7 +38,7 @@ public class SequencesOnInit {
new RepoSequence(
repoManager,
GitReferenceUpdated.DISABLED,
- new Project.NameKey(allUsersName.get()),
+ Project.nameKey(allUsersName.get()),
Sequences.NAME_ACCOUNTS,
() -> Sequences.FIRST_ACCOUNT_ID,
1);
diff --git a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
index 738cafdd87..f77960131a 100644
--- a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
@@ -14,7 +14,7 @@
package com.google.gerrit.pgm.init.api;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.meta.VersionedMetaData;
@@ -58,7 +58,7 @@ public abstract class VersionedMetaDataOnInit extends VersionedMetaData {
File path = getPath();
if (path != null) {
try (Repository repo = new FileRepository(path)) {
- load(new Project.NameKey(project), repo);
+ load(Project.nameKey(project), repo);
}
}
return this;
diff --git a/java/com/google/gerrit/pgm/rules/PrologCompiler.java b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
index 2663f42f51..0a41db5828 100644
--- a/java/com/google/gerrit/pgm/rules/PrologCompiler.java
+++ b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
@@ -15,7 +15,7 @@
package com.google.gerrit.pgm.rules;
import com.google.gerrit.common.Version;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.util.time.TimeUtil;
diff --git a/java/com/google/gerrit/pgm/util/BUILD b/java/com/google/gerrit/pgm/util/BUILD
index 60fb5e4faa..1b979717db 100644
--- a/java/com/google/gerrit/pgm/util/BUILD
+++ b/java/com/google/gerrit/pgm/util/BUILD
@@ -6,11 +6,11 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/metrics/dropwizard",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/cache/mem",
@@ -19,9 +19,9 @@ java_library(
"//java/com/google/gerrit/util/cli",
"//lib:args4j",
"//lib:guava",
+ "//lib:jgit",
"//lib/flogger:api",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:jsonevent-layout",
"//lib/log:log4j",
],
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 8e2a24410a..ce2b05df69 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -19,13 +19,13 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCacheImpl;
diff --git a/java/com/google/gerrit/pgm/util/SiteProgram.java b/java/com/google/gerrit/pgm/util/SiteProgram.java
index eed307fd47..98558fbafa 100644
--- a/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -18,6 +18,7 @@ import static com.google.gerrit.server.config.GerritServerConfigModule.getSecure
import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.common.Die;
+import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
@@ -28,6 +29,7 @@ import com.google.gerrit.server.config.GerritRuntime;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.GitRepositoryManagerModule;
+import com.google.gerrit.server.git.SystemReaderInstaller;
import com.google.gerrit.server.schema.SchemaModule;
import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.inject.AbstractModule;
@@ -106,6 +108,13 @@ public abstract class SiteProgram extends AbstractProgram {
});
}
+ modules.add(
+ new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(SystemReaderInstaller.class);
+ }
+ });
Module configModule = new GerritServerConfigModule();
modules.add(configModule);
modules.add(
diff --git a/java/com/google/gerrit/prettify/BUILD b/java/com/google/gerrit/prettify/BUILD
index 76afbe77b8..7c1241a9d7 100644
--- a/java/com/google/gerrit/prettify/BUILD
+++ b/java/com/google/gerrit/prettify/BUILD
@@ -5,8 +5,7 @@ java_library(
srcs = glob(["common/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/reviewdb:server",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
],
)
diff --git a/java/com/google/gerrit/proto/testing/BUILD b/java/com/google/gerrit/proto/testing/BUILD
index acfa8f06ee..0e5f887241 100644
--- a/java/com/google/gerrit/proto/testing/BUILD
+++ b/java/com/google/gerrit/proto/testing/BUILD
@@ -7,7 +7,6 @@ java_library(
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/server/cache/serialize",
"//lib:guava",
"//lib/commons:lang3",
"//lib/truth",
diff --git a/java/com/google/gerrit/proto/testing/SerializedClassSubject.java b/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
index 546ff89bf2..79affc6a17 100644
--- a/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
+++ b/java/com/google/gerrit/proto/testing/SerializedClassSubject.java
@@ -32,7 +32,7 @@ import org.apache.commons.lang3.reflect.FieldUtils;
* Subject about classes that are serialized into persistent caches or indices.
*
* <p>Hand-written {@link com.google.gerrit.server.cache.serialize.CacheSerializer CacheSerializer}
- * and {@link com.google.gerrit.reviewdb.converter.ProtoConverter ProtoConverter} implementations
+ * and {@link com.google.gerrit.entities.converter.ProtoConverter ProtoConverter} implementations
* depend on the exact representation of the data stored in a class, so it is important to verify
* any assumptions about the structure of the serialized classes. This class contains assertions
* about serialized classes, and should be used for every class that has a custom serializer
@@ -48,7 +48,7 @@ import org.apache.commons.lang3.reflect.FieldUtils;
* the hand-written serializer. Usually, serializer implementations should be written in such a way
* that new fields are considered optional, and won't require bumping the version.
*/
-public class SerializedClassSubject extends Subject<SerializedClassSubject, Class<?>> {
+public class SerializedClassSubject extends Subject {
public static SerializedClassSubject assertThatSerializedClass(Class<?> actual) {
// This formulation fails in Eclipse 4.7.3a with "The type
// SerializedClassSubject does not define SerializedClassSubject() that is
@@ -60,20 +60,23 @@ public class SerializedClassSubject extends Subject<SerializedClassSubject, Clas
return assertAbout(factory).that(actual);
}
- private SerializedClassSubject(FailureMetadata metadata, Class<?> actual) {
- super(metadata, actual);
+ private final Class<?> clazz;
+
+ private SerializedClassSubject(FailureMetadata metadata, Class<?> clazz) {
+ super(metadata, clazz);
+ this.clazz = clazz;
}
public void isAbstract() {
isNotNull();
- if (!Modifier.isAbstract(actual().getModifiers())) {
+ if (!Modifier.isAbstract(clazz.getModifiers())) {
failWithActual(simpleFact("expected class to be abstract"));
}
}
public void isConcrete() {
isNotNull();
- if (Modifier.isAbstract(actual().getModifiers())) {
+ if (Modifier.isAbstract(clazz.getModifiers())) {
failWithActual(simpleFact("expected class to be concrete"));
}
}
@@ -82,7 +85,7 @@ public class SerializedClassSubject extends Subject<SerializedClassSubject, Clas
isConcrete();
check("fields()")
.that(
- FieldUtils.getAllFieldsList(actual()).stream()
+ FieldUtils.getAllFieldsList(clazz).stream()
.filter(f -> !Modifier.isStatic(f.getModifiers()))
.collect(toImmutableMap(Field::getName, Field::getGenericType)))
.containsExactlyEntriesIn(expectedFields);
@@ -91,9 +94,9 @@ public class SerializedClassSubject extends Subject<SerializedClassSubject, Clas
public void hasAutoValueMethods(Map<String, Type> expectedMethods) {
// Would be nice if we could check clazz is an @AutoValue, but the retention is not RUNTIME.
isAbstract();
- check("noArgumentAbstractMethodsOn(%s)", actual().getName())
+ check("noArgumentAbstractMethods()")
.that(
- Arrays.stream(actual().getDeclaredMethods())
+ Arrays.stream(clazz.getDeclaredMethods())
.filter(m -> !Modifier.isStatic(m.getModifiers()))
.filter(m -> Modifier.isAbstract(m.getModifiers()))
.filter(m -> m.getParameters().length == 0)
@@ -103,8 +106,6 @@ public class SerializedClassSubject extends Subject<SerializedClassSubject, Clas
public void extendsClass(Type superclassType) {
isNotNull();
- check("superclass(%s)", actual().getName())
- .that(actual().getGenericSuperclass())
- .isEqualTo(superclassType);
+ check("getGenericSuperclass()").that(clazz.getGenericSuperclass()).isEqualTo(superclassType);
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroupById.java b/java/com/google/gerrit/reviewdb/client/AccountGroupById.java
deleted file mode 100644
index 578865c1a7..0000000000
--- a/java/com/google/gerrit/reviewdb/client/AccountGroupById.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gwtorm.client.CompoundKey;
-import java.util.Objects;
-
-/** Membership of an {@link AccountGroup} in an {@link AccountGroup}. */
-public final class AccountGroupById {
- public static Key key(AccountGroup.Id groupId, AccountGroup.UUID includeUuid) {
- return new Key(groupId, includeUuid);
- }
-
- public static class Key extends CompoundKey<AccountGroup.Id> {
- private static final long serialVersionUID = 1L;
-
- protected AccountGroup.Id groupId;
-
- protected AccountGroup.UUID includeUUID;
-
- protected Key() {
- groupId = new AccountGroup.Id();
- includeUUID = new AccountGroup.UUID();
- }
-
- public Key(AccountGroup.Id g, AccountGroup.UUID u) {
- groupId = g;
- includeUUID = u;
- }
-
- @Override
- public AccountGroup.Id getParentKey() {
- return groupId;
- }
-
- public AccountGroup.Id getGroupId() {
- return groupId;
- }
-
- public AccountGroup.Id groupId() {
- return getParentKey();
- }
-
- public AccountGroup.UUID getIncludeUUID() {
- return includeUUID;
- }
-
- public AccountGroup.UUID includeUuid() {
- return getIncludeUUID();
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {includeUUID};
- }
- }
-
- protected Key key;
-
- protected AccountGroupById() {}
-
- public AccountGroupById(AccountGroupById.Key k) {
- key = k;
- }
-
- public AccountGroupById.Key getKey() {
- return key;
- }
-
- public AccountGroup.Id getGroupId() {
- return key.groupId;
- }
-
- public AccountGroup.UUID getIncludeUUID() {
- return key.includeUUID;
- }
-
- @Override
- public boolean equals(Object o) {
- return (o instanceof AccountGroupById) && Objects.equals(key, ((AccountGroupById) o).key);
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "{key=" + key + "}";
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroupByIdAud.java b/java/com/google/gerrit/reviewdb/client/AccountGroupByIdAud.java
deleted file mode 100644
index 308e1e1dcc..0000000000
--- a/java/com/google/gerrit/reviewdb/client/AccountGroupByIdAud.java
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.CompoundKey;
-import java.sql.Timestamp;
-import java.util.Objects;
-
-/** Inclusion of an {@link AccountGroup} in another {@link AccountGroup}. */
-public final class AccountGroupByIdAud {
- public static Key key(AccountGroup.Id groupId, AccountGroup.UUID includeUuid, Timestamp addedOn) {
- return new Key(groupId, includeUuid, addedOn);
- }
-
- public static class Key extends CompoundKey<AccountGroup.Id> {
- private static final long serialVersionUID = 1L;
-
- protected AccountGroup.Id groupId;
-
- protected AccountGroup.UUID includeUUID;
-
- protected Timestamp addedOn;
-
- protected Key() {
- groupId = new AccountGroup.Id();
- includeUUID = new AccountGroup.UUID();
- }
-
- public Key(AccountGroup.Id g, AccountGroup.UUID u, Timestamp t) {
- groupId = g;
- includeUUID = u;
- addedOn = t;
- }
-
- @Override
- public AccountGroup.Id getParentKey() {
- return groupId;
- }
-
- public AccountGroup.Id groupId() {
- return getParentKey();
- }
-
- public AccountGroup.UUID getIncludeUUID() {
- return includeUUID;
- }
-
- public AccountGroup.UUID includeUuid() {
- return getIncludeUUID();
- }
-
- public Timestamp getAddedOn() {
- return addedOn;
- }
-
- public Timestamp addedOn() {
- return getAddedOn();
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {includeUUID};
- }
-
- @Override
- public String toString() {
- return "Key{"
- + "groupId="
- + groupId
- + ", includeUUID="
- + includeUUID
- + ", addedOn="
- + addedOn
- + '}';
- }
- }
-
- protected Key key;
-
- protected Account.Id addedBy;
-
- @Nullable protected Account.Id removedBy;
-
- @Nullable protected Timestamp removedOn;
-
- protected AccountGroupByIdAud() {}
-
- public AccountGroupByIdAud(final AccountGroupById m, Account.Id adder, Timestamp when) {
- final AccountGroup.Id group = m.getGroupId();
- final AccountGroup.UUID include = m.getIncludeUUID();
- key = new AccountGroupByIdAud.Key(group, include, when);
- addedBy = adder;
- }
-
- public AccountGroupByIdAud(AccountGroupByIdAud.Key key, Account.Id adder) {
- this.key = key;
- addedBy = adder;
- }
-
- public AccountGroupByIdAud.Key getKey() {
- return key;
- }
-
- public AccountGroup.Id getGroupId() {
- return key.getParentKey();
- }
-
- public AccountGroup.UUID getIncludeUUID() {
- return key.getIncludeUUID();
- }
-
- public boolean isActive() {
- return removedOn == null;
- }
-
- public void removed(Account.Id deleter, Timestamp when) {
- removedBy = deleter;
- removedOn = when;
- }
-
- public Account.Id getAddedBy() {
- return addedBy;
- }
-
- public Timestamp getAddedOn() {
- return key.getAddedOn();
- }
-
- public Account.Id getRemovedBy() {
- return removedBy;
- }
-
- public Timestamp getRemovedOn() {
- return removedOn;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof AccountGroupByIdAud)) {
- return false;
- }
- AccountGroupByIdAud a = (AccountGroupByIdAud) o;
- return Objects.equals(key, a.key)
- && Objects.equals(addedBy, a.addedBy)
- && Objects.equals(removedBy, a.removedBy)
- && Objects.equals(removedOn, a.removedOn);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(key, addedBy, removedBy, removedOn);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName()
- + "{"
- + "key="
- + key
- + ", addedBy="
- + addedBy
- + ", removedBy="
- + removedBy
- + ", removedOn="
- + removedOn
- + "}";
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroupMember.java b/java/com/google/gerrit/reviewdb/client/AccountGroupMember.java
deleted file mode 100644
index dfa7d24426..0000000000
--- a/java/com/google/gerrit/reviewdb/client/AccountGroupMember.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gwtorm.client.CompoundKey;
-import java.util.Objects;
-
-/** Membership of an {@link Account} in an {@link AccountGroup}. */
-public final class AccountGroupMember {
- public static Key key(Account.Id accountId, AccountGroup.Id groupId) {
- return new Key(accountId, groupId);
- }
-
- public static class Key extends CompoundKey<Account.Id> {
- private static final long serialVersionUID = 1L;
-
- protected Account.Id accountId;
-
- protected AccountGroup.Id groupId;
-
- protected Key() {
- accountId = new Account.Id();
- groupId = new AccountGroup.Id();
- }
-
- public Key(Account.Id a, AccountGroup.Id g) {
- accountId = a;
- groupId = g;
- }
-
- @Override
- public Account.Id getParentKey() {
- return accountId;
- }
-
- public Account.Id accountId() {
- return getParentKey();
- }
-
- public AccountGroup.Id getAccountGroupId() {
- return groupId;
- }
-
- public AccountGroup.Id groupId() {
- return getAccountGroupId();
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {groupId};
- }
- }
-
- protected Key key;
-
- protected AccountGroupMember() {}
-
- public AccountGroupMember(AccountGroupMember.Key k) {
- key = k;
- }
-
- public AccountGroupMember.Key getKey() {
- return key;
- }
-
- public Account.Id getAccountId() {
- return key.accountId;
- }
-
- public AccountGroup.Id getAccountGroupId() {
- return key.groupId;
- }
-
- @Override
- public boolean equals(Object o) {
- return (o instanceof AccountGroupMember) && Objects.equals(key, ((AccountGroupMember) o).key);
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "{key=" + key + "}";
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroupMemberAudit.java b/java/com/google/gerrit/reviewdb/client/AccountGroupMemberAudit.java
deleted file mode 100644
index 5d43b4a915..0000000000
--- a/java/com/google/gerrit/reviewdb/client/AccountGroupMemberAudit.java
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.CompoundKey;
-import java.sql.Timestamp;
-import java.util.Objects;
-
-/** Membership of an {@link Account} in an {@link AccountGroup}. */
-public final class AccountGroupMemberAudit {
- public static Key key(Account.Id accountId, AccountGroup.Id groupId, Timestamp addedOn) {
- return new Key(accountId, groupId, addedOn);
- }
-
- public static class Key extends CompoundKey<Account.Id> {
- private static final long serialVersionUID = 1L;
-
- protected Account.Id accountId;
-
- protected AccountGroup.Id groupId;
-
- protected Timestamp addedOn;
-
- protected Key() {
- accountId = new Account.Id();
- groupId = new AccountGroup.Id();
- }
-
- public Key(Account.Id a, AccountGroup.Id g, Timestamp t) {
- accountId = a;
- groupId = g;
- addedOn = t;
- }
-
- @Override
- public Account.Id getParentKey() {
- return accountId;
- }
-
- public Account.Id accountId() {
- return getParentKey();
- }
-
- public AccountGroup.Id getGroupId() {
- return groupId;
- }
-
- public AccountGroup.Id groupId() {
- return getGroupId();
- }
-
- public Timestamp getAddedOn() {
- return addedOn;
- }
-
- public Timestamp addedOn() {
- return getAddedOn();
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {groupId};
- }
-
- @Override
- public String toString() {
- return "Key{"
- + "groupId="
- + groupId
- + ", accountId="
- + accountId
- + ", addedOn="
- + addedOn
- + '}';
- }
- }
-
- protected Key key;
-
- protected Account.Id addedBy;
-
- @Nullable protected Account.Id removedBy;
-
- @Nullable protected Timestamp removedOn;
-
- protected AccountGroupMemberAudit() {}
-
- public AccountGroupMemberAudit(final AccountGroupMember m, Account.Id adder, Timestamp addedOn) {
- final Account.Id who = m.getAccountId();
- final AccountGroup.Id group = m.getAccountGroupId();
- key = new AccountGroupMemberAudit.Key(who, group, addedOn);
- addedBy = adder;
- }
-
- public AccountGroupMemberAudit(AccountGroupMemberAudit.Key key, Account.Id adder) {
- this.key = key;
- addedBy = adder;
- }
-
- public AccountGroupMemberAudit.Key getKey() {
- return key;
- }
-
- public AccountGroup.Id getGroupId() {
- return key.getGroupId();
- }
-
- public Account.Id getMemberId() {
- return key.getParentKey();
- }
-
- public boolean isActive() {
- return removedOn == null;
- }
-
- public void removed(Account.Id deleter, Timestamp when) {
- removedBy = deleter;
- removedOn = when;
- }
-
- public void removedLegacy() {
- removedBy = addedBy;
- removedOn = key.addedOn;
- }
-
- public Account.Id getAddedBy() {
- return addedBy;
- }
-
- public Timestamp getAddedOn() {
- return key.getAddedOn();
- }
-
- public Account.Id getRemovedBy() {
- return removedBy;
- }
-
- public Timestamp getRemovedOn() {
- return removedOn;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof AccountGroupMemberAudit)) {
- return false;
- }
- AccountGroupMemberAudit a = (AccountGroupMemberAudit) o;
- return Objects.equals(key, a.key)
- && Objects.equals(addedBy, a.addedBy)
- && Objects.equals(removedBy, a.removedBy)
- && Objects.equals(removedOn, a.removedOn);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(key, addedBy, removedBy, removedOn);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName()
- + "{"
- + "key="
- + key
- + ", addedBy="
- + addedBy
- + ", removedBy="
- + removedBy
- + ", removedOn="
- + removedOn
- + "}";
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/Branch.java b/java/com/google/gerrit/reviewdb/client/Branch.java
deleted file mode 100644
index 4ea49b7637..0000000000
--- a/java/com/google/gerrit/reviewdb/client/Branch.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gwtorm.client.StringKey;
-
-/** Line of development within a {@link Project}. */
-public final class Branch {
- public static NameKey nameKey(Project.NameKey projectName, String branchName) {
- return new NameKey(projectName, RefNames.fullName(branchName));
- }
-
- public static NameKey nameKey(String projectName, String branchName) {
- return nameKey(Project.nameKey(projectName), branchName);
- }
-
- /** Branch name key */
- public static class NameKey extends StringKey<Project.NameKey> {
- private static final long serialVersionUID = 1L;
-
- protected Project.NameKey projectName;
-
- protected String branchName;
-
- protected NameKey() {
- projectName = new Project.NameKey();
- }
-
- public NameKey(Project.NameKey proj, String branchName) {
- projectName = proj;
- set(branchName);
- }
-
- public NameKey(String proj, String branchName) {
- this(new Project.NameKey(proj), branchName);
- }
-
- @Override
- public String get() {
- return branchName;
- }
-
- public String branch() {
- return get();
- }
-
- @Override
- protected void set(String newValue) {
- branchName = RefNames.fullName(newValue);
- }
-
- @Override
- public Project.NameKey getParentKey() {
- return projectName;
- }
-
- public Project.NameKey project() {
- return getParentKey();
- }
-
- public String getShortName() {
- return RefNames.shortName(get());
- }
- }
-
- protected NameKey name;
- protected RevId revision;
- protected boolean canDelete;
-
- protected Branch() {}
-
- public Branch(Branch.NameKey newName) {
- name = newName;
- }
-
- public Branch.NameKey getNameKey() {
- return name;
- }
-
- public String getName() {
- return name.get();
- }
-
- public String getShortName() {
- return name.getShortName();
- }
-
- public RevId getRevision() {
- return revision;
- }
-
- public void setRevision(RevId id) {
- revision = id;
- }
-
- public boolean getCanDelete() {
- return canDelete;
- }
-
- public void setCanDelete(boolean canDelete) {
- this.canDelete = canDelete;
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
deleted file mode 100644
index ce218c0128..0000000000
--- a/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ /dev/null
@@ -1,361 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.client.Comment.Range;
-import com.google.gwtorm.client.StringKey;
-import java.sql.Timestamp;
-import java.util.Objects;
-
-/**
- * A comment left by a user on a specific line of a {@link Patch}.
- *
- * <p>New APIs should not expose this class.
- *
- * @see Comment
- */
-public final class PatchLineComment {
- public static class Key extends StringKey<Patch.Key> {
- private static final long serialVersionUID = 1L;
-
- public static Key from(Change.Id changeId, Comment.Key key) {
- return new Key(
- new Patch.Key(new PatchSet.Id(changeId, key.patchSetId), key.filename), key.uuid);
- }
-
- protected Patch.Key patchKey;
-
- protected String uuid;
-
- protected Key() {
- patchKey = new Patch.Key();
- }
-
- public Key(Patch.Key p, String uuid) {
- this.patchKey = p;
- this.uuid = uuid;
- }
-
- @Override
- public Patch.Key getParentKey() {
- return patchKey;
- }
-
- @Override
- public String get() {
- return uuid;
- }
-
- @Override
- public void set(String newValue) {
- uuid = newValue;
- }
-
- public Comment.Key asCommentKey() {
- return new Comment.Key(
- get(), getParentKey().getFileName(), getParentKey().getParentKey().get());
- }
- }
-
- public static final char STATUS_DRAFT = 'd';
- public static final char STATUS_PUBLISHED = 'P';
-
- public enum Status {
- DRAFT(STATUS_DRAFT),
-
- PUBLISHED(STATUS_PUBLISHED);
-
- private final char code;
-
- Status(char c) {
- code = c;
- }
-
- public char getCode() {
- return code;
- }
-
- public static Status forCode(char c) {
- for (Status s : Status.values()) {
- if (s.code == c) {
- return s;
- }
- }
- return null;
- }
- }
-
- public static PatchLineComment from(
- Change.Id changeId, PatchLineComment.Status status, Comment c) {
- PatchLineComment.Key key =
- new PatchLineComment.Key(
- new Patch.Key(new PatchSet.Id(changeId, c.key.patchSetId), c.key.filename), c.key.uuid);
-
- PatchLineComment plc =
- new PatchLineComment(key, c.lineNbr, c.author.getId(), c.parentUuid, c.writtenOn);
- plc.setSide(c.side);
- plc.setMessage(c.message);
- if (c.range != null) {
- Comment.Range r = c.range;
- plc.setRange(new CommentRange(r.startLine, r.startChar, r.endLine, r.endChar));
- }
- plc.setTag(c.tag);
- plc.setRevId(new RevId(c.revId));
- plc.setStatus(status);
- plc.setRealAuthor(c.getRealAuthor().getId());
- plc.setUnresolved(c.unresolved);
- return plc;
- }
-
- protected Key key;
-
- /** Line number this comment applies to; it should display after the line. */
- protected int lineNbr;
-
- /** Who wrote this comment. */
- protected Account.Id author;
-
- /** When this comment was drafted. */
- protected Timestamp writtenOn;
-
- /** Current publication state of the comment; see {@link Status}. */
- protected char status;
-
- /** Which file is this comment; 0 is ancestor, 1 is new version. */
- protected short side;
-
- /** The text left by the user. */
- @Nullable protected String message;
-
- /** The parent of this comment, or null if this is the first comment on this line */
- @Nullable protected String parentUuid;
-
- @Nullable protected CommentRange range;
-
- @Nullable protected String tag;
-
- /** Real user that added this comment on behalf of the user recorded in {@link #author}. */
- @Nullable protected Account.Id realAuthor;
-
- /** True if this comment requires further action. */
- protected boolean unresolved;
-
- /** The RevId for the commit to which this comment is referring. */
- protected RevId revId;
-
- protected PatchLineComment() {}
-
- public PatchLineComment(
- PatchLineComment.Key id, int line, Account.Id a, String parentUuid, Timestamp when) {
- key = id;
- lineNbr = line;
- author = a;
- setParentUuid(parentUuid);
- setStatus(Status.DRAFT);
- setWrittenOn(when);
- }
-
- public PatchLineComment(PatchLineComment o) {
- key = o.key;
- lineNbr = o.lineNbr;
- author = o.author;
- realAuthor = o.realAuthor;
- writtenOn = o.writtenOn;
- status = o.status;
- side = o.side;
- message = o.message;
- parentUuid = o.parentUuid;
- revId = o.revId;
- if (o.range != null) {
- range =
- new CommentRange(
- o.range.getStartLine(),
- o.range.getStartCharacter(),
- o.range.getEndLine(),
- o.range.getEndCharacter());
- }
- }
-
- public PatchLineComment.Key getKey() {
- return key;
- }
-
- public PatchSet.Id getPatchSetId() {
- return key.getParentKey().getParentKey();
- }
-
- public int getLine() {
- return lineNbr;
- }
-
- public void setLine(int line) {
- lineNbr = line;
- }
-
- public Account.Id getAuthor() {
- return author;
- }
-
- public Account.Id getRealAuthor() {
- return realAuthor != null ? realAuthor : getAuthor();
- }
-
- public void setRealAuthor(Account.Id id) {
- // Use null for same real author, as before the column was added.
- realAuthor = Objects.equals(getAuthor(), id) ? null : id;
- }
-
- public Timestamp getWrittenOn() {
- return writtenOn;
- }
-
- public Status getStatus() {
- return Status.forCode(status);
- }
-
- public void setStatus(Status s) {
- status = s.getCode();
- }
-
- public short getSide() {
- return side;
- }
-
- public void setSide(short s) {
- side = s;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String s) {
- message = s;
- }
-
- public void setWrittenOn(Timestamp ts) {
- writtenOn = ts;
- }
-
- public String getParentUuid() {
- return parentUuid;
- }
-
- public void setParentUuid(String inReplyTo) {
- parentUuid = inReplyTo;
- }
-
- public void setRange(Range r) {
- if (r != null) {
- range =
- new CommentRange(
- r.startLine, r.startCharacter,
- r.endLine, r.endCharacter);
- } else {
- range = null;
- }
- }
-
- public void setRange(CommentRange r) {
- range = r;
- }
-
- public CommentRange getRange() {
- return range;
- }
-
- public void setRevId(RevId rev) {
- revId = rev;
- }
-
- public RevId getRevId() {
- return revId;
- }
-
- public void setTag(String tag) {
- this.tag = tag;
- }
-
- public String getTag() {
- return tag;
- }
-
- public void setUnresolved(Boolean unresolved) {
- this.unresolved = unresolved;
- }
-
- public Boolean getUnresolved() {
- return unresolved;
- }
-
- public Comment asComment(String serverId) {
- Comment c =
- new Comment(key.asCommentKey(), author, writtenOn, side, message, serverId, unresolved);
- c.setRevId(revId);
- c.setRange(range);
- c.lineNbr = lineNbr;
- c.parentUuid = parentUuid;
- c.tag = tag;
- c.setRealAuthor(getRealAuthor());
- return c;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof PatchLineComment) {
- PatchLineComment c = (PatchLineComment) o;
- return Objects.equals(key, c.getKey())
- && Objects.equals(lineNbr, c.getLine())
- && Objects.equals(author, c.getAuthor())
- && Objects.equals(writtenOn, c.getWrittenOn())
- && Objects.equals(status, c.getStatus().getCode())
- && Objects.equals(side, c.getSide())
- && Objects.equals(message, c.getMessage())
- && Objects.equals(parentUuid, c.getParentUuid())
- && Objects.equals(range, c.getRange())
- && Objects.equals(revId, c.getRevId())
- && Objects.equals(tag, c.getTag())
- && Objects.equals(unresolved, c.getUnresolved());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("PatchLineComment{");
- builder.append("key=").append(key).append(',');
- builder.append("lineNbr=").append(lineNbr).append(',');
- builder.append("author=").append(author.get()).append(',');
- builder.append("realAuthor=").append(realAuthor != null ? realAuthor.get() : "").append(',');
- builder.append("writtenOn=").append(writtenOn.toString()).append(',');
- builder.append("status=").append(status).append(',');
- builder.append("side=").append(side).append(',');
- builder.append("message=").append(Objects.toString(message, "")).append(',');
- builder.append("parentUuid=").append(Objects.toString(parentUuid, "")).append(',');
- builder.append("range=").append(Objects.toString(range, "")).append(',');
- builder.append("revId=").append(revId != null ? revId.get() : "").append(',');
- builder.append("tag=").append(Objects.toString(tag, "")).append(',');
- builder.append("unresolved=").append(unresolved);
- builder.append('}');
- return builder.toString();
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/PatchSet.java b/java/com/google/gerrit/reviewdb/client/PatchSet.java
deleted file mode 100644
index 684f09236b..0000000000
--- a/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ /dev/null
@@ -1,304 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.IntKey;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/** A single revision of a {@link Change}. */
-public final class PatchSet {
- /** Is the reference name a change reference? */
- public static boolean isChangeRef(String name) {
- return Id.fromRef(name) != null;
- }
-
- /**
- * Is the reference name a change reference?
- *
- * @deprecated use isChangeRef instead.
- */
- @Deprecated
- public static boolean isRef(String name) {
- return isChangeRef(name);
- }
-
- public static String joinGroups(List<String> groups) {
- if (groups == null) {
- throw new IllegalArgumentException("groups may not be null");
- }
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (String g : groups) {
- if (!first) {
- sb.append(',');
- } else {
- first = false;
- }
- sb.append(g);
- }
- return sb.toString();
- }
-
- public static List<String> splitGroups(String joinedGroups) {
- if (joinedGroups == null) {
- throw new IllegalArgumentException("groups may not be null");
- }
- List<String> groups = new ArrayList<>();
- int i = 0;
- while (true) {
- int idx = joinedGroups.indexOf(',', i);
- if (idx < 0) {
- groups.add(joinedGroups.substring(i));
- break;
- }
- groups.add(joinedGroups.substring(i, idx));
- i = idx + 1;
- }
- return groups;
- }
-
- public static Id id(Change.Id changeId, int id) {
- return new Id(changeId, id);
- }
-
- public static class Id extends IntKey<Change.Id> {
- private static final long serialVersionUID = 1L;
-
- public Change.Id changeId;
-
- public int patchSetId;
-
- public Id() {
- changeId = new Change.Id();
- }
-
- public Id(Change.Id change, int id) {
- this.changeId = change;
- this.patchSetId = id;
- }
-
- @Override
- public Change.Id getParentKey() {
- return changeId;
- }
-
- public Change.Id changeId() {
- return getParentKey();
- }
-
- @Override
- public int get() {
- return patchSetId;
- }
-
- @Override
- protected void set(int newValue) {
- patchSetId = newValue;
- }
-
- public String toRefName() {
- return changeId.refPrefixBuilder().append(patchSetId).toString();
- }
-
- /** Parse a PatchSet.Id out of a string representation. */
- public static Id parse(String str) {
- final Id r = new Id();
- r.fromString(str);
- return r;
- }
-
- /** Parse a PatchSet.Id from a {@link PatchSet#getRefName()} result. */
- public static Id fromRef(String ref) {
- int cs = Change.Id.startIndex(ref);
- if (cs < 0) {
- return null;
- }
- int ce = Change.Id.nextNonDigit(ref, cs);
- int patchSetId = fromRef(ref, ce);
- if (patchSetId < 0) {
- return null;
- }
- int changeId = Integer.parseInt(ref.substring(cs, ce));
- return new PatchSet.Id(new Change.Id(changeId), patchSetId);
- }
-
- static int fromRef(String ref, int changeIdEnd) {
- // Patch set ID.
- int ps = changeIdEnd + 1;
- if (ps >= ref.length() || ref.charAt(ps) == '0') {
- return -1;
- }
- for (int i = ps; i < ref.length(); i++) {
- if (ref.charAt(i) < '0' || ref.charAt(i) > '9') {
- return -1;
- }
- }
- return Integer.parseInt(ref.substring(ps));
- }
-
- public String getId() {
- return toId(patchSetId);
- }
-
- public static String toId(int number) {
- return number == 0 ? "edit" : String.valueOf(number);
- }
- }
-
- protected Id id;
-
- @Nullable protected RevId revision;
-
- protected Account.Id uploader;
-
- /** When this patch set was first introduced onto the change. */
- protected Timestamp createdOn;
-
- /**
- * Opaque group identifier, usually assigned during creation.
- *
- * <p>This field is actually a comma-separated list of values, as in rare cases involving merge
- * commits a patch set may belong to multiple groups.
- *
- * <p>Changes on the same branch having patch sets with intersecting groups are considered
- * related, as in the "Related Changes" tab.
- */
- @Nullable protected String groups;
-
- // DELETED id = 7 (pushCertficate)
-
- /** Certificate sent with a push that created this patch set. */
- @Nullable protected String pushCertificate;
-
- /**
- * Optional user-supplied description for this patch set.
- *
- * <p>When this field is null, the description was never set on the patch set. When this field is
- * an empty string, the description was set and later cleared.
- */
- @Nullable protected String description;
-
- protected PatchSet() {}
-
- public PatchSet(PatchSet.Id k) {
- id = k;
- }
-
- public PatchSet(PatchSet src) {
- this.id = src.id;
- this.revision = src.revision;
- this.uploader = src.uploader;
- this.createdOn = src.createdOn;
- this.groups = src.groups;
- this.pushCertificate = src.pushCertificate;
- this.description = src.description;
- }
-
- public PatchSet.Id getId() {
- return id;
- }
-
- public int getPatchSetId() {
- return id.get();
- }
-
- public RevId getRevision() {
- return revision;
- }
-
- public void setRevision(RevId i) {
- revision = i;
- }
-
- public Account.Id getUploader() {
- return uploader;
- }
-
- public void setUploader(Account.Id who) {
- uploader = who;
- }
-
- public Timestamp getCreatedOn() {
- return createdOn;
- }
-
- public void setCreatedOn(Timestamp ts) {
- createdOn = ts;
- }
-
- public List<String> getGroups() {
- if (groups == null) {
- return Collections.emptyList();
- }
- return splitGroups(groups);
- }
-
- public void setGroups(List<String> groups) {
- if (groups == null) {
- groups = Collections.emptyList();
- }
- this.groups = joinGroups(groups);
- }
-
- public String getRefName() {
- return id.toRefName();
- }
-
- public String getPushCertificate() {
- return pushCertificate;
- }
-
- public void setPushCertificate(String cert) {
- pushCertificate = cert;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof PatchSet)) {
- return false;
- }
- PatchSet p = (PatchSet) o;
- return Objects.equals(id, p.id)
- && Objects.equals(revision, p.revision)
- && Objects.equals(uploader, p.uploader)
- && Objects.equals(createdOn, p.createdOn)
- && Objects.equals(groups, p.groups)
- && Objects.equals(pushCertificate, p.pushCertificate)
- && Objects.equals(description, p.description);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, revision, uploader, createdOn, groups, pushCertificate, description);
- }
-
- @Override
- public String toString() {
- return "[PatchSet " + getId().toString() + "]";
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
deleted file mode 100644
index e1c4ea9989..0000000000
--- a/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gwtorm.client.CompoundKey;
-import java.sql.Timestamp;
-import java.util.Date;
-import java.util.Objects;
-
-/** An approval (or negative approval) on a patch set. */
-public final class PatchSetApproval {
- public static Key key(PatchSet.Id patchSetId, Account.Id accountId, LabelId labelId) {
- return new Key(patchSetId, accountId, labelId);
- }
-
- public static class Key extends CompoundKey<PatchSet.Id> {
- private static final long serialVersionUID = 1L;
-
- protected PatchSet.Id patchSetId;
-
- protected Account.Id accountId;
-
- protected LabelId categoryId;
-
- protected Key() {
- patchSetId = new PatchSet.Id();
- accountId = new Account.Id();
- categoryId = new LabelId();
- }
-
- public Key(PatchSet.Id ps, Account.Id a, LabelId c) {
- this.patchSetId = ps;
- this.accountId = a;
- this.categoryId = c;
- }
-
- @Override
- public PatchSet.Id getParentKey() {
- return patchSetId;
- }
-
- public PatchSet.Id patchSetId() {
- return getParentKey();
- }
-
- public Account.Id getAccountId() {
- return accountId;
- }
-
- public Account.Id accountId() {
- return getAccountId();
- }
-
- public LabelId getLabelId() {
- return categoryId;
- }
-
- public LabelId labelId() {
- return getLabelId();
- }
-
- @Override
- public com.google.gwtorm.client.Key<?>[] members() {
- return new com.google.gwtorm.client.Key<?>[] {accountId, categoryId};
- }
- }
-
- protected Key key;
-
- /**
- * Value assigned by the user.
- *
- * <p>The precise meaning of "value" is up to each category.
- *
- * <p>In general:
- *
- * <ul>
- * <li><b>&lt; 0:</b> The approval is rejected/revoked.
- * <li><b>= 0:</b> No indication either way is provided.
- * <li><b>&gt; 0:</b> The approval is approved/positive.
- * </ul>
- *
- * and in the negative and positive direction a magnitude can be assumed.The further from 0 the
- * more assertive the approval.
- */
- protected short value;
-
- protected Timestamp granted;
-
- @Nullable protected String tag;
-
- /** Real user that made this approval on behalf of the user recorded in {@link Key#accountId}. */
- @Nullable protected Account.Id realAccountId;
-
- protected boolean postSubmit;
-
- // DELETED: id = 4 (changeOpen)
- // DELETED: id = 5 (changeSortKey)
-
- protected PatchSetApproval() {}
-
- public PatchSetApproval(PatchSetApproval.Key k, short v, Date ts) {
- key = k;
- setValue(v);
- setGranted(ts);
- }
-
- public PatchSetApproval(PatchSet.Id psId, PatchSetApproval src) {
- key = new PatchSetApproval.Key(psId, src.getAccountId(), src.getLabelId());
- value = src.getValue();
- granted = src.granted;
- realAccountId = src.realAccountId;
- tag = src.tag;
- postSubmit = src.postSubmit;
- }
-
- public PatchSetApproval(PatchSetApproval src) {
- this(src.getPatchSetId(), src);
- }
-
- public PatchSetApproval.Key getKey() {
- return key;
- }
-
- public PatchSet.Id getPatchSetId() {
- return key.patchSetId;
- }
-
- public Account.Id getAccountId() {
- return key.accountId;
- }
-
- public Account.Id getRealAccountId() {
- return realAccountId != null ? realAccountId : getAccountId();
- }
-
- public void setRealAccountId(Account.Id id) {
- // Use null for same real author, as before the column was added.
- realAccountId = Objects.equals(getAccountId(), id) ? null : id;
- }
-
- public LabelId getLabelId() {
- return key.categoryId;
- }
-
- public short getValue() {
- return value;
- }
-
- public void setValue(short v) {
- value = v;
- }
-
- public Timestamp getGranted() {
- return granted;
- }
-
- public void setGranted(Date when) {
- if (when instanceof Timestamp) {
- granted = (Timestamp) when;
- } else {
- granted = new Timestamp(when.getTime());
- }
- }
-
- public void setTag(String t) {
- tag = t;
- }
-
- public String getLabel() {
- return getLabelId().get();
- }
-
- public boolean isLegacySubmit() {
- return LabelId.LEGACY_SUBMIT_NAME.equals(getLabel());
- }
-
- public String getTag() {
- return tag;
- }
-
- public void setPostSubmit(boolean postSubmit) {
- this.postSubmit = postSubmit;
- }
-
- public boolean isPostSubmit() {
- return postSubmit;
- }
-
- @Override
- public String toString() {
- StringBuilder sb =
- new StringBuilder("[")
- .append(key)
- .append(": ")
- .append(value)
- .append(",tag:")
- .append(tag)
- .append(",realAccountId:")
- .append(realAccountId);
- if (postSubmit) {
- sb.append(",postSubmit");
- }
- return sb.append(']').toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof PatchSetApproval) {
- PatchSetApproval p = (PatchSetApproval) o;
- return Objects.equals(key, p.key)
- && Objects.equals(value, p.value)
- && Objects.equals(granted, p.granted)
- && Objects.equals(tag, p.tag)
- && Objects.equals(realAccountId, p.realAccountId)
- && postSubmit == p.postSubmit;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(key, value, granted, tag, realAccountId, postSubmit);
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/RevId.java b/java/com/google/gerrit/reviewdb/client/RevId.java
deleted file mode 100644
index 99b6c2ce56..0000000000
--- a/java/com/google/gerrit/reviewdb/client/RevId.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-/** A revision identifier for a file or a change. */
-public final class RevId {
- public static final int ABBREV_LEN = 7;
- public static final int LEN = 40;
-
- protected String id;
-
- protected RevId() {}
-
- public RevId(String str) {
- id = str;
- }
-
- /** @return the value of this revision id. */
- public String get() {
- return id;
- }
-
- /** @return true if this revision id has all required digits. */
- public boolean isComplete() {
- return get().length() == LEN;
- }
-
- /**
- * @return if {@link #isComplete()}, {@code this}; otherwise a new RevId with 'z' appended on the
- * end.
- */
- public RevId max() {
- if (isComplete()) {
- return this;
- }
-
- final StringBuilder revEnd = new StringBuilder(get().length() + 1);
- revEnd.append(get());
- revEnd.append('z');
- return new RevId(revEnd.toString());
- }
-
- @Override
- public int hashCode() {
- return id.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- return (o instanceof RevId) && id.equals(((RevId) o).id);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "{" + id + "}";
- }
-
- public boolean matches(String str) {
- return id.startsWith(str.toLowerCase());
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/RobotComment.java b/java/com/google/gerrit/reviewdb/client/RobotComment.java
deleted file mode 100644
index eceb0bfc8e..0000000000
--- a/java/com/google/gerrit/reviewdb/client/RobotComment.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import java.sql.Timestamp;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-public class RobotComment extends Comment {
- public String robotId;
- public String robotRunId;
- public String url;
- public Map<String, String> properties;
- public List<FixSuggestion> fixSuggestions;
-
- public RobotComment(
- Key key,
- Account.Id author,
- Timestamp writtenOn,
- short side,
- String message,
- String serverId,
- String robotId,
- String robotRunId) {
- super(key, author, writtenOn, side, message, serverId, false);
- this.robotId = robotId;
- this.robotRunId = robotRunId;
- }
-
- @Override
- public String toString() {
- return new StringBuilder()
- .append("RobotComment{")
- .append("key=")
- .append(key)
- .append(',')
- .append("robotId=")
- .append(robotId)
- .append(',')
- .append("robotRunId=")
- .append(robotRunId)
- .append(',')
- .append("lineNbr=")
- .append(lineNbr)
- .append(',')
- .append("author=")
- .append(author.getId().get())
- .append(',')
- .append("realAuthor=")
- .append(realAuthor != null ? realAuthor.getId().get() : "")
- .append(',')
- .append("writtenOn=")
- .append(writtenOn.toString())
- .append(',')
- .append("side=")
- .append(side)
- .append(',')
- .append("message=")
- .append(Objects.toString(message, ""))
- .append(',')
- .append("parentUuid=")
- .append(Objects.toString(parentUuid, ""))
- .append(',')
- .append("range=")
- .append(Objects.toString(range, ""))
- .append(',')
- .append("revId=")
- .append(revId != null ? revId : "")
- .append(',')
- .append("tag=")
- .append(Objects.toString(tag, ""))
- .append(',')
- .append("unresolved=")
- .append(unresolved)
- .append(',')
- .append("url=")
- .append(url)
- .append(',')
- .append("properties=")
- .append(properties != null ? properties : "")
- .append("fixSuggestions=")
- .append(fixSuggestions != null ? fixSuggestions : "")
- .append('}')
- .toString();
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/client/SubmoduleSubscription.java b/java/com/google/gerrit/reviewdb/client/SubmoduleSubscription.java
deleted file mode 100644
index b297dfb2f5..0000000000
--- a/java/com/google/gerrit/reviewdb/client/SubmoduleSubscription.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.client;
-
-import com.google.gwtorm.client.StringKey;
-
-/**
- * Defining a project/branch subscription to a project/branch project.
- *
- * <p>This means a class instance represents a repo/branch subscription to a project/branch (the
- * subscriber).
- *
- * <p>A subscriber operates a submodule in defined path.
- */
-public final class SubmoduleSubscription {
- /** Subscription key */
- public static class Key extends StringKey<Branch.NameKey> {
- private static final long serialVersionUID = 1L;
-
- /**
- * Indicates the super project, aka subscriber: the project owner of the gitlinks to the
- * submodules.
- */
- protected Branch.NameKey superProject;
-
- protected String submodulePath;
-
- protected Key() {
- superProject = new Branch.NameKey();
- }
-
- protected Key(Branch.NameKey superProject, String path) {
- this.superProject = superProject;
- this.submodulePath = path;
- }
-
- @Override
- public Branch.NameKey getParentKey() {
- return superProject;
- }
-
- @Override
- public String get() {
- return submodulePath;
- }
-
- @Override
- protected void set(String newValue) {
- this.submodulePath = newValue;
- }
- }
-
- protected Key key;
-
- protected Branch.NameKey submodule;
-
- protected SubmoduleSubscription() {}
-
- public SubmoduleSubscription(Branch.NameKey superProject, Branch.NameKey submodule, String path) {
- this.key = new Key(superProject, path);
- this.submodule = submodule;
- }
-
- public Key getKey() {
- return key;
- }
-
- public Branch.NameKey getSuperProject() {
- return key.superProject;
- }
-
- public String getPath() {
- return key.get();
- }
-
- public Branch.NameKey getSubmodule() {
- return submodule;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof SubmoduleSubscription) {
- return key.equals(((SubmoduleSubscription) o).key)
- && submodule.equals(((SubmoduleSubscription) o).submodule);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(getSuperProject()).append(':').append(getPath());
- sb.append(" follows ");
- sb.append(getSubmodule());
- return sb.toString();
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/converter/PatchSetProtoConverter.java b/java/com/google/gerrit/reviewdb/converter/PatchSetProtoConverter.java
deleted file mode 100644
index 75ee80099f..0000000000
--- a/java/com/google/gerrit/reviewdb/converter/PatchSetProtoConverter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.converter;
-
-import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.protobuf.Parser;
-import java.sql.Timestamp;
-import java.util.List;
-
-public enum PatchSetProtoConverter implements ProtoConverter<Entities.PatchSet, PatchSet> {
- INSTANCE;
-
- private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
- PatchSetIdProtoConverter.INSTANCE;
- private final ProtoConverter<Entities.RevId, RevId> revIdConverter = RevIdProtoConverter.INSTANCE;
- private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
- AccountIdProtoConverter.INSTANCE;
-
- @Override
- public Entities.PatchSet toProto(PatchSet patchSet) {
- Entities.PatchSet.Builder builder =
- Entities.PatchSet.newBuilder().setId(patchSetIdConverter.toProto(patchSet.getId()));
- RevId revision = patchSet.getRevision();
- if (revision != null) {
- builder.setRevision(revIdConverter.toProto(revision));
- }
- Account.Id uploader = patchSet.getUploader();
- if (uploader != null) {
- builder.setUploaderAccountId(accountIdConverter.toProto(uploader));
- }
- Timestamp createdOn = patchSet.getCreatedOn();
- if (createdOn != null) {
- builder.setCreatedOn(createdOn.getTime());
- }
- List<String> groups = patchSet.getGroups();
- if (!groups.isEmpty()) {
- builder.setGroups(PatchSet.joinGroups(groups));
- }
- String pushCertificate = patchSet.getPushCertificate();
- if (pushCertificate != null) {
- builder.setPushCertificate(pushCertificate);
- }
- String description = patchSet.getDescription();
- if (description != null) {
- builder.setDescription(description);
- }
- return builder.build();
- }
-
- @Override
- public PatchSet fromProto(Entities.PatchSet proto) {
- PatchSet patchSet = new PatchSet(patchSetIdConverter.fromProto(proto.getId()));
- if (proto.hasRevision()) {
- patchSet.setRevision(revIdConverter.fromProto(proto.getRevision()));
- }
- if (proto.hasUploaderAccountId()) {
- patchSet.setUploader(accountIdConverter.fromProto(proto.getUploaderAccountId()));
- }
- if (proto.hasCreatedOn()) {
- patchSet.setCreatedOn(new Timestamp(proto.getCreatedOn()));
- }
- if (proto.hasGroups()) {
- patchSet.setGroups(PatchSet.splitGroups(proto.getGroups()));
- }
- if (proto.hasPushCertificate()) {
- patchSet.setPushCertificate(proto.getPushCertificate());
- }
- if (proto.hasDescription()) {
- patchSet.setDescription(proto.getDescription());
- }
- return patchSet;
- }
-
- @Override
- public Parser<Entities.PatchSet> getParser() {
- return Entities.PatchSet.parser();
- }
-}
diff --git a/java/com/google/gerrit/reviewdb/converter/RevIdProtoConverter.java b/java/com/google/gerrit/reviewdb/converter/RevIdProtoConverter.java
deleted file mode 100644
index b3c998b2aa..0000000000
--- a/java/com/google/gerrit/reviewdb/converter/RevIdProtoConverter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.converter;
-
-import com.google.gerrit.proto.Entities;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.protobuf.Parser;
-
-public enum RevIdProtoConverter implements ProtoConverter<Entities.RevId, RevId> {
- INSTANCE;
-
- @Override
- public Entities.RevId toProto(RevId revId) {
- return Entities.RevId.newBuilder().setId(revId.get()).build();
- }
-
- @Override
- public RevId fromProto(Entities.RevId proto) {
- return new RevId(proto.getId());
- }
-
- @Override
- public Parser<Entities.RevId> getParser() {
- return Entities.RevId.parser();
- }
-}
diff --git a/java/com/google/gerrit/server/ApprovalCopier.java b/java/com/google/gerrit/server/ApprovalInference.java
index 09508bb9f2..566a32bf56 100644
--- a/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/java/com/google/gerrit/server/ApprovalInference.java
@@ -15,170 +15,96 @@
package com.google.gerrit.server;
import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Objects.requireNonNull;
+import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ListMultimap;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.ChangeKind;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.change.ChangeKindCache;
import com.google.gerrit.server.change.LabelNormalizer;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
-import java.util.TreeMap;
+import java.util.Map;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevWalk;
/**
- * Copies approvals between patch sets.
+ * Computes approvals for a given patch set by looking at approvals applied to the given patch set
+ * and by additionally inferring approvals from the patch set's parents. The latter is done by
+ * asserting a change's kind and checking the project config for allowed forward-inference.
*
* <p>The result of a copy may either be stored, as when stamping approvals in the database at
* submit time, or refreshed on demand, as when reading approvals from the NoteDb.
*/
@Singleton
-public class ApprovalCopier {
+public class ApprovalInference {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ProjectCache projectCache;
private final ChangeKindCache changeKindCache;
private final LabelNormalizer labelNormalizer;
- private final ChangeData.Factory changeDataFactory;
- private final PatchSetUtil psUtil;
@Inject
- ApprovalCopier(
- ProjectCache projectCache,
- ChangeKindCache changeKindCache,
- LabelNormalizer labelNormalizer,
- ChangeData.Factory changeDataFactory,
- PatchSetUtil psUtil) {
+ ApprovalInference(
+ ProjectCache projectCache, ChangeKindCache changeKindCache, LabelNormalizer labelNormalizer) {
this.projectCache = projectCache;
this.changeKindCache = changeKindCache;
this.labelNormalizer = labelNormalizer;
- this.changeDataFactory = changeDataFactory;
- this.psUtil = psUtil;
}
- Iterable<PatchSetApproval> getForPatchSet(
+ /**
+ * Returns all approvals that apply to the given patch set. Honors direct and indirect (approval
+ * on parents) approvals.
+ */
+ Iterable<PatchSetApproval> forPatchSet(
ChangeNotes notes, PatchSet.Id psId, @Nullable RevWalk rw, @Nullable Config repoConfig) {
- return getForPatchSet(notes, psId, rw, repoConfig, Collections.emptyList());
- }
-
- Iterable<PatchSetApproval> getForPatchSet(
- ChangeNotes notes,
- PatchSet.Id psId,
- @Nullable RevWalk rw,
- @Nullable Config repoConfig,
- Iterable<PatchSetApproval> dontCopy) {
- PatchSet ps = psUtil.get(notes, psId);
- if (ps == null) {
- return Collections.emptyList();
- }
- return getForPatchSet(notes, ps, rw, repoConfig, dontCopy);
- }
-
- private Iterable<PatchSetApproval> getForPatchSet(
- ChangeNotes notes,
- PatchSet ps,
- @Nullable RevWalk rw,
- @Nullable Config repoConfig,
- Iterable<PatchSetApproval> dontCopy) {
- requireNonNull(ps, "ps should not be null");
- ChangeData cd = changeDataFactory.create(notes);
- try {
- ProjectState project = projectCache.checkedGet(cd.change().getDest().getParentKey());
- ListMultimap<PatchSet.Id, PatchSetApproval> all = cd.approvals();
- requireNonNull(all, "all should not be null");
-
- Table<String, Account.Id, PatchSetApproval> wontCopy = HashBasedTable.create();
- for (PatchSetApproval psa : dontCopy) {
- wontCopy.put(psa.getLabel(), psa.getAccountId(), psa);
- }
-
- Table<String, Account.Id, PatchSetApproval> byUser = HashBasedTable.create();
- for (PatchSetApproval psa : all.get(ps.getId())) {
- if (!wontCopy.contains(psa.getLabel(), psa.getAccountId())) {
- byUser.put(psa.getLabel(), psa.getAccountId(), psa);
- }
- }
-
- TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
-
- // Walk patch sets strictly less than current in descending order.
- Collection<PatchSet> allPrior =
- patchSets.descendingMap().tailMap(ps.getId().get(), false).values();
- for (PatchSet priorPs : allPrior) {
- List<PatchSetApproval> priorApprovals = all.get(priorPs.getId());
- if (priorApprovals.isEmpty()) {
- continue;
- }
-
- ChangeKind kind =
- changeKindCache.getChangeKind(
- project.getNameKey(),
- rw,
- repoConfig,
- ObjectId.fromString(priorPs.getRevision().get()),
- ObjectId.fromString(ps.getRevision().get()));
-
- for (PatchSetApproval psa : priorApprovals) {
- if (wontCopy.contains(psa.getLabel(), psa.getAccountId())) {
- continue;
- }
- if (byUser.contains(psa.getLabel(), psa.getAccountId())) {
- continue;
- }
- if (!canCopy(project, psa, ps.getId(), kind)) {
- wontCopy.put(psa.getLabel(), psa.getAccountId(), psa);
- continue;
- }
- byUser.put(psa.getLabel(), psa.getAccountId(), copy(psa, ps.getId()));
- }
- }
- return labelNormalizer.normalize(notes, byUser.values()).getNormalized();
+ ProjectState project;
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Computing labels for patch set",
+ Metadata.builder()
+ .changeId(notes.load().getChangeId().get())
+ .patchSetId(psId.get())
+ .build())) {
+ project = projectCache.checkedGet(notes.getProjectName());
+ Collection<PatchSetApproval> approvals =
+ getForPatchSetWithoutNormalization(notes, project, psId, rw, repoConfig);
+ return labelNormalizer.normalize(notes, approvals).getNormalized();
} catch (IOException e) {
throw new StorageException(e);
}
}
- private static TreeMap<Integer, PatchSet> getPatchSets(ChangeData cd) {
- Collection<PatchSet> patchSets = cd.patchSets();
- TreeMap<Integer, PatchSet> result = new TreeMap<>();
- for (PatchSet ps : patchSets) {
- result.put(ps.getId().get(), ps);
- }
- return result;
- }
-
private static boolean canCopy(
ProjectState project, PatchSetApproval psa, PatchSet.Id psId, ChangeKind kind) {
- int n = psa.getKey().getParentKey().get();
+ int n = psa.key().patchSetId().get();
checkArgument(n != psId.get());
- LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
+ LabelType type = project.getLabelTypes().byLabel(psa.labelId());
if (type == null) {
logger.atFine().log(
"approval %d on label %s of patch set %d of change %d cannot be copied"
+ " to patch set %d because the label no longer exists on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
project.getName());
return false;
@@ -186,10 +112,10 @@ public class ApprovalCopier {
logger.atFine().log(
"veto approval %s on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because the label has set copyMinScore = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
project.getName());
return true;
@@ -197,10 +123,21 @@ public class ApprovalCopier {
logger.atFine().log(
"max approval %s on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because the label has set copyMaxScore = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
+ n,
+ psa.key().patchSetId().changeId().get(),
+ psId.get(),
+ project.getName());
+ return true;
+ } else if (type.isCopyAnyScore()) {
+ logger.atFine().log(
+ "approval %d on label %s of patch set %d of change %d can be copied"
+ + " to patch set %d because the label has set copyAnyScore = true on project %s",
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
project.getName());
return true;
@@ -212,10 +149,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresOnMergeFirstParentUpdate = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -228,10 +165,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresIfNoCodeChange = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -244,10 +181,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresOnTrivialRebase = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -260,10 +197,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresIfNoCodeChange = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -274,10 +211,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresOnTrivialRebase = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -288,10 +225,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresOnMergeFirstParentUpdate = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -302,10 +239,10 @@ public class ApprovalCopier {
"approval %d on label %s of patch set %d of change %d can be copied"
+ " to patch set %d because change kind is %s and the label has set"
+ " copyAllScoresIfNoCodeChange = true on project %s",
- psa.getValue(),
- psa.getLabel(),
+ psa.value(),
+ psa.label(),
n,
- psa.getKey().getParentKey().changeId.get(),
+ psa.key().patchSetId().changeId().get(),
psId.get(),
kind,
project.getName());
@@ -317,20 +254,80 @@ public class ApprovalCopier {
logger.atFine().log(
"approval %d on label %s of patch set %d of change %d cannot be copied"
+ " to patch set %d because change kind is %s",
- psa.getValue(),
- psa.getLabel(),
- n,
- psa.getKey().getParentKey().changeId.get(),
- psId.get(),
- kind);
+ psa.value(), psa.label(), n, psa.key().patchSetId().changeId().get(), psId.get(), kind);
return false;
}
}
- private static PatchSetApproval copy(PatchSetApproval src, PatchSet.Id psId) {
- if (src.getKey().getParentKey().equals(psId)) {
- return src;
+ private Collection<PatchSetApproval> getForPatchSetWithoutNormalization(
+ ChangeNotes notes,
+ ProjectState project,
+ PatchSet.Id psId,
+ @Nullable RevWalk rw,
+ @Nullable Config repoConfig) {
+ checkState(
+ project.getNameKey().equals(notes.getProjectName()),
+ "project must match %s, %s",
+ project.getNameKey(),
+ notes.getProjectName());
+
+ PatchSet ps = notes.load().getPatchSets().get(psId);
+ if (ps == null) {
+ return Collections.emptyList();
+ }
+
+ // Add approvals on the given patch set to the result
+ Table<String, Account.Id, PatchSetApproval> resultByUser = HashBasedTable.create();
+ ImmutableList<PatchSetApproval> approvalsForGivenPatchSet =
+ notes.load().getApprovals().get(ps.id());
+ approvalsForGivenPatchSet.forEach(psa -> resultByUser.put(psa.label(), psa.accountId(), psa));
+
+ // Bail out immediately if this is the first patch set. Return only approvals granted on the
+ // given patch set.
+ if (psId.get() == 1) {
+ return resultByUser.values();
+ }
+
+ // Call this algorithm recursively to check if the prior patch set had approvals. This has the
+ // advantage that all caches - most importantly ChangeKindCache - have values cached for what we
+ // need for this computation.
+ // The way this algorithm is written is that any approval will be copied forward by one patch
+ // set at a time if configs and change kind allow so. Once an approval is held back - for
+ // example because the patch set is a REWORK - it will not be picked up again in a future
+ // patch set.
+ Map.Entry<PatchSet.Id, PatchSet> priorPatchSet = notes.load().getPatchSets().lowerEntry(psId);
+ if (priorPatchSet == null) {
+ return resultByUser.values();
+ }
+
+ Iterable<PatchSetApproval> priorApprovals =
+ getForPatchSetWithoutNormalization(
+ notes, project, priorPatchSet.getValue().id(), rw, repoConfig);
+ if (!priorApprovals.iterator().hasNext()) {
+ return resultByUser.values();
+ }
+
+ // Add labels from the previous patch set to the result in case the label isn't already there
+ // and settings as well as change kind allow copying.
+ ChangeKind kind =
+ changeKindCache.getChangeKind(
+ project.getNameKey(),
+ rw,
+ repoConfig,
+ priorPatchSet.getValue().commitId(),
+ ps.commitId());
+ logger.atFine().log(
+ "change kind for patch set %d of change %d against prior patch set %s is %s",
+ ps.id().get(), ps.id().changeId().get(), priorPatchSet.getValue().id().changeId(), kind);
+ for (PatchSetApproval psa : priorApprovals) {
+ if (resultByUser.contains(psa.label(), psa.accountId())) {
+ continue;
+ }
+ if (!canCopy(project, psa, ps.id(), kind)) {
+ continue;
+ }
+ resultByUser.put(psa.label(), psa.accountId(), psa.copyWithPatchSet(ps.id()));
}
- return new PatchSetApproval(psId, src);
+ return resultByUser.values();
}
}
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 135276e33f..58b601f01f 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -25,20 +25,19 @@ import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
-import com.google.common.primitives.Shorts;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -71,38 +70,38 @@ import org.eclipse.jgit.revwalk.RevWalk;
* for each reviewer, even if the reviewer hasn't actually given a score to the change. To mark the
* "no score" case, a dummy approval, which may live in any of the available categories, with a
* score of 0 is used.
- *
- * <p>The methods in this class only modify the gwtorm database.
*/
@Singleton
public class ApprovalsUtil {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- public static PatchSetApproval newApproval(
+ public static PatchSetApproval.Builder newApproval(
PatchSet.Id psId, CurrentUser user, LabelId labelId, int value, Date when) {
- PatchSetApproval psa =
- new PatchSetApproval(
- new PatchSetApproval.Key(psId, user.getAccountId(), labelId),
- Shorts.checkedCast(value),
- when);
- user.updateRealAccountId(psa::setRealAccountId);
- return psa;
+ PatchSetApproval.Builder b =
+ PatchSetApproval.builder()
+ .key(PatchSetApproval.key(psId, user.getAccountId(), labelId))
+ .value(value)
+ .granted(when);
+ user.updateRealAccountId(b::realAccountId);
+ return b;
}
private static Iterable<PatchSetApproval> filterApprovals(
Iterable<PatchSetApproval> psas, Account.Id accountId) {
- return Iterables.filter(psas, a -> Objects.equals(a.getAccountId(), accountId));
+ return Iterables.filter(psas, a -> Objects.equals(a.accountId(), accountId));
}
- private final ApprovalCopier copier;
+ private final ApprovalInference approvalInference;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@VisibleForTesting
@Inject
public ApprovalsUtil(
- ApprovalCopier copier, PermissionBackend permissionBackend, ProjectCache projectCache) {
- this.copier = copier;
+ ApprovalInference approvalInference,
+ PermissionBackend permissionBackend,
+ ProjectCache projectCache) {
+ this.approvalInference = approvalInference;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
}
@@ -149,7 +148,7 @@ public class ApprovalsUtil {
update,
labelTypes,
change,
- ps.getId(),
+ ps.id(),
info.getAuthor().getAccount(),
info.getCommitter().getAccount(),
wantReviewers,
@@ -209,8 +208,11 @@ public class ApprovalsUtil {
LabelId labelId = Iterables.getLast(allTypes).getLabelId();
for (Account.Id account : need) {
cells.add(
- new PatchSetApproval(
- new PatchSetApproval.Key(psId, account, labelId), (short) 0, update.getWhen()));
+ PatchSetApproval.builder()
+ .key(PatchSetApproval.key(psId, account, labelId))
+ .value(0)
+ .granted(update.getWhen())
+ .build());
update.putReviewer(account, REVIEWER);
}
return Collections.unmodifiableList(cells);
@@ -239,17 +241,28 @@ public class ApprovalsUtil {
* @param notes change notes.
* @param update change update.
* @param wantCCs accounts to CC.
+ * @param keepExistingReviewers whether provided accounts that are already reviewer should be kept
+ * as reviewer or be downgraded to CC
* @return whether a change was made.
*/
public Collection<Account.Id> addCcs(
- ChangeNotes notes, ChangeUpdate update, Collection<Account.Id> wantCCs) {
- return addCcs(update, wantCCs, notes.load().getReviewers());
+ ChangeNotes notes,
+ ChangeUpdate update,
+ Collection<Account.Id> wantCCs,
+ boolean keepExistingReviewers) {
+ return addCcs(update, wantCCs, notes.load().getReviewers(), keepExistingReviewers);
}
private Collection<Account.Id> addCcs(
- ChangeUpdate update, Collection<Account.Id> wantCCs, ReviewerSet existingReviewers) {
+ ChangeUpdate update,
+ Collection<Account.Id> wantCCs,
+ ReviewerSet existingReviewers,
+ boolean keepExistingReviewers) {
Set<Account.Id> need = new LinkedHashSet<>(wantCCs);
- need.removeAll(existingReviewers.all());
+ need.removeAll(existingReviewers.byState(CC));
+ if (keepExistingReviewers) {
+ need.removeAll(existingReviewers.byState(REVIEWER));
+ }
need.removeAll(update.getReviewers().keySet());
for (Account.Id account : need) {
update.putReviewer(account, CC);
@@ -276,10 +289,10 @@ public class ApprovalsUtil {
throws RestApiException, PermissionBackendException {
Account.Id accountId = user.getAccountId();
checkArgument(
- accountId.equals(ps.getUploader()),
+ accountId.equals(ps.uploader()),
"expected user %s to match patch set uploader %s",
accountId,
- ps.getUploader());
+ ps.uploader());
if (approvals.isEmpty()) {
return ImmutableList.of();
}
@@ -288,10 +301,10 @@ public class ApprovalsUtil {
Date ts = update.getWhen();
for (Map.Entry<String, Short> vote : approvals.entrySet()) {
LabelType lt = labelTypes.byLabel(vote.getKey());
- cells.add(newApproval(ps.getId(), user, lt.getLabelId(), vote.getValue(), ts));
+ cells.add(newApproval(ps.id(), user, lt.getLabelId(), vote.getValue(), ts).build());
}
for (PatchSetApproval psa : cells) {
- update.putApproval(psa.getLabel(), psa.getValue());
+ update.putApproval(psa.label(), psa.value());
}
return cells;
}
@@ -329,7 +342,7 @@ public class ApprovalsUtil {
public Iterable<PatchSetApproval> byPatchSet(
ChangeNotes notes, PatchSet.Id psId, @Nullable RevWalk rw, @Nullable Config repoConfig) {
- return copier.getForPatchSet(notes, psId, rw, repoConfig);
+ return approvalInference.forPatchSet(notes, psId, rw, repoConfig);
}
public Iterable<PatchSetApproval> byPatchSetUser(
@@ -359,8 +372,8 @@ public class ApprovalsUtil {
}
PatchSetApproval submitter = null;
for (PatchSetApproval a : approvals) {
- if (a.getPatchSetId().equals(c) && a.getValue() > 0 && a.isLegacySubmit()) {
- if (submitter == null || a.getGranted().compareTo(submitter.getGranted()) > 0) {
+ if (a.patchSetId().equals(c) && a.value() > 0 && a.isLegacySubmit()) {
+ if (submitter == null || a.granted().compareTo(submitter.granted()) > 0) {
submitter = a;
}
}
@@ -374,7 +387,7 @@ public class ApprovalsUtil {
if (!n.isEmpty()) {
boolean first = true;
for (Map.Entry<String, Short> e : n.entrySet()) {
- if (c.containsKey(e.getKey()) && c.get(e.getKey()).getValue() == e.getValue()) {
+ if (c.containsKey(e.getKey()) && c.get(e.getKey()).value() == e.getValue()) {
continue;
}
if (first) {
diff --git a/java/com/google/gerrit/server/AssigneeStatusUpdate.java b/java/com/google/gerrit/server/AssigneeStatusUpdate.java
new file mode 100644
index 0000000000..3d6242ba9e
--- /dev/null
+++ b/java/com/google/gerrit/server/AssigneeStatusUpdate.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.entities.Account;
+import java.sql.Timestamp;
+import java.util.Optional;
+
+/** Change to an assignee's status. */
+@AutoValue
+public abstract class AssigneeStatusUpdate {
+ public static AssigneeStatusUpdate create(
+ Timestamp ts, Account.Id updatedBy, Optional<Account.Id> currentAssignee) {
+ return new AutoValue_AssigneeStatusUpdate(ts, updatedBy, currentAssignee);
+ }
+
+ public abstract Timestamp date();
+
+ public abstract Account.Id updatedBy();
+
+ public abstract Optional<Account.Id> currentAssignee();
+}
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 9e7f3dd4fc..667559527e 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -9,6 +9,11 @@ GERRIT_GLOBAL_MODULE_SRC = [
"config/GerritGlobalModule.java",
]
+TESTING_SRC = [
+ "account/externalids/testing/ExternalIdInserter.java",
+ "account/externalids/testing/ExternalIdTestUtil.java",
+]
+
java_library(
name = "constants",
srcs = CONSTANTS_SRC,
@@ -25,7 +30,7 @@ java_library(
name = "server",
srcs = glob(
["**/*.java"],
- exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC,
+ exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + TESTING_SRC,
),
resource_strip_prefix = "resources",
resources = ["//resources/com/google/gerrit/server"],
@@ -34,6 +39,7 @@ java_library(
":constants",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/git",
@@ -47,7 +53,6 @@ java_library(
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/prettify:server",
"//java/com/google/gerrit/proto",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
@@ -55,7 +60,6 @@ java_library(
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/ssl",
- "//java/com/google/gwtorm",
"//java/org/apache/commons/net",
"//lib:args4j",
"//lib:autolink",
@@ -89,6 +93,8 @@ java_library(
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jgit-archive",
"//lib:jsch",
"//lib:juniversalchardet",
"//lib:mime-util",
@@ -110,8 +116,6 @@ java_library(
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jsoup",
"//lib/log:jsonevent-layout",
"//lib/log:log4j",
@@ -138,12 +142,13 @@ java_library(
":server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
"//lib:blame-cache",
"//lib:guava",
+ "//lib:jgit",
"//lib:soy",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 97ba8f09f4..5f00b69890 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -18,10 +18,10 @@ import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -72,10 +72,7 @@ public class ChangeMessagesUtil {
Account.Id accountId = user.isInternalUser() ? null : user.getAccountId();
ChangeMessage m =
new ChangeMessage(
- new ChangeMessage.Key(psId.getParentKey(), ChangeUtil.messageUuid()),
- accountId,
- when,
- psId);
+ ChangeMessage.key(psId.changeId(), ChangeUtil.messageUuid()), accountId, when, psId);
m.setMessage(body);
m.setTag(tag);
user.updateRealAccountId(m::setRealAuthor);
@@ -127,7 +124,7 @@ public class ChangeMessagesUtil {
ChangeMessage message, AccountLoader accountLoader) {
PatchSet.Id patchNum = message.getPatchSetId();
ChangeMessageInfo cmi = new ChangeMessageInfo();
- cmi.id = message.getKey().get();
+ cmi.id = message.getKey().uuid();
cmi.author = accountLoader.get(message.getAuthor());
cmi.date = message.getWrittenOn();
cmi.message = message.getMessage();
diff --git a/java/com/google/gerrit/server/ChangeUtil.java b/java/com/google/gerrit/server/ChangeUtil.java
index b39b544cda..7a41fd191e 100644
--- a/java/com/google/gerrit/server/ChangeUtil.java
+++ b/java/com/google/gerrit/server/ChangeUtil.java
@@ -20,10 +20,10 @@ import static java.util.stream.Collectors.toSet;
import com.google.common.collect.Ordering;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.util.CommitMessageUtil;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -48,7 +48,7 @@ public class ChangeUtil {
private static final BaseEncoding UUID_ENCODING = BaseEncoding.base16().lowerCase();
public static final Ordering<PatchSet> PS_ID_ORDER =
- Ordering.from(comparingInt(PatchSet::getPatchSetId));
+ Ordering.from(comparingInt(PatchSet::number));
/** @return a new unique identifier for change message entities. */
public static String messageUuid() {
@@ -91,7 +91,7 @@ public class ChangeUtil {
Set<PatchSet.Id> existing =
changeRefNames
.map(PatchSet.Id::fromRef)
- .filter(psId -> psId != null && psId.getParentKey().equals(id.getParentKey()))
+ .filter(psId -> psId != null && psId.changeId().equals(id.changeId()))
.collect(toSet());
PatchSet.Id next = nextPatchSetId(id);
while (existing.contains(next)) {
@@ -111,7 +111,7 @@ public class ChangeUtil {
* @return next patch set ID for the same change, incrementing by 1.
*/
public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
- return new PatchSet.Id(id.getParentKey(), id.get() + 1);
+ return PatchSet.id(id.changeId(), id.get() + 1);
}
/**
@@ -124,7 +124,7 @@ public class ChangeUtil {
*/
public static PatchSet.Id nextPatchSetId(Repository git, PatchSet.Id id) throws IOException {
return nextPatchSetIdFromChangeRefs(
- git.getRefDatabase().getRefsByPrefix(id.getParentKey().toRefPrefix()).stream()
+ git.getRefDatabase().getRefsByPrefix(id.changeId().toRefPrefix()).stream()
.map(Ref::getName),
id);
}
diff --git a/java/com/google/gerrit/server/CmdLineParserModule.java b/java/com/google/gerrit/server/CmdLineParserModule.java
index d7f6e308b6..d943889d98 100644
--- a/java/com/google/gerrit/server/CmdLineParserModule.java
+++ b/java/com/google/gerrit/server/CmdLineParserModule.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.args4j.AccountGroupIdHandler;
import com.google.gerrit.server.args4j.AccountGroupUUIDHandler;
import com.google.gerrit.server.args4j.AccountIdHandler;
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index a5332eb284..ae4ba4b876 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -19,22 +19,20 @@ import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ComparisonChain;
-import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -50,7 +48,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -92,7 +89,7 @@ public class CommentsUtil {
};
public static PatchSet.Id getCommentPsId(Change.Id changeId, Comment comment) {
- return new PatchSet.Id(changeId, comment.key.patchSetId);
+ return PatchSet.id(changeId, comment.key.patchSetId);
}
public static String extractMessageId(@Nullable String tag) {
@@ -131,7 +128,7 @@ public class CommentsUtil {
unresolved = false;
} else {
// Inherit unresolved value from inReplyTo comment if not specified.
- Comment.Key key = new Comment.Key(parentUuid, path, psId.patchSetId);
+ Comment.Key key = new Comment.Key(parentUuid, path, psId.get());
Optional<Comment> parent = getPublished(ctx.getNotes(), key);
if (!parent.isPresent()) {
throw new UnprocessableEntityException("Invalid parentUuid supplied for comment");
@@ -260,8 +257,7 @@ public class CommentsUtil {
return sort(comments);
}
- public void putComments(
- ChangeUpdate update, PatchLineComment.Status status, Iterable<Comment> comments) {
+ public void putComments(ChangeUpdate update, Comment.Status status, Iterable<Comment> comments) {
for (Comment c : comments) {
update.putComment(status, c);
}
@@ -306,22 +302,22 @@ public class CommentsUtil {
return sort(result);
}
- public static void setCommentRevId(Comment c, PatchListCache cache, Change change, PatchSet ps)
+ public static void setCommentCommitId(Comment c, PatchListCache cache, Change change, PatchSet ps)
throws PatchListNotAvailableException {
checkArgument(
- c.key.patchSetId == ps.getId().get(),
- "cannot set RevId for patch set %s on comment %s",
- ps.getId(),
+ c.key.patchSetId == ps.id().get(),
+ "cannot set commit ID for patch set %s on comment %s",
+ ps.id(),
c);
- if (c.revId == null) {
+ if (c.getCommitId() == null) {
if (Side.fromShort(c.side) == Side.PARENT) {
if (c.side < 0) {
- c.revId = ObjectId.toString(cache.getOldId(change, ps, -c.side));
+ c.setCommitId(cache.getOldId(change, ps, -c.side));
} else {
- c.revId = ObjectId.toString(cache.getOldId(change, ps, null));
+ c.setCommitId(cache.getOldId(change, ps, null));
}
} else {
- c.revId = ps.getRevision().get();
+ c.setCommitId(ps.commitId());
}
}
}
@@ -354,15 +350,4 @@ public class CommentsUtil {
comments.sort(COMMENT_ORDER);
return comments;
}
-
- public static Iterable<PatchLineComment> toPatchLineComments(
- Change.Id changeId, PatchLineComment.Status status, Iterable<Comment> comments) {
- return FluentIterable.from(comments).transform(c -> PatchLineComment.from(changeId, status, c));
- }
-
- public static List<Comment> toComments(
- final String serverId, Iterable<PatchLineComment> comments) {
- return COMMENT_ORDER.sortedCopy(
- FluentIterable.from(comments).transform(plc -> plc.asComment(serverId)));
- }
}
diff --git a/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java b/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
index 27fb9d7a7d..996257c12f 100644
--- a/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
+++ b/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
@@ -22,8 +22,8 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.ChangeMergedListener;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
diff --git a/java/com/google/gerrit/server/CurrentUser.java b/java/com/google/gerrit/server/CurrentUser.java
index 03b9f54e12..75afc04ce3 100644
--- a/java/com/google/gerrit/server/CurrentUser.java
+++ b/java/com/google/gerrit/server/CurrentUser.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.inject.servlet.RequestScoped;
diff --git a/java/com/google/gerrit/server/ExceptionHook.java b/java/com/google/gerrit/server/ExceptionHook.java
new file mode 100644
index 0000000000..a0d98d2866
--- /dev/null
+++ b/java/com/google/gerrit/server/ExceptionHook.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/**
+ * Allows implementors to control how certain exceptions should be handled.
+ *
+ * <p>This interface is intended to be implemented for cluster setups with multiple primary nodes to
+ * control the behavior for handling exceptions that are thrown by a lower layer that handles the
+ * consensus and synchronization between different server nodes. E.g. if an operation fails because
+ * consensus for a Git update could not be achieved (e.g. due to slow responding server nodes) this
+ * interface can be used to retry the request instead of failing it immediately.
+ */
+@ExtensionPoint
+public interface ExceptionHook {
+ /**
+ * Whether an operation should be retried if it failed with the given throwable.
+ *
+ * <p>Only affects operations that are executed with {@link
+ * com.google.gerrit.server.update.RetryHelper}.
+ *
+ * @param throwable throwable that was thrown while executing the operation
+ * @return whether the operation should be retried
+ */
+ default boolean shouldRetry(Throwable throwable) {
+ return false;
+ }
+}
diff --git a/java/com/google/gerrit/server/IdentifiedUser.java b/java/com/google/gerrit/server/IdentifiedUser.java
index e65f56257e..7cafdc046d 100644
--- a/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/java/com/google/gerrit/server/IdentifiedUser.java
@@ -22,7 +22,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.GroupBackend;
@@ -234,7 +234,7 @@ public class IdentifiedUser extends CurrentUser {
groupBackend,
enableReverseDnsLookup,
remotePeerProvider,
- state.getAccount().getId(),
+ state.account().id(),
realUser);
this.state = state;
}
@@ -323,15 +323,14 @@ public class IdentifiedUser extends CurrentUser {
*/
@Override
public Optional<String> getUserName() {
- return state().getUserName();
+ return state().userName();
}
/** @return unique name of the user for logging, never {@code null} */
@Override
public String getLoggableName() {
return getUserName()
- .orElseGet(
- () -> firstNonNull(getAccount().getPreferredEmail(), "a/" + getAccountId().get()));
+ .orElseGet(() -> firstNonNull(getAccount().preferredEmail(), "a/" + getAccountId().get()));
}
/**
@@ -340,7 +339,7 @@ public class IdentifiedUser extends CurrentUser {
* @return the account of the identified user, an empty account if the account is missing
*/
public Account getAccount() {
- return state().getAccount();
+ return state().account();
}
public boolean hasEmailAddress(String email) {
@@ -377,7 +376,7 @@ public class IdentifiedUser extends CurrentUser {
@Override
public GroupMembership getEffectiveGroups() {
if (effectiveGroups == null) {
- if (authConfig.isIdentityTrustable(state().getExternalIds())) {
+ if (authConfig.isIdentityTrustable(state().externalIds())) {
effectiveGroups = groupBackend.membershipsOf(this);
logger.atFinest().log(
"Known groups of %s: %s", getLoggableName(), lazy(effectiveGroups::getKnownGroups));
@@ -403,29 +402,29 @@ public class IdentifiedUser extends CurrentUser {
public PersonIdent newRefLogIdent(Date when, TimeZone tz) {
final Account ua = getAccount();
- String name = ua.getFullName();
+ String name = ua.fullName();
if (name == null || name.isEmpty()) {
- name = ua.getPreferredEmail();
+ name = ua.preferredEmail();
}
if (name == null || name.isEmpty()) {
name = anonymousCowardName;
}
- String user = getUserName().orElse("") + "|account-" + ua.getId().toString();
+ String user = getUserName().orElse("") + "|account-" + ua.id().toString();
return new PersonIdent(name, user + "@" + guessHost(), when, tz);
}
public PersonIdent newCommitterIdent(Date when, TimeZone tz) {
final Account ua = getAccount();
- String name = ua.getFullName();
- String email = ua.getPreferredEmail();
+ String name = ua.fullName();
+ String email = ua.preferredEmail();
if (email == null || email.isEmpty()) {
// No preferred email is configured. Use a generic identity so we
// don't leak an address the user may have given us, but doesn't
// necessarily want to publish through Git records.
//
- String user = getUserName().orElseGet(() -> "account-" + ua.getId().toString());
+ String user = getUserName().orElseGet(() -> "account-" + ua.id().toString());
String host;
if (canonicalUrl.get() != null) {
diff --git a/java/com/google/gerrit/server/PatchSetUtil.java b/java/com/google/gerrit/server/PatchSetUtil.java
index 2a78eb6c77..b53e666ab1 100644
--- a/java/com/google/gerrit/server/PatchSetUtil.java
+++ b/java/com/google/gerrit/server/PatchSetUtil.java
@@ -20,14 +20,14 @@ import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -39,6 +39,7 @@ import com.google.inject.Singleton;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -89,8 +90,8 @@ public class PatchSetUtil {
PatchSet.Id psId,
ObjectId commit,
List<String> groups,
- String pushCertificate,
- String description)
+ @Nullable String pushCertificate,
+ @Nullable String description)
throws IOException {
requireNonNull(groups, "groups may not be null");
ensurePatchSetMatches(psId, update);
@@ -99,20 +100,21 @@ public class PatchSetUtil {
update.setPsDescription(description);
update.setGroups(groups);
- PatchSet ps = new PatchSet(psId);
- ps.setRevision(new RevId(commit.name()));
- ps.setUploader(update.getAccountId());
- ps.setCreatedOn(new Timestamp(update.getWhen().getTime()));
- ps.setGroups(groups);
- ps.setPushCertificate(pushCertificate);
- ps.setDescription(description);
- return ps;
+ return PatchSet.builder()
+ .id(psId)
+ .commitId(commit)
+ .uploader(update.getAccountId())
+ .createdOn(new Timestamp(update.getWhen().getTime()))
+ .groups(groups)
+ .pushCertificate(Optional.ofNullable(pushCertificate))
+ .description(Optional.ofNullable(description))
+ .build();
}
private static void ensurePatchSetMatches(PatchSet.Id psId, ChangeUpdate update) {
- Change.Id changeId = update.getChange().getId();
+ Change.Id changeId = update.getId();
checkArgument(
- psId.getParentKey().equals(changeId),
+ psId.changeId().equals(changeId),
"cannot modify patch set %s on update for change %s",
psId,
changeId);
@@ -127,11 +129,6 @@ public class PatchSetUtil {
}
}
- public void setGroups(ChangeUpdate update, PatchSet ps, List<String> groups) {
- ps.setGroups(groups);
- update.setGroups(groups);
- }
-
/** Check if the current patch set of the change is locked. */
public void checkPatchSetNotLocked(ChangeNotes notes)
throws IOException, ResourceConflictException {
@@ -155,10 +152,8 @@ public class PatchSetUtil {
ApprovalsUtil approvalsUtil = approvalsUtilProvider.get();
for (PatchSetApproval ap :
approvalsUtil.byPatchSet(notes, change.currentPatchSetId(), null, null)) {
- LabelType type = projectState.getLabelTypes(notes).byLabel(ap.getLabel());
- if (type != null
- && ap.getValue() == 1
- && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
+ LabelType type = projectState.getLabelTypes(notes).byLabel(ap.label());
+ if (type != null && ap.value() == 1 && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
return true;
}
}
@@ -169,7 +164,7 @@ public class PatchSetUtil {
public RevCommit getRevCommit(Project.NameKey project, PatchSet patchSet) throws IOException {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
- RevCommit src = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ RevCommit src = rw.parseCommit(patchSet.commitId());
rw.parseBody(src);
return src;
}
diff --git a/java/com/google/gerrit/server/ProjectUtil.java b/java/com/google/gerrit/server/ProjectUtil.java
index 1db4aa3a6f..fa056b3585 100644
--- a/java/com/google/gerrit/server/ProjectUtil.java
+++ b/java/com/google/gerrit/server/ProjectUtil.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import java.io.IOException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -33,13 +33,14 @@ public class ProjectUtil {
* @throws RepositoryNotFoundException the repository of the branch's project does not exist.
* @throws IOException error while retrieving the branch from the repository.
*/
- public static boolean branchExists(final GitRepositoryManager repoManager, Branch.NameKey branch)
+ public static boolean branchExists(final GitRepositoryManager repoManager, BranchNameKey branch)
throws RepositoryNotFoundException, IOException {
- try (Repository repo = repoManager.openRepository(branch.getParentKey())) {
- boolean exists = repo.getRefDatabase().exactRef(branch.get()) != null;
+ try (Repository repo = repoManager.openRepository(branch.project())) {
+ boolean exists = repo.getRefDatabase().exactRef(branch.branch()) != null;
if (!exists) {
exists =
- repo.getFullBranch().equals(branch.get()) || RefNames.REFS_CONFIG.equals(branch.get());
+ repo.getFullBranch().equals(branch.branch())
+ || RefNames.REFS_CONFIG.equals(branch.branch());
}
return exists;
}
diff --git a/java/com/google/gerrit/server/PublishCommentUtil.java b/java/com/google/gerrit/server/PublishCommentUtil.java
index ad93ef008d..26539c52db 100644
--- a/java/com/google/gerrit/server/PublishCommentUtil.java
+++ b/java/com/google/gerrit/server/PublishCommentUtil.java
@@ -15,16 +15,21 @@
package com.google.gerrit.server;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
+import static com.google.gerrit.entities.Comment.Status;
import static java.util.stream.Collectors.toSet;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.update.ChangeContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -46,35 +51,55 @@ public class PublishCommentUtil {
}
public void publish(
- ChangeContext ctx, PatchSet.Id psId, Collection<Comment> drafts, @Nullable String tag) {
+ ChangeContext ctx,
+ PatchSet.Id psId,
+ Collection<Comment> draftComments,
+ @Nullable String tag) {
ChangeNotes notes = ctx.getNotes();
checkArgument(notes != null);
- if (drafts.isEmpty()) {
+ if (draftComments.isEmpty()) {
return;
}
Map<PatchSet.Id, PatchSet> patchSets =
- psUtil.getAsMap(notes, drafts.stream().map(d -> psId(notes, d)).collect(toSet()));
- for (Comment d : drafts) {
- PatchSet ps = patchSets.get(psId(notes, d));
+ psUtil.getAsMap(notes, draftComments.stream().map(d -> psId(notes, d)).collect(toSet()));
+ for (Comment draftComment : draftComments) {
+ PatchSet.Id psIdOfDraftComment = psId(notes, draftComment);
+ PatchSet ps = patchSets.get(psIdOfDraftComment);
if (ps == null) {
- throw new StorageException("patch set " + ps + " not found");
+ throw new StorageException("patch set " + psIdOfDraftComment + " not found");
}
- d.writtenOn = ctx.getWhen();
- d.tag = tag;
+ draftComment.writtenOn = ctx.getWhen();
+ draftComment.tag = tag;
// Draft may have been created by a different real user; copy the current real user. (Only
// applies to X-Gerrit-RunAs, since modifying drafts via on_behalf_of is not allowed.)
- ctx.getUser().updateRealAccountId(d::setRealAuthor);
+ ctx.getUser().updateRealAccountId(draftComment::setRealAuthor);
try {
- CommentsUtil.setCommentRevId(d, patchListCache, notes.getChange(), ps);
+ CommentsUtil.setCommentCommitId(draftComment, patchListCache, notes.getChange(), ps);
} catch (PatchListNotAvailableException e) {
throw new StorageException(e);
}
}
- commentsUtil.putComments(ctx.getUpdate(psId), PUBLISHED, drafts);
+ commentsUtil.putComments(ctx.getUpdate(psId), Status.PUBLISHED, draftComments);
}
private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
- return new PatchSet.Id(notes.getChangeId(), c.key.patchSetId);
+ return PatchSet.id(notes.getChangeId(), c.key.patchSetId);
+ }
+
+ /**
+ * Helper to run the specified set of {@link CommentValidator}-s on the specified comments.
+ *
+ * @return See {@link CommentValidator#validateComments(ImmutableList)}.
+ */
+ public static ImmutableList<CommentValidationFailure> findInvalidComments(
+ PluginSetContext<CommentValidator> commentValidators,
+ ImmutableList<CommentForValidation> commentsForValidation) {
+ ImmutableList.Builder<CommentValidationFailure> commentValidationFailures =
+ new ImmutableList.Builder<>();
+ commentValidators.runEach(
+ listener ->
+ commentValidationFailures.addAll(listener.validateComments(commentsForValidation)));
+ return commentValidationFailures.build();
}
}
diff --git a/java/com/google/gerrit/server/RequestInfo.java b/java/com/google/gerrit/server/RequestInfo.java
new file mode 100644
index 0000000000..f369239d72
--- /dev/null
+++ b/java/com/google/gerrit/server/RequestInfo.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.logging.TraceContext;
+import java.util.Optional;
+
+/** Information about a request that was received from a user. */
+@AutoValue
+public abstract class RequestInfo {
+ /** Channel through which a user request was received. */
+ public enum RequestType {
+ /** request type for git push */
+ GIT_RECEIVE,
+
+ /** request type for git fetch */
+ GIT_UPLOAD,
+
+ /** request type for call to REST API */
+ REST,
+
+ /** request type for call to SSH API */
+ SSH
+ }
+
+ /**
+ * Type of the request, telling through which channel the request was coming in.
+ *
+ * <p>See {@link RequestType} for the types that are used by Gerrit core. Other request types are
+ * possible, e.g. if a plugin supports receiving requests through another channel.
+ */
+ public abstract String requestType();
+
+ /**
+ * Request URI.
+ *
+ * <p>Only set if request type is {@link RequestType#REST}.
+ *
+ * <p>Never includes the "/a" prefix.
+ */
+ public abstract Optional<String> requestUri();
+
+ /** The user that has sent the request. */
+ public abstract CurrentUser callingUser();
+
+ /** The trace context of the request. */
+ public abstract TraceContext traceContext();
+
+ /**
+ * The name of the project for which the request is being done. Only available if the request is
+ * tied to a project or change. If a project is available it's not guaranteed that it actually
+ * exists (e.g. if a user made a request for a project that doesn't exist).
+ */
+ public abstract Optional<Project.NameKey> project();
+
+ public static RequestInfo.Builder builder(
+ RequestType requestType, CurrentUser callingUser, TraceContext traceContext) {
+ return new AutoValue_RequestInfo.Builder()
+ .requestType(requestType)
+ .callingUser(callingUser)
+ .traceContext(traceContext);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder requestType(String requestType);
+
+ public Builder requestType(RequestType requestType) {
+ return requestType(requestType.name());
+ }
+
+ public abstract Builder requestUri(String requestUri);
+
+ public abstract Builder callingUser(CurrentUser callingUser);
+
+ public abstract Builder traceContext(TraceContext traceContext);
+
+ public abstract Builder project(Project.NameKey projectName);
+
+ public abstract RequestInfo build();
+ }
+}
diff --git a/java/com/google/gerrit/server/RequestListener.java b/java/com/google/gerrit/server/RequestListener.java
new file mode 100644
index 0000000000..461b91aecb
--- /dev/null
+++ b/java/com/google/gerrit/server/RequestListener.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface RequestListener {
+ void onRequest(RequestInfo requestInfo);
+}
diff --git a/java/com/google/gerrit/server/ReviewerSet.java b/java/com/google/gerrit/server/ReviewerSet.java
index f36e3abe48..0f6bf2927d 100644
--- a/java/com/google/gerrit/server/ReviewerSet.java
+++ b/java/com/google/gerrit/server/ReviewerSet.java
@@ -22,8 +22,8 @@ import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import java.sql.Timestamp;
@@ -44,18 +44,14 @@ public class ReviewerSet {
first = psa;
} else {
checkArgument(
- first
- .getKey()
- .getParentKey()
- .getParentKey()
- .equals(psa.getKey().getParentKey().getParentKey()),
+ first.key().patchSetId().changeId().equals(psa.key().patchSetId().changeId()),
"multiple change IDs: %s, %s",
- first.getKey(),
- psa.getKey());
+ first.key(),
+ psa.key());
}
- Account.Id id = psa.getAccountId();
- reviewers.put(REVIEWER, id, psa.getGranted());
- if (psa.getValue() != 0) {
+ Account.Id id = psa.accountId();
+ reviewers.put(REVIEWER, id, psa.granted());
+ if (psa.value() != 0) {
reviewers.remove(CC, id);
}
}
diff --git a/java/com/google/gerrit/server/ReviewerStatusUpdate.java b/java/com/google/gerrit/server/ReviewerStatusUpdate.java
index c4f3e2a24e..938d98508e 100644
--- a/java/com/google/gerrit/server/ReviewerStatusUpdate.java
+++ b/java/com/google/gerrit/server/ReviewerStatusUpdate.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index 902002562f..ad5f0aae5e 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -31,18 +31,20 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.git.GitUpdateFailureException;
import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -89,7 +91,7 @@ public class StarredChangesUtil {
if (id == null) {
return null;
}
- Account.Id accountId = new Account.Id(id);
+ Account.Id accountId = Account.id(id);
String label = s.substring(p + 1);
return create(accountId, label);
}
@@ -234,7 +236,16 @@ public class StarredChangesUtil {
}
}
- public void unstarAll(Project.NameKey project, Change.Id changeId) {
+ /**
+ * Unstar the given change for all users.
+ *
+ * <p>Intended for use only when we're about to delete a change. For that reason, the change is
+ * not reindexed.
+ *
+ * @param changeId change ID.
+ * @throws IOException if an error occurred.
+ */
+ public void unstarAllForChangeDeletion(Change.Id changeId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo)) {
BatchRefUpdate batchUpdate = repo.getRefDatabase().newBatchUpdate();
@@ -244,7 +255,9 @@ public class StarredChangesUtil {
for (Account.Id accountId : byChangeFromIndex(changeId).keySet()) {
String refName = RefNames.refsStarredChanges(changeId, accountId);
Ref ref = repo.getRefDatabase().exactRef(refName);
- batchUpdate.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), refName));
+ if (ref != null) {
+ batchUpdate.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), refName));
+ }
}
batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
for (ReceiveCommand command : batchUpdate.getCommands()) {
@@ -256,12 +269,9 @@ public class StarredChangesUtil {
if (command.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
throw new LockFailureException(message, batchUpdate);
}
- throw new IOException(message);
+ throw new GitUpdateFailureException(message, batchUpdate);
}
}
- indexer.index(project, changeId);
- } catch (IOException e) {
- throw new StorageException(String.format("Unstar change %d failed", changeId.get()), e);
}
}
@@ -273,7 +283,7 @@ public class StarredChangesUtil {
if (id == null) {
continue;
}
- Account.Id accountId = new Account.Id(id);
+ Account.Id accountId = Account.id(id);
builder.put(accountId, readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
}
return builder.build();
@@ -375,7 +385,9 @@ public class StarredChangesUtil {
}
public static StarRef readLabels(Repository repo, String refName) throws IOException {
- try (TraceTimer traceTimer = TraceContext.newTimer("Read star labels from %s", refName)) {
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Read star labels", Metadata.builder().noteDbRefName(refName).build())) {
Ref ref = repo.exactRef(refName);
return readLabels(repo, ref);
}
@@ -386,7 +398,8 @@ public class StarredChangesUtil {
return StarRef.MISSING;
}
try (TraceTimer traceTimer =
- TraceContext.newTimer("Read star labels from %s (without ref lookup)", ref.getName());
+ TraceContext.newTimer(
+ String.format("Read star labels from %s (without ref lookup)", ref.getName()));
ObjectReader reader = repo.newObjectReader()) {
ObjectLoader obj = reader.open(ref.getObjectId(), Constants.OBJ_BLOB);
return StarRef.create(
@@ -454,7 +467,9 @@ public class StarredChangesUtil {
Repository repo, String refName, ObjectId oldObjectId, Collection<String> labels)
throws IOException, InvalidLabelsException {
try (TraceTimer traceTimer =
- TraceContext.newTimer("Update star labels in %s (labels=%s)", refName, labels);
+ TraceContext.newTimer(
+ "Update star labels",
+ Metadata.builder().noteDbRefName(refName).resourceCount(labels.size()).build());
RevWalk rw = new RevWalk(repo)) {
RefUpdate u = repo.updateRef(refName);
u.setExpectedOldObjectId(oldObjectId);
@@ -491,7 +506,9 @@ public class StarredChangesUtil {
return;
}
- try (TraceTimer traceTimer = TraceContext.newTimer("Delete star labels in %s", refName)) {
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Delete star labels", Metadata.builder().noteDbRefName(refName).build())) {
RefUpdate u = repo.updateRef(refName);
u.setForceUpdate(true);
u.setExpectedOldObjectId(oldObjectId);
diff --git a/java/com/google/gerrit/server/TraceRequestListener.java b/java/com/google/gerrit/server/TraceRequestListener.java
new file mode 100644
index 0000000000..20c9f57bf8
--- /dev/null
+++ b/java/com/google/gerrit/server/TraceRequestListener.java
@@ -0,0 +1,228 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Request listener that sets additional logging tags and enables tracing automatically if the
+ * request matches any tracing configuration in gerrit.config (see description of
+ * 'tracing.<trace-id>' subsection in config-gerrit.txt).
+ */
+@Singleton
+public class TraceRequestListener implements RequestListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Config cfg;
+ private final ImmutableList<TraceConfig> traceConfigs;
+
+ @Inject
+ TraceRequestListener(@GerritServerConfig Config cfg) {
+ this.cfg = cfg;
+ this.traceConfigs = parseTraceConfigs();
+ }
+
+ @Override
+ public void onRequest(RequestInfo requestInfo) {
+ requestInfo.project().ifPresent(p -> requestInfo.traceContext().addTag("project", p));
+ traceConfigs.stream()
+ .filter(traceConfig -> traceConfig.matches(requestInfo))
+ .forEach(
+ traceConfig ->
+ requestInfo
+ .traceContext()
+ .forceLogging()
+ .addTag(RequestId.Type.TRACE_ID, traceConfig.traceId()));
+ }
+
+ private ImmutableList<TraceConfig> parseTraceConfigs() {
+ ImmutableList.Builder<TraceConfig> traceConfigs = ImmutableList.builder();
+
+ for (String traceId : cfg.getSubsections("tracing")) {
+ try {
+ TraceConfig.Builder traceConfig = TraceConfig.builder();
+ traceConfig.traceId(traceId);
+ traceConfig.requestTypes(parseRequestTypes(traceId));
+ traceConfig.requestUriPatterns(parseRequestUriPatterns(traceId));
+ traceConfig.accountIds(parseAccounts(traceId));
+ traceConfig.projectPatterns(parseProjectPatterns(traceId));
+ traceConfigs.add(traceConfig.build());
+ } catch (ConfigInvalidException e) {
+ logger.atWarning().log("Ignoring invalid tracing configuration:\n %s", e.getMessage());
+ }
+ }
+
+ return traceConfigs.build();
+ }
+
+ private ImmutableSet<String> parseRequestTypes(String traceId) {
+ return ImmutableSet.copyOf(cfg.getStringList("tracing", traceId, "requestType"));
+ }
+
+ private ImmutableSet<Pattern> parseRequestUriPatterns(String traceId)
+ throws ConfigInvalidException {
+ return parsePatterns(traceId, "requestUriPattern");
+ }
+
+ private ImmutableSet<Account.Id> parseAccounts(String traceId) throws ConfigInvalidException {
+ ImmutableSet.Builder<Account.Id> accountIds = ImmutableSet.builder();
+ String[] accounts = cfg.getStringList("tracing", traceId, "account");
+ for (String account : accounts) {
+ Optional<Account.Id> accountId = Account.Id.tryParse(account);
+ if (!accountId.isPresent()) {
+ throw new ConfigInvalidException(
+ String.format(
+ "Invalid tracing config ('tracing.%s.account = %s'): invalid account ID",
+ traceId, account));
+ }
+ accountIds.add(accountId.get());
+ }
+ return accountIds.build();
+ }
+
+ private ImmutableSet<Pattern> parseProjectPatterns(String traceId) throws ConfigInvalidException {
+ return parsePatterns(traceId, "projectPattern");
+ }
+
+ private ImmutableSet<Pattern> parsePatterns(String traceId, String name)
+ throws ConfigInvalidException {
+ ImmutableSet.Builder<Pattern> patterns = ImmutableSet.builder();
+ String[] patternRegExs = cfg.getStringList("tracing", traceId, name);
+ for (String patternRegEx : patternRegExs) {
+ try {
+ patterns.add(Pattern.compile(patternRegEx));
+ } catch (PatternSyntaxException e) {
+ throw new ConfigInvalidException(
+ String.format(
+ "Invalid tracing config ('tracing.%s.%s = %s'): %s",
+ traceId, name, patternRegEx, e.getMessage()));
+ }
+ }
+ return patterns.build();
+ }
+
+ @AutoValue
+ abstract static class TraceConfig {
+ /** ID for the trace */
+ abstract String traceId();
+
+ /** request types that should be traced */
+ abstract ImmutableSet<String> requestTypes();
+
+ /** pattern matching request URIs */
+ abstract ImmutableSet<Pattern> requestUriPatterns();
+
+ /** accounts IDs matching calling user */
+ abstract ImmutableSet<Account.Id> accountIds();
+
+ /** pattern matching projects names */
+ abstract ImmutableSet<Pattern> projectPatterns();
+
+ static Builder builder() {
+ return new AutoValue_TraceRequestListener_TraceConfig.Builder();
+ }
+
+ /**
+ * Whether this trace config matches a given request.
+ *
+ * @param requestInfo request info
+ * @return whether this trace config matches
+ */
+ boolean matches(RequestInfo requestInfo) {
+ // If in the trace config request types are set and none of them matches, then the request is
+ // not matched.
+ if (!requestTypes().isEmpty()
+ && requestTypes().stream()
+ .noneMatch(type -> type.equalsIgnoreCase(requestInfo.requestType()))) {
+ return false;
+ }
+
+ // If in the trace config request URI patterns are set and none of them matches, then the
+ // request is not matched.
+ if (!requestUriPatterns().isEmpty()) {
+ if (!requestInfo.requestUri().isPresent()) {
+ // The request has no request URI, hence it cannot match a request URI pattern.
+ return false;
+ }
+
+ if (requestUriPatterns().stream()
+ .noneMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) {
+ return false;
+ }
+ }
+
+ // If in the trace config accounts are set and none of them matches, then the request is not
+ // matched.
+ if (!accountIds().isEmpty()) {
+ try {
+ if (accountIds().stream()
+ .noneMatch(id -> id.equals(requestInfo.callingUser().getAccountId()))) {
+ return false;
+ }
+ } catch (UnsupportedOperationException e) {
+ // The calling user is not logged in, hence it cannot match an account.
+ return false;
+ }
+ }
+
+ // If in the trace config project patterns are set and none of them matches, then the request
+ // is not matched.
+ if (!projectPatterns().isEmpty()) {
+ if (!requestInfo.project().isPresent()) {
+ // The request is not for a project, hence it cannot match a project pattern.
+ return false;
+ }
+
+ if (projectPatterns().stream()
+ .noneMatch(p -> p.matcher(requestInfo.project().get().get()).matches())) {
+ return false;
+ }
+ }
+
+ // For any match criteria (request type, request URI pattern, account, project pattern) that
+ // was specified in the trace config, at least one of the configured value matched the
+ // request.
+ return true;
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder traceId(String traceId);
+
+ abstract Builder requestTypes(ImmutableSet<String> requestTypes);
+
+ abstract Builder requestUriPatterns(ImmutableSet<Pattern> requestUriPatterns);
+
+ abstract Builder accountIds(ImmutableSet<Account.Id> accountIds);
+
+ abstract Builder projectPatterns(ImmutableSet<Pattern> projectPatterns);
+
+ abstract TraceConfig build();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/WebLinks.java b/java/com/google/gerrit/server/WebLinks.java
index 589344c314..88b0b215ad 100644
--- a/java/com/google/gerrit/server/WebLinks.java
+++ b/java/com/google/gerrit/server/WebLinks.java
@@ -14,11 +14,14 @@
package com.google.gerrit.server;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -31,12 +34,10 @@ import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TagWebLink;
import com.google.gerrit.extensions.webui.WebLink;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Collections;
-import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
@Singleton
public class WebLinks {
@@ -87,7 +88,7 @@ public class WebLinks {
* @param commit SHA1 of commit.
* @return Links for patch sets.
*/
- public List<WebLinkInfo> getPatchSetLinks(Project.NameKey project, String commit) {
+ public ImmutableList<WebLinkInfo> getPatchSetLinks(Project.NameKey project, String commit) {
return filterLinks(patchSetLinks, webLink -> webLink.getPatchSetWebLink(project.get(), commit));
}
@@ -96,7 +97,7 @@ public class WebLinks {
* @param revision SHA1 of the parent revision.
* @return Links for patch sets.
*/
- public List<WebLinkInfo> getParentLinks(Project.NameKey project, String revision) {
+ public ImmutableList<WebLinkInfo> getParentLinks(Project.NameKey project, String revision) {
return filterLinks(parentLinks, webLink -> webLink.getParentWebLink(project.get(), revision));
}
@@ -106,9 +107,9 @@ public class WebLinks {
* @param file File name.
* @return Links for files.
*/
- public List<WebLinkInfo> getFileLinks(String project, String revision, String file) {
+ public ImmutableList<WebLinkInfo> getFileLinks(String project, String revision, String file) {
return Patch.isMagic(file)
- ? Collections.emptyList()
+ ? ImmutableList.of()
: filterLinks(fileLinks, webLink -> webLink.getFileWebLink(project, revision, file));
}
@@ -118,14 +119,15 @@ public class WebLinks {
* @param file File name.
* @return Links for file history
*/
- public List<WebLinkInfo> getFileHistoryLinks(String project, String revision, String file) {
+ public ImmutableList<WebLinkInfo> getFileHistoryLinks(
+ String project, String revision, String file) {
if (Patch.isMagic(file)) {
- return Collections.emptyList();
+ return ImmutableList.of();
}
- return FluentIterable.from(fileHistoryLinks)
- .transform(webLink -> webLink.getFileHistoryWebLink(project, revision, file))
+ return Streams.stream(fileHistoryLinks)
+ .map(webLink -> webLink.getFileHistoryWebLink(project, revision, file))
.filter(INVALID_WEBLINK)
- .toList();
+ .collect(toImmutableList());
}
/**
@@ -138,20 +140,20 @@ public class WebLinks {
* @param fileB File name of side B.
* @return Links for file diffs.
*/
- public List<DiffWebLinkInfo> getDiffLinks(
- final String project,
- final int changeId,
- final Integer patchSetIdA,
- final String revisionA,
- final String fileA,
- final int patchSetIdB,
- final String revisionB,
- final String fileB) {
+ public ImmutableList<DiffWebLinkInfo> getDiffLinks(
+ String project,
+ int changeId,
+ Integer patchSetIdA,
+ String revisionA,
+ String fileA,
+ int patchSetIdB,
+ String revisionB,
+ String fileB) {
if (Patch.isMagic(fileA) || Patch.isMagic(fileB)) {
- return Collections.emptyList();
+ return ImmutableList.of();
}
- return FluentIterable.from(diffLinks)
- .transform(
+ return Streams.stream(diffLinks)
+ .map(
webLink ->
webLink.getDiffLink(
project,
@@ -163,14 +165,14 @@ public class WebLinks {
revisionB,
fileB))
.filter(INVALID_WEBLINK)
- .toList();
+ .collect(toImmutableList());
}
/**
* @param project Project name.
* @return Links for projects.
*/
- public List<WebLinkInfo> getProjectLinks(String project) {
+ public ImmutableList<WebLinkInfo> getProjectLinks(String project) {
return filterLinks(projectLinks, webLink -> webLink.getProjectWeblink(project));
}
@@ -179,7 +181,7 @@ public class WebLinks {
* @param branch Branch name
* @return Links for branches.
*/
- public List<WebLinkInfo> getBranchLinks(String project, String branch) {
+ public ImmutableList<WebLinkInfo> getBranchLinks(String project, String branch) {
return filterLinks(branchLinks, webLink -> webLink.getBranchWebLink(project, branch));
}
@@ -188,12 +190,15 @@ public class WebLinks {
* @param tag Tag name
* @return Links for tags.
*/
- public List<WebLinkInfo> getTagLinks(String project, String tag) {
+ public ImmutableList<WebLinkInfo> getTagLinks(String project, String tag) {
return filterLinks(tagLinks, webLink -> webLink.getTagWebLink(project, tag));
}
- private <T extends WebLink> List<WebLinkInfo> filterLinks(
+ private <T extends WebLink> ImmutableList<WebLinkInfo> filterLinks(
DynamicSet<T> links, Function<T, WebLinkInfo> transformer) {
- return FluentIterable.from(links).transform(transformer).filter(INVALID_WEBLINK).toList();
+ return Streams.stream(links)
+ .map(transformer)
+ .filter(INVALID_WEBLINK)
+ .collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/account/AbstractGroupBackend.java b/java/com/google/gerrit/server/account/AbstractGroupBackend.java
index b50b003c5f..93241d6cc5 100644
--- a/java/com/google/gerrit/server/account/AbstractGroupBackend.java
+++ b/java/com/google/gerrit/server/account/AbstractGroupBackend.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
public abstract class AbstractGroupBackend implements GroupBackend {
@Override
diff --git a/java/com/google/gerrit/server/account/AbstractRealm.java b/java/com/google/gerrit/server/account/AbstractRealm.java
index e61736dca1..380001d833 100644
--- a/java/com/google/gerrit/server/account/AbstractRealm.java
+++ b/java/com/google/gerrit/server/account/AbstractRealm.java
@@ -53,7 +53,7 @@ public abstract class AbstractRealm implements Realm {
@Override
public boolean hasEmailAddress(IdentifiedUser user, String email) {
- for (ExternalId ext : user.state().getExternalIds()) {
+ for (ExternalId ext : user.state().externalIds()) {
if (email != null && email.equalsIgnoreCase(ext.email())) {
return true;
}
@@ -63,7 +63,7 @@ public abstract class AbstractRealm implements Realm {
@Override
public Set<String> getEmailAddresses(IdentifiedUser user) {
- Collection<ExternalId> ids = user.state().getExternalIds();
+ Collection<ExternalId> ids = user.state().externalIds();
Set<String> emails = Sets.newHashSetWithExpectedSize(ids.size());
for (ExternalId ext : ids) {
if (!Strings.isNullOrEmpty(ext.email())) {
diff --git a/java/com/google/gerrit/server/account/AccountCache.java b/java/com/google/gerrit/server/account/AccountCache.java
index 17493bf842..47cf25ba41 100644
--- a/java/com/google/gerrit/server/account/AccountCache.java
+++ b/java/com/google/gerrit/server/account/AccountCache.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index acd7bd358a..ef4e1c080c 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -21,12 +21,12 @@ import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.FanOutExecutor;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.util.time.TimeUtil;
@@ -59,7 +59,7 @@ public class AccountCacheImpl implements AccountCache {
return new CacheModule() {
@Override
protected void configure() {
- cache(BYID_NAME, Account.Id.class, new TypeLiteral<Optional<AccountState>>() {})
+ cache(BYID_NAME, Account.Id.class, new TypeLiteral<AccountState>() {})
.loader(ByIdLoader.class);
bind(AccountCacheImpl.class);
@@ -68,18 +68,15 @@ public class AccountCacheImpl implements AccountCache {
};
}
- private final AllUsersName allUsersName;
private final ExternalIds externalIds;
- private final LoadingCache<Account.Id, Optional<AccountState>> byId;
+ private final LoadingCache<Account.Id, AccountState> byId;
private final ExecutorService executor;
@Inject
AccountCacheImpl(
- AllUsersName allUsersName,
ExternalIds externalIds,
- @Named(BYID_NAME) LoadingCache<Account.Id, Optional<AccountState>> byId,
+ @Named(BYID_NAME) LoadingCache<Account.Id, AccountState> byId,
@FanOutExecutor ExecutorService executor) {
- this.allUsersName = allUsersName;
this.externalIds = externalIds;
this.byId = byId;
this.executor = executor;
@@ -88,9 +85,11 @@ public class AccountCacheImpl implements AccountCache {
@Override
public AccountState getEvenIfMissing(Account.Id accountId) {
try {
- return byId.get(accountId).orElse(missing(accountId));
+ return byId.get(accountId);
} catch (ExecutionException e) {
- logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ if (!(e.getCause() instanceof AccountNotFoundException)) {
+ logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ }
return missing(accountId);
}
}
@@ -98,9 +97,11 @@ public class AccountCacheImpl implements AccountCache {
@Override
public Optional<AccountState> get(Account.Id accountId) {
try {
- return byId.get(accountId);
+ return Optional.ofNullable(byId.get(accountId));
} catch (ExecutionException e) {
- logger.atWarning().withCause(e).log("Cannot load AccountState for ID %s", accountId);
+ if (!(e.getCause() instanceof AccountNotFoundException)) {
+ logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ }
return Optional.empty();
}
}
@@ -110,10 +111,10 @@ public class AccountCacheImpl implements AccountCache {
Map<Account.Id, AccountState> accountStates = new HashMap<>(accountIds.size());
List<Callable<Optional<AccountState>>> callables = new ArrayList<>();
for (Account.Id accountId : accountIds) {
- Optional<AccountState> state = byId.getIfPresent(accountId);
+ AccountState state = byId.getIfPresent(accountId);
if (state != null) {
// The value is in-memory, so we just get the state
- state.ifPresent(s -> accountStates.put(accountId, s));
+ accountStates.put(accountId, state);
} else {
// Queue up a callable so that we can load accounts in parallel
callables.add(() -> get(accountId));
@@ -132,7 +133,7 @@ public class AccountCacheImpl implements AccountCache {
}
for (Future<Optional<AccountState>> f : futures) {
try {
- f.get().ifPresent(s -> accountStates.put(s.getAccount().getId(), s));
+ f.get().ifPresent(s -> accountStates.put(s.account().id(), s));
} catch (InterruptedException | ExecutionException e) {
logger.atSevere().withCause(e).log("Cannot load AccountState");
}
@@ -168,12 +169,12 @@ public class AccountCacheImpl implements AccountCache {
}
private AccountState missing(Account.Id accountId) {
- Account account = new Account(accountId, TimeUtil.nowTs());
+ Account.Builder account = Account.builder(accountId, TimeUtil.nowTs());
account.setActive(false);
- return AccountState.forAccount(allUsersName, account);
+ return AccountState.forAccount(account.build());
}
- static class ByIdLoader extends CacheLoader<Account.Id, Optional<AccountState>> {
+ static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
private final Accounts accounts;
@Inject
@@ -182,10 +183,23 @@ public class AccountCacheImpl implements AccountCache {
}
@Override
- public Optional<AccountState> load(Account.Id who) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading account %s", who)) {
- return accounts.get(who);
+ public AccountState load(Account.Id who) throws Exception {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading account", Metadata.builder().accountId(who.get()).build())) {
+ return accounts
+ .get(who)
+ .orElseThrow(() -> new AccountNotFoundException(who + " not found"));
}
}
}
+
+ /** Signals that the account was not found in the primary storage. */
+ private static class AccountNotFoundException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public AccountNotFoundException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 06f7a08002..5a1bb8a7db 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -21,12 +21,12 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -68,7 +68,7 @@ import org.eclipse.jgit.revwalk.RevSort;
* <li>'account.config': Contains the account properties. Parsing and writing it is delegated to
* {@link AccountProperties}.
* <li>'preferences.config': Contains the preferences. Parsing and writing it is delegated to
- * {@link Preferences}.
+ * {@link StoredPreferences}.
* <li>'account.config': Contains the project watches. Parsing and writing it is delegated to
* {@link ProjectWatches}.
* </ul>
@@ -85,7 +85,7 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
private Optional<AccountProperties> loadedAccountProperties;
private Optional<ObjectId> externalIdsRev;
private ProjectWatches projectWatches;
- private Preferences preferences;
+ private StoredPreferences preferences;
private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
private List<ValidationError> validationErrors;
@@ -122,7 +122,7 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
* Returns the revision of the {@code refs/meta/external-ids} branch.
*
* <p>This revision can be used to load the external IDs of the loaded account lazily via {@link
- * ExternalIds#byAccount(com.google.gerrit.reviewdb.client.Account.Id, ObjectId)}.
+ * ExternalIds#byAccount(com.google.gerrit.entities.Account.Id, ObjectId)}.
*
* @return revision of the {@code refs/meta/external-ids} branch, {@link Optional#empty()} if no
* {@code refs/meta/external-ids} branch exists
@@ -184,14 +184,14 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
checkLoaded();
this.loadedAccountProperties =
Optional.of(
- new AccountProperties(account.getId(), account.getRegisteredOn(), new Config(), null));
+ new AccountProperties(account.id(), account.registeredOn(), new Config(), null));
this.accountUpdate =
Optional.of(
InternalAccountUpdate.builder()
.setActive(account.isActive())
- .setFullName(account.getFullName())
- .setPreferredEmail(account.getPreferredEmail())
- .setStatus(account.getStatus())
+ .setFullName(account.fullName())
+ .setPreferredEmail(account.preferredEmail())
+ .setStatus(account.status())
.build());
return this;
}
@@ -242,10 +242,10 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
projectWatches = new ProjectWatches(accountId, readConfig(ProjectWatches.WATCH_CONFIG), this);
preferences =
- new Preferences(
+ new StoredPreferences(
accountId,
- readConfig(Preferences.PREFERENCES_CONFIG),
- Preferences.readDefaultConfig(allUsersName, repo),
+ readConfig(StoredPreferences.PREFERENCES_CONFIG),
+ StoredPreferences.readDefaultConfig(allUsersName, repo),
this);
projectWatches.parse();
@@ -256,8 +256,11 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
projectWatches = new ProjectWatches(accountId, new Config(), this);
preferences =
- new Preferences(
- accountId, new Config(), Preferences.readDefaultConfig(allUsersName, repo), this);
+ new StoredPreferences(
+ accountId,
+ new Config(),
+ StoredPreferences.readDefaultConfig(allUsersName, repo),
+ this);
}
Ref externalIdsRef = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
@@ -331,7 +334,7 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
}
saveConfig(
- Preferences.PREFERENCES_CONFIG,
+ StoredPreferences.PREFERENCES_CONFIG,
preferences.saveGeneralPreferences(
accountUpdate.get().getGeneralPreferences(),
accountUpdate.get().getDiffPreferences(),
diff --git a/java/com/google/gerrit/server/account/AccountControl.java b/java/com/google/gerrit/server/account/AccountControl.java
index 4b8be810f0..f8a5c5c8a0 100644
--- a/java/com/google/gerrit/server/account/AccountControl.java
+++ b/java/com/google/gerrit/server/account/AccountControl.java
@@ -17,11 +17,11 @@ package com.google.gerrit.server.account;
import static java.util.stream.Collectors.toSet;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.SystemGroupBackend;
@@ -133,7 +133,7 @@ public class AccountControl {
new OtherUser() {
@Override
Account.Id getId() {
- return otherUser.getAccount().getId();
+ return otherUser.account().id();
}
@Override
diff --git a/java/com/google/gerrit/server/account/AccountDeactivator.java b/java/com/google/gerrit/server/account/AccountDeactivator.java
index 3c33c98ee9..ea327f8403 100644
--- a/java/com/google/gerrit/server/account/AccountDeactivator.java
+++ b/java/com/google/gerrit/server/account/AccountDeactivator.java
@@ -104,15 +104,15 @@ public class AccountDeactivator implements Runnable {
}
private boolean processAccount(AccountState accountState) {
- if (!accountState.getUserName().isPresent()) {
+ if (!accountState.userName().isPresent()) {
return false;
}
- String userName = accountState.getUserName().get();
+ String userName = accountState.userName().get();
logger.atFine().log("processing account %s", userName);
try {
- if (realm.accountBelongsToRealm(accountState.getExternalIds()) && !realm.isActive(userName)) {
- sif.deactivate(accountState.getAccount().getId());
+ if (realm.accountBelongsToRealm(accountState.externalIds()) && !realm.isActive(userName)) {
+ sif.deactivate(accountState.account().id());
logger.atInfo().log("deactivated account %s", userName);
return true;
}
@@ -121,7 +121,7 @@ public class AccountDeactivator implements Runnable {
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Error deactivating account: %s (%s) %s",
- userName, accountState.getAccount().getId(), e.getMessage());
+ userName, accountState.account().id(), e.getMessage());
}
return false;
}
diff --git a/java/com/google/gerrit/server/account/AccountDirectory.java b/java/com/google/gerrit/server/account/AccountDirectory.java
index ee9265f26a..60c1678a8f 100644
--- a/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -45,7 +45,10 @@ public abstract class AccountDirectory {
ID,
/** The user-settable status of this account (e.g. busy, OOO, available) */
- STATUS
+ STATUS,
+
+ /** The state of the account (e.g. active or inactive) */
+ STATE
}
public abstract void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options)
diff --git a/java/com/google/gerrit/server/account/AccountExternalIdCreator.java b/java/com/google/gerrit/server/account/AccountExternalIdCreator.java
index 8cf4ee06f4..dedd916e67 100644
--- a/java/com/google/gerrit/server/account/AccountExternalIdCreator.java
+++ b/java/com/google/gerrit/server/account/AccountExternalIdCreator.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
import java.util.List;
diff --git a/java/com/google/gerrit/server/account/AccountLoader.java b/java/com/google/gerrit/server/account/AccountLoader.java
index 4398d9ed7f..a8e419497b 100644
--- a/java/com/google/gerrit/server/account/AccountLoader.java
+++ b/java/com/google/gerrit/server/account/AccountLoader.java
@@ -17,8 +17,9 @@ package com.google.gerrit.server.account;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.assistedinject.Assisted;
@@ -41,6 +42,7 @@ public class AccountLoader {
FillOptions.EMAIL,
FillOptions.USERNAME,
FillOptions.STATUS,
+ FillOptions.STATE,
FillOptions.AVATARS));
public interface Factory {
@@ -67,7 +69,8 @@ public class AccountLoader {
provided = new ArrayList<>();
}
- public synchronized AccountInfo get(Account.Id id) {
+ @Nullable
+ public synchronized AccountInfo get(@Nullable Account.Id id) {
if (id == null) {
return null;
}
@@ -95,7 +98,8 @@ public class AccountLoader {
fill();
}
- public AccountInfo fillOne(Account.Id id) throws PermissionBackendException {
+ @Nullable
+ public AccountInfo fillOne(@Nullable Account.Id id) throws PermissionBackendException {
AccountInfo info = get(id);
fill();
return info;
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index c52b8bc43f..7a5b1aae5d 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.annotations.VisibleForTesting;
@@ -26,11 +27,11 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate.AccountUpdater;
@@ -154,7 +155,7 @@ public class AccountManager {
}
// Account exists
- Optional<Account> act = updateAccountActiveStatus(who, accountState.get().getAccount());
+ Optional<Account> act = updateAccountActiveStatus(who, accountState.get().account());
if (!act.isPresent()) {
// The account was deleted since we checked for it last time. This should never happen
// since we don't support deletion of accounts.
@@ -199,18 +200,18 @@ public class AccountManager {
if (authRequest.isActive()) {
try {
- setInactiveFlag.activate(account.getId());
+ setInactiveFlag.activate(account.id());
} catch (Exception e) {
- throw new AccountException("Unable to activate account " + account.getId(), e);
+ throw new AccountException("Unable to activate account " + account.id(), e);
}
} else {
try {
- setInactiveFlag.deactivate(account.getId());
+ setInactiveFlag.deactivate(account.id());
} catch (Exception e) {
- throw new AccountException("Unable to deactivate account " + account.getId(), e);
+ throw new AccountException("Unable to deactivate account " + account.id(), e);
}
}
- return byIdCache.get(account.getId()).map(AccountState::getAccount);
+ return byIdCache.get(account.id()).map(AccountState::account);
}
private boolean shouldUpdateActiveStatus(AuthRequest authRequest) {
@@ -233,13 +234,13 @@ public class AccountManager {
checkEmailNotUsed(extId.accountId(), extIdWithNewEmail);
accountUpdates.add(u -> u.replaceExternalId(extId, extIdWithNewEmail));
- if (oldEmail != null && oldEmail.equals(user.getAccount().getPreferredEmail())) {
+ if (oldEmail != null && oldEmail.equals(user.getAccount().preferredEmail())) {
accountUpdates.add(u -> u.setPreferredEmail(newEmail));
}
}
if (!Strings.isNullOrEmpty(who.getDisplayName())
- && !Objects.equals(user.getAccount().getFullName(), who.getDisplayName())) {
+ && !Objects.equals(user.getAccount().fullName(), who.getDisplayName())) {
accountUpdates.add(a -> a.setFullName(who.getDisplayName()));
}
@@ -269,7 +270,7 @@ public class AccountManager {
private AuthResult create(AuthRequest who)
throws AccountException, IOException, ConfigInvalidException {
- Account.Id newId = new Account.Id(sequences.nextAccountId());
+ Account.Id newId = Account.id(sequences.nextAccountId());
logger.atFine().log("Assigning new Id %s to account", newId);
ExternalId extId =
@@ -333,7 +334,7 @@ public class AccountManager {
addGroupMember(adminGroupUuid, user);
}
- realm.onCreateAccount(who, accountState.getAccount());
+ realm.onCreateAccount(who, accountState.account());
return new AuthResult(newId, extId.key(), true);
}
@@ -422,7 +423,7 @@ public class AccountManager {
to,
(a, u) -> {
u.addExternalId(newExtId);
- if (who.getEmailAddress() != null && a.getAccount().getPreferredEmail() == null) {
+ if (who.getEmailAddress() != null && a.account().preferredEmail() == null) {
u.setPreferredEmail(who.getEmailAddress());
}
});
@@ -450,8 +451,10 @@ public class AccountManager {
"Delete External IDs on Update Link",
to,
(a, u) -> {
- Collection<ExternalId> filteredExtIdsByScheme =
- a.getExternalIds(who.getExternalIdKey().scheme());
+ Set<ExternalId> filteredExtIdsByScheme =
+ a.externalIds().stream()
+ .filter(e -> e.key().isScheme(who.getExternalIdKey().scheme()))
+ .collect(toImmutableSet());
if (filteredExtIdsByScheme.isEmpty()) {
return;
}
@@ -513,9 +516,9 @@ public class AccountManager {
from,
(a, u) -> {
u.deleteExternalIds(extIds);
- if (a.getAccount().getPreferredEmail() != null
+ if (a.account().preferredEmail() != null
&& extIds.stream()
- .anyMatch(e -> a.getAccount().getPreferredEmail().equals(e.email()))) {
+ .anyMatch(e -> a.account().preferredEmail().equals(e.email()))) {
u.setPreferredEmail(null);
}
});
diff --git a/java/com/google/gerrit/server/account/AccountProperties.java b/java/com/google/gerrit/server/account/AccountProperties.java
index 6fcf56dce8..4f29b2512e 100644
--- a/java/com/google/gerrit/server/account/AccountProperties.java
+++ b/java/com/google/gerrit/server/account/AccountProperties.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.account;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.sql.Timestamp;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@@ -88,15 +88,16 @@ public class AccountProperties {
}
private void parse() {
- account = new Account(accountId, registeredOn);
- account.setActive(accountConfig.getBoolean(ACCOUNT, null, KEY_ACTIVE, true));
- account.setFullName(get(accountConfig, KEY_FULL_NAME));
+ Account.Builder accountBuilder = Account.builder(accountId, registeredOn);
+ accountBuilder.setActive(accountConfig.getBoolean(ACCOUNT, null, KEY_ACTIVE, true));
+ accountBuilder.setFullName(get(accountConfig, KEY_FULL_NAME));
String preferredEmail = get(accountConfig, KEY_PREFERRED_EMAIL);
- account.setPreferredEmail(preferredEmail);
+ accountBuilder.setPreferredEmail(preferredEmail);
- account.setStatus(get(accountConfig, KEY_STATUS));
- account.setMetaId(metaId != null ? metaId.name() : null);
+ accountBuilder.setStatus(get(accountConfig, KEY_STATUS));
+ accountBuilder.setMetaId(metaId != null ? metaId.name() : null);
+ account = accountBuilder.build();
}
Config save(InternalAccountUpdate accountUpdate) {
diff --git a/java/com/google/gerrit/server/account/AccountResolver.java b/java/com/google/gerrit/server/account/AccountResolver.java
index f9945b5046..23244659f8 100644
--- a/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/java/com/google/gerrit/server/account/AccountResolver.java
@@ -26,9 +26,9 @@ import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.index.Schema;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -109,13 +109,16 @@ public class AccountResolver {
return result.asList().stream()
.map(a -> formatForException(result, a))
- .collect(joining("\n", "Account '" + result.input() + "' is ambiguous:\n", ""));
+ .limit(3)
+ .collect(
+ joining(
+ "\n", "Account '" + result.input() + "' is ambiguous (at most 3 shown):\n", ""));
}
private static String formatForException(Result result, AccountState state) {
- return state.getAccount().getId()
+ return state.account().id()
+ ": "
- + state.getAccount().getNameEmail(result.accountResolver().anonymousCowardName);
+ + state.account().getNameEmail(result.accountResolver().anonymousCowardName);
}
public static boolean isSelf(String input) {
@@ -135,7 +138,7 @@ public class AccountResolver {
}
private ImmutableList<AccountState> canonicalize(List<AccountState> list) {
- TreeSet<AccountState> set = new TreeSet<>(comparing(a -> a.getAccount().getId().get()));
+ TreeSet<AccountState> set = new TreeSet<>(comparing(a -> a.account().id().get()));
set.addAll(requireNonNull(list));
return ImmutableList.copyOf(set);
}
@@ -160,7 +163,7 @@ public class AccountResolver {
}
public ImmutableSet<Account.Id> asIdSet() {
- return list.stream().map(a -> a.getAccount().getId()).collect(toImmutableSet());
+ return list.stream().map(a -> a.account().id()).collect(toImmutableSet());
}
public AccountState asUnique() throws UnresolvableAccountException {
@@ -192,7 +195,7 @@ public class AccountResolver {
return self.get().asIdentifiedUser();
}
return userFactory.runAs(
- null, list.get(0).getAccount().getId(), requireNonNull(caller).getRealUser());
+ null, list.get(0).account().id(), requireNonNull(caller).getRealUser());
}
@VisibleForTesting
@@ -349,7 +352,7 @@ public class AccountResolver {
String name = nameOrEmail.substring(0, lt - 1);
ImmutableList<AccountState> nameMatches =
allMatches.stream()
- .filter(a -> name.equals(a.getAccount().getFullName()))
+ .filter(a -> name.equals(a.account().fullName()))
.collect(toImmutableList());
return !nameMatches.isEmpty() ? nameMatches.stream() : allMatches.stream();
}
@@ -558,7 +561,7 @@ public class AccountResolver {
}
private Predicate<AccountState> accountActivityPredicate() {
- return (AccountState accountState) -> accountState.getAccount().isActive();
+ return (AccountState accountState) -> accountState.account().isActive();
}
@VisibleForTesting
diff --git a/java/com/google/gerrit/server/account/AccountResource.java b/java/com/google/gerrit/server/account/AccountResource.java
index d09dff51d8..4fb69bd989 100644
--- a/java/com/google/gerrit/server/account/AccountResource.java
+++ b/java/com/google/gerrit/server/account/AccountResource.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.TypeLiteral;
diff --git a/java/com/google/gerrit/server/account/AccountSshKey.java b/java/com/google/gerrit/server/account/AccountSshKey.java
index f13258572e..d2f8775904 100644
--- a/java/com/google/gerrit/server/account/AccountSshKey.java
+++ b/java/com/google/gerrit/server/account/AccountSshKey.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.account;
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.List;
/** An SSH key approved for use by an {@link Account}. */
diff --git a/java/com/google/gerrit/server/account/AccountState.java b/java/com/google/gerrit/server/account/AccountState.java
index 1854dc10e5..a270a76d16 100644
--- a/java/com/google/gerrit/server/account/AccountState.java
+++ b/java/com/google/gerrit/server/account/AccountState.java
@@ -14,34 +14,23 @@
package com.google.gerrit.server.account;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
-
-import com.google.common.base.Function;
+import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.CurrentUser.PropertyKey;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.config.AllUsersName;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
-import org.apache.commons.codec.DecoderException;
import org.eclipse.jgit.lib.ObjectId;
/**
@@ -51,25 +40,19 @@ import org.eclipse.jgit.lib.ObjectId;
* <p>Most callers should not construct AccountStates directly but rather lookup accounts via the
* account cache (see {@link AccountCache#get(Account.Id)}).
*/
-public class AccountState {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
- a -> a.getAccount().getId();
-
+@AutoValue
+public abstract class AccountState {
/**
* Creates an AccountState from the given account config.
*
- * @param allUsersName the name of the All-Users repository
* @param externalIds class to access external IDs
* @param accountConfig the account config, must already be loaded
* @return the account state, {@link Optional#empty()} if the account doesn't exist
* @throws IOException if accessing the external IDs fails
*/
public static Optional<AccountState> fromAccountConfig(
- AllUsersName allUsersName, ExternalIds externalIds, AccountConfig accountConfig)
- throws IOException {
- return fromAccountConfig(allUsersName, externalIds, accountConfig, null);
+ ExternalIds externalIds, AccountConfig accountConfig) throws IOException {
+ return fromAccountConfig(externalIds, accountConfig, null);
}
/**
@@ -82,7 +65,6 @@ public class AccountState {
* updated the revision of the external IDs branch in account config is outdated. Hence after
* updating external IDs the external ID notes must be provided.
*
- * @param allUsersName the name of the All-Users repository
* @param externalIds class to access external IDs
* @param accountConfig the account config, must already be loaded
* @param extIdNotes external ID notes, must already be loaded, may be {@code null}
@@ -90,10 +72,7 @@ public class AccountState {
* @throws IOException if accessing the external IDs fails
*/
public static Optional<AccountState> fromAccountConfig(
- AllUsersName allUsersName,
- ExternalIds externalIds,
- AccountConfig accountConfig,
- @Nullable ExternalIdNotes extIdNotes)
+ ExternalIds externalIds, AccountConfig accountConfig, @Nullable ExternalIdNotes extIdNotes)
throws IOException {
if (!accountConfig.getLoadedAccount().isPresent()) {
return Optional.empty();
@@ -106,22 +85,25 @@ public class AccountState {
: accountConfig.getExternalIdsRev();
ImmutableSet<ExternalId> extIds =
extIdsRev.isPresent()
- ? ImmutableSet.copyOf(externalIds.byAccount(account.getId(), extIdsRev.get()))
+ ? ImmutableSet.copyOf(externalIds.byAccount(account.id(), extIdsRev.get()))
: ImmutableSet.of();
// Don't leak references to AccountConfig into the AccountState, since it holds a reference to
// an open Repository instance.
ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches =
accountConfig.getProjectWatches();
- GeneralPreferencesInfo generalPreferences = accountConfig.getGeneralPreferences();
- DiffPreferencesInfo diffPreferences = accountConfig.getDiffPreferences();
- EditPreferencesInfo editPreferences = accountConfig.getEditPreferences();
+ Preferences.General generalPreferences =
+ Preferences.General.fromInfo(accountConfig.getGeneralPreferences());
+ Preferences.Diff diffPreferences =
+ Preferences.Diff.fromInfo(accountConfig.getDiffPreferences());
+ Preferences.Edit editPreferences =
+ Preferences.Edit.fromInfo(accountConfig.getEditPreferences());
return Optional.of(
- new AccountState(
- allUsersName,
+ new AutoValue_AccountState(
account,
extIds,
+ ExternalId.getUserName(extIds),
projectWatches,
generalPreferences,
diffPreferences,
@@ -132,71 +114,35 @@ public class AccountState {
* Creates an AccountState for a given account with no external IDs, no project watches and
* default preferences.
*
- * @param allUsersName the name of the All-Users repository
* @param account the account
* @return the account state
*/
- public static AccountState forAccount(AllUsersName allUsersName, Account account) {
- return forAccount(allUsersName, account, ImmutableSet.of());
+ public static AccountState forAccount(Account account) {
+ return forAccount(account, ImmutableSet.of());
}
/**
* Creates an AccountState for a given account with no project watches and default preferences.
*
- * @param allUsersName the name of the All-Users repository
* @param account the account
* @param extIds the external IDs
* @return the account state
*/
- public static AccountState forAccount(
- AllUsersName allUsersName, Account account, Collection<ExternalId> extIds) {
- return new AccountState(
- allUsersName,
+ public static AccountState forAccount(Account account, Collection<ExternalId> extIds) {
+ return new AutoValue_AccountState(
account,
ImmutableSet.copyOf(extIds),
+ ExternalId.getUserName(extIds),
ImmutableMap.of(),
- GeneralPreferencesInfo.defaults(),
- DiffPreferencesInfo.defaults(),
- EditPreferencesInfo.defaults());
- }
-
- private final AllUsersName allUsersName;
- private final Account account;
- private final ImmutableSet<ExternalId> externalIds;
- private final Optional<String> userName;
- private final ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches;
- private final GeneralPreferencesInfo generalPreferences;
- private final DiffPreferencesInfo diffPreferences;
- private final EditPreferencesInfo editPreferences;
- private Cache<IdentifiedUser.PropertyKey<Object>, Object> properties;
-
- private AccountState(
- AllUsersName allUsersName,
- Account account,
- ImmutableSet<ExternalId> externalIds,
- ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches,
- GeneralPreferencesInfo generalPreferences,
- DiffPreferencesInfo diffPreferences,
- EditPreferencesInfo editPreferences) {
- this.allUsersName = allUsersName;
- this.account = account;
- this.externalIds = externalIds;
- this.userName = ExternalId.getUserName(externalIds);
- this.projectWatches = projectWatches;
- this.generalPreferences = generalPreferences;
- this.diffPreferences = diffPreferences;
- this.editPreferences = editPreferences;
- }
-
- public AllUsersName getAllUsersNameForIndexing() {
- return allUsersName;
+ Preferences.General.fromInfo(GeneralPreferencesInfo.defaults()),
+ Preferences.Diff.fromInfo(DiffPreferencesInfo.defaults()),
+ Preferences.Edit.fromInfo(EditPreferencesInfo.defaults()));
}
/** Get the cached account metadata. */
- public Account getAccount() {
- return account;
- }
-
+ public abstract Account account();
+ /** The external identities that identify the account holder. */
+ public abstract ImmutableSet<ExternalId> externalIds();
/**
* Get the username, if one has been declared for this user.
*
@@ -205,122 +151,36 @@ public class AccountState {
* @return the username, {@link Optional#empty()} if the user has no username, or if the username
* is empty
*/
- public Optional<String> getUserName() {
- return userName;
- }
-
- public boolean checkPassword(@Nullable String password, String username) {
- if (password == null) {
- return false;
- }
- for (ExternalId id : getExternalIds()) {
- // Only process the "username:$USER" entry, which is unique.
- if (!id.isScheme(SCHEME_USERNAME) || !username.equals(id.key().id())) {
- continue;
- }
-
- String hashedStr = id.password();
- if (!Strings.isNullOrEmpty(hashedStr)) {
- try {
- return HashedPassword.decode(hashedStr).checkPassword(password);
- } catch (DecoderException e) {
- logger.atSevere().log("DecoderException for user %s: %s ", username, e.getMessage());
- return false;
- }
- }
- }
- return false;
- }
-
- /** The external identities that identify the account holder. */
- public ImmutableSet<ExternalId> getExternalIds() {
- return externalIds;
- }
-
- /** The external identities that identify the account holder that match the given scheme. */
- public ImmutableSet<ExternalId> getExternalIds(String scheme) {
- return externalIds.stream().filter(e -> e.key().isScheme(scheme)).collect(toImmutableSet());
- }
-
+ public abstract Optional<String> userName();
/** The project watches of the account. */
- public ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> getProjectWatches() {
- return projectWatches;
- }
+ public abstract ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches();
+ /** The general preferences of the account. */
/** The general preferences of the account. */
- public GeneralPreferencesInfo getGeneralPreferences() {
- return generalPreferences;
+ public GeneralPreferencesInfo generalPreferences() {
+ return immutableGeneralPreferences().toInfo();
}
/** The diff preferences of the account. */
- public DiffPreferencesInfo getDiffPreferences() {
- return diffPreferences;
+ public DiffPreferencesInfo diffPreferences() {
+ return immutableDiffPreferences().toInfo();
}
/** The edit preferences of the account. */
- public EditPreferencesInfo getEditPreferences() {
- return editPreferences;
- }
-
- /**
- * Lookup a previously stored property.
- *
- * <p>All properties are automatically cleared when the account cache invalidates the {@code
- * AccountState}. This method is thread-safe.
- *
- * @param key unique property key.
- * @return previously stored value, or {@code null}.
- */
- @Nullable
- public <T> T get(PropertyKey<T> key) {
- Cache<PropertyKey<Object>, Object> p = properties(false);
- if (p != null) {
- @SuppressWarnings("unchecked")
- T value = (T) p.getIfPresent(key);
- return value;
- }
- return null;
- }
-
- /**
- * Store a property for later retrieval.
- *
- * <p>This method is thread-safe.
- *
- * @param key unique property key.
- * @param value value to store; or {@code null} to clear the value.
- */
- public <T> void put(PropertyKey<T> key, @Nullable T value) {
- Cache<PropertyKey<Object>, Object> p = properties(value != null);
- if (p != null) {
- @SuppressWarnings("unchecked")
- PropertyKey<Object> k = (PropertyKey<Object>) key;
- if (value != null) {
- p.put(k, value);
- } else {
- p.invalidate(k);
- }
- }
- }
-
- private synchronized Cache<PropertyKey<Object>, Object> properties(boolean allocate) {
- if (properties == null && allocate) {
- properties =
- CacheBuilder.newBuilder()
- .concurrencyLevel(1)
- .initialCapacity(16)
- // Use weakKeys to ensure plugins that garbage collect will also
- // eventually release data held in any still live AccountState.
- .weakKeys()
- .build();
- }
- return properties;
+ public EditPreferencesInfo editPreferences() {
+ return immutableEditPreferences().toInfo();
}
@Override
- public String toString() {
+ public final String toString() {
MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
- h.addValue(getAccount().getId());
+ h.addValue(account().id());
return h.toString();
}
+
+ protected abstract Preferences.General immutableGeneralPreferences();
+
+ protected abstract Preferences.Diff immutableDiffPreferences();
+
+ protected abstract Preferences.Edit immutableEditPreferences();
}
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java
index f3758bf2c4..81366318c7 100644
--- a/java/com/google/gerrit/server/account/Accounts.java
+++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -19,8 +19,8 @@ import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -134,9 +134,7 @@ public class Accounts {
private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId)
throws IOException, ConfigInvalidException {
return AccountState.fromAccountConfig(
- allUsersName,
- externalIds,
- new AccountConfig(accountId, allUsersName, allUsersRepository).load());
+ externalIds, new AccountConfig(accountId, allUsersName, allUsersRepository).load());
}
public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException {
diff --git a/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java b/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
index b43d86c731..db350c6fe4 100644
--- a/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -35,14 +35,14 @@ public class AccountsConsistencyChecker {
List<ConsistencyProblemInfo> problems = new ArrayList<>();
for (AccountState accountState : accounts.all()) {
- Account account = accountState.getAccount();
- if (account.getPreferredEmail() != null) {
- if (accountState.getExternalIds().stream()
- .noneMatch(e -> account.getPreferredEmail().equals(e.email()))) {
+ Account account = accountState.account();
+ if (account.preferredEmail() != null) {
+ if (accountState.externalIds().stream()
+ .noneMatch(e -> account.preferredEmail().equals(e.email()))) {
addError(
String.format(
"Account '%s' has no external ID for its preferred email '%s'",
- account.getId().get(), account.getPreferredEmail()),
+ account.id().get(), account.preferredEmail()),
problems);
}
}
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 20a1c978f1..1caee58937 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -24,11 +24,11 @@ import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Runnables;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
@@ -88,9 +88,9 @@ import org.eclipse.jgit.lib.Repository;
* The timestamp of the first commit on a user branch denotes the registration date. The initial
* commit on the user branch may be empty (since having an 'account.config' is optional). See {@link
* AccountConfig} for details of the 'account.config' file format. In addition the user branch can
- * contain a 'preferences.config' config file to store preferences (see {@link Preferences}) and a
- * 'watch.config' config file to store project watches (see {@link ProjectWatches}). External IDs
- * are stored separately in the {@code refs/meta/external-ids} notes branch (see {@link
+ * contain a 'preferences.config' config file to store preferences (see {@link StoredPreferences})
+ * and a 'watch.config' config file to store project watches (see {@link ProjectWatches}). External
+ * IDs are stored separately in the {@code refs/meta/external-ids} notes branch (see {@link
* ExternalIdNotes}).
*
* <p>On updating an account the account is evicted from the account cache and reindexed. The
@@ -321,7 +321,7 @@ public class AccountsUpdate {
AccountConfig accountConfig = read(r, accountId);
Account account =
accountConfig.getNewAccount(new Timestamp(committerIdent.getWhen().getTime()));
- AccountState accountState = AccountState.forAccount(allUsersName, account);
+ AccountState accountState = AccountState.forAccount(account);
InternalAccountUpdate.Builder updateBuilder = InternalAccountUpdate.builder();
updater.update(accountState, updateBuilder);
@@ -330,7 +330,7 @@ public class AccountsUpdate {
ExternalIdNotes extIdNotes =
createExternalIdNotes(r, accountConfig.getExternalIdsRev(), accountId, update);
UpdatedAccount updatedAccounts =
- new UpdatedAccount(allUsersName, externalIds, message, accountConfig, extIdNotes);
+ new UpdatedAccount(externalIds, message, accountConfig, extIdNotes);
updatedAccounts.setCreated(true);
return updatedAccounts;
})
@@ -377,7 +377,7 @@ public class AccountsUpdate {
r -> {
AccountConfig accountConfig = read(r, accountId);
Optional<AccountState> account =
- AccountState.fromAccountConfig(allUsersName, externalIds, accountConfig);
+ AccountState.fromAccountConfig(externalIds, accountConfig);
if (!account.isPresent()) {
return null;
}
@@ -390,7 +390,7 @@ public class AccountsUpdate {
ExternalIdNotes extIdNotes =
createExternalIdNotes(r, accountConfig.getExternalIdsRev(), accountId, update);
UpdatedAccount updatedAccounts =
- new UpdatedAccount(allUsersName, externalIds, message, accountConfig, extIdNotes);
+ new UpdatedAccount(externalIds, message, accountConfig, extIdNotes);
return updatedAccounts;
});
}
@@ -561,7 +561,6 @@ public class AccountsUpdate {
}
private static class UpdatedAccount {
- private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final String message;
private final AccountConfig accountConfig;
@@ -570,13 +569,11 @@ public class AccountsUpdate {
private boolean created;
private UpdatedAccount(
- AllUsersName allUsersName,
ExternalIds externalIds,
String message,
AccountConfig accountConfig,
ExternalIdNotes extIdNotes) {
checkState(!Strings.isNullOrEmpty(message), "message for account update must be set");
- this.allUsersName = requireNonNull(allUsersName);
this.externalIds = requireNonNull(externalIds);
this.message = requireNonNull(message);
this.accountConfig = requireNonNull(accountConfig);
@@ -592,8 +589,7 @@ public class AccountsUpdate {
}
public AccountState getAccount() throws IOException {
- return AccountState.fromAccountConfig(allUsersName, externalIds, accountConfig, extIdNotes)
- .get();
+ return AccountState.fromAccountConfig(externalIds, accountConfig, extIdNotes).get();
}
public ExternalIdNotes getExternalIdNotes() {
diff --git a/java/com/google/gerrit/server/account/AuthResult.java b/java/com/google/gerrit/server/account/AuthResult.java
index 2b1bc96f20..1f898275a4 100644
--- a/java/com/google/gerrit/server/account/AuthResult.java
+++ b/java/com/google/gerrit/server/account/AuthResult.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
/** Result from {@link AccountManager#authenticate(AuthRequest)}. */
diff --git a/java/com/google/gerrit/server/account/AuthorizedKeys.java b/java/com/google/gerrit/server/account/AuthorizedKeys.java
index b392c181e9..203ac5c588 100644
--- a/java/com/google/gerrit/server/account/AuthorizedKeys.java
+++ b/java/com/google/gerrit/server/account/AuthorizedKeys.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.account;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
diff --git a/java/com/google/gerrit/server/account/CreateGroupArgs.java b/java/com/google/gerrit/server/account/CreateGroupArgs.java
index 5bcb84b1b6..2a764ccd6d 100644
--- a/java/com/google/gerrit/server/account/CreateGroupArgs.java
+++ b/java/com/google/gerrit/server/account/CreateGroupArgs.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Collection;
public class CreateGroupArgs {
private AccountGroup.NameKey groupName;
+ public AccountGroup.UUID uuid;
public String groupDescription;
public boolean visibleToAll;
public AccountGroup.UUID ownerGroupUuid;
@@ -34,7 +35,7 @@ public class CreateGroupArgs {
}
public void setGroupName(String n) {
- groupName = n != null ? new AccountGroup.NameKey(n) : null;
+ groupName = n != null ? AccountGroup.nameKey(n) : null;
}
public void setGroupName(AccountGroup.NameKey n) {
diff --git a/java/com/google/gerrit/server/account/DefaultRealm.java b/java/com/google/gerrit/server/account/DefaultRealm.java
index 33de2d2d0d..329825f4d1 100644
--- a/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -16,10 +16,10 @@ package com.google.gerrit.server.account;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/account/DestinationList.java b/java/com/google/gerrit/server/account/DestinationList.java
index 04e710a755..15c1e25af0 100644
--- a/java/com/google/gerrit/server/account/DestinationList.java
+++ b/java/com/google/gerrit/server/account/DestinationList.java
@@ -18,8 +18,8 @@ import com.google.common.collect.Lists;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.TabFile;
import java.io.IOException;
@@ -28,10 +28,10 @@ import java.util.Set;
public class DestinationList extends TabFile {
public static final String DIR_NAME = "destinations";
- private SetMultimap<String, Branch.NameKey> destinations =
+ private SetMultimap<String, BranchNameKey> destinations =
MultimapBuilder.hashKeys().hashSetValues().build();
- public Set<Branch.NameKey> getDestinations(String label) {
+ public Set<BranchNameKey> getDestinations(String label) {
return destinations.get(label);
}
@@ -40,21 +40,21 @@ public class DestinationList extends TabFile {
}
String asText(String label) {
- Set<Branch.NameKey> dests = destinations.get(label);
+ Set<BranchNameKey> dests = destinations.get(label);
if (dests == null) {
return null;
}
List<Row> rows = Lists.newArrayListWithCapacity(dests.size());
- for (Branch.NameKey dest : sort(dests)) {
- rows.add(new Row(dest.get(), dest.getParentKey().get()));
+ for (BranchNameKey dest : sort(dests)) {
+ rows.add(new Row(dest.branch(), dest.project().get()));
}
return asText("Ref", "Project", rows);
}
- private static Set<Branch.NameKey> toSet(List<Row> destRows) {
- Set<Branch.NameKey> dests = Sets.newHashSetWithExpectedSize(destRows.size());
+ private static Set<BranchNameKey> toSet(List<Row> destRows) {
+ Set<BranchNameKey> dests = Sets.newHashSetWithExpectedSize(destRows.size());
for (Row row : destRows) {
- dests.add(new Branch.NameKey(new Project.NameKey(row.right), row.left));
+ dests.add(BranchNameKey.create(Project.nameKey(row.right), row.left));
}
return dests;
}
diff --git a/java/com/google/gerrit/server/account/Emails.java b/java/com/google/gerrit/server/account/Emails.java
index 426d6ea7d9..76c22cf75a 100644
--- a/java/com/google/gerrit/server/account/Emails.java
+++ b/java/com/google/gerrit/server/account/Emails.java
@@ -14,14 +14,17 @@
package com.google.gerrit.server.account;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.Streams;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.SetMultimap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.UserIdentity;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.query.account.InternalAccountQuery;
@@ -32,6 +35,11 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.jgit.lib.PersonIdent;
/** Class to access accounts by email. */
@Singleton
@@ -65,15 +73,20 @@ public class Emails {
* have no external ID for the preferred email. Having accounts with a preferred email that does
* not exist as external ID is an inconsistency, but existing functionality relies on still
* getting those accounts, which is why they are included. Accounts by preferred email are fetched
- * from the account index.
+ * from the account index as a fallback for email addresses that could not be resolved using
+ * {@link ExternalIds}.
*
* @see #getAccountsFor(String...)
*/
public ImmutableSet<Account.Id> getAccountFor(String email) throws IOException {
- return Streams.concat(
- externalIds.byEmail(email).stream().map(ExternalId::accountId),
- executeIndexQuery(() -> queryProvider.get().byPreferredEmail(email).stream())
- .map(a -> a.getAccount().getId()))
+ ImmutableSet<Account.Id> accounts =
+ externalIds.byEmail(email).stream().map(ExternalId::accountId).collect(toImmutableSet());
+ if (!accounts.isEmpty()) {
+ return accounts;
+ }
+
+ return executeIndexQuery(() -> queryProvider.get().byPreferredEmail(email).stream())
+ .map(a -> a.account().id())
.collect(toImmutableSet());
}
@@ -84,12 +97,18 @@ public class Emails {
*/
public ImmutableSetMultimap<String, Account.Id> getAccountsFor(String... emails)
throws IOException {
- ImmutableSetMultimap.Builder<String, Account.Id> builder = ImmutableSetMultimap.builder();
+ SetMultimap<String, Account.Id> result =
+ MultimapBuilder.hashKeys(emails.length).hashSetValues(1).build();
externalIds.byEmails(emails).entries().stream()
- .forEach(e -> builder.put(e.getKey(), e.getValue().accountId()));
- executeIndexQuery(() -> queryProvider.get().byPreferredEmail(emails).entries().stream())
- .forEach(e -> builder.put(e.getKey(), e.getValue().getAccount().getId()));
- return builder.build();
+ .forEach(e -> result.put(e.getKey(), e.getValue().accountId()));
+ List<String> emailsToBackfill =
+ Arrays.stream(emails).filter(e -> !result.containsKey(e)).collect(toImmutableList());
+ if (!emailsToBackfill.isEmpty()) {
+ executeIndexQuery(
+ () -> queryProvider.get().byPreferredEmail(emailsToBackfill).entries().stream())
+ .forEach(e -> result.put(e.getKey(), e.getValue().account().id()));
+ }
+ return ImmutableSetMultimap.copyOf(result);
}
/**
@@ -102,6 +121,24 @@ public class Emails {
return externalIds.byEmail(email).stream().map(ExternalId::accountId).collect(toImmutableSet());
}
+ public UserIdentity toUserIdentity(PersonIdent who) throws IOException {
+ UserIdentity u = new UserIdentity();
+ u.setName(who.getName());
+ u.setEmail(who.getEmailAddress());
+ u.setDate(new Timestamp(who.getWhen().getTime()));
+ u.setTimeZone(who.getTimeZoneOffset());
+
+ // If only one account has access to this email address, select it
+ // as the identity of the user.
+ //
+ Set<Account.Id> a = getAccountFor(u.getEmail());
+ if (a.size() == 1) {
+ u.setAccount(a.iterator().next());
+ }
+
+ return u;
+ }
+
private <T> T executeIndexQuery(Action<T> action) {
try {
return retryHelper.execute(
diff --git a/java/com/google/gerrit/server/account/FakeRealm.java b/java/com/google/gerrit/server/account/FakeRealm.java
index a53f64ec34..30274e0fed 100644
--- a/java/com/google/gerrit/server/account/FakeRealm.java
+++ b/java/com/google/gerrit/server/account/FakeRealm.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.reviewdb.client.Account;
/** Fake implementation of {@link Realm} that does not communicate. */
public class FakeRealm extends AbstractRealm {
diff --git a/java/com/google/gerrit/server/account/GroupBackend.java b/java/com/google/gerrit/server/account/GroupBackend.java
index 2d46260235..3a874bb1fa 100644
--- a/java/com/google/gerrit/server/account/GroupBackend.java
+++ b/java/com/google/gerrit/server/account/GroupBackend.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.account;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectState;
import java.util.Collection;
diff --git a/java/com/google/gerrit/server/account/GroupCache.java b/java/com/google/gerrit/server/account/GroupCache.java
index 8133d9ce41..90d3aa95ea 100644
--- a/java/com/google/gerrit/server/account/GroupCache.java
+++ b/java/com/google/gerrit/server/account/GroupCache.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/account/GroupCacheImpl.java b/java/com/google/gerrit/server/account/GroupCacheImpl.java
index c85e2dfb39..fe22028e77 100644
--- a/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -17,10 +17,11 @@ package com.google.gerrit.server.account;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.query.group.InternalGroupQuery;
@@ -149,7 +150,9 @@ public class GroupCacheImpl implements GroupCache {
@Override
public Optional<InternalGroup> load(AccountGroup.Id key) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading group %s by ID", key)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading group by ID", Metadata.builder().groupId(key.get()).build())) {
return groupQueryProvider.get().byId(key);
}
}
@@ -165,8 +168,10 @@ public class GroupCacheImpl implements GroupCache {
@Override
public Optional<InternalGroup> load(String name) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading group '%s' by name", name)) {
- return groupQueryProvider.get().byName(new AccountGroup.NameKey(name));
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading group by name", Metadata.builder().groupName(name).build())) {
+ return groupQueryProvider.get().byName(AccountGroup.nameKey(name));
}
}
}
@@ -181,8 +186,10 @@ public class GroupCacheImpl implements GroupCache {
@Override
public Optional<InternalGroup> load(String uuid) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading group %s by UUID", uuid)) {
- return groups.getGroup(new AccountGroup.UUID(uuid));
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading group by UUID", Metadata.builder().groupUuid(uuid).build())) {
+ return groups.getGroup(AccountGroup.uuid(uuid));
}
}
}
diff --git a/java/com/google/gerrit/server/account/GroupControl.java b/java/com/google/gerrit/server/account/GroupControl.java
index b3e6739c2d..2228525ed7 100644
--- a/java/com/google/gerrit/server/account/GroupControl.java
+++ b/java/com/google/gerrit/server/account/GroupControl.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCache.java b/java/com/google/gerrit/server/account/GroupIncludeCache.java
index 612730bae5..65476190fe 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCache.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCache.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Collection;
/** Tracks group inclusions in memory for efficient access. */
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index c27d6c30c5..7883b114b5 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -22,11 +22,12 @@ import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.query.group.InternalGroupQuery;
@@ -152,7 +153,9 @@ public class GroupIncludeCacheImpl implements GroupIncludeCache {
@Override
public ImmutableSet<AccountGroup.UUID> load(Account.Id memberId) {
- try (TraceTimer timer = TraceContext.newTimer("Loading groups with member %s", memberId)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading groups with member", Metadata.builder().accountId(memberId.get()).build())) {
return groupQueryProvider.get().byMember(memberId).stream()
.map(InternalGroup::getGroupUUID)
.collect(toImmutableSet());
@@ -171,7 +174,9 @@ public class GroupIncludeCacheImpl implements GroupIncludeCache {
@Override
public ImmutableList<AccountGroup.UUID> load(AccountGroup.UUID key) {
- try (TraceTimer timer = TraceContext.newTimer("Loading parent groups of %s", key)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading parent groups", Metadata.builder().groupUuid(key.get()).build())) {
return groupQueryProvider.get().bySubgroup(key).stream()
.map(InternalGroup::getGroupUUID)
.collect(toImmutableList());
diff --git a/java/com/google/gerrit/server/account/GroupMembers.java b/java/com/google/gerrit/server/account/GroupMembers.java
index d7e97ba551..c2b935b25d 100644
--- a/java/com/google/gerrit/server/account/GroupMembers.java
+++ b/java/com/google/gerrit/server/account/GroupMembers.java
@@ -19,9 +19,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.InternalGroupDescription;
import com.google.gerrit.server.group.SystemGroupBackend;
@@ -131,7 +131,7 @@ public class GroupMembers {
.filter(groupControl::canSeeMember)
.map(accountCache::get)
.flatMap(Streams::stream)
- .map(AccountState::getAccount)
+ .map(AccountState::account)
.collect(toImmutableSet());
Set<Account> indirectMembers = new HashSet<>();
diff --git a/java/com/google/gerrit/server/account/GroupMembership.java b/java/com/google/gerrit/server/account/GroupMembership.java
index b59b9897fc..e051794e1e 100644
--- a/java/com/google/gerrit/server/account/GroupMembership.java
+++ b/java/com/google/gerrit/server/account/GroupMembership.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Collections;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/account/GroupUUID.java b/java/com/google/gerrit/server/account/GroupUUID.java
index a7b32a1156..ac834821b7 100644
--- a/java/com/google/gerrit/server/account/GroupUUID.java
+++ b/java/com/google/gerrit/server/account/GroupUUID.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import java.security.MessageDigest;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -26,7 +26,7 @@ public class GroupUUID {
md.update(Constants.encode("group " + groupName + "\n"));
md.update(Constants.encode("creator " + creator.toExternalString() + "\n"));
md.update(Constants.encode(String.valueOf(Math.random())));
- return new AccountGroup.UUID(ObjectId.fromRaw(md.digest()).name());
+ return AccountGroup.uuid(ObjectId.fromRaw(md.digest()).name());
}
private GroupUUID() {}
diff --git a/java/com/google/gerrit/server/account/HashedPassword.java b/java/com/google/gerrit/server/account/HashedPassword.java
index bffa3ced6d..64a44950c7 100644
--- a/java/com/google/gerrit/server/account/HashedPassword.java
+++ b/java/com/google/gerrit/server/account/HashedPassword.java
@@ -22,7 +22,6 @@ import com.google.common.primitives.Ints;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.List;
-import org.apache.commons.codec.DecoderException;
import org.bouncycastle.crypto.generators.BCrypt;
import org.bouncycastle.util.Arrays;
@@ -39,6 +38,14 @@ public class HashedPassword {
// for a high cost.
private static final int DEFAULT_COST = 4;
+ public static class DecoderException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public DecoderException(String message) {
+ super(message);
+ }
+ }
+
/**
* decodes a hashed password encoded with {@link #encode}.
*
diff --git a/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index b6969ac9a8..6dc79761ad 100644
--- a/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.account;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/account/InternalAccountDirectory.java b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
index ce97ff9602..e27b77cb3a 100644
--- a/java/com/google/gerrit/server/account/InternalAccountDirectory.java
+++ b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
@@ -20,11 +20,11 @@ import static java.util.stream.Collectors.toSet;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.AvatarInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -98,15 +98,14 @@ public class InternalAccountDirectory extends AccountDirectory {
Set<FillOptions> fillOptionsWithoutSecondaryEmails =
Sets.difference(options, EnumSet.of(FillOptions.SECONDARY_EMAILS));
- Set<Account.Id> ids =
- Streams.stream(in).map(a -> new Account.Id(a._accountId)).collect(toSet());
+ Set<Account.Id> ids = Streams.stream(in).map(a -> Account.id(a._accountId)).collect(toSet());
Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
for (AccountInfo info : in) {
- Account.Id id = new Account.Id(info._accountId);
+ Account.Id id = Account.id(info._accountId);
AccountState state = accountStates.get(id);
if (state != null) {
if (!options.contains(FillOptions.SECONDARY_EMAILS)
- || Objects.equals(currentUserId, state.getAccount().getId())
+ || Objects.equals(currentUserId, state.account().id())
|| canModifyAccount) {
fill(info, accountStates.get(id), options);
} else {
@@ -121,50 +120,53 @@ public class InternalAccountDirectory extends AccountDirectory {
}
private void fill(AccountInfo info, AccountState accountState, Set<FillOptions> options) {
- Account account = accountState.getAccount();
+ Account account = accountState.account();
if (options.contains(FillOptions.ID)) {
- info._accountId = account.getId().get();
+ info._accountId = account.id().get();
} else {
// Was previously set to look up account for filling.
info._accountId = null;
}
if (options.contains(FillOptions.NAME)) {
- info.name = Strings.emptyToNull(account.getFullName());
+ info.name = Strings.emptyToNull(account.fullName());
if (info.name == null) {
- info.name = accountState.getUserName().orElse(null);
+ info.name = accountState.userName().orElse(null);
}
}
if (options.contains(FillOptions.EMAIL)) {
- info.email = account.getPreferredEmail();
+ info.email = account.preferredEmail();
}
if (options.contains(FillOptions.SECONDARY_EMAILS)) {
- info.secondaryEmails = getSecondaryEmails(account, accountState.getExternalIds());
+ info.secondaryEmails = getSecondaryEmails(account, accountState.externalIds());
}
if (options.contains(FillOptions.USERNAME)) {
- info.username = accountState.getUserName().orElse(null);
+ info.username = accountState.userName().orElse(null);
}
if (options.contains(FillOptions.STATUS)) {
- info.status = account.getStatus();
+ info.status = account.status();
+ }
+
+ if (options.contains(FillOptions.STATE)) {
+ info.inactive = account.inactive() ? true : null;
}
if (options.contains(FillOptions.AVATARS)) {
AvatarProvider ap = avatar.get();
if (ap != null) {
- info.avatars = new ArrayList<>(3);
- IdentifiedUser user = userFactory.create(account.getId());
-
- // GWT UI uses DEFAULT_SIZE (26px).
+ info.avatars = new ArrayList<>();
+ IdentifiedUser user = userFactory.create(account.id());
+
+ // PolyGerrit UI uses the following sizes for avatars:
+ // - 32px for avatars next to names e.g. on the dashboard. This is also Gerrit's default.
+ // - 56px for the user's own avatar in the menu
+ // - 100ox for other user's avatars on dashboards
+ // - 120px for the user's own profile settings page
addAvatar(ap, info, user, AvatarInfo.DEFAULT_SIZE);
-
- // PolyGerrit UI prefers 32px and 100px.
if (!info.avatars.isEmpty()) {
- if (32 != AvatarInfo.DEFAULT_SIZE) {
- addAvatar(ap, info, user, 32);
- }
- if (100 != AvatarInfo.DEFAULT_SIZE) {
- addAvatar(ap, info, user, 100);
- }
+ addAvatar(ap, info, user, 56);
+ addAvatar(ap, info, user, 100);
+ addAvatar(ap, info, user, 120);
}
}
}
@@ -172,7 +174,7 @@ public class InternalAccountDirectory extends AccountDirectory {
public List<String> getSecondaryEmails(Account account, Collection<ExternalId> externalIds) {
return ExternalId.getEmails(externalIds)
- .filter(e -> !e.equals(account.getPreferredEmail()))
+ .filter(e -> !e.equals(account.preferredEmail()))
.sorted()
.collect(toList());
}
diff --git a/java/com/google/gerrit/server/account/InternalAccountUpdate.java b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
index c778fca17c..cf77a75b26 100644
--- a/java/com/google/gerrit/server/account/InternalAccountUpdate.java
+++ b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
@@ -18,10 +18,10 @@ import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
diff --git a/java/com/google/gerrit/server/account/InternalGroupBackend.java b/java/com/google/gerrit/server/account/InternalGroupBackend.java
index ea6eb87443..ddd3da20b4 100644
--- a/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -19,7 +19,7 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.InternalGroupDescription;
diff --git a/java/com/google/gerrit/server/account/ListGroupMembership.java b/java/com/google/gerrit/server/account/ListGroupMembership.java
index 60e73454fc..0f4fb78c6f 100644
--- a/java/com/google/gerrit/server/account/ListGroupMembership.java
+++ b/java/com/google/gerrit/server/account/ListGroupMembership.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.account;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import java.util.Set;
/** GroupMembership over an explicit list. */
diff --git a/java/com/google/gerrit/server/account/Preferences.java b/java/com/google/gerrit/server/account/Preferences.java
index cd849b8273..ece610b687 100644
--- a/java/com/google/gerrit/server/account/Preferences.java
+++ b/java/com/google/gerrit/server/account/Preferences.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,566 +11,419 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
package com.google.gerrit.server.account;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.config.ConfigUtil.loadSection;
-import static com.google.gerrit.server.config.ConfigUtil.skipField;
-import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE;
-import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.flogger.FluentLogger;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
import com.google.gerrit.extensions.client.MenuItem;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.UserConfigSections;
-import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.git.meta.VersionedMetaData;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Repository;
-
-/**
- * Parses/writes preferences from/to a {@link Config} file.
- *
- * <p>This is a low-level API. Read/write of preferences in a user branch should be done through
- * {@link AccountsUpdate} or {@link AccountConfig}.
- *
- * <p>The config file has separate sections for general, diff and edit preferences:
- *
- * <pre>
- * [general]
- * showSiteHeader = false
- * [diff]
- * hideTopMenu = true
- * [edit]
- * lineLength = 80
- * </pre>
- *
- * <p>The parameter names match the names that are used in the preferences REST API.
- *
- * <p>If the preference is omitted in the config file, then the default value for the preference is
- * used.
- *
- * <p>Defaults for preferences that apply for all accounts can be configured in the {@code
- * refs/users/default} branch in the {@code All-Users} repository. The config for the default
- * preferences must be provided to this class so that it can read default values from it.
- *
- * <p>The preferences are lazily parsed.
- */
-public class Preferences {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- public static final String PREFERENCES_CONFIG = "preferences.config";
-
- private final Account.Id accountId;
- private final Config cfg;
- private final Config defaultCfg;
- private final ValidationError.Sink validationErrorSink;
-
- private GeneralPreferencesInfo generalPreferences;
- private DiffPreferencesInfo diffPreferences;
- private EditPreferencesInfo editPreferences;
-
- Preferences(
- Account.Id accountId,
- Config cfg,
- Config defaultCfg,
- ValidationError.Sink validationErrorSink) {
- this.accountId = requireNonNull(accountId, "accountId");
- this.cfg = requireNonNull(cfg, "cfg");
- this.defaultCfg = requireNonNull(defaultCfg, "defaultCfg");
- this.validationErrorSink = requireNonNull(validationErrorSink, "validationErrorSink");
- }
- public GeneralPreferencesInfo getGeneralPreferences() {
- if (generalPreferences == null) {
- parse();
- }
- return generalPreferences;
- }
+@AutoValue
+public abstract class Preferences {
+ @AutoValue
+ public abstract static class General {
+ public abstract Optional<Integer> changesPerPage();
- public DiffPreferencesInfo getDiffPreferences() {
- if (diffPreferences == null) {
- parse();
- }
- return diffPreferences;
- }
+ public abstract Optional<String> downloadScheme();
- public EditPreferencesInfo getEditPreferences() {
- if (editPreferences == null) {
- parse();
- }
- return editPreferences;
- }
+ public abstract Optional<DateFormat> dateFormat();
- public void parse() {
- generalPreferences = parseGeneralPreferences(null);
- diffPreferences = parseDiffPreferences(null);
- editPreferences = parseEditPreferences(null);
- }
+ public abstract Optional<TimeFormat> timeFormat();
- public Config saveGeneralPreferences(
- Optional<GeneralPreferencesInfo> generalPreferencesInput,
- Optional<DiffPreferencesInfo> diffPreferencesInput,
- Optional<EditPreferencesInfo> editPreferencesInput)
- throws ConfigInvalidException {
- if (generalPreferencesInput.isPresent()) {
- GeneralPreferencesInfo mergedGeneralPreferencesInput =
- parseGeneralPreferences(generalPreferencesInput.get());
-
- storeSection(
- cfg,
- UserConfigSections.GENERAL,
- null,
- mergedGeneralPreferencesInput,
- parseDefaultGeneralPreferences(defaultCfg, null));
- setChangeTable(cfg, mergedGeneralPreferencesInput.changeTable);
- setMy(cfg, mergedGeneralPreferencesInput.my);
-
- // evict the cached general preferences
- this.generalPreferences = null;
- }
+ public abstract Optional<Boolean> expandInlineDiffs();
- if (diffPreferencesInput.isPresent()) {
- DiffPreferencesInfo mergedDiffPreferencesInput =
- parseDiffPreferences(diffPreferencesInput.get());
+ public abstract Optional<Boolean> highlightAssigneeInChangeTable();
- storeSection(
- cfg,
- UserConfigSections.DIFF,
- null,
- mergedDiffPreferencesInput,
- parseDefaultDiffPreferences(defaultCfg, null));
+ public abstract Optional<Boolean> relativeDateInChangeTable();
- // evict the cached diff preferences
- this.diffPreferences = null;
- }
+ public abstract Optional<DiffView> diffView();
- if (editPreferencesInput.isPresent()) {
- EditPreferencesInfo mergedEditPreferencesInput =
- parseEditPreferences(editPreferencesInput.get());
+ public abstract Optional<Boolean> sizeBarInChangeTable();
- storeSection(
- cfg,
- UserConfigSections.EDIT,
- null,
- mergedEditPreferencesInput,
- parseDefaultEditPreferences(defaultCfg, null));
+ public abstract Optional<Boolean> legacycidInChangeTable();
- // evict the cached edit preferences
- this.editPreferences = null;
- }
+ public abstract Optional<Boolean> muteCommonPathPrefixes();
- return cfg;
- }
+ public abstract Optional<Boolean> signedOffBy();
- private GeneralPreferencesInfo parseGeneralPreferences(@Nullable GeneralPreferencesInfo input) {
- try {
- return parseGeneralPreferences(cfg, defaultCfg, input);
- } catch (ConfigInvalidException e) {
- validationErrorSink.error(
- new ValidationError(
- PREFERENCES_CONFIG,
- String.format(
- "Invalid general preferences for account %d: %s",
- accountId.get(), e.getMessage())));
- return new GeneralPreferencesInfo();
- }
- }
+ public abstract Optional<EmailStrategy> emailStrategy();
- private DiffPreferencesInfo parseDiffPreferences(@Nullable DiffPreferencesInfo input) {
- try {
- return parseDiffPreferences(cfg, defaultCfg, input);
- } catch (ConfigInvalidException e) {
- validationErrorSink.error(
- new ValidationError(
- PREFERENCES_CONFIG,
- String.format(
- "Invalid diff preferences for account %d: %s", accountId.get(), e.getMessage())));
- return new DiffPreferencesInfo();
- }
- }
+ public abstract Optional<EmailFormat> emailFormat();
- private EditPreferencesInfo parseEditPreferences(@Nullable EditPreferencesInfo input) {
- try {
- return parseEditPreferences(cfg, defaultCfg, input);
- } catch (ConfigInvalidException e) {
- validationErrorSink.error(
- new ValidationError(
- PREFERENCES_CONFIG,
- String.format(
- "Invalid edit preferences for account %d: %s", accountId.get(), e.getMessage())));
- return new EditPreferencesInfo();
- }
- }
+ public abstract Optional<DefaultBase> defaultBaseForMerges();
- private static GeneralPreferencesInfo parseGeneralPreferences(
- Config cfg, @Nullable Config defaultCfg, @Nullable GeneralPreferencesInfo input)
- throws ConfigInvalidException {
- GeneralPreferencesInfo r =
- loadSection(
- cfg,
- UserConfigSections.GENERAL,
- null,
- new GeneralPreferencesInfo(),
- defaultCfg != null
- ? parseDefaultGeneralPreferences(defaultCfg, input)
- : GeneralPreferencesInfo.defaults(),
- input);
- if (input != null) {
- r.changeTable = input.changeTable;
- r.my = input.my;
- } else {
- r.changeTable = parseChangeTableColumns(cfg, defaultCfg);
- r.my = parseMyMenus(cfg, defaultCfg);
- }
- return r;
- }
+ public abstract Optional<Boolean> publishCommentsOnPush();
- private static DiffPreferencesInfo parseDiffPreferences(
- Config cfg, @Nullable Config defaultCfg, @Nullable DiffPreferencesInfo input)
- throws ConfigInvalidException {
- return loadSection(
- cfg,
- UserConfigSections.DIFF,
- null,
- new DiffPreferencesInfo(),
- defaultCfg != null
- ? parseDefaultDiffPreferences(defaultCfg, input)
- : DiffPreferencesInfo.defaults(),
- input);
- }
+ public abstract Optional<Boolean> workInProgressByDefault();
- private static EditPreferencesInfo parseEditPreferences(
- Config cfg, @Nullable Config defaultCfg, @Nullable EditPreferencesInfo input)
- throws ConfigInvalidException {
- return loadSection(
- cfg,
- UserConfigSections.EDIT,
- null,
- new EditPreferencesInfo(),
- defaultCfg != null
- ? parseDefaultEditPreferences(defaultCfg, input)
- : EditPreferencesInfo.defaults(),
- input);
- }
+ public abstract Optional<ImmutableList<MenuItem>> my();
- private static GeneralPreferencesInfo parseDefaultGeneralPreferences(
- Config defaultCfg, GeneralPreferencesInfo input) throws ConfigInvalidException {
- GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
- loadSection(
- defaultCfg,
- UserConfigSections.GENERAL,
- null,
- allUserPrefs,
- GeneralPreferencesInfo.defaults(),
- input);
- return updateGeneralPreferencesDefaults(allUserPrefs);
- }
+ public abstract Optional<ImmutableList<String>> changeTable();
- private static DiffPreferencesInfo parseDefaultDiffPreferences(
- Config defaultCfg, DiffPreferencesInfo input) throws ConfigInvalidException {
- DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
- loadSection(
- defaultCfg,
- UserConfigSections.DIFF,
- null,
- allUserPrefs,
- DiffPreferencesInfo.defaults(),
- input);
- return updateDiffPreferencesDefaults(allUserPrefs);
- }
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder changesPerPage(@Nullable Integer val);
- private static EditPreferencesInfo parseDefaultEditPreferences(
- Config defaultCfg, EditPreferencesInfo input) throws ConfigInvalidException {
- EditPreferencesInfo allUserPrefs = new EditPreferencesInfo();
- loadSection(
- defaultCfg,
- UserConfigSections.EDIT,
- null,
- allUserPrefs,
- EditPreferencesInfo.defaults(),
- input);
- return updateEditPreferencesDefaults(allUserPrefs);
- }
+ abstract Builder downloadScheme(@Nullable String val);
- private static GeneralPreferencesInfo updateGeneralPreferencesDefaults(
- GeneralPreferencesInfo input) {
- GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
- try {
- for (Field field : input.getClass().getDeclaredFields()) {
- if (skipField(field)) {
- continue;
- }
- Object newVal = field.get(input);
- if (newVal != null) {
- field.set(result, newVal);
- }
- }
- } catch (IllegalAccessException e) {
- logger.atSevere().withCause(e).log("Failed to apply default general preferences");
- return GeneralPreferencesInfo.defaults();
- }
- return result;
- }
+ abstract Builder dateFormat(@Nullable DateFormat val);
- private static DiffPreferencesInfo updateDiffPreferencesDefaults(DiffPreferencesInfo input) {
- DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
- try {
- for (Field field : input.getClass().getDeclaredFields()) {
- if (skipField(field)) {
- continue;
- }
- Object newVal = field.get(input);
- if (newVal != null) {
- field.set(result, newVal);
- }
- }
- } catch (IllegalAccessException e) {
- logger.atSevere().withCause(e).log("Failed to apply default diff preferences");
- return DiffPreferencesInfo.defaults();
- }
- return result;
- }
+ abstract Builder timeFormat(@Nullable TimeFormat val);
- private static EditPreferencesInfo updateEditPreferencesDefaults(EditPreferencesInfo input) {
- EditPreferencesInfo result = EditPreferencesInfo.defaults();
- try {
- for (Field field : input.getClass().getDeclaredFields()) {
- if (skipField(field)) {
- continue;
- }
- Object newVal = field.get(input);
- if (newVal != null) {
- field.set(result, newVal);
- }
- }
- } catch (IllegalAccessException e) {
- logger.atSevere().withCause(e).log("Failed to apply default edit preferences");
- return EditPreferencesInfo.defaults();
- }
- return result;
- }
+ abstract Builder expandInlineDiffs(@Nullable Boolean val);
+
+ abstract Builder highlightAssigneeInChangeTable(@Nullable Boolean val);
- private static List<String> parseChangeTableColumns(Config cfg, @Nullable Config defaultCfg) {
- List<String> changeTable = changeTable(cfg);
- if (changeTable == null && defaultCfg != null) {
- changeTable = changeTable(defaultCfg);
+ abstract Builder relativeDateInChangeTable(@Nullable Boolean val);
+
+ abstract Builder diffView(@Nullable DiffView val);
+
+ abstract Builder sizeBarInChangeTable(@Nullable Boolean val);
+
+ abstract Builder legacycidInChangeTable(@Nullable Boolean val);
+
+ abstract Builder muteCommonPathPrefixes(@Nullable Boolean val);
+
+ abstract Builder signedOffBy(@Nullable Boolean val);
+
+ abstract Builder emailStrategy(@Nullable EmailStrategy val);
+
+ abstract Builder emailFormat(@Nullable EmailFormat val);
+
+ abstract Builder defaultBaseForMerges(@Nullable DefaultBase val);
+
+ abstract Builder publishCommentsOnPush(@Nullable Boolean val);
+
+ abstract Builder workInProgressByDefault(@Nullable Boolean val);
+
+ abstract Builder my(@Nullable ImmutableList<MenuItem> val);
+
+ abstract Builder changeTable(@Nullable ImmutableList<String> val);
+
+ abstract General build();
}
- return changeTable;
- }
- private static List<MenuItem> parseMyMenus(Config cfg, @Nullable Config defaultCfg) {
- List<MenuItem> my = my(cfg);
- if (my.isEmpty() && defaultCfg != null) {
- my = my(defaultCfg);
+ public static General fromInfo(GeneralPreferencesInfo info) {
+ return (new AutoValue_Preferences_General.Builder())
+ .changesPerPage(info.changesPerPage)
+ .downloadScheme(info.downloadScheme)
+ .dateFormat(info.dateFormat)
+ .timeFormat(info.timeFormat)
+ .expandInlineDiffs(info.expandInlineDiffs)
+ .highlightAssigneeInChangeTable(info.highlightAssigneeInChangeTable)
+ .relativeDateInChangeTable(info.relativeDateInChangeTable)
+ .diffView(info.diffView)
+ .sizeBarInChangeTable(info.sizeBarInChangeTable)
+ .legacycidInChangeTable(info.legacycidInChangeTable)
+ .muteCommonPathPrefixes(info.muteCommonPathPrefixes)
+ .signedOffBy(info.signedOffBy)
+ .emailStrategy(info.emailStrategy)
+ .emailFormat(info.emailFormat)
+ .defaultBaseForMerges(info.defaultBaseForMerges)
+ .publishCommentsOnPush(info.publishCommentsOnPush)
+ .workInProgressByDefault(info.workInProgressByDefault)
+ .my(info.my == null ? null : ImmutableList.copyOf(info.my))
+ .changeTable(info.changeTable == null ? null : ImmutableList.copyOf(info.changeTable))
+ .build();
}
- if (my.isEmpty()) {
- my.add(new MenuItem("Changes", "#/dashboard/self", null));
- my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
- my.add(new MenuItem("Edits", "#/q/has:edit", null));
- my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
- my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
- my.add(new MenuItem("Groups", "#/groups/self", null));
+
+ public GeneralPreferencesInfo toInfo() {
+ GeneralPreferencesInfo info = new GeneralPreferencesInfo();
+ info.changesPerPage = changesPerPage().orElse(null);
+ info.downloadScheme = downloadScheme().orElse(null);
+ info.dateFormat = dateFormat().orElse(null);
+ info.timeFormat = timeFormat().orElse(null);
+ info.expandInlineDiffs = expandInlineDiffs().orElse(null);
+ info.highlightAssigneeInChangeTable = highlightAssigneeInChangeTable().orElse(null);
+ info.relativeDateInChangeTable = relativeDateInChangeTable().orElse(null);
+ info.diffView = diffView().orElse(null);
+ info.sizeBarInChangeTable = sizeBarInChangeTable().orElse(null);
+ info.legacycidInChangeTable = legacycidInChangeTable().orElse(null);
+ info.muteCommonPathPrefixes = muteCommonPathPrefixes().orElse(null);
+ info.signedOffBy = signedOffBy().orElse(null);
+ info.emailStrategy = emailStrategy().orElse(null);
+ info.emailFormat = emailFormat().orElse(null);
+ info.defaultBaseForMerges = defaultBaseForMerges().orElse(null);
+ info.publishCommentsOnPush = publishCommentsOnPush().orElse(null);
+ info.workInProgressByDefault = workInProgressByDefault().orElse(null);
+ info.my = my().orElse(null);
+ info.changeTable = changeTable().orElse(null);
+ return info;
}
- return my;
}
- public static GeneralPreferencesInfo readDefaultGeneralPreferences(
- AllUsersName allUsersName, Repository allUsersRepo)
- throws IOException, ConfigInvalidException {
- return parseGeneralPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
- }
+ @AutoValue
+ public abstract static class Edit {
+ public abstract Optional<Integer> tabSize();
- public static DiffPreferencesInfo readDefaultDiffPreferences(
- AllUsersName allUsersName, Repository allUsersRepo)
- throws IOException, ConfigInvalidException {
- return parseDiffPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
- }
+ public abstract Optional<Integer> lineLength();
- public static EditPreferencesInfo readDefaultEditPreferences(
- AllUsersName allUsersName, Repository allUsersRepo)
- throws IOException, ConfigInvalidException {
- return parseEditPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
- }
+ public abstract Optional<Integer> indentUnit();
- static Config readDefaultConfig(AllUsersName allUsersName, Repository allUsersRepo)
- throws IOException, ConfigInvalidException {
- VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
- defaultPrefs.load(allUsersName, allUsersRepo);
- return defaultPrefs.getConfig();
- }
+ public abstract Optional<Integer> cursorBlinkRate();
- public static GeneralPreferencesInfo updateDefaultGeneralPreferences(
- MetaDataUpdate md, GeneralPreferencesInfo input) throws IOException, ConfigInvalidException {
- VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
- defaultPrefs.load(md);
- storeSection(
- defaultPrefs.getConfig(),
- UserConfigSections.GENERAL,
- null,
- input,
- GeneralPreferencesInfo.defaults());
- setMy(defaultPrefs.getConfig(), input.my);
- setChangeTable(defaultPrefs.getConfig(), input.changeTable);
- defaultPrefs.commit(md);
-
- return parseGeneralPreferences(defaultPrefs.getConfig(), null, null);
- }
+ public abstract Optional<Boolean> hideTopMenu();
- public static DiffPreferencesInfo updateDefaultDiffPreferences(
- MetaDataUpdate md, DiffPreferencesInfo input) throws IOException, ConfigInvalidException {
- VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
- defaultPrefs.load(md);
- storeSection(
- defaultPrefs.getConfig(),
- UserConfigSections.DIFF,
- null,
- input,
- DiffPreferencesInfo.defaults());
- defaultPrefs.commit(md);
-
- return parseDiffPreferences(defaultPrefs.getConfig(), null, null);
- }
+ public abstract Optional<Boolean> showTabs();
- public static EditPreferencesInfo updateDefaultEditPreferences(
- MetaDataUpdate md, EditPreferencesInfo input) throws IOException, ConfigInvalidException {
- VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
- defaultPrefs.load(md);
- storeSection(
- defaultPrefs.getConfig(),
- UserConfigSections.EDIT,
- null,
- input,
- EditPreferencesInfo.defaults());
- defaultPrefs.commit(md);
-
- return parseEditPreferences(defaultPrefs.getConfig(), null, null);
- }
+ public abstract Optional<Boolean> showWhitespaceErrors();
- private static List<String> changeTable(Config cfg) {
- return Lists.newArrayList(cfg.getStringList(CHANGE_TABLE, null, CHANGE_TABLE_COLUMN));
- }
+ public abstract Optional<Boolean> syntaxHighlighting();
- private static void setChangeTable(Config cfg, List<String> changeTable) {
- if (changeTable != null) {
- unsetSection(cfg, UserConfigSections.CHANGE_TABLE);
- cfg.setStringList(UserConfigSections.CHANGE_TABLE, null, CHANGE_TABLE_COLUMN, changeTable);
- }
- }
+ public abstract Optional<Boolean> hideLineNumbers();
- private static List<MenuItem> my(Config cfg) {
- List<MenuItem> my = new ArrayList<>();
- for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
- String url = my(cfg, subsection, KEY_URL, "#/");
- String target = my(cfg, subsection, KEY_TARGET, url.startsWith("#") ? null : "_blank");
- my.add(new MenuItem(subsection, url, target, my(cfg, subsection, KEY_ID, null)));
- }
- return my;
- }
+ public abstract Optional<Boolean> matchBrackets();
- private static String my(Config cfg, String subsection, String key, String defaultValue) {
- String val = cfg.getString(UserConfigSections.MY, subsection, key);
- return !Strings.isNullOrEmpty(val) ? val : defaultValue;
- }
+ public abstract Optional<Boolean> lineWrapping();
- private static void setMy(Config cfg, List<MenuItem> my) {
- if (my != null) {
- unsetSection(cfg, UserConfigSections.MY);
- for (MenuItem item : my) {
- checkState(!isNullOrEmpty(item.name), "MenuItem.name must not be null or empty");
- checkState(!isNullOrEmpty(item.url), "MenuItem.url must not be null or empty");
-
- setMy(cfg, item.name, KEY_URL, item.url);
- setMy(cfg, item.name, KEY_TARGET, item.target);
- setMy(cfg, item.name, KEY_ID, item.id);
- }
- }
- }
+ public abstract Optional<Boolean> indentWithTabs();
+
+ public abstract Optional<Boolean> autoCloseBrackets();
+
+ public abstract Optional<Boolean> showBase();
- public static void validateMy(List<MenuItem> my) throws BadRequestException {
- if (my == null) {
- return;
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder tabSize(@Nullable Integer val);
+
+ abstract Builder lineLength(@Nullable Integer val);
+
+ abstract Builder indentUnit(@Nullable Integer val);
+
+ abstract Builder cursorBlinkRate(@Nullable Integer val);
+
+ abstract Builder hideTopMenu(@Nullable Boolean val);
+
+ abstract Builder showTabs(@Nullable Boolean val);
+
+ abstract Builder showWhitespaceErrors(@Nullable Boolean val);
+
+ abstract Builder syntaxHighlighting(@Nullable Boolean val);
+
+ abstract Builder hideLineNumbers(@Nullable Boolean val);
+
+ abstract Builder matchBrackets(@Nullable Boolean val);
+
+ abstract Builder lineWrapping(@Nullable Boolean val);
+
+ abstract Builder indentWithTabs(@Nullable Boolean val);
+
+ abstract Builder autoCloseBrackets(@Nullable Boolean val);
+
+ abstract Builder showBase(@Nullable Boolean val);
+
+ abstract Edit build();
}
- for (MenuItem item : my) {
- checkRequiredMenuItemField(item.name, "name");
- checkRequiredMenuItemField(item.url, "URL");
+
+ public static Edit fromInfo(EditPreferencesInfo info) {
+ return (new AutoValue_Preferences_Edit.Builder())
+ .tabSize(info.tabSize)
+ .lineLength(info.lineLength)
+ .indentUnit(info.indentUnit)
+ .cursorBlinkRate(info.cursorBlinkRate)
+ .hideTopMenu(info.hideTopMenu)
+ .showTabs(info.showTabs)
+ .showWhitespaceErrors(info.showWhitespaceErrors)
+ .syntaxHighlighting(info.syntaxHighlighting)
+ .hideLineNumbers(info.hideLineNumbers)
+ .matchBrackets(info.matchBrackets)
+ .lineWrapping(info.lineWrapping)
+ .indentWithTabs(info.indentWithTabs)
+ .autoCloseBrackets(info.autoCloseBrackets)
+ .showBase(info.showBase)
+ .build();
}
- }
- private static void checkRequiredMenuItemField(String value, String name)
- throws BadRequestException {
- if (isNullOrEmpty(value)) {
- throw new BadRequestException(name + " for menu item is required");
+ public EditPreferencesInfo toInfo() {
+ EditPreferencesInfo info = new EditPreferencesInfo();
+ info.tabSize = tabSize().orElse(null);
+ info.lineLength = lineLength().orElse(null);
+ info.indentUnit = indentUnit().orElse(null);
+ info.cursorBlinkRate = cursorBlinkRate().orElse(null);
+ info.hideTopMenu = hideTopMenu().orElse(null);
+ info.showTabs = showTabs().orElse(null);
+ info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
+ info.syntaxHighlighting = syntaxHighlighting().orElse(null);
+ info.hideLineNumbers = hideLineNumbers().orElse(null);
+ info.matchBrackets = matchBrackets().orElse(null);
+ info.lineWrapping = lineWrapping().orElse(null);
+ info.indentWithTabs = indentWithTabs().orElse(null);
+ info.autoCloseBrackets = autoCloseBrackets().orElse(null);
+ info.showBase = showBase().orElse(null);
+ return info;
}
}
- private static boolean isNullOrEmpty(String value) {
- return value == null || value.trim().isEmpty();
- }
+ @AutoValue
+ public abstract static class Diff {
+ public abstract Optional<Integer> context();
- private static void setMy(Config cfg, String section, String key, @Nullable String val) {
- if (val == null || val.trim().isEmpty()) {
- cfg.unset(UserConfigSections.MY, section.trim(), key);
- } else {
- cfg.setString(UserConfigSections.MY, section.trim(), key, val.trim());
- }
- }
+ public abstract Optional<Integer> tabSize();
- private static void unsetSection(Config cfg, String section) {
- cfg.unsetSection(section, null);
- for (String subsection : cfg.getSubsections(section)) {
- cfg.unsetSection(section, subsection);
- }
- }
+ public abstract Optional<Integer> fontSize();
- private static class VersionedDefaultPreferences extends VersionedMetaData {
- private Config cfg;
+ public abstract Optional<Integer> lineLength();
- @Override
- protected String getRefName() {
- return RefNames.REFS_USERS_DEFAULT;
- }
+ public abstract Optional<Integer> cursorBlinkRate();
+
+ public abstract Optional<Boolean> expandAllComments();
+
+ public abstract Optional<Boolean> intralineDifference();
+
+ public abstract Optional<Boolean> manualReview();
+
+ public abstract Optional<Boolean> showLineEndings();
+
+ public abstract Optional<Boolean> showTabs();
+
+ public abstract Optional<Boolean> showWhitespaceErrors();
+
+ public abstract Optional<Boolean> syntaxHighlighting();
+
+ public abstract Optional<Boolean> hideTopMenu();
+
+ public abstract Optional<Boolean> autoHideDiffTableHeader();
+
+ public abstract Optional<Boolean> hideLineNumbers();
+
+ public abstract Optional<Boolean> renderEntireFile();
+
+ public abstract Optional<Boolean> hideEmptyPane();
+
+ public abstract Optional<Boolean> matchBrackets();
+
+ public abstract Optional<Boolean> lineWrapping();
+
+ public abstract Optional<Whitespace> ignoreWhitespace();
+
+ public abstract Optional<Boolean> retainHeader();
+
+ public abstract Optional<Boolean> skipDeleted();
+
+ public abstract Optional<Boolean> skipUnchanged();
+
+ public abstract Optional<Boolean> skipUncommented();
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder context(@Nullable Integer val);
+
+ abstract Builder tabSize(@Nullable Integer val);
+
+ abstract Builder fontSize(@Nullable Integer val);
+
+ abstract Builder lineLength(@Nullable Integer val);
+
+ abstract Builder cursorBlinkRate(@Nullable Integer val);
+
+ abstract Builder expandAllComments(@Nullable Boolean val);
+
+ abstract Builder intralineDifference(@Nullable Boolean val);
+
+ abstract Builder manualReview(@Nullable Boolean val);
+
+ abstract Builder showLineEndings(@Nullable Boolean val);
+
+ abstract Builder showTabs(@Nullable Boolean val);
+
+ abstract Builder showWhitespaceErrors(@Nullable Boolean val);
+
+ abstract Builder syntaxHighlighting(@Nullable Boolean val);
+
+ abstract Builder hideTopMenu(@Nullable Boolean val);
+
+ abstract Builder autoHideDiffTableHeader(@Nullable Boolean val);
+
+ abstract Builder hideLineNumbers(@Nullable Boolean val);
+
+ abstract Builder renderEntireFile(@Nullable Boolean val);
+
+ abstract Builder hideEmptyPane(@Nullable Boolean val);
+
+ abstract Builder matchBrackets(@Nullable Boolean val);
+
+ abstract Builder lineWrapping(@Nullable Boolean val);
+
+ abstract Builder ignoreWhitespace(@Nullable Whitespace val);
+
+ abstract Builder retainHeader(@Nullable Boolean val);
+
+ abstract Builder skipDeleted(@Nullable Boolean val);
+
+ abstract Builder skipUnchanged(@Nullable Boolean val);
+
+ abstract Builder skipUncommented(@Nullable Boolean val);
- private Config getConfig() {
- checkState(cfg != null, "Default preferences not loaded yet.");
- return cfg;
+ abstract Diff build();
}
- @Override
- protected void onLoad() throws IOException, ConfigInvalidException {
- cfg = readConfig(PREFERENCES_CONFIG);
+ public static Diff fromInfo(DiffPreferencesInfo info) {
+ return (new AutoValue_Preferences_Diff.Builder())
+ .context(info.context)
+ .tabSize(info.tabSize)
+ .fontSize(info.fontSize)
+ .lineLength(info.lineLength)
+ .cursorBlinkRate(info.cursorBlinkRate)
+ .expandAllComments(info.expandAllComments)
+ .intralineDifference(info.intralineDifference)
+ .manualReview(info.manualReview)
+ .showLineEndings(info.showLineEndings)
+ .showTabs(info.showTabs)
+ .showWhitespaceErrors(info.showWhitespaceErrors)
+ .syntaxHighlighting(info.syntaxHighlighting)
+ .hideTopMenu(info.hideTopMenu)
+ .autoHideDiffTableHeader(info.autoHideDiffTableHeader)
+ .hideLineNumbers(info.hideLineNumbers)
+ .renderEntireFile(info.renderEntireFile)
+ .hideEmptyPane(info.hideEmptyPane)
+ .matchBrackets(info.matchBrackets)
+ .lineWrapping(info.lineWrapping)
+ .ignoreWhitespace(info.ignoreWhitespace)
+ .retainHeader(info.retainHeader)
+ .skipDeleted(info.skipDeleted)
+ .skipUnchanged(info.skipUnchanged)
+ .skipUncommented(info.skipUncommented)
+ .build();
}
- @Override
- protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
- if (Strings.isNullOrEmpty(commit.getMessage())) {
- commit.setMessage("Update default preferences\n");
- }
- saveConfig(PREFERENCES_CONFIG, cfg);
- return true;
+ public DiffPreferencesInfo toInfo() {
+ DiffPreferencesInfo info = new DiffPreferencesInfo();
+ info.context = context().orElse(null);
+ info.tabSize = tabSize().orElse(null);
+ info.fontSize = fontSize().orElse(null);
+ info.lineLength = lineLength().orElse(null);
+ info.cursorBlinkRate = cursorBlinkRate().orElse(null);
+ info.expandAllComments = expandAllComments().orElse(null);
+ info.intralineDifference = intralineDifference().orElse(null);
+ info.manualReview = manualReview().orElse(null);
+ info.showLineEndings = showLineEndings().orElse(null);
+ info.showTabs = showTabs().orElse(null);
+ info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
+ info.syntaxHighlighting = syntaxHighlighting().orElse(null);
+ info.hideTopMenu = hideTopMenu().orElse(null);
+ info.autoHideDiffTableHeader = autoHideDiffTableHeader().orElse(null);
+ info.hideLineNumbers = hideLineNumbers().orElse(null);
+ info.renderEntireFile = renderEntireFile().orElse(null);
+ info.hideEmptyPane = hideEmptyPane().orElse(null);
+ info.matchBrackets = matchBrackets().orElse(null);
+ info.lineWrapping = lineWrapping().orElse(null);
+ info.ignoreWhitespace = ignoreWhitespace().orElse(null);
+ info.retainHeader = retainHeader().orElse(null);
+ info.skipDeleted = skipDeleted().orElse(null);
+ info.skipUnchanged = skipUnchanged().orElse(null);
+ info.skipUncommented = skipUncommented().orElse(null);
+ return info;
}
}
}
diff --git a/java/com/google/gerrit/server/account/ProjectWatches.java b/java/com/google/gerrit/server/account/ProjectWatches.java
index a750ba5056..b153b781ad 100644
--- a/java/com/google/gerrit/server/account/ProjectWatches.java
+++ b/java/com/google/gerrit/server/account/ProjectWatches.java
@@ -30,8 +30,8 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
import java.util.ArrayList;
import java.util.Collection;
@@ -168,7 +168,7 @@ public class ProjectWatches {
}
ProjectWatchKey key =
- ProjectWatchKey.create(new Project.NameKey(projectName), notifyValue.filter());
+ ProjectWatchKey.create(Project.nameKey(projectName), notifyValue.filter());
if (!projectWatches.containsKey(key)) {
projectWatches.put(key, EnumSet.noneOf(NotifyType.class));
}
diff --git a/java/com/google/gerrit/server/account/Realm.java b/java/com/google/gerrit/server/account/Realm.java
index 4e8cf09713..798b4e89d6 100644
--- a/java/com/google/gerrit/server/account/Realm.java
+++ b/java/com/google/gerrit/server/account/Realm.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import java.io.IOException;
diff --git a/java/com/google/gerrit/server/account/SetInactiveFlag.java b/java/com/google/gerrit/server/account/SetInactiveFlag.java
index da2d640b01..40cc185ff4 100644
--- a/java/com/google/gerrit/server/account/SetInactiveFlag.java
+++ b/java/com/google/gerrit/server/account/SetInactiveFlag.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.events.AccountActivationListener;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.validators.AccountActivationValidationListener;
@@ -37,13 +38,16 @@ public class SetInactiveFlag {
private final PluginSetContext<AccountActivationValidationListener>
accountActivationValidationListeners;
private final Provider<AccountsUpdate> accountsUpdateProvider;
+ private final PluginSetContext<AccountActivationListener> accountActivationListeners;
@Inject
SetInactiveFlag(
PluginSetContext<AccountActivationValidationListener> accountActivationValidationListeners,
- @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
+ @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
+ PluginSetContext<AccountActivationListener> accountActivationListeners) {
this.accountActivationValidationListeners = accountActivationValidationListeners;
this.accountsUpdateProvider = accountsUpdateProvider;
+ this.accountActivationListeners = accountActivationListeners;
}
public Response<?> deactivate(Account.Id accountId)
@@ -56,7 +60,7 @@ public class SetInactiveFlag {
"Deactivate Account via API",
accountId,
(a, u) -> {
- if (!a.getAccount().isActive()) {
+ if (!a.account().isActive()) {
alreadyInactive.set(true);
} else {
try {
@@ -76,6 +80,12 @@ public class SetInactiveFlag {
if (alreadyInactive.get()) {
throw new ResourceConflictException("account not active");
}
+
+ // At this point the account got set inactive and no errors occurred
+
+ int id = accountId.get();
+ accountActivationListeners.runEach(l -> l.onAccountDeactivated(id));
+
return Response.none();
}
@@ -89,7 +99,7 @@ public class SetInactiveFlag {
"Activate Account via API",
accountId,
(a, u) -> {
- if (a.getAccount().isActive()) {
+ if (a.account().isActive()) {
alreadyActive.set(true);
} else {
try {
@@ -106,6 +116,16 @@ public class SetInactiveFlag {
if (exception.get().isPresent()) {
throw exception.get().get();
}
- return alreadyActive.get() ? Response.ok("") : Response.created("");
+
+ Response<String> res;
+ if (alreadyActive.get()) {
+ res = Response.ok("");
+ } else {
+ res = Response.created("");
+
+ int id = accountId.get();
+ accountActivationListeners.runEach(l -> l.onAccountActivated(id));
+ }
+ return res;
}
}
diff --git a/java/com/google/gerrit/server/account/StoredPreferences.java b/java/com/google/gerrit/server/account/StoredPreferences.java
new file mode 100644
index 0000000000..0e8eb04af1
--- /dev/null
+++ b/java/com/google/gerrit/server/account/StoredPreferences.java
@@ -0,0 +1,574 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
+import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE;
+import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.MenuItem;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.git.ValidationError;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.git.meta.VersionedMetaData;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Parses/writes preferences from/to a {@link Config} file.
+ *
+ * <p>This is a low-level API. Read/write of preferences in a user branch should be done through
+ * {@link AccountsUpdate} or {@link AccountConfig}.
+ *
+ * <p>The config file has separate sections for general, diff and edit preferences:
+ *
+ * <pre>
+ * [diff]
+ * hideTopMenu = true
+ * [edit]
+ * lineLength = 80
+ * </pre>
+ *
+ * <p>The parameter names match the names that are used in the preferences REST API.
+ *
+ * <p>If the preference is omitted in the config file, then the default value for the preference is
+ * used.
+ *
+ * <p>Defaults for preferences that apply for all accounts can be configured in the {@code
+ * refs/users/default} branch in the {@code All-Users} repository. The config for the default
+ * preferences must be provided to this class so that it can read default values from it.
+ *
+ * <p>The preferences are lazily parsed.
+ */
+public class StoredPreferences {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ public static final String PREFERENCES_CONFIG = "preferences.config";
+
+ private final Account.Id accountId;
+ private final Config cfg;
+ private final Config defaultCfg;
+ private final ValidationError.Sink validationErrorSink;
+
+ private GeneralPreferencesInfo generalPreferences;
+ private DiffPreferencesInfo diffPreferences;
+ private EditPreferencesInfo editPreferences;
+
+ StoredPreferences(
+ Account.Id accountId,
+ Config cfg,
+ Config defaultCfg,
+ ValidationError.Sink validationErrorSink) {
+ this.accountId = requireNonNull(accountId, "accountId");
+ this.cfg = requireNonNull(cfg, "cfg");
+ this.defaultCfg = requireNonNull(defaultCfg, "defaultCfg");
+ this.validationErrorSink = requireNonNull(validationErrorSink, "validationErrorSink");
+ }
+
+ public GeneralPreferencesInfo getGeneralPreferences() {
+ if (generalPreferences == null) {
+ parse();
+ }
+ return generalPreferences;
+ }
+
+ public DiffPreferencesInfo getDiffPreferences() {
+ if (diffPreferences == null) {
+ parse();
+ }
+ return diffPreferences;
+ }
+
+ public EditPreferencesInfo getEditPreferences() {
+ if (editPreferences == null) {
+ parse();
+ }
+ return editPreferences;
+ }
+
+ public void parse() {
+ generalPreferences = parseGeneralPreferences(null);
+ diffPreferences = parseDiffPreferences(null);
+ editPreferences = parseEditPreferences(null);
+ }
+
+ public Config saveGeneralPreferences(
+ Optional<GeneralPreferencesInfo> generalPreferencesInput,
+ Optional<DiffPreferencesInfo> diffPreferencesInput,
+ Optional<EditPreferencesInfo> editPreferencesInput)
+ throws ConfigInvalidException {
+ if (generalPreferencesInput.isPresent()) {
+ GeneralPreferencesInfo mergedGeneralPreferencesInput =
+ parseGeneralPreferences(generalPreferencesInput.get());
+
+ storeSection(
+ cfg,
+ UserConfigSections.GENERAL,
+ null,
+ mergedGeneralPreferencesInput,
+ parseDefaultGeneralPreferences(defaultCfg, null));
+ setChangeTable(cfg, mergedGeneralPreferencesInput.changeTable);
+ setMy(cfg, mergedGeneralPreferencesInput.my);
+
+ // evict the cached general preferences
+ this.generalPreferences = null;
+ }
+
+ if (diffPreferencesInput.isPresent()) {
+ DiffPreferencesInfo mergedDiffPreferencesInput =
+ parseDiffPreferences(diffPreferencesInput.get());
+
+ storeSection(
+ cfg,
+ UserConfigSections.DIFF,
+ null,
+ mergedDiffPreferencesInput,
+ parseDefaultDiffPreferences(defaultCfg, null));
+
+ // evict the cached diff preferences
+ this.diffPreferences = null;
+ }
+
+ if (editPreferencesInput.isPresent()) {
+ EditPreferencesInfo mergedEditPreferencesInput =
+ parseEditPreferences(editPreferencesInput.get());
+
+ storeSection(
+ cfg,
+ UserConfigSections.EDIT,
+ null,
+ mergedEditPreferencesInput,
+ parseDefaultEditPreferences(defaultCfg, null));
+
+ // evict the cached edit preferences
+ this.editPreferences = null;
+ }
+
+ return cfg;
+ }
+
+ private GeneralPreferencesInfo parseGeneralPreferences(@Nullable GeneralPreferencesInfo input) {
+ try {
+ return parseGeneralPreferences(cfg, defaultCfg, input);
+ } catch (ConfigInvalidException e) {
+ validationErrorSink.error(
+ new ValidationError(
+ PREFERENCES_CONFIG,
+ String.format(
+ "Invalid general preferences for account %d: %s",
+ accountId.get(), e.getMessage())));
+ return new GeneralPreferencesInfo();
+ }
+ }
+
+ private DiffPreferencesInfo parseDiffPreferences(@Nullable DiffPreferencesInfo input) {
+ try {
+ return parseDiffPreferences(cfg, defaultCfg, input);
+ } catch (ConfigInvalidException e) {
+ validationErrorSink.error(
+ new ValidationError(
+ PREFERENCES_CONFIG,
+ String.format(
+ "Invalid diff preferences for account %d: %s", accountId.get(), e.getMessage())));
+ return new DiffPreferencesInfo();
+ }
+ }
+
+ private EditPreferencesInfo parseEditPreferences(@Nullable EditPreferencesInfo input) {
+ try {
+ return parseEditPreferences(cfg, defaultCfg, input);
+ } catch (ConfigInvalidException e) {
+ validationErrorSink.error(
+ new ValidationError(
+ PREFERENCES_CONFIG,
+ String.format(
+ "Invalid edit preferences for account %d: %s", accountId.get(), e.getMessage())));
+ return new EditPreferencesInfo();
+ }
+ }
+
+ private static GeneralPreferencesInfo parseGeneralPreferences(
+ Config cfg, @Nullable Config defaultCfg, @Nullable GeneralPreferencesInfo input)
+ throws ConfigInvalidException {
+ GeneralPreferencesInfo r =
+ loadSection(
+ cfg,
+ UserConfigSections.GENERAL,
+ null,
+ new GeneralPreferencesInfo(),
+ defaultCfg != null
+ ? parseDefaultGeneralPreferences(defaultCfg, input)
+ : GeneralPreferencesInfo.defaults(),
+ input);
+ if (input != null) {
+ r.changeTable = input.changeTable;
+ r.my = input.my;
+ } else {
+ r.changeTable = parseChangeTableColumns(cfg, defaultCfg);
+ r.my = parseMyMenus(cfg, defaultCfg);
+ }
+ return r;
+ }
+
+ private static DiffPreferencesInfo parseDiffPreferences(
+ Config cfg, @Nullable Config defaultCfg, @Nullable DiffPreferencesInfo input)
+ throws ConfigInvalidException {
+ return loadSection(
+ cfg,
+ UserConfigSections.DIFF,
+ null,
+ new DiffPreferencesInfo(),
+ defaultCfg != null
+ ? parseDefaultDiffPreferences(defaultCfg, input)
+ : DiffPreferencesInfo.defaults(),
+ input);
+ }
+
+ private static EditPreferencesInfo parseEditPreferences(
+ Config cfg, @Nullable Config defaultCfg, @Nullable EditPreferencesInfo input)
+ throws ConfigInvalidException {
+ return loadSection(
+ cfg,
+ UserConfigSections.EDIT,
+ null,
+ new EditPreferencesInfo(),
+ defaultCfg != null
+ ? parseDefaultEditPreferences(defaultCfg, input)
+ : EditPreferencesInfo.defaults(),
+ input);
+ }
+
+ private static GeneralPreferencesInfo parseDefaultGeneralPreferences(
+ Config defaultCfg, GeneralPreferencesInfo input) throws ConfigInvalidException {
+ GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
+ loadSection(
+ defaultCfg,
+ UserConfigSections.GENERAL,
+ null,
+ allUserPrefs,
+ GeneralPreferencesInfo.defaults(),
+ input);
+ return updateGeneralPreferencesDefaults(allUserPrefs);
+ }
+
+ private static DiffPreferencesInfo parseDefaultDiffPreferences(
+ Config defaultCfg, DiffPreferencesInfo input) throws ConfigInvalidException {
+ DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+ loadSection(
+ defaultCfg,
+ UserConfigSections.DIFF,
+ null,
+ allUserPrefs,
+ DiffPreferencesInfo.defaults(),
+ input);
+ return updateDiffPreferencesDefaults(allUserPrefs);
+ }
+
+ private static EditPreferencesInfo parseDefaultEditPreferences(
+ Config defaultCfg, EditPreferencesInfo input) throws ConfigInvalidException {
+ EditPreferencesInfo allUserPrefs = new EditPreferencesInfo();
+ loadSection(
+ defaultCfg,
+ UserConfigSections.EDIT,
+ null,
+ allUserPrefs,
+ EditPreferencesInfo.defaults(),
+ input);
+ return updateEditPreferencesDefaults(allUserPrefs);
+ }
+
+ private static GeneralPreferencesInfo updateGeneralPreferencesDefaults(
+ GeneralPreferencesInfo input) {
+ GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
+ try {
+ for (Field field : input.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object newVal = field.get(input);
+ if (newVal != null) {
+ field.set(result, newVal);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ logger.atSevere().withCause(e).log("Failed to apply default general preferences");
+ return GeneralPreferencesInfo.defaults();
+ }
+ return result;
+ }
+
+ private static DiffPreferencesInfo updateDiffPreferencesDefaults(DiffPreferencesInfo input) {
+ DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
+ try {
+ for (Field field : input.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object newVal = field.get(input);
+ if (newVal != null) {
+ field.set(result, newVal);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ logger.atSevere().withCause(e).log("Failed to apply default diff preferences");
+ return DiffPreferencesInfo.defaults();
+ }
+ return result;
+ }
+
+ private static EditPreferencesInfo updateEditPreferencesDefaults(EditPreferencesInfo input) {
+ EditPreferencesInfo result = EditPreferencesInfo.defaults();
+ try {
+ for (Field field : input.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object newVal = field.get(input);
+ if (newVal != null) {
+ field.set(result, newVal);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ logger.atSevere().withCause(e).log("Failed to apply default edit preferences");
+ return EditPreferencesInfo.defaults();
+ }
+ return result;
+ }
+
+ private static List<String> parseChangeTableColumns(Config cfg, @Nullable Config defaultCfg) {
+ List<String> changeTable = changeTable(cfg);
+ if (changeTable == null && defaultCfg != null) {
+ changeTable = changeTable(defaultCfg);
+ }
+ return changeTable;
+ }
+
+ private static List<MenuItem> parseMyMenus(Config cfg, @Nullable Config defaultCfg) {
+ List<MenuItem> my = my(cfg);
+ if (my.isEmpty() && defaultCfg != null) {
+ my = my(defaultCfg);
+ }
+ if (my.isEmpty()) {
+ my.add(new MenuItem("Changes", "#/dashboard/self", null));
+ my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
+ my.add(new MenuItem("Edits", "#/q/has:edit", null));
+ my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
+ my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
+ my.add(new MenuItem("Groups", "#/settings/#Groups", null));
+ }
+ return my;
+ }
+
+ public static GeneralPreferencesInfo readDefaultGeneralPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return parseGeneralPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
+ }
+
+ public static DiffPreferencesInfo readDefaultDiffPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return parseDiffPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
+ }
+
+ public static EditPreferencesInfo readDefaultEditPreferences(
+ AllUsersName allUsersName, Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return parseEditPreferences(readDefaultConfig(allUsersName, allUsersRepo), null, null);
+ }
+
+ static Config readDefaultConfig(AllUsersName allUsersName, Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(allUsersName, allUsersRepo);
+ return defaultPrefs.getConfig();
+ }
+
+ public static GeneralPreferencesInfo updateDefaultGeneralPreferences(
+ MetaDataUpdate md, GeneralPreferencesInfo input) throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(md);
+ storeSection(
+ defaultPrefs.getConfig(),
+ UserConfigSections.GENERAL,
+ null,
+ input,
+ GeneralPreferencesInfo.defaults());
+ setMy(defaultPrefs.getConfig(), input.my);
+ setChangeTable(defaultPrefs.getConfig(), input.changeTable);
+ defaultPrefs.commit(md);
+
+ return parseGeneralPreferences(defaultPrefs.getConfig(), null, null);
+ }
+
+ public static DiffPreferencesInfo updateDefaultDiffPreferences(
+ MetaDataUpdate md, DiffPreferencesInfo input) throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(md);
+ storeSection(
+ defaultPrefs.getConfig(),
+ UserConfigSections.DIFF,
+ null,
+ input,
+ DiffPreferencesInfo.defaults());
+ defaultPrefs.commit(md);
+
+ return parseDiffPreferences(defaultPrefs.getConfig(), null, null);
+ }
+
+ public static EditPreferencesInfo updateDefaultEditPreferences(
+ MetaDataUpdate md, EditPreferencesInfo input) throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(md);
+ storeSection(
+ defaultPrefs.getConfig(),
+ UserConfigSections.EDIT,
+ null,
+ input,
+ EditPreferencesInfo.defaults());
+ defaultPrefs.commit(md);
+
+ return parseEditPreferences(defaultPrefs.getConfig(), null, null);
+ }
+
+ private static List<String> changeTable(Config cfg) {
+ return Lists.newArrayList(cfg.getStringList(CHANGE_TABLE, null, CHANGE_TABLE_COLUMN));
+ }
+
+ private static void setChangeTable(Config cfg, List<String> changeTable) {
+ if (changeTable != null) {
+ unsetSection(cfg, UserConfigSections.CHANGE_TABLE);
+ cfg.setStringList(UserConfigSections.CHANGE_TABLE, null, CHANGE_TABLE_COLUMN, changeTable);
+ }
+ }
+
+ private static List<MenuItem> my(Config cfg) {
+ List<MenuItem> my = new ArrayList<>();
+ for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
+ String url = my(cfg, subsection, KEY_URL, "#/");
+ String target = my(cfg, subsection, KEY_TARGET, url.startsWith("#") ? null : "_blank");
+ my.add(new MenuItem(subsection, url, target, my(cfg, subsection, KEY_ID, null)));
+ }
+ return my;
+ }
+
+ private static String my(Config cfg, String subsection, String key, String defaultValue) {
+ String val = cfg.getString(UserConfigSections.MY, subsection, key);
+ return !Strings.isNullOrEmpty(val) ? val : defaultValue;
+ }
+
+ private static void setMy(Config cfg, List<MenuItem> my) {
+ if (my != null) {
+ unsetSection(cfg, UserConfigSections.MY);
+ for (MenuItem item : my) {
+ checkState(!isNullOrEmpty(item.name), "MenuItem.name must not be null or empty");
+ checkState(!isNullOrEmpty(item.url), "MenuItem.url must not be null or empty");
+
+ setMy(cfg, item.name, KEY_URL, item.url);
+ setMy(cfg, item.name, KEY_TARGET, item.target);
+ setMy(cfg, item.name, KEY_ID, item.id);
+ }
+ }
+ }
+
+ public static void validateMy(List<MenuItem> my) throws BadRequestException {
+ if (my == null) {
+ return;
+ }
+ for (MenuItem item : my) {
+ checkRequiredMenuItemField(item.name, "name");
+ checkRequiredMenuItemField(item.url, "URL");
+ }
+ }
+
+ private static void checkRequiredMenuItemField(String value, String name)
+ throws BadRequestException {
+ if (isNullOrEmpty(value)) {
+ throw new BadRequestException(name + " for menu item is required");
+ }
+ }
+
+ private static boolean isNullOrEmpty(String value) {
+ return value == null || value.trim().isEmpty();
+ }
+
+ private static void setMy(Config cfg, String section, String key, @Nullable String val) {
+ if (val == null || val.trim().isEmpty()) {
+ cfg.unset(UserConfigSections.MY, section.trim(), key);
+ } else {
+ cfg.setString(UserConfigSections.MY, section.trim(), key, val.trim());
+ }
+ }
+
+ private static void unsetSection(Config cfg, String section) {
+ cfg.unsetSection(section, null);
+ for (String subsection : cfg.getSubsections(section)) {
+ cfg.unsetSection(section, subsection);
+ }
+ }
+
+ private static class VersionedDefaultPreferences extends VersionedMetaData {
+ private Config cfg;
+
+ @Override
+ protected String getRefName() {
+ return RefNames.REFS_USERS_DEFAULT;
+ }
+
+ private Config getConfig() {
+ checkState(cfg != null, "Default preferences not loaded yet.");
+ return cfg;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ cfg = readConfig(PREFERENCES_CONFIG);
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Update default preferences\n");
+ }
+ saveConfig(PREFERENCES_CONFIG, cfg);
+ return true;
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index ecd7468632..fddbd2ba01 100644
--- a/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -26,7 +26,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StartupCheck;
import com.google.gerrit.server.StartupException;
@@ -220,7 +220,7 @@ public class UniversalGroupBackend implements GroupBackend {
cfg.getSubsections("groups").stream()
.filter(
sub -> {
- AccountGroup.UUID uuid = new AccountGroup.UUID(sub);
+ AccountGroup.UUID uuid = AccountGroup.uuid(sub);
GroupBackend groupBackend = universalGroupBackend.backend(uuid);
return groupBackend == null || groupBackend.get(uuid) == null;
})
diff --git a/java/com/google/gerrit/server/account/VersionedAccountDestinations.java b/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
index e2f1bc222c..f1cf9fe6db 100644
--- a/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
+++ b/java/com/google/gerrit/server/account/VersionedAccountDestinations.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.account;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
diff --git a/java/com/google/gerrit/server/account/VersionedAccountQueries.java b/java/com/google/gerrit/server/account/VersionedAccountQueries.java
index daf7100568..e2ffe9bde7 100644
--- a/java/com/google/gerrit/server/account/VersionedAccountQueries.java
+++ b/java/com/google/gerrit/server/account/VersionedAccountQueries.java
@@ -14,11 +14,17 @@
package com.google.gerrit.server.account;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -46,6 +52,16 @@ public class VersionedAccountQueries extends VersionedMetaData {
return queryList;
}
+ public void setQueryList(String text) throws IOException, ConfigInvalidException {
+ List<ValidationError> errors = new ArrayList<>();
+ QueryList newQueryList = QueryList.parse(text, error -> errors.add(error));
+ if (!errors.isEmpty()) {
+ String messages = errors.stream().map(ValidationError::getMessage).collect(joining(", "));
+ throw new ConfigInvalidException("Invalid named queries: " + messages);
+ }
+ queryList = newQueryList;
+ }
+
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
queryList =
@@ -58,6 +74,10 @@ public class VersionedAccountQueries extends VersionedMetaData {
@Override
protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
- throw new UnsupportedOperationException("Cannot yet save named queries");
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Updated named queries\n");
+ }
+ saveUTF8(QueryList.FILE_NAME, queryList.asText());
+ return true;
}
}
diff --git a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index c7808de9b5..235537c716 100644
--- a/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -20,9 +20,9 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
import com.google.common.collect.Ordering;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidSshKeyException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
diff --git a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
index 5d12ae101b..4da2a9eff3 100644
--- a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
@@ -21,8 +21,8 @@ import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
@@ -96,7 +96,7 @@ public abstract class AllExternalIds {
private static ExternalId toExternalId(ObjectIdConverter idConverter, ExternalIdProto proto) {
return ExternalId.create(
ExternalId.Key.parse(proto.getKey()),
- new Account.Id(proto.getAccountId()),
+ Account.id(proto.getAccountId()),
// ExternalId treats null and empty strings the same, so no need to distinguish here.
proto.getEmail(),
proto.getPassword(),
diff --git a/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java b/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
index 589405129b..e1e9c70842 100644
--- a/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
+++ b/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.account.externalids;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import java.io.IOException;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index ab8271493e..2d501ad37f 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -27,8 +27,9 @@ import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.server.account.HashedPassword;
import java.io.Serializable;
import java.util.Collection;
@@ -39,7 +40,6 @@ import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@AutoValue
@@ -364,7 +364,7 @@ public abstract class ExternalId implements Serializable {
return create(
externalIdKey,
- new Account.Id(accountId),
+ Account.id(accountId),
Strings.emptyToNull(email),
Strings.emptyToNull(password),
blobId);
@@ -432,10 +432,10 @@ public abstract class ExternalId implements Serializable {
public byte[] toByteArray() {
checkState(blobId() != null, "Missing blobId in external ID %s", key().get());
- byte[] b = new byte[2 * Constants.OBJECT_ID_STRING_LENGTH + 1];
+ byte[] b = new byte[2 * ObjectIds.STR_LEN + 1];
key().sha1().copyTo(b, 0);
- b[Constants.OBJECT_ID_STRING_LENGTH] = ':';
- blobId().copyTo(b, Constants.OBJECT_ID_STRING_LENGTH + 1);
+ b[ObjectIds.STR_LEN] = ':';
+ blobId().copyTo(b, ObjectIds.STR_LEN + 1);
return b;
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
index 1ac737e2a9..0edf15481d 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.account.externalids;
import com.google.common.collect.SetMultimap;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
index 5aa19d88f5..84b25c0167 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
@@ -14,16 +14,12 @@
package com.google.gerrit.server.account.externalids;
-import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.entities.Account;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
@@ -146,23 +142,4 @@ class ExternalIdCacheImpl implements ExternalIdCache {
lock.unlock();
}
}
-
- static class Loader extends CacheLoader<ObjectId, AllExternalIds> {
- private final ExternalIdReader externalIdReader;
-
- @Inject
- Loader(ExternalIdReader externalIdReader) {
- this.externalIdReader = externalIdReader;
- }
-
- @Override
- public AllExternalIds load(ObjectId notesRev) throws Exception {
- try (TraceTimer timer =
- TraceContext.newTimer("Loading external IDs (revision=%s)", notesRev)) {
- ImmutableSet<ExternalId> externalIds = externalIdReader.all(notesRev);
- externalIds.forEach(ExternalId::checkThatBlobIdIsSet);
- return AllExternalIds.create(externalIds);
- }
- }
- }
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
new file mode 100644
index 0000000000..8887e064cd
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
@@ -0,0 +1,277 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.metrics.Counter1;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/** Loads cache values for the external ID cache using either a full or a partial reload. */
+@Singleton
+public class ExternalIdCacheLoader extends CacheLoader<ObjectId, AllExternalIds> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ // Maximum number of prior states we inspect to find a base for differential. If no cached state
+ // is found within this number of parents, we fall back to reading everything from scratch.
+ private static final int MAX_HISTORY_LOOKBACK = 10;
+
+ private final ExternalIdReader externalIdReader;
+ private final Provider<Cache<ObjectId, AllExternalIds>> externalIdCache;
+ private final GitRepositoryManager gitRepositoryManager;
+ private final AllUsersName allUsersName;
+ private final Counter1<Boolean> reloadCounter;
+ private final Timer0 reloadDifferential;
+ private final boolean enablePartialReloads;
+ private final boolean isPersistentCache;
+
+ @Inject
+ ExternalIdCacheLoader(
+ GitRepositoryManager gitRepositoryManager,
+ AllUsersName allUsersName,
+ ExternalIdReader externalIdReader,
+ @Named(ExternalIdCacheImpl.CACHE_NAME)
+ Provider<Cache<ObjectId, AllExternalIds>> externalIdCache,
+ MetricMaker metricMaker,
+ @GerritServerConfig Config config) {
+ this.externalIdReader = externalIdReader;
+ this.externalIdCache = externalIdCache;
+ this.gitRepositoryManager = gitRepositoryManager;
+ this.allUsersName = allUsersName;
+ this.reloadCounter =
+ metricMaker.newCounter(
+ "notedb/external_id_cache_load_count",
+ new Description("Total number of external ID cache reloads from Git.")
+ .setRate()
+ .setUnit("updates"),
+ Field.ofBoolean("partial", Metadata.Builder::partial).build());
+ this.reloadDifferential =
+ metricMaker.newTimer(
+ "notedb/external_id_partial_read_latency",
+ new Description(
+ "Latency for generating a new external ID cache state from a prior state.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
+ this.enablePartialReloads =
+ config.getBoolean("cache", ExternalIdCacheImpl.CACHE_NAME, "enablePartialReloads", true);
+ this.isPersistentCache =
+ config.getInt("cache", ExternalIdCacheImpl.CACHE_NAME, "diskLimit", 0) > 0;
+ }
+
+ @Override
+ public AllExternalIds load(ObjectId notesRev) throws IOException, ConfigInvalidException {
+ if (!enablePartialReloads) {
+ logger.atInfo().log(
+ "Partial reloads of "
+ + ExternalIdCacheImpl.CACHE_NAME
+ + " disabled. Falling back to full reload.");
+ return reloadAllExternalIds(notesRev);
+ }
+
+ // The requested value was not in the cache (hence, this loader was invoked). Therefore, try to
+ // create this entry from a past value using the minimal amount of Git operations possible to
+ // reduce latency.
+ //
+ // First, try to find the most recent state we have in the cache. Most of the time, this will be
+ // the state before the last update happened, but it can also date further back. We try a best
+ // effort approach and check the last 10 states. If nothing is found, we default to loading the
+ // value from scratch.
+ //
+ // If a prior state was found, we use Git to diff the trees and find modifications. This is
+ // faster than just loading the complete current tree and working off of that because of how the
+ // data is structured: NotesMaps use nested trees, so, for example, a NotesMap with 200k entries
+ // has two layers of nesting: 12/34/1234..99. TreeWalk is smart in skipping the traversal of
+ // identical subtrees.
+ //
+ // Once we know what files changed, we apply additions and removals to the previously cached
+ // state.
+
+ try (Repository repo = gitRepositoryManager.openRepository(allUsersName);
+ RevWalk rw = new RevWalk(repo)) {
+ long start = System.nanoTime();
+ Ref extIdRef = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
+ if (extIdRef == null) {
+ logger.atInfo().log(
+ RefNames.REFS_EXTERNAL_IDS + " not initialized, falling back to full reload.");
+ return reloadAllExternalIds(notesRev);
+ }
+
+ RevCommit currentCommit = rw.parseCommit(extIdRef.getObjectId());
+ rw.markStart(currentCommit);
+ RevCommit parentWithCacheValue;
+ AllExternalIds oldExternalIds = null;
+ int i = 0;
+ while ((parentWithCacheValue = rw.next()) != null
+ && i++ < MAX_HISTORY_LOOKBACK
+ && parentWithCacheValue.getParentCount() < 2) {
+ oldExternalIds = externalIdCache.get().getIfPresent(parentWithCacheValue.getId());
+ if (oldExternalIds != null) {
+ // We found a previously cached state.
+ break;
+ }
+ }
+ if (oldExternalIds == null) {
+ if (isPersistentCache) {
+ // If there is no persistence, this is normal. Don't upset admins reading the logs.
+ logger.atWarning().log(
+ "Unable to find an old ExternalId cache state, falling back to full reload");
+ }
+ return reloadAllExternalIds(notesRev);
+ }
+
+ // Diff trees to recognize modifications
+ Set<ObjectId> removals = new HashSet<>(); // Set<Blob-Object-Id>
+ Map<ObjectId, ObjectId> additions = new HashMap<>(); // Map<Name-ObjectId, Blob-Object-Id>
+ try (TreeWalk treeWalk = new TreeWalk(repo)) {
+ treeWalk.setFilter(TreeFilter.ANY_DIFF);
+ treeWalk.setRecursive(true);
+ treeWalk.reset(parentWithCacheValue.getTree(), currentCommit.getTree());
+ while (treeWalk.next()) {
+ String path = treeWalk.getPathString();
+ ObjectId oldBlob = treeWalk.getObjectId(0);
+ ObjectId newBlob = treeWalk.getObjectId(1);
+ if (ObjectId.zeroId().equals(newBlob)) {
+ // Deletion
+ removals.add(oldBlob);
+ } else if (ObjectId.zeroId().equals(oldBlob)) {
+ // Addition
+ additions.put(fileNameToObjectId(path), newBlob);
+ } else {
+ // Modification
+ removals.add(oldBlob);
+ additions.put(fileNameToObjectId(path), newBlob);
+ }
+ }
+ }
+
+ AllExternalIds allExternalIds =
+ buildAllExternalIds(repo, oldExternalIds, additions, removals);
+ reloadCounter.increment(true);
+ reloadDifferential.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
+ return allExternalIds;
+ }
+ }
+
+ private static ObjectId fileNameToObjectId(String path) {
+ return ObjectId.fromString(CharMatcher.is('/').removeFrom(path));
+ }
+
+ /**
+ * Build a new {@link AllExternalIds} from an old state by applying additions and removals that
+ * were performed since then.
+ *
+ * <p>Removals are applied before additions.
+ *
+ * @param repo open repository
+ * @param oldExternalIds prior state that is used as base
+ * @param additions map of name to blob ID for each external ID that should be added
+ * @param removals set of name {@link ObjectId}s that should be removed
+ */
+ private static AllExternalIds buildAllExternalIds(
+ Repository repo,
+ AllExternalIds oldExternalIds,
+ Map<ObjectId, ObjectId> additions,
+ Set<ObjectId> removals)
+ throws IOException {
+ ImmutableSetMultimap.Builder<Account.Id, ExternalId> byAccount = ImmutableSetMultimap.builder();
+ ImmutableSetMultimap.Builder<String, ExternalId> byEmail = ImmutableSetMultimap.builder();
+
+ // Copy over old ExternalIds but exclude deleted ones
+ for (ExternalId externalId : oldExternalIds.byAccount().values()) {
+ if (removals.contains(externalId.blobId())) {
+ continue;
+ }
+
+ byAccount.put(externalId.accountId(), externalId);
+ if (externalId.email() != null) {
+ byEmail.put(externalId.email(), externalId);
+ }
+ }
+
+ // Add newly discovered ExternalIds
+ try (ObjectReader reader = repo.newObjectReader()) {
+ for (Map.Entry<ObjectId, ObjectId> nameToBlob : additions.entrySet()) {
+ ExternalId parsedExternalId;
+ try {
+ parsedExternalId =
+ ExternalId.parse(
+ nameToBlob.getKey().name(),
+ reader.open(nameToBlob.getValue()).getCachedBytes(),
+ nameToBlob.getValue());
+ } catch (ConfigInvalidException | RuntimeException e) {
+ logger.atSevere().withCause(e).log(
+ "Ignoring invalid external ID note %s", nameToBlob.getKey().name());
+ continue;
+ }
+
+ byAccount.put(parsedExternalId.accountId(), parsedExternalId);
+ if (parsedExternalId.email() != null) {
+ byEmail.put(parsedExternalId.email(), parsedExternalId);
+ }
+ }
+ }
+ return new AutoValue_AllExternalIds(byAccount.build(), byEmail.build());
+ }
+
+ private AllExternalIds reloadAllExternalIds(ObjectId notesRev)
+ throws IOException, ConfigInvalidException {
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Loading external IDs from scratch",
+ Metadata.builder().revision(notesRev.name()).build())) {
+ ImmutableSet<ExternalId> externalIds = externalIdReader.all(notesRev);
+ externalIds.forEach(ExternalId::checkThatBlobIdIsSet);
+ AllExternalIds allExternalIds = AllExternalIds.create(externalIds);
+ reloadCounter.increment(false);
+ return allExternalIds;
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
index fc311e7aab..3e5d7b8340 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdModule.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.account.externalids;
-import com.google.gerrit.server.account.externalids.ExternalIdCacheImpl.Loader;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
import com.google.inject.TypeLiteral;
@@ -31,9 +30,12 @@ public class ExternalIdModule extends CacheModule {
// from the cache. Extend the cache size by 1 to cover this case, but expire the extra
// object after a short period of time, since it may be a potentially large amount of
// memory.
+ // When loading a new value because the primary data advanced, we want to leverage the old
+ // cache state to recompute only what changed. This doesn't affect cache size though as
+ // Guava calls the loader first and evicts later on.
.maximumWeight(2)
.expireFromMemoryAfterAccess(Duration.ofMinutes(1))
- .loader(Loader.class)
+ .loader(ExternalIdCacheLoader.class)
.diskLimit(-1)
.version(1)
.keySerializer(ObjectIdCacheSerializer.INSTANCE)
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index 9c35232acc..ec4f55344d 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -28,17 +28,21 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.index.account.AccountIndexer;
+import com.google.gerrit.server.logging.CallerFinder;
+import com.google.gerrit.server.update.RetryHelper;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -264,6 +268,7 @@ public class ExternalIdNotes extends VersionedMetaData {
private final AllUsersName allUsersName;
private final Counter0 updateCount;
private final Repository repo;
+ private final CallerFinder callerFinder;
private NoteMap noteMap;
private ObjectId oldRev;
@@ -293,6 +298,19 @@ public class ExternalIdNotes extends VersionedMetaData {
new Description("Total number of external ID updates.").setRate().setUnit("updates"));
this.allUsersName = requireNonNull(allUsersName, "allUsersRepo");
this.repo = requireNonNull(allUsersRepo, "allUsersRepo");
+ this.callerFinder =
+ CallerFinder.builder()
+ // 1. callers that come through ExternalIds
+ .addTarget(ExternalIds.class)
+
+ // 2. callers that come through AccountsUpdate
+ .addTarget(AccountsUpdate.class)
+ .addIgnoredPackage("com.github.rholder.retry")
+ .addIgnoredClass(RetryHelper.class)
+
+ // 3. direct callers
+ .addTarget(ExternalIdNotes.class)
+ .build();
}
public ExternalIdNotes setAfterReadRevision(Runnable afterReadRevision) {
@@ -657,7 +675,8 @@ public class ExternalIdNotes extends VersionedMetaData {
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
if (revision != null) {
- logger.atFine().log("Reading external ID note map");
+ logger.atFine().log(
+ "Reading external ID note map (caller: %s)", callerFinder.findCallerLazy());
noteMap = NoteMap.read(reader, revision);
} else {
noteMap = NoteMap.newEmptyMap();
@@ -670,7 +689,7 @@ public class ExternalIdNotes extends VersionedMetaData {
@Override
public RevCommit commit(MetaDataUpdate update) throws IOException {
- oldRev = revision != null ? revision.copy() : ObjectId.zeroId();
+ oldRev = ObjectIds.copyOrZero(revision);
RevCommit commit = super.commit(update);
updateCount.increment();
return commit;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index cf5500ee81..6334265ca7 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -17,11 +17,11 @@ package com.google.gerrit.server.account.externalids;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIds.java b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
index 9098630f02..28d3af2d20 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
@@ -18,7 +18,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
index 489748771f..815f7d0dae 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
@@ -30,7 +30,6 @@ import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import org.apache.commons.codec.DecoderException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -134,7 +133,7 @@ public class ExternalIdsConsistencyChecker {
if (extId.password() != null && extId.isScheme(SCHEME_USERNAME)) {
try {
HashedPassword.decode(extId.password());
- } catch (DecoderException e) {
+ } catch (HashedPassword.DecoderException e) {
addError(
String.format(
"External ID '%s' has an invalid password: %s", extId.key().get(), e.getMessage()),
diff --git a/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java b/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java
new file mode 100644
index 0000000000..3f2f7749df
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/PasswordVerifier.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+
+import com.google.common.base.Strings;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.account.HashedPassword;
+import java.util.Collection;
+
+/** Checks if a given username and password match a user's external IDs. */
+public class PasswordVerifier {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ /** Returns {@code true} if there is an external ID matching both the username and password. */
+ public static boolean checkPassword(
+ Collection<ExternalId> externalIds, String username, @Nullable String password) {
+ if (password == null) {
+ return false;
+ }
+ for (ExternalId id : externalIds) {
+ // Only process the "username:$USER" entry, which is unique.
+ if (!id.isScheme(SCHEME_USERNAME) || !username.equals(id.key().id())) {
+ continue;
+ }
+
+ String hashedStr = id.password();
+ if (!Strings.isNullOrEmpty(hashedStr)) {
+ try {
+ return HashedPassword.decode(hashedStr).checkPassword(password);
+ } catch (HashedPassword.DecoderException e) {
+ logger.atSevere().log("DecoderException for user %s: %s ", username, e.getMessage());
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/testing/BUILD b/java/com/google/gerrit/server/account/externalids/testing/BUILD
new file mode 100644
index 0000000000..0e469e3206
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/testing/BUILD
@@ -0,0 +1,13 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+java_library(
+ name = "testing",
+ testonly = 1,
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/server",
+ "//lib:jgit",
+ ],
+)
diff --git a/java/com/google/gerrit/server/account/externalids/testing/ExternalIdInserter.java b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdInserter.java
new file mode 100644
index 0000000000..a93192a559
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdInserter.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids.testing;
+
+import java.io.IOException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.notes.NoteMap;
+
+@FunctionalInterface
+public interface ExternalIdInserter {
+ public ObjectId addNote(ObjectInserter ins, NoteMap noteMap) throws IOException;
+}
diff --git a/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java
new file mode 100644
index 0000000000..b8040f7308
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/testing/ExternalIdTestUtil.java
@@ -0,0 +1,163 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids.testing;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdReader;
+import java.io.IOException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Common methods for dealing with external IDs in tests. */
+public class ExternalIdTestUtil {
+
+ public static String insertExternalIdWithoutAccountId(
+ Repository repo, RevWalk rw, PersonIdent ident, Account.Id accountId, String externalId)
+ throws IOException {
+ return insertExternalId(
+ repo,
+ rw,
+ ident,
+ (ins, noteMap) -> {
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), accountId);
+ ObjectId noteId = extId.key().sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ c.unset("externalId", extId.key().get(), "accountId");
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
+ }
+
+ public static String insertExternalIdWithKeyThatDoesntMatchNoteId(
+ Repository repo, RevWalk rw, PersonIdent ident, Account.Id accountId, String externalId)
+ throws IOException {
+ return insertExternalId(
+ repo,
+ rw,
+ ident,
+ (ins, noteMap) -> {
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), accountId);
+ ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
+ }
+
+ public static String insertExternalIdWithInvalidConfig(
+ Repository repo, RevWalk rw, PersonIdent ident, String externalId) throws IOException {
+ return insertExternalId(
+ repo,
+ rw,
+ ident,
+ (ins, noteMap) -> {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "bad-config".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
+ }
+
+ public static String insertExternalIdWithEmptyNote(
+ Repository repo, RevWalk rw, PersonIdent ident, String externalId) throws IOException {
+ return insertExternalId(
+ repo,
+ rw,
+ ident,
+ (ins, noteMap) -> {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
+ }
+
+ private static String insertExternalId(
+ Repository repo, RevWalk rw, PersonIdent ident, ExternalIdInserter extIdInserter)
+ throws IOException {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId noteId = extIdInserter.addNote(ins, noteMap);
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setMessage("Update external IDs");
+ cb.setTreeId(noteMap.writeTree(ins));
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ if (!rev.equals(ObjectId.zeroId())) {
+ cb.setParentId(rev);
+ } else {
+ cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
+ }
+ if (cb.getTreeId() == null) {
+ if (rev.equals(ObjectId.zeroId())) {
+ cb.setTreeId(ins.insert(OBJ_TREE, new byte[] {})); // No parent, assume empty tree.
+ } else {
+ RevCommit p = rw.parseCommit(rev);
+ cb.setTreeId(p.getTree()); // Copy tree from parent.
+ }
+ }
+ ObjectId commitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate u = repo.updateRef(RefNames.REFS_EXTERNAL_IDS);
+ u.setExpectedOldObjectId(rev);
+ u.setNewObjectId(commitId);
+ RefUpdate.Result res = u.update();
+ switch (res) {
+ case NEW:
+ case FAST_FORWARD:
+ case NO_CHANGE:
+ case RENAMED:
+ case FORCED:
+ break;
+ case LOCK_FAILURE:
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new IOException("Updating external IDs failed with " + res);
+ }
+ return noteId.getName();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/api/BUILD b/java/com/google/gerrit/server/api/BUILD
index 37e04bb588..0275c797ba 100644
--- a/java/com/google/gerrit/server/api/BUILD
+++ b/java/com/google/gerrit/server/api/BUILD
@@ -9,17 +9,17 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/util/cli",
"//lib:args4j",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 673f5aede1..7fa976716b 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -240,7 +240,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public AccountDetailInfo detail() throws RestApiException {
try {
- return getDetail.apply(account);
+ return getDetail.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get detail", e);
}
@@ -248,8 +248,12 @@ public class AccountApiImpl implements AccountApi {
@Override
public boolean getActive() throws RestApiException {
- Response<String> result = getActive.apply(account);
- return result.statusCode() == SC_OK && result.value().equals("ok");
+ try {
+ Response<String> result = getActive.apply(account);
+ return result.statusCode() == SC_OK && result.value().equals("ok");
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get active", e);
+ }
}
@Override
@@ -274,7 +278,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public GeneralPreferencesInfo getPreferences() throws RestApiException {
try {
- return getPreferences.apply(account);
+ return getPreferences.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get preferences", e);
}
@@ -283,7 +287,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public GeneralPreferencesInfo setPreferences(GeneralPreferencesInfo in) throws RestApiException {
try {
- return setPreferences.apply(account, in);
+ return setPreferences.apply(account, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set preferences", e);
}
@@ -292,7 +296,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public DiffPreferencesInfo getDiffPreferences() throws RestApiException {
try {
- return getDiffPreferences.apply(account);
+ return getDiffPreferences.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot query diff preferences", e);
}
@@ -301,7 +305,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public DiffPreferencesInfo setDiffPreferences(DiffPreferencesInfo in) throws RestApiException {
try {
- return setDiffPreferences.apply(account, in);
+ return setDiffPreferences.apply(account, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set diff preferences", e);
}
@@ -310,7 +314,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public EditPreferencesInfo getEditPreferences() throws RestApiException {
try {
- return getEditPreferences.apply(account);
+ return getEditPreferences.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot query edit preferences", e);
}
@@ -319,7 +323,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public EditPreferencesInfo setEditPreferences(EditPreferencesInfo in) throws RestApiException {
try {
- return setEditPreferences.apply(account, in);
+ return setEditPreferences.apply(account, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set edit preferences", e);
}
@@ -328,7 +332,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<ProjectWatchInfo> getWatchedProjects() throws RestApiException {
try {
- return getWatchedProjects.apply(account);
+ return getWatchedProjects.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get watched projects", e);
}
@@ -338,7 +342,7 @@ public class AccountApiImpl implements AccountApi {
public List<ProjectWatchInfo> setWatchedProjects(List<ProjectWatchInfo> in)
throws RestApiException {
try {
- return postWatchedProjects.apply(account, in);
+ return postWatchedProjects.apply(account, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot update watched projects", e);
}
@@ -389,7 +393,7 @@ public class AccountApiImpl implements AccountApi {
public SortedSet<String> getStars(String changeId) throws RestApiException {
try {
AccountResource.Star rsrc = stars.parse(account, IdString.fromUrl(changeId));
- return starsGet.apply(rsrc);
+ return starsGet.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot get stars", e);
}
@@ -398,7 +402,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<ChangeInfo> getStarredChanges() throws RestApiException {
try {
- return stars.list().apply(account);
+ return stars.list().apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get starred changes", e);
}
@@ -407,7 +411,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<GroupInfo> getGroups() throws RestApiException {
try {
- return getGroups.apply(account);
+ return getGroups.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get groups", e);
}
@@ -416,7 +420,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<EmailInfo> getEmails() throws RestApiException {
try {
- return getEmails.apply(account);
+ return getEmails.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get emails", e);
}
@@ -475,7 +479,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<SshKeyInfo> listSshKeys() throws RestApiException {
try {
- return getSshKeys.apply(account);
+ return getSshKeys.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot list SSH keys", e);
}
@@ -534,7 +538,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<AgreementInfo> listAgreements() throws RestApiException {
try {
- return getAgreements.apply(account);
+ return getAgreements.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get agreements", e);
}
@@ -563,7 +567,7 @@ public class AccountApiImpl implements AccountApi {
@Override
public List<AccountExternalIdInfo> getExternalIds() throws RestApiException {
try {
- return getExternalIds.apply(account);
+ return getExternalIds.apply(account).value();
} catch (Exception e) {
throw asRestApiException("Cannot get external IDs", e);
}
@@ -582,7 +586,7 @@ public class AccountApiImpl implements AccountApi {
public List<DeletedDraftCommentInfo> deleteDraftComments(DeleteDraftCommentsInput input)
throws RestApiException {
try {
- return deleteDraftComments.apply(account, input);
+ return deleteDraftComments.apply(account, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot delete draft comments", e);
}
diff --git a/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index 9d298884b8..012e6ce229 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -133,7 +133,7 @@ public class AccountsImpl implements Accounts {
myQueryAccounts.setSuggest(true);
myQueryAccounts.setQuery(r.getQuery());
myQueryAccounts.setLimit(r.getLimit());
- return myQueryAccounts.apply(TopLevelResource.INSTANCE);
+ return myQueryAccounts.apply(TopLevelResource.INSTANCE).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve suggested accounts", e);
}
@@ -164,7 +164,7 @@ public class AccountsImpl implements Accounts {
for (ListAccountsOption option : r.getOptions()) {
myQueryAccounts.addOption(option);
}
- return myQueryAccounts.apply(TopLevelResource.INSTANCE);
+ return myQueryAccounts.apply(TopLevelResource.INSTANCE).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve suggested accounts", e);
}
diff --git a/java/com/google/gerrit/server/api/accounts/EmailApiImpl.java b/java/com/google/gerrit/server/api/accounts/EmailApiImpl.java
index 759f60ca1d..f68142f982 100644
--- a/java/com/google/gerrit/server/api/accounts/EmailApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/EmailApiImpl.java
@@ -61,7 +61,7 @@ public class EmailApiImpl implements EmailApi {
@Override
public EmailInfo get() throws RestApiException {
try {
- return get.apply(resource());
+ return get.apply(resource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot read email", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index f027c92b3a..a04be30908 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -351,7 +351,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public ChangeApi revert(RevertInput in) throws RestApiException {
try {
- return changeApi.id(revert.apply(change, in)._number);
+ return changeApi.id(revert.apply(change, in).value()._number);
} catch (Exception e) {
throw asRestApiException("Cannot revert change", e);
}
@@ -401,7 +401,11 @@ class ChangeApiImpl implements ChangeApi {
@Override
public String topic() throws RestApiException {
- return getTopic.apply(change);
+ try {
+ return getTopic.apply(change).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get topic", e);
+ }
}
@Override
@@ -418,7 +422,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public IncludedInInfo includedIn() throws RestApiException {
try {
- return includedIn.apply(change);
+ return includedIn.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Could not extract IncludedIn data", e);
}
@@ -427,7 +431,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public AddReviewerResult addReviewer(AddReviewerInput in) throws RestApiException {
try {
- return postReviewers.apply(change, in);
+ return postReviewers.apply(change, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot add change reviewer", e);
}
@@ -448,7 +452,9 @@ class ChangeApiImpl implements ChangeApi {
try {
suggestReviewers.setQuery(r.getQuery());
suggestReviewers.setLimit(r.getLimit());
- return suggestReviewers.apply(change);
+ suggestReviewers.setExcludeGroups(r.getExcludeGroups());
+ suggestReviewers.setReviewerState(r.getReviewerState());
+ return suggestReviewers.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve suggested reviewers", e);
}
@@ -457,7 +463,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public List<ReviewerInfo> reviewers() throws RestApiException {
try {
- return listReviewers.apply(change);
+ return listReviewers.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve reviewers", e);
}
@@ -512,7 +518,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public AccountInfo setAssignee(AssigneeInput input) throws RestApiException {
try {
- return putAssignee.apply(change, input);
+ return putAssignee.apply(change, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot set assignee", e);
}
@@ -550,7 +556,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public Map<String, List<CommentInfo>> comments() throws RestApiException {
try {
- return listComments.apply(change);
+ return listComments.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot get comments", e);
}
@@ -568,7 +574,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public Map<String, List<RobotCommentInfo>> robotComments() throws RestApiException {
try {
- return listChangeRobotComments.apply(change);
+ return listChangeRobotComments.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot get robot comments", e);
}
@@ -577,7 +583,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public Map<String, List<CommentInfo>> drafts() throws RestApiException {
try {
- return listDrafts.apply(change);
+ return listDrafts.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot get drafts", e);
}
@@ -671,7 +677,7 @@ class ChangeApiImpl implements ChangeApi {
try {
GetPureRevert getPureRevert = getPureRevertProvider.get();
getPureRevert.setClaimedOriginal(claimedOriginal);
- return getPureRevert.apply(change);
+ return getPureRevert.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot compute pure revert", e);
}
@@ -680,7 +686,7 @@ class ChangeApiImpl implements ChangeApi {
@Override
public List<ChangeMessageInfo> messages() throws RestApiException {
try {
- return changeMessages.list().apply(change);
+ return changeMessages.list().apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot list change messages", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
index ffc65245d6..7f0feba9b4 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
@@ -221,7 +221,7 @@ public class ChangeEditApiImpl implements ChangeEditApi {
public String getCommitMessage() throws RestApiException {
try {
try (BinaryResult binaryResult =
- getChangeEditCommitMessageProvider.get().apply(changeResource)) {
+ getChangeEditCommitMessageProvider.get().apply(changeResource).value()) {
return binaryResult.asString();
}
} catch (Exception e) {
diff --git a/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java
index 14310e85f3..490ec5b0d7 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java
@@ -48,7 +48,7 @@ class ChangeMessageApiImpl implements ChangeMessageApi {
@Override
public ChangeMessageInfo get() throws RestApiException {
try {
- return getChangeMessage.apply(changeMessageResource);
+ return getChangeMessage.apply(changeMessageResource).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve change message", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index acff137628..b9635fbeb8 100644
--- a/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes;
import com.google.gerrit.extensions.client.ListChangesOption;
@@ -29,7 +30,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.api.changes.ChangeApiImpl.DynamicOptionParser;
import com.google.gerrit.server.restapi.change.ChangesCollection;
import com.google.gerrit.server.restapi.change.CreateChange;
@@ -92,7 +92,7 @@ class ChangesImpl implements Changes {
public ChangeApi create(ChangeInput in) throws RestApiException {
try {
ChangeInfo out = createChange.apply(TopLevelResource.INSTANCE, in).value();
- return api.create(changes.parse(new Change.Id(out._number)));
+ return api.create(changes.parse(Change.id(out._number)));
} catch (Exception e) {
throw asRestApiException("Cannot create change", e);
}
@@ -127,7 +127,7 @@ class ChangesImpl implements Changes {
dynamicOptionParser.parseDynamicOptions(qc, q.getPluginOptions());
try {
- List<?> result = qc.apply(TopLevelResource.INSTANCE);
+ List<?> result = qc.apply(TopLevelResource.INSTANCE).value();
if (result.isEmpty()) {
return ImmutableList.of();
}
diff --git a/java/com/google/gerrit/server/api/changes/CommentApiImpl.java b/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
index 418187db32..c5fcab104b 100644
--- a/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/CommentApiImpl.java
@@ -46,7 +46,7 @@ class CommentApiImpl implements CommentApi {
@Override
public CommentInfo get() throws RestApiException {
try {
- return getComment.apply(comment);
+ return getComment.apply(comment).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve comment", e);
}
@@ -55,7 +55,7 @@ class CommentApiImpl implements CommentApi {
@Override
public CommentInfo delete(DeleteCommentInput input) throws RestApiException {
try {
- return deleteComment.apply(comment, input);
+ return deleteComment.apply(comment, input).value();
} catch (Exception e) {
throw asRestApiException("Cannot delete comment", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/DraftApiImpl.java b/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
index 4d26b11336..f6eb3c53e0 100644
--- a/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/DraftApiImpl.java
@@ -54,7 +54,7 @@ class DraftApiImpl implements DraftApi {
@Override
public CommentInfo get() throws RestApiException {
try {
- return getDraft.apply(draft);
+ return getDraft.apply(draft).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve draft", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/FileApiImpl.java b/java/com/google/gerrit/server/api/changes/FileApiImpl.java
index 883ab99184..c506b2ec96 100644
--- a/java/com/google/gerrit/server/api/changes/FileApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/FileApiImpl.java
@@ -63,7 +63,7 @@ class FileApiImpl implements FileApi {
@Override
public BinaryResult content() throws RestApiException {
try {
- return getContent.apply(file);
+ return getContent.apply(file).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve file content", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java b/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java
index 11536cbfe9..2174ef0d43 100644
--- a/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ReviewerApiImpl.java
@@ -54,7 +54,7 @@ public class ReviewerApiImpl implements ReviewerApi {
@Override
public Map<String, Short> votes() throws RestApiException {
try {
- return listVotes.apply(reviewer);
+ return listVotes.apply(reviewer).value();
} catch (Exception e) {
throw asRestApiException("Cannot list votes", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 2df7ae6c5f..01dfe36454 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder.ListMultimapBuilder;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
@@ -54,7 +55,6 @@ import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountLoader;
@@ -254,7 +254,7 @@ class RevisionApiImpl implements RevisionApi {
public BinaryResult submitPreview(String format) throws RestApiException {
try {
submitPreview.setFormat(format);
- return submitPreview.apply(revision);
+ return submitPreview.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot get submit preview", e);
}
@@ -263,7 +263,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public ChangeApi rebase(RebaseInput in) throws RestApiException {
try {
- return changes.id(rebase.apply(revision, in)._number);
+ return changes.id(rebase.apply(revision, in).value()._number);
} catch (Exception e) {
throw asRestApiException("Cannot rebase ps", e);
}
@@ -282,7 +282,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public ChangeApi cherryPick(CherryPickInput in) throws RestApiException {
try {
- return changes.id(cherryPick.apply(revision, in)._number);
+ return changes.id(cherryPick.apply(revision, in).value()._number);
} catch (Exception e) {
throw asRestApiException("Cannot cherry pick", e);
}
@@ -291,7 +291,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public CherryPickChangeInfo cherryPickAsInfo(CherryPickInput in) throws RestApiException {
try {
- return cherryPick.apply(revision, in);
+ return cherryPick.apply(revision, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot cherry pick", e);
}
@@ -336,7 +336,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public MergeableInfo mergeable() throws RestApiException {
try {
- return mergeable.apply(revision);
+ return mergeable.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot check mergeability", e);
}
@@ -346,7 +346,7 @@ class RevisionApiImpl implements RevisionApi {
public MergeableInfo mergeableOtherBranches() throws RestApiException {
try {
mergeable.setOtherBranches(true);
- return mergeable.apply(revision);
+ return mergeable.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot check mergeability", e);
}
@@ -400,7 +400,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public Map<String, List<CommentInfo>> comments() throws RestApiException {
try {
- return listComments.apply(revision);
+ return listComments.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve comments", e);
}
@@ -409,7 +409,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public Map<String, List<RobotCommentInfo>> robotComments() throws RestApiException {
try {
- return listRobotComments.apply(revision);
+ return listRobotComments.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve robot comments", e);
}
@@ -427,7 +427,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public Map<String, List<CommentInfo>> drafts() throws RestApiException {
try {
- return listDrafts.apply(revision);
+ return listDrafts.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve drafts", e);
}
@@ -476,7 +476,7 @@ class RevisionApiImpl implements RevisionApi {
// Reread change to pick up new notes refs.
return changes
.id(revision.getChange().getId().get())
- .revision(revision.getPatchSet().getId().get())
+ .revision(revision.getPatchSet().id().get())
.draft(id);
} catch (Exception e) {
throw asRestApiException("Cannot create draft", e);
@@ -504,7 +504,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public BinaryResult patch() throws RestApiException {
try {
- return getPatch.apply(revision);
+ return getPatch.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot get patch", e);
}
@@ -513,7 +513,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public BinaryResult patch(String path) throws RestApiException {
try {
- return getPatch.setPath(path).apply(revision);
+ return getPatch.setPath(path).apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot get patch", e);
}
@@ -531,7 +531,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public SubmitType submitType() throws RestApiException {
try {
- return getSubmitType.apply(revision);
+ return getSubmitType.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot get submit type", e);
}
@@ -540,16 +540,16 @@ class RevisionApiImpl implements RevisionApi {
@Override
public SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException {
try {
- return testSubmitType.apply(revision, in);
+ return testSubmitType.apply(revision, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot test submit type", e);
}
}
@Override
- public List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
+ public TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
try {
- return testSubmitRule.get().apply(revision, in);
+ return testSubmitRule.get().apply(revision, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot test submit rule", e);
}
@@ -575,7 +575,7 @@ class RevisionApiImpl implements RevisionApi {
@Override
public RelatedChangesInfo related() throws RestApiException {
try {
- return getRelated.apply(revision);
+ return getRelated.apply(revision).value();
} catch (Exception e) {
throw asRestApiException("Cannot get related changes", e);
}
@@ -587,20 +587,20 @@ class RevisionApiImpl implements RevisionApi {
ListMultimapBuilder.treeKeys().arrayListValues().build();
try {
Iterable<PatchSetApproval> approvals =
- approvalsUtil.byPatchSet(revision.getNotes(), revision.getPatchSet().getId(), null, null);
+ approvalsUtil.byPatchSet(revision.getNotes(), revision.getPatchSet().id(), null, null);
AccountLoader accountLoader =
accountLoaderFactory.create(
EnumSet.of(
FillOptions.ID, FillOptions.NAME, FillOptions.EMAIL, FillOptions.USERNAME));
for (PatchSetApproval approval : approvals) {
- String label = approval.getLabel();
+ String label = approval.label();
ApprovalInfo info =
new ApprovalInfo(
- approval.getAccountId().get(),
- Integer.valueOf(approval.getValue()),
+ approval.accountId().get(),
+ Integer.valueOf(approval.value()),
null,
- approval.getTag(),
- approval.getGranted());
+ approval.tag().orElse(null),
+ approval.granted());
accountLoader.put(info);
result.get(label).add(info);
}
@@ -624,7 +624,11 @@ class RevisionApiImpl implements RevisionApi {
@Override
public String description() throws RestApiException {
- return getDescription.apply(revision);
+ try {
+ return getDescription.apply(revision).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get description", e);
+ }
}
@Override
diff --git a/java/com/google/gerrit/server/api/changes/RevisionReviewerApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionReviewerApiImpl.java
index 8cad507aad..49c2d4931c 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionReviewerApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionReviewerApiImpl.java
@@ -47,7 +47,7 @@ public class RevisionReviewerApiImpl implements RevisionReviewerApi {
@Override
public Map<String, Short> votes() throws RestApiException {
try {
- return listVotes.apply(reviewer);
+ return listVotes.apply(reviewer).value();
} catch (Exception e) {
throw asRestApiException("Cannot list votes", e);
}
diff --git a/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java b/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java
index 37a56fe0fb..ec13061493 100644
--- a/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RobotCommentApiImpl.java
@@ -41,7 +41,7 @@ public class RobotCommentApiImpl implements RobotCommentApi {
@Override
public RobotCommentInfo get() throws RestApiException {
try {
- return getComment.apply(comment);
+ return getComment.apply(comment).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve robot comment", e);
}
diff --git a/java/com/google/gerrit/server/api/config/ServerImpl.java b/java/com/google/gerrit/server/api/config/ServerImpl.java
index 6e78be241c..ab40ec8743 100644
--- a/java/com/google/gerrit/server/api/config/ServerImpl.java
+++ b/java/com/google/gerrit/server/api/config/ServerImpl.java
@@ -83,7 +83,7 @@ public class ServerImpl implements Server {
@Override
public ServerInfo getInfo() throws RestApiException {
try {
- return getServerInfo.apply(new ConfigResource());
+ return getServerInfo.apply(new ConfigResource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get server info", e);
}
@@ -92,7 +92,7 @@ public class ServerImpl implements Server {
@Override
public GeneralPreferencesInfo getDefaultPreferences() throws RestApiException {
try {
- return getPreferences.apply(new ConfigResource());
+ return getPreferences.apply(new ConfigResource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get default general preferences", e);
}
@@ -102,7 +102,7 @@ public class ServerImpl implements Server {
public GeneralPreferencesInfo setDefaultPreferences(GeneralPreferencesInfo in)
throws RestApiException {
try {
- return setPreferences.apply(new ConfigResource(), in);
+ return setPreferences.apply(new ConfigResource(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set default general preferences", e);
}
@@ -111,7 +111,7 @@ public class ServerImpl implements Server {
@Override
public DiffPreferencesInfo getDefaultDiffPreferences() throws RestApiException {
try {
- return getDiffPreferences.apply(new ConfigResource());
+ return getDiffPreferences.apply(new ConfigResource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get default diff preferences", e);
}
@@ -121,7 +121,7 @@ public class ServerImpl implements Server {
public DiffPreferencesInfo setDefaultDiffPreferences(DiffPreferencesInfo in)
throws RestApiException {
try {
- return setDiffPreferences.apply(new ConfigResource(), in);
+ return setDiffPreferences.apply(new ConfigResource(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set default diff preferences", e);
}
@@ -130,7 +130,7 @@ public class ServerImpl implements Server {
@Override
public EditPreferencesInfo getDefaultEditPreferences() throws RestApiException {
try {
- return getEditPreferences.apply(new ConfigResource());
+ return getEditPreferences.apply(new ConfigResource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get default edit preferences", e);
}
@@ -140,7 +140,7 @@ public class ServerImpl implements Server {
public EditPreferencesInfo setDefaultEditPreferences(EditPreferencesInfo in)
throws RestApiException {
try {
- return setEditPreferences.apply(new ConfigResource(), in);
+ return setEditPreferences.apply(new ConfigResource(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot set default edit preferences", e);
}
@@ -149,14 +149,18 @@ public class ServerImpl implements Server {
@Override
public ConsistencyCheckInfo checkConsistency(ConsistencyCheckInput in) throws RestApiException {
try {
- return checkConsistency.get().apply(new ConfigResource(), in);
+ return checkConsistency.get().apply(new ConfigResource(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot check consistency", e);
}
}
@Override
- public List<TopMenu.MenuEntry> topMenus() {
- return listTopMenus.apply(new ConfigResource()).value();
+ public List<TopMenu.MenuEntry> topMenus() throws RestApiException {
+ try {
+ return listTopMenus.apply(new ConfigResource()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get top menus", e);
+ }
}
}
diff --git a/java/com/google/gerrit/server/api/groups/GroupApiImpl.java b/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
index b70a0294a3..bb04ab4da2 100644
--- a/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
+++ b/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
@@ -119,7 +119,7 @@ class GroupApiImpl implements GroupApi {
@Override
public GroupInfo get() throws RestApiException {
try {
- return getGroup.apply(rsrc);
+ return getGroup.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve group", e);
}
@@ -128,7 +128,7 @@ class GroupApiImpl implements GroupApi {
@Override
public GroupInfo detail() throws RestApiException {
try {
- return getDetail.apply(rsrc);
+ return getDetail.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve group", e);
}
@@ -136,7 +136,11 @@ class GroupApiImpl implements GroupApi {
@Override
public String name() throws RestApiException {
- return getName.apply(rsrc);
+ try {
+ return getName.apply(rsrc).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get group name", e);
+ }
}
@Override
@@ -153,7 +157,7 @@ class GroupApiImpl implements GroupApi {
@Override
public GroupInfo owner() throws RestApiException {
try {
- return getOwner.apply(rsrc);
+ return getOwner.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot get group owner", e);
}
@@ -172,7 +176,11 @@ class GroupApiImpl implements GroupApi {
@Override
public String description() throws RestApiException {
- return getDescription.apply(rsrc);
+ try {
+ return getDescription.apply(rsrc).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get group description", e);
+ }
}
@Override
@@ -188,7 +196,11 @@ class GroupApiImpl implements GroupApi {
@Override
public GroupOptionsInfo options() throws RestApiException {
- return getOptions.apply(rsrc);
+ try {
+ return getOptions.apply(rsrc).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get group options", e);
+ }
}
@Override
@@ -209,7 +221,7 @@ class GroupApiImpl implements GroupApi {
public List<AccountInfo> members(boolean recursive) throws RestApiException {
listMembers.setRecursive(recursive);
try {
- return listMembers.apply(rsrc);
+ return listMembers.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot list group members", e);
}
@@ -236,7 +248,7 @@ class GroupApiImpl implements GroupApi {
@Override
public List<GroupInfo> includedGroups() throws RestApiException {
try {
- return listSubgroups.apply(rsrc);
+ return listSubgroups.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot list subgroups", e);
}
@@ -263,7 +275,7 @@ class GroupApiImpl implements GroupApi {
@Override
public List<? extends GroupAuditEventInfo> auditLog() throws RestApiException {
try {
- return getAuditLog.apply(rsrc);
+ return getAuditLog.apply(rsrc).value();
} catch (Exception e) {
throw asRestApiException("Cannot get audit log", e);
}
diff --git a/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/java/com/google/gerrit/server/api/groups/GroupsImpl.java
index bae75dbea3..95dcad436b 100644
--- a/java/com/google/gerrit/server/api/groups/GroupsImpl.java
+++ b/java/com/google/gerrit/server/api/groups/GroupsImpl.java
@@ -98,7 +98,7 @@ class GroupsImpl implements Groups {
.currentUser()
.checkAny(GlobalPermission.fromAnnotation(createGroup.getClass()));
GroupInfo info =
- createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(in.name), in);
+ createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(in.name), in).value();
return id(info.id);
} catch (Exception e) {
throw asRestApiException("Cannot create group " + in.name, e);
@@ -141,7 +141,7 @@ class GroupsImpl implements Groups {
if (req.getUser() != null) {
try {
- list.setUser(accountResolver.resolve(req.getUser()).asUnique().getAccount().getId());
+ list.setUser(accountResolver.resolve(req.getUser()).asUnique().account().id());
} catch (Exception e) {
throw asRestApiException("Error looking up user " + req.getUser(), e);
}
@@ -154,7 +154,7 @@ class GroupsImpl implements Groups {
list.setMatchRegex(req.getRegex());
list.setSuggest(req.getSuggest());
try {
- return list.apply(tlr);
+ return list.apply(tlr).value();
} catch (Exception e) {
throw asRestApiException("Cannot list groups", e);
}
@@ -184,7 +184,7 @@ class GroupsImpl implements Groups {
for (ListGroupsOption option : r.getOptions()) {
myQueryGroups.addOption(option);
}
- return myQueryGroups.apply(TopLevelResource.INSTANCE);
+ return myQueryGroups.apply(TopLevelResource.INSTANCE).value();
} catch (Exception e) {
throw asRestApiException("Cannot query groups", e);
}
diff --git a/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java b/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
index 71f78320c0..39321779e4 100644
--- a/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
+++ b/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.api.plugins;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.api.plugins.PluginApi;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
@@ -53,7 +55,11 @@ public class PluginApiImpl implements PluginApi {
@Override
public PluginInfo get() throws RestApiException {
- return getStatus.apply(resource);
+ try {
+ return getStatus.apply(resource).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get status", e);
+ }
}
@Override
diff --git a/java/com/google/gerrit/server/api/plugins/PluginsImpl.java b/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
index e5706559a0..c2750937ab 100644
--- a/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
+++ b/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.api.plugins;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.api.plugins.InstallPluginInput;
import com.google.gerrit.extensions.api.plugins.PluginApi;
import com.google.gerrit.extensions.api.plugins.Plugins;
@@ -27,7 +29,6 @@ import com.google.gerrit.server.plugins.PluginsCollection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import java.util.SortedMap;
@Singleton
@@ -59,7 +60,11 @@ public class PluginsImpl implements Plugins {
return new ListRequest() {
@Override
public SortedMap<String, PluginInfo> getAsMap() throws RestApiException {
- return listProvider.get().request(this).apply(TopLevelResource.INSTANCE);
+ try {
+ return listProvider.get().request(this).apply(TopLevelResource.INSTANCE).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list plugins", e);
+ }
}
};
}
@@ -87,8 +92,8 @@ public class PluginsImpl implements Plugins {
Response<PluginInfo> created =
installProvider.get().setName(name).apply(TopLevelResource.INSTANCE, input);
return pluginApi.create(plugins.parse(created.value().id));
- } catch (IOException e) {
- throw new RestApiException("could not install plugin", e);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot install plugin", e);
}
}
}
diff --git a/java/com/google/gerrit/server/api/projects/BranchApiImpl.java b/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
index b3506fc5d7..c7cca6f247 100644
--- a/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
@@ -90,7 +90,7 @@ public class BranchApiImpl implements BranchApi {
@Override
public BranchInfo get() throws RestApiException {
try {
- return getBranch.apply(resource());
+ return getBranch.apply(resource()).value();
} catch (Exception e) {
throw asRestApiException("Cannot read branch", e);
}
@@ -109,7 +109,7 @@ public class BranchApiImpl implements BranchApi {
public BinaryResult file(String path) throws RestApiException {
try {
FileResource resource = filesCollection.parse(resource(), IdString.fromDecoded(path));
- return getContent.apply(resource);
+ return getContent.apply(resource).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve file", e);
}
@@ -118,9 +118,9 @@ public class BranchApiImpl implements BranchApi {
@Override
public List<ReflogEntryInfo> reflog() throws RestApiException {
try {
- return getReflog.apply(resource());
- } catch (IOException | PermissionBackendException e) {
- throw new RestApiException("Cannot retrieve reflog", e);
+ return getReflog.apply(resource()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot retrieve reflog", e);
}
}
diff --git a/java/com/google/gerrit/server/api/projects/ChildProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ChildProjectApiImpl.java
index d7c9bc7add..25e56febd7 100644
--- a/java/com/google/gerrit/server/api/projects/ChildProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ChildProjectApiImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.api.projects;
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
import com.google.gerrit.extensions.api.projects.ChildProjectApi;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -43,7 +45,11 @@ public class ChildProjectApiImpl implements ChildProjectApi {
@Override
public ProjectInfo get(boolean recursive) throws RestApiException {
- getChildProject.setRecursive(recursive);
- return getChildProject.apply(rsrc);
+ try {
+ getChildProject.setRecursive(recursive);
+ return getChildProject.apply(rsrc).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get child project", e);
+ }
}
}
diff --git a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
index 0a3b2369a7..5c7921ac88 100644
--- a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
@@ -58,7 +58,7 @@ public class CommitApiImpl implements CommitApi {
@Override
public CommitInfo get() throws RestApiException {
try {
- return getCommit.apply(commitResource);
+ return getCommit.apply(commitResource).value();
} catch (Exception e) {
throw asRestApiException("Cannot get commit info", e);
}
@@ -67,7 +67,7 @@ public class CommitApiImpl implements CommitApi {
@Override
public ChangeApi cherryPick(CherryPickInput input) throws RestApiException {
try {
- return changes.id(cherryPickCommit.apply(commitResource, input)._number);
+ return changes.id(cherryPickCommit.apply(commitResource, input).value()._number);
} catch (Exception e) {
throw asRestApiException("Cannot cherry pick", e);
}
@@ -76,7 +76,7 @@ public class CommitApiImpl implements CommitApi {
@Override
public IncludedInInfo includedIn() throws RestApiException {
try {
- return includedIn.apply(commitResource);
+ return includedIn.apply(commitResource).value();
} catch (Exception e) {
throw asRestApiException("Could not extract IncludedIn data", e);
}
diff --git a/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java b/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
index c44f5bb502..61736f6d5c 100644
--- a/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
@@ -67,8 +67,8 @@ public class DashboardApiImpl implements DashboardApi {
@Override
public DashboardInfo get(boolean inherited) throws RestApiException {
try {
- return get.get().setInherited(inherited).apply(resource());
- } catch (IOException | PermissionBackendException | ConfigInvalidException e) {
+ return get.get().setInherited(inherited).apply(resource()).value();
+ } catch (Exception e) {
throw asRestApiException("Cannot read dashboard", e);
}
}
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 354331efcf..1ac905d7a0 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -43,6 +43,7 @@ import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.TagApi;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -69,6 +70,7 @@ import com.google.gerrit.server.restapi.project.GetDescription;
import com.google.gerrit.server.restapi.project.GetHead;
import com.google.gerrit.server.restapi.project.GetParent;
import com.google.gerrit.server.restapi.project.Index;
+import com.google.gerrit.server.restapi.project.IndexChanges;
import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.gerrit.server.restapi.project.ListDashboards;
import com.google.gerrit.server.restapi.project.ListTags;
@@ -124,6 +126,7 @@ public class ProjectApiImpl implements ProjectApi {
private final GetParent getParent;
private final SetParent setParent;
private final Index index;
+ private final IndexChanges indexChanges;
@AssistedInject
ProjectApiImpl(
@@ -158,6 +161,7 @@ public class ProjectApiImpl implements ProjectApi {
GetParent getParent,
SetParent setParent,
Index index,
+ IndexChanges indexChanges,
@Assisted ProjectResource project) {
this(
permissionBackend,
@@ -192,6 +196,7 @@ public class ProjectApiImpl implements ProjectApi {
getParent,
setParent,
index,
+ indexChanges,
null);
}
@@ -228,6 +233,7 @@ public class ProjectApiImpl implements ProjectApi {
GetParent getParent,
SetParent setParent,
Index index,
+ IndexChanges indexChanges,
@Assisted String name) {
this(
permissionBackend,
@@ -262,6 +268,7 @@ public class ProjectApiImpl implements ProjectApi {
getParent,
setParent,
index,
+ indexChanges,
name);
}
@@ -298,6 +305,7 @@ public class ProjectApiImpl implements ProjectApi {
GetParent getParent,
SetParent setParent,
Index index,
+ IndexChanges indexChanges,
String name) {
this.permissionBackend = permissionBackend;
this.createProject = createProject;
@@ -332,6 +340,7 @@ public class ProjectApiImpl implements ProjectApi {
this.setParent = setParent;
this.name = name;
this.index = index;
+ this.indexChanges = indexChanges;
}
@Override
@@ -368,13 +377,17 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public String description() throws RestApiException {
- return getDescription.apply(checkExists());
+ try {
+ return getDescription.apply(checkExists()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get description", e);
+ }
}
@Override
public ProjectAccessInfo access() throws RestApiException {
try {
- return getAccess.apply(checkExists());
+ return getAccess.apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get access rights", e);
}
@@ -383,7 +396,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public ProjectAccessInfo access(ProjectAccessInput p) throws RestApiException {
try {
- return setAccess.apply(checkExists(), p);
+ return setAccess.apply(checkExists(), p).value();
} catch (Exception e) {
throw asRestApiException("Cannot put access rights", e);
}
@@ -401,7 +414,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public AccessCheckInfo checkAccess(AccessCheckInput in) throws RestApiException {
try {
- return checkAccess.apply(checkExists(), in);
+ return checkAccess.apply(checkExists(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot check access rights", e);
}
@@ -410,7 +423,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public CheckProjectResultInfo check(CheckProjectInput in) throws RestApiException {
try {
- return check.apply(checkExists(), in);
+ return check.apply(checkExists(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot check project", e);
}
@@ -427,13 +440,17 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public ConfigInfo config() throws RestApiException {
- return getConfig.apply(checkExists());
+ try {
+ return getConfig.apply(checkExists()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get config", e);
+ }
}
@Override
public ConfigInfo config(ConfigInput in) throws RestApiException {
try {
- return putConfig.apply(checkExists(), in);
+ return putConfig.apply(checkExists(), in).value();
} catch (Exception e) {
throw asRestApiException("Cannot list tags", e);
}
@@ -445,7 +462,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public List<BranchInfo> get() throws RestApiException {
try {
- return listBranches.get().request(this).apply(checkExists());
+ return listBranches.get().request(this).apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot list branches", e);
}
@@ -459,7 +476,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public List<TagInfo> get() throws RestApiException {
try {
- return listTags.get().request(this).apply(checkExists());
+ return listTags.get().request(this).apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot list tags", e);
}
@@ -475,7 +492,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public List<ProjectInfo> children(boolean recursive) throws RestApiException {
try {
- return children.list().withRecursive(recursive).apply(checkExists());
+ return children.list().withRecursive(recursive).apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot list children", e);
}
@@ -484,7 +501,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public List<ProjectInfo> children(int limit) throws RestApiException {
try {
- return children.list().withLimit(limit).apply(checkExists());
+ return children.list().withLimit(limit).apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot list children", e);
}
@@ -574,7 +591,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public List<DashboardInfo> get() throws RestApiException {
try {
- List<?> r = listDashboards.get().apply(checkExists());
+ List<?> r = listDashboards.get().apply(checkExists()).value();
if (r.isEmpty()) {
return Collections.emptyList();
}
@@ -592,7 +609,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public String head() throws RestApiException {
try {
- return getHead.apply(checkExists());
+ return getHead.apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get HEAD", e);
}
@@ -612,7 +629,7 @@ public class ProjectApiImpl implements ProjectApi {
@Override
public String parent() throws RestApiException {
try {
- return getParent.apply(checkExists());
+ return getParent.apply(checkExists()).value();
} catch (Exception e) {
throw asRestApiException("Cannot get parent", e);
}
@@ -640,6 +657,15 @@ public class ProjectApiImpl implements ProjectApi {
}
}
+ @Override
+ public void indexChanges() throws RestApiException {
+ try {
+ indexChanges.apply(checkExists(), new Input());
+ } catch (Exception e) {
+ throw asRestApiException("Cannot index changes", e);
+ }
+ }
+
private ProjectResource checkExists() throws ResourceNotFoundException {
if (project == null) {
throw new ResourceNotFoundException(name);
diff --git a/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java b/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
index 46a22c06ed..66d0224899 100644
--- a/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountGroupIdHandler.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.args4j;
import static com.google.gerrit.util.cli.Localizable.localizable;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Inject;
@@ -45,7 +45,7 @@ public class AccountGroupIdHandler extends OptionHandler<AccountGroup.Id> {
@Override
public final int parseArguments(Parameters params) throws CmdLineException {
final String n = params.getParameter(0);
- Optional<InternalGroup> group = groupCache.get(new AccountGroup.NameKey(n));
+ Optional<InternalGroup> group = groupCache.get(AccountGroup.nameKey(n));
if (!group.isPresent()) {
throw new CmdLineException(owner, localizable("Group \"%s\" does not exist"), n);
}
diff --git a/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java b/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
index 2dd0c7ab5b..20e8441774 100644
--- a/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountGroupUUIDHandler.java
@@ -18,7 +18,7 @@ import static com.google.gerrit.util.cli.Localizable.localizable;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.GroupCache;
@@ -53,7 +53,7 @@ public class AccountGroupUUIDHandler extends OptionHandler<AccountGroup.UUID> {
@Override
public final int parseArguments(Parameters params) throws CmdLineException {
final String n = params.getParameter(0);
- AccountGroup.UUID uuid = new AccountGroup.UUID(n);
+ AccountGroup.UUID uuid = AccountGroup.uuid(n);
if (groupBackend.handles(uuid)) {
GroupDescription.Basic d = groupBackend.get(uuid);
if (d != null) {
diff --git a/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index 37e52f4c82..73a970ba86 100644
--- a/java/com/google/gerrit/server/args4j/AccountIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.args4j;
import static com.google.gerrit.util.cli.Localizable.localizable;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountResolver;
@@ -65,7 +65,7 @@ public class AccountIdHandler extends OptionHandler<Account.Id> {
Account.Id accountId;
try {
try {
- accountId = accountResolver.resolve(token).asUnique().getAccount().getId();
+ accountId = accountResolver.resolve(token).asUnique().account().id();
} catch (UnprocessableEntityException e) {
switch (authType) {
case HTTP_LDAP:
diff --git a/java/com/google/gerrit/server/args4j/ChangeIdHandler.java b/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
index ceba3e620d..448c654105 100644
--- a/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/ChangeIdHandler.java
@@ -18,10 +18,10 @@ import static com.google.gerrit.util.cli.Localizable.localizable;
import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.inject.Inject;
@@ -62,8 +62,8 @@ public class ChangeIdHandler extends OptionHandler<Change.Id> {
try {
Change.Key key = Change.Key.parse(tokens.get(2));
- Project.NameKey project = new Project.NameKey(tokens.get(0));
- Branch.NameKey branch = new Branch.NameKey(project, tokens.get(1));
+ Project.NameKey project = Project.nameKey(tokens.get(0));
+ BranchNameKey branch = BranchNameKey.create(project, tokens.get(1));
List<ChangeData> changes = queryProvider.get().byBranchKey(branch, key);
if (!changes.isEmpty()) {
if (changes.size() > 1) {
diff --git a/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java b/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
index 4581fe02dc..8b7cbd6fd6 100644
--- a/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/PatchSetIdHandler.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.args4j;
import static com.google.gerrit.util.cli.Localizable.localizable;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.PatchSet;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.kohsuke.args4j.CmdLineException;
diff --git a/java/com/google/gerrit/server/args4j/ProjectHandler.java b/java/com/google/gerrit/server/args4j/ProjectHandler.java
index e3af82b9ce..61dbd2c456 100644
--- a/java/com/google/gerrit/server/args4j/ProjectHandler.java
+++ b/java/com/google/gerrit/server/args4j/ProjectHandler.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.args4j;
import static com.google.gerrit.util.cli.Localizable.localizable;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -72,7 +72,7 @@ public class ProjectHandler extends OptionHandler<ProjectState> {
}
String nameWithoutSuffix = ProjectUtil.stripGitSuffix(projectName);
- Project.NameKey nameKey = new Project.NameKey(nameWithoutSuffix);
+ Project.NameKey nameKey = Project.nameKey(nameWithoutSuffix);
ProjectState state;
try {
diff --git a/java/com/google/gerrit/server/audit/AuditService.java b/java/com/google/gerrit/server/audit/AuditService.java
index 425e22a7ec..2a5d8689f8 100644
--- a/java/com/google/gerrit/server/audit/AuditService.java
+++ b/java/com/google/gerrit/server/audit/AuditService.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.audit;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.audit.group.GroupAuditListener;
import com.google.gerrit.server.audit.group.GroupMemberAuditEvent;
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index c8b71a9be2..95929d365d 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -9,15 +9,9 @@ java_library(
resources = ["//resources/com/google/gerrit/server"],
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/ioutil",
- "//java/com/google/gerrit/server/logging",
- "//java/com/google/gerrit/server/util/time",
- "//java/com/google/gerrit/util/cli",
- "//java/com/google/gerrit/util/ssl",
- "//java/org/apache/commons/net",
"//lib:args4j",
"//lib:autolink",
"//lib:automaton",
@@ -50,6 +44,8 @@ java_library(
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jgit-archive",
"//lib:jsch",
"//lib:juniversalchardet",
"//lib:mime-util",
@@ -71,8 +67,6 @@ java_library(
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jsoup",
"//lib/log:jsonevent-layout",
"//lib/log:log4j",
diff --git a/java/com/google/gerrit/server/audit/group/GroupAuditEvent.java b/java/com/google/gerrit/server/audit/group/GroupAuditEvent.java
index ae26e12907..252a1e262b 100644
--- a/java/com/google/gerrit/server/audit/group/GroupAuditEvent.java
+++ b/java/com/google/gerrit/server/audit/group/GroupAuditEvent.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.audit.group;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
/** An audit event for groups. */
@@ -23,14 +23,14 @@ public interface GroupAuditEvent {
/**
* Gets the acting user who is updating the group.
*
- * @return the {@link com.google.gerrit.reviewdb.client.Account.Id} of the acting user.
+ * @return the {@link com.google.gerrit.entities.Account.Id} of the acting user.
*/
Account.Id getActor();
/**
- * Gets the {@link com.google.gerrit.reviewdb.client.AccountGroup.UUID} of the updated group.
+ * Gets the {@link com.google.gerrit.entities.AccountGroup.UUID} of the updated group.
*
- * @return the {@link com.google.gerrit.reviewdb.client.AccountGroup.UUID} of the updated group.
+ * @return the {@link com.google.gerrit.entities.AccountGroup.UUID} of the updated group.
*/
AccountGroup.UUID getUpdatedGroup();
diff --git a/java/com/google/gerrit/server/audit/group/GroupMemberAuditEvent.java b/java/com/google/gerrit/server/audit/group/GroupMemberAuditEvent.java
index a5c11bc1f3..eccfbf40ec 100644
--- a/java/com/google/gerrit/server/audit/group/GroupMemberAuditEvent.java
+++ b/java/com/google/gerrit/server/audit/group/GroupMemberAuditEvent.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.audit.group;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
@AutoValue
diff --git a/java/com/google/gerrit/server/audit/group/GroupSubgroupAuditEvent.java b/java/com/google/gerrit/server/audit/group/GroupSubgroupAuditEvent.java
index 0d5b26f9e6..0fe396280c 100644
--- a/java/com/google/gerrit/server/audit/group/GroupSubgroupAuditEvent.java
+++ b/java/com/google/gerrit/server/audit/group/GroupSubgroupAuditEvent.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.audit.group;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
@AutoValue
diff --git a/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index c06c66bd8e..2f8886b88b 100644
--- a/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.auth;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.externalids.PasswordVerifier;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -55,14 +56,14 @@ public class InternalAuthBackend implements AuthBackend {
AccountState who = accountCache.getByUsername(username).orElseThrow(UnknownUserException::new);
- if (!who.getAccount().isActive()) {
+ if (!who.account().isActive()) {
throw new UserNotAllowedException(
"Authentication failed for "
+ username
+ ": account inactive or not provisioned in Gerrit");
}
- if (!who.checkPassword(req.getPassword().get(), username)) {
+ if (!PasswordVerifier.checkPassword(who.externalIds(), username, req.getPassword().get())) {
throw new InvalidCredentialsException();
}
return new AuthUser(AuthUser.UUID.create(username), username);
diff --git a/java/com/google/gerrit/server/auth/ldap/Helper.java b/java/com/google/gerrit/server/auth/ldap/Helper.java
index 4b14c385dc..b0f011a3a6 100644
--- a/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -19,7 +19,11 @@ import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AuthenticationFailedException;
import com.google.gerrit.server.auth.NoSuchUserException;
@@ -81,11 +85,16 @@ class Helper {
private final String connectTimeoutMillis;
private final boolean useConnectionPooling;
private final boolean groupsVisibleToAll;
+ private final Timer0 loginLatencyTimer;
+ private final Timer0 userSearchLatencyTimer;
+ private final Timer0 groupSearchLatencyTimer;
+ private final Timer0 groupExpansionLatencyTimer;
@Inject
Helper(
@GerritServerConfig Config config,
- @Named(LdapModule.PARENT_GROUPS_CACHE) Cache<String, ImmutableSet<String>> parentGroups) {
+ @Named(LdapModule.PARENT_GROUPS_CACHE) Cache<String, ImmutableSet<String>> parentGroups,
+ MetricMaker metricMaker) {
this.config = config;
this.server = LdapRealm.optional(config, "server");
this.username = LdapRealm.optional(config, "username");
@@ -112,6 +121,33 @@ class Helper {
}
this.parentGroups = parentGroups;
this.useConnectionPooling = LdapRealm.optional(config, "useConnectionPooling", false);
+
+ this.loginLatencyTimer =
+ metricMaker.newTimer(
+ "ldap/login_latency",
+ new Description("Latency of logins").setCumulative().setUnit(Units.NANOSECONDS));
+ this.userSearchLatencyTimer =
+ metricMaker.newTimer(
+ "ldap/user_search_latency",
+ new Description("Latency for searching the user account")
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+ this.groupSearchLatencyTimer =
+ metricMaker.newTimer(
+ "ldap/group_search_latency",
+ new Description("Latency for querying the groups membership of an account")
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+ this.groupExpansionLatencyTimer =
+ metricMaker.newTimer(
+ "ldap/group_expansion_latency",
+ new Description("Latency for expanding nested groups")
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+ }
+
+ Timer0 getGroupSearchLatencyTimer() {
+ return groupSearchLatencyTimer;
}
private Properties createContextProperties() {
@@ -191,7 +227,9 @@ class Helper {
private DirContext kerberosOpen(Properties env)
throws IOException, LoginException, NamingException {
LoginContext ctx = new LoginContext("KerberosLogin");
- ctx.login();
+ try (Timer0.Context ignored = loginLatencyTimer.start()) {
+ ctx.login();
+ }
Subject subject = ctx.getSubject();
try {
return Subject.doAs(
@@ -209,7 +247,7 @@ class Helper {
DirContext authenticate(String dn, String password) throws AccountException {
final Properties env = createContextProperties();
- try {
+ try (Timer0.Context ignored = loginLatencyTimer.start()) {
env.put(Context.REFERRAL, referral);
if (!supportAnonymous) {
@@ -258,7 +296,7 @@ class Helper {
}
for (LdapQuery accountQuery : accountQueryList) {
- List<LdapQuery.Result> res = accountQuery.query(ctx, params);
+ List<LdapQuery.Result> res = accountQuery.query(ctx, params, userSearchLatencyTimer);
if (res.size() == 1) {
return res.get(0);
} else if (res.size() > 1) {
@@ -290,8 +328,10 @@ class Helper {
params.put(LdapRealm.USERNAME, username);
for (LdapQuery groupMemberQuery : schema.groupMemberQueryList) {
- for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
- recursivelyExpandGroups(groupDNs, schema, ctx, r.getDN());
+ for (LdapQuery.Result r : groupMemberQuery.query(ctx, params, groupSearchLatencyTimer)) {
+ try (Timer0.Context ignored = groupExpansionLatencyTimer.start()) {
+ recursivelyExpandGroups(groupDNs, schema, ctx, r.getDN());
+ }
}
}
}
@@ -321,7 +361,7 @@ class Helper {
final Set<AccountGroup.UUID> actual = new HashSet<>();
for (String dn : groupDNs) {
- actual.add(new AccountGroup.UUID(LDAP_UUID + dn));
+ actual.add(AccountGroup.uuid(LDAP_UUID + dn));
}
if (actual.isEmpty()) {
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 87a4abfacb..0d8f3f8a95 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -27,7 +27,7 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
@@ -92,7 +92,7 @@ public class LdapGroupBackend implements GroupBackend {
private static GroupReference groupReference(ParameterizedString p, LdapQuery.Result res)
throws NamingException {
return new GroupReference(
- new AccountGroup.UUID(LDAP_UUID + res.getDN()), LDAP_NAME + LdapRealm.apply(p, res));
+ AccountGroup.uuid(LDAP_UUID + res.getDN()), LDAP_NAME + LdapRealm.apply(p, res));
}
private static String cnFor(String dn) {
@@ -164,7 +164,7 @@ public class LdapGroupBackend implements GroupBackend {
@Override
public Collection<GroupReference> suggest(String name, ProjectState project) {
- AccountGroup.UUID uuid = new AccountGroup.UUID(name);
+ AccountGroup.UUID uuid = AccountGroup.uuid(name);
if (isLdapUUID(uuid)) {
GroupDescription.Basic g = get(uuid);
if (g == null) {
@@ -179,7 +179,7 @@ public class LdapGroupBackend implements GroupBackend {
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
- String id = findId(user.state().getExternalIds());
+ String id = findId(user.state().externalIds());
if (id == null) {
return GroupMembership.EMPTY;
}
@@ -213,7 +213,8 @@ public class LdapGroupBackend implements GroupBackend {
Map<String, String> params = Collections.emptyMap();
for (String groupBase : schema.groupBases) {
LdapQuery query = new LdapQuery(groupBase, schema.groupScope, filter, returnAttrs);
- for (LdapQuery.Result res : query.query(ctx, params)) {
+ for (LdapQuery.Result res :
+ query.query(ctx, params, helper.getGroupSearchLatencyTimer())) {
out.add(groupReference(schema.groupName, res));
}
}
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
index f5406c25d3..a6aa2f6162 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.auth.ldap;
import com.google.common.cache.LoadingCache;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.project.ProjectCache;
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapModule.java b/java/com/google/gerrit/server/auth/ldap/LdapModule.java
index 3fbf04996f..092b5ac4a2 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapModule.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapModule.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.auth.ldap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.CacheModule;
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapQuery.java b/java/com/google/gerrit/server/auth/ldap/LdapQuery.java
index 3d25e864b5..3e549f6fdd 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapQuery.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapQuery.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.auth.ldap;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.metrics.Timer0;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -61,13 +62,16 @@ class LdapQuery {
return pattern.getParameterNames();
}
- List<Result> query(DirContext ctx, Map<String, String> params) throws NamingException {
+ List<Result> query(DirContext ctx, Map<String, String> params, Timer0 queryTimer)
+ throws NamingException {
final SearchControls sc = new SearchControls();
final NamingEnumeration<SearchResult> res;
sc.setSearchScope(searchScope.scope());
sc.setReturningAttributes(returnAttributes);
- res = ctx.search(base, pattern.getRawPattern(), pattern.bind(params), sc);
+ try (Timer0.Context ignored = queryTimer.start()) {
+ res = ctx.search(base, pattern.getRawPattern(), pattern.bind(params), sc);
+ }
try {
final List<Result> r = new ArrayList<>();
try {
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index be6303dfca..1421f1792e 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -22,10 +22,10 @@ import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.client.AuthType;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AbstractRealm;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AuthRequest;
@@ -37,6 +37,7 @@ import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.auth.NoSuchUserException;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.inject.Inject;
@@ -301,7 +302,7 @@ class LdapRealm extends AbstractRealm {
@Override
public void onCreateAccount(AuthRequest who, Account account) {
- usernameCache.put(who.getLocalUser(), Optional.of(account.getId()));
+ usernameCache.put(who.getLocalUser(), Optional.of(account.id()));
}
@Override
@@ -353,7 +354,9 @@ class LdapRealm extends AbstractRealm {
@Override
public Optional<Account.Id> load(String username) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading account for username %s", username)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading account for username", Metadata.builder().username(username).build())) {
return externalIds
.get(ExternalId.Key.create(SCHEME_GERRIT, username))
.map(ExternalId::accountId);
@@ -372,7 +375,9 @@ class LdapRealm extends AbstractRealm {
@Override
public Set<AccountGroup.UUID> load(String username) throws Exception {
try (TraceTimer timer =
- TraceContext.newTimer("Loading group for member with username %s", username)) {
+ TraceContext.newTimer(
+ "Loading group for member with username",
+ Metadata.builder().username(username).build())) {
final DirContext ctx = helper.open();
try {
return helper.queryForGroups(ctx, username, null);
@@ -393,7 +398,9 @@ class LdapRealm extends AbstractRealm {
@Override
public Boolean load(String groupDn) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading groupDn %s", groupDn)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading groupDn", Metadata.builder().authDomainName(groupDn).build())) {
final DirContext ctx = helper.open();
try {
Name compositeGroupName = new CompositeName().add(groupDn);
diff --git a/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java b/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
index 2cef62d723..944bd44e05 100644
--- a/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
+++ b/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
@@ -17,11 +17,11 @@ package com.google.gerrit.server.auth.oauth;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_EXTERNAL;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AbstractRealm;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
diff --git a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
index 0980116c7a..f58b0f7cca 100644
--- a/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
+++ b/java/com/google/gerrit/server/auth/oauth/OAuthTokenCache.java
@@ -17,17 +17,18 @@ package com.google.gerrit.server.auth.oauth;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Converter;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
-import com.google.gerrit.server.cache.serialize.IntKeyCacheSerializer;
+import com.google.gerrit.server.cache.serialize.IntegerCacheSerializer;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
@@ -45,7 +46,9 @@ public class OAuthTokenCache {
protected void configure() {
persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class)
.version(1)
- .keySerializer(new IntKeyCacheSerializer<>(Account.Id::new))
+ .keySerializer(
+ CacheSerializer.convert(
+ IntegerCacheSerializer.INSTANCE, Converter.from(Account.Id::get, Account::id)))
.valueSerializer(new Serializer());
}
};
diff --git a/java/com/google/gerrit/server/cache/CacheMetrics.java b/java/com/google/gerrit/server/cache/CacheMetrics.java
index 063ddbc660..12194e75cd 100644
--- a/java/com/google/gerrit/server/cache/CacheMetrics.java
+++ b/java/com/google/gerrit/server/cache/CacheMetrics.java
@@ -26,6 +26,7 @@ import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Set;
@@ -33,7 +34,8 @@ import org.eclipse.jgit.lib.Config;
@Singleton
public class CacheMetrics {
- private static final Field<String> F_NAME = Field.ofString("cache_name");
+ private static final Field<String> F_NAME =
+ Field.ofString("cache_name", Metadata.Builder::cacheName).build();
@Inject
public CacheMetrics(
diff --git a/java/com/google/gerrit/server/cache/h2/BUILD b/java/com/google/gerrit/server/cache/h2/BUILD
index a191f75fcb..5e64aa765d 100644
--- a/java/com/google/gerrit/server/cache/h2/BUILD
+++ b/java/com/google/gerrit/server/cache/h2/BUILD
@@ -14,8 +14,8 @@ java_library(
"//java/com/google/gerrit/server/util/time",
"//lib:guava",
"//lib:h2",
+ "//lib:jgit",
"//lib/flogger:api",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 3732e37e19..ef4e44cd4f 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -26,6 +26,7 @@ import com.google.common.hash.BloomFilter;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.cache.PersistentCache;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.util.time.TimeUtil;
@@ -237,7 +238,9 @@ public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements Per
@Override
public ValueHolder<V> load(K key) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading value for %s from cache", key)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading value from cache", Metadata.builder().cacheKey(key.toString()).build())) {
if (store.mightContain(key)) {
ValueHolder<V> h = store.getIfPresent(key);
if (h != null) {
diff --git a/java/com/google/gerrit/server/cache/mem/BUILD b/java/com/google/gerrit/server/cache/mem/BUILD
index bc5b66ac6b..a666df7bc8 100644
--- a/java/com/google/gerrit/server/cache/mem/BUILD
+++ b/java/com/google/gerrit/server/cache/mem/BUILD
@@ -11,7 +11,7 @@ java_library(
"//lib:caffeine",
"//lib:caffeine-guava",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/cache/serialize/BUILD b/java/com/google/gerrit/server/cache/serialize/BUILD
index 76dcbb1c28..aa9106bfb8 100644
--- a/java/com/google/gerrit/server/cache/serialize/BUILD
+++ b/java/com/google/gerrit/server/cache/serialize/BUILD
@@ -6,10 +6,10 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/proto",
- "//java/com/google/gwtorm",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
index 2d41f2c50b..5377fc1f5b 100644
--- a/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/CacheSerializer.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.cache.serialize;
+import com.google.common.base.Converter;
+
/**
* Interface for serializing/deserializing a type to/from a persistent cache.
*
@@ -22,6 +24,27 @@ package com.google.gerrit.server.cache.serialize;
*/
public interface CacheSerializer<T> {
/**
+ * Convert a serializer of one type to another type using a {@link Converter}.
+ *
+ * @param delegate underlying serializer.
+ * @param converter converter between an arbitrary type {@code T} and {@code delegate}'s type.
+ * @return serializer of type {@code T}.
+ */
+ static <T, D> CacheSerializer<T> convert(CacheSerializer<D> delegate, Converter<T, D> converter) {
+ return new CacheSerializer<T>() {
+ @Override
+ public byte[] serialize(T object) {
+ return delegate.serialize(converter.convert(object));
+ }
+
+ @Override
+ public T deserialize(byte[] in) {
+ return converter.reverse().convert(delegate.deserialize(in));
+ }
+ };
+ }
+
+ /**
* Serializes the object to a new byte array.
*
* @param object object to serialize.
diff --git a/java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java
deleted file mode 100644
index 85530f4a0e..0000000000
--- a/java/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache.serialize;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.gwtorm.client.IntKey;
-import java.util.function.Function;
-
-public class IntKeyCacheSerializer<K extends IntKey<?>> implements CacheSerializer<K> {
- private final Function<Integer, K> factory;
-
- public IntKeyCacheSerializer(Function<Integer, K> factory) {
- this.factory = requireNonNull(factory);
- }
-
- @Override
- public byte[] serialize(K object) {
- return IntegerCacheSerializer.INSTANCE.serialize(object.get());
- }
-
- @Override
- public K deserialize(byte[] in) {
- return factory.apply(IntegerCacheSerializer.INSTANCE.deserialize(in));
- }
-}
diff --git a/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java b/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java
index 500875dcb8..7c0f84f832 100644
--- a/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.cache.serialize;
-import org.eclipse.jgit.lib.Constants;
+import com.google.gerrit.git.ObjectIds;
import org.eclipse.jgit.lib.ObjectId;
public enum ObjectIdCacheSerializer implements CacheSerializer<ObjectId> {
@@ -22,14 +22,14 @@ public enum ObjectIdCacheSerializer implements CacheSerializer<ObjectId> {
@Override
public byte[] serialize(ObjectId object) {
- byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ byte[] buf = new byte[ObjectIds.LEN];
object.copyRawTo(buf, 0);
return buf;
}
@Override
public ObjectId deserialize(byte[] in) {
- if (in == null || in.length != Constants.OBJECT_ID_LENGTH) {
+ if (in == null || in.length != ObjectIds.LEN) {
throw new IllegalArgumentException("Failed to deserialize ObjectId");
}
return ObjectId.fromRaw(in);
diff --git a/java/com/google/gerrit/server/cache/serialize/ObjectIdConverter.java b/java/com/google/gerrit/server/cache/serialize/ObjectIdConverter.java
index eb946a9a4a..22654e5bf2 100644
--- a/java/com/google/gerrit/server/cache/serialize/ObjectIdConverter.java
+++ b/java/com/google/gerrit/server/cache/serialize/ObjectIdConverter.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.base.Preconditions.checkArgument;
-import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import com.google.gerrit.git.ObjectIds;
import com.google.protobuf.ByteString;
import org.eclipse.jgit.lib.ObjectId;
@@ -35,7 +35,7 @@ public class ObjectIdConverter {
return new ObjectIdConverter();
}
- private final byte[] buf = new byte[OBJECT_ID_LENGTH];
+ private final byte[] buf = new byte[ObjectIds.LEN];
private ObjectIdConverter() {}
@@ -46,10 +46,7 @@ public class ObjectIdConverter {
public ObjectId fromByteString(ByteString in) {
checkArgument(
- in.size() == OBJECT_ID_LENGTH,
- "expected ByteString of length %s: %s",
- OBJECT_ID_LENGTH,
- in);
+ in.size() == ObjectIds.LEN, "expected ByteString of length %s: %s", ObjectIds.LEN, in);
in.copyTo(buf, 0);
return ObjectId.fromRaw(buf);
}
diff --git a/java/com/google/gerrit/server/change/AbandonOp.java b/java/com/google/gerrit/server/change/AbandonOp.java
index 5ee5bc7fa1..eb6e8d7062 100644
--- a/java/com/google/gerrit/server/change/AbandonOp.java
+++ b/java/com/google/gerrit/server/change/AbandonOp.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.change;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
@@ -112,7 +112,7 @@ public class AbandonOp implements BatchUpdateOp {
try {
ReplyToChangeSender cm = abandonedSenderFactory.create(ctx.getProject(), change.getId());
if (accountState != null) {
- cm.setFrom(accountState.getAccount().getId());
+ cm.setFrom(accountState.account().id());
}
cm.setChangeMessage(message.getMessage(), ctx.getWhen());
cm.setNotify(notify);
diff --git a/java/com/google/gerrit/server/change/AbandonUtil.java b/java/com/google/gerrit/server/change/AbandonUtil.java
index 6f464984a4..1bc1fad379 100644
--- a/java/com/google/gerrit/server/change/AbandonUtil.java
+++ b/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.change;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.InternalUser;
import com.google.gerrit.server.config.ChangeCleanupConfig;
import com.google.gerrit.server.query.change.ChangeData;
@@ -40,7 +40,10 @@ public class AbandonUtil {
private final ChangeCleanupConfig cfg;
private final Provider<ChangeQueryProcessor> queryProvider;
- private final ChangeQueryBuilder queryBuilder;
+ // Provider is needed, because AbandonUtil is singleton, but ChangeQueryBuilder accesses
+ // index collection, that is only provided when multiversion index module is started.
+ // TODO(davido); Remove provider again, when support for legacy numeric fields is removed.
+ private final Provider<ChangeQueryBuilder> queryBuilderProvider;
private final BatchAbandon batchAbandon;
private final InternalUser internalUser;
@@ -49,11 +52,11 @@ public class AbandonUtil {
ChangeCleanupConfig cfg,
InternalUser.Factory internalUserFactory,
Provider<ChangeQueryProcessor> queryProvider,
- ChangeQueryBuilder queryBuilder,
+ Provider<ChangeQueryBuilder> queryBuilderProvider,
BatchAbandon batchAbandon) {
this.cfg = cfg;
this.queryProvider = queryProvider;
- this.queryBuilder = queryBuilder;
+ this.queryBuilderProvider = queryBuilderProvider;
this.batchAbandon = batchAbandon;
internalUser = internalUserFactory.create();
}
@@ -71,7 +74,11 @@ public class AbandonUtil {
}
List<ChangeData> changesToAbandon =
- queryProvider.get().enforceVisibility(false).query(queryBuilder.parse(query)).entities();
+ queryProvider
+ .get()
+ .enforceVisibility(false)
+ .query(queryBuilderProvider.get().parse(query))
+ .entities();
ImmutableListMultimap.Builder<Project.NameKey, ChangeData> builder =
ImmutableListMultimap.builder();
for (ChangeData cd : changesToAbandon) {
@@ -111,7 +118,7 @@ public class AbandonUtil {
queryProvider
.get()
.enforceVisibility(false)
- .query(queryBuilder.parse(newQuery))
+ .query(queryBuilderProvider.get().parse(newQuery))
.entities();
if (!changesToAbandon.isEmpty()) {
validChanges.add(cd);
diff --git a/java/com/google/gerrit/server/change/AccountPatchReviewStore.java b/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
index fff3274447..8da2a90660 100644
--- a/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
+++ b/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.change;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import java.util.Collection;
import java.util.Optional;
@@ -30,7 +30,8 @@ import java.util.Optional;
* number of reviewed flags is growing without bound. The store must be able handle this data volume
* efficiently.
*
- * <p>For a multi-master setup the store must replicate the data between the masters.
+ * <p>For a cluster setups with multiple primary nodes the store must replicate the data between the
+ * primary servers.
*/
public interface AccountPatchReviewStore {
diff --git a/java/com/google/gerrit/server/change/AddReviewersEmail.java b/java/com/google/gerrit/server/change/AddReviewersEmail.java
index d9c5dade4f..664b84ddc0 100644
--- a/java/com/google/gerrit/server/change/AddReviewersEmail.java
+++ b/java/com/google/gerrit/server/change/AddReviewersEmail.java
@@ -18,9 +18,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.mail.send.AddReviewerSender;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/change/AddReviewersOp.java b/java/com/google/gerrit/server/change/AddReviewersOp.java
index 610290dfeb..87d34a4712 100644
--- a/java/com/google/gerrit/server/change/AddReviewersOp.java
+++ b/java/com/google/gerrit/server/change/AddReviewersOp.java
@@ -27,13 +27,13 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountCache;
@@ -64,10 +64,14 @@ public class AddReviewersOp implements BatchUpdateOp {
* @param accountIds account IDs to add.
* @param addresses email addresses to add.
* @param state resulting reviewer state.
+ * @param forGroup whether this reviewer addition adds accounts for a group
* @return batch update operation.
*/
AddReviewersOp create(
- Set<Account.Id> accountIds, Collection<Address> addresses, ReviewerState state);
+ Set<Account.Id> accountIds,
+ Collection<Address> addresses,
+ ReviewerState state,
+ boolean forGroup);
}
@AutoValue
@@ -107,6 +111,7 @@ public class AddReviewersOp implements BatchUpdateOp {
private final Set<Account.Id> accountIds;
private final Collection<Address> addresses;
private final ReviewerState state;
+ private final boolean forGroup;
// Unlike addedCCs, addedReviewers is a PatchSetApproval because the AddReviewerResult returned
// via the REST API is supposed to include vote information.
@@ -130,7 +135,8 @@ public class AddReviewersOp implements BatchUpdateOp {
AddReviewersEmail addReviewersEmail,
@Assisted Set<Account.Id> accountIds,
@Assisted Collection<Address> addresses,
- @Assisted ReviewerState state) {
+ @Assisted ReviewerState state,
+ @Assisted boolean forGroup) {
checkArgument(state == REVIEWER || state == CC, "must be %s or %s: %s", REVIEWER, CC, state);
this.approvalsUtil = approvalsUtil;
this.psUtil = psUtil;
@@ -142,6 +148,7 @@ public class AddReviewersOp implements BatchUpdateOp {
this.accountIds = accountIds;
this.addresses = addresses;
this.state = state;
+ this.forGroup = forGroup;
}
// TODO(dborowitz): This mutable setter is ugly, but a) it's less ugly than adding boolean args
@@ -162,7 +169,7 @@ public class AddReviewersOp implements BatchUpdateOp {
if (state == CC) {
addedCCs =
approvalsUtil.addCcs(
- ctx.getNotes(), ctx.getUpdate(change.currentPatchSetId()), accountIds);
+ ctx.getNotes(), ctx.getUpdate(change.currentPatchSetId()), accountIds, forGroup);
} else {
addedReviewers =
approvalsUtil.addReviewers(
@@ -174,12 +181,11 @@ public class AddReviewersOp implements BatchUpdateOp {
}
}
- ImmutableList<Address> addressesToAdd = ImmutableList.of();
ReviewerStateInternal internalState = ReviewerStateInternal.fromReviewerState(state);
// TODO(dborowitz): This behavior should live in ApprovalsUtil or something, like addCcs does.
ImmutableSet<Address> existing = ctx.getNotes().getReviewersByEmail().byState(internalState);
- addressesToAdd =
+ ImmutableList<Address> addressesToAdd =
addresses.stream().filter(a -> !existing.contains(a)).collect(toImmutableList());
if (state == CC) {
@@ -240,7 +246,7 @@ public class AddReviewersOp implements BatchUpdateOp {
addReviewersEmail.emailReviewers(
ctx.getUser().asIdentifiedUser(),
change,
- Lists.transform(addedReviewers, PatchSetApproval::getAccountId),
+ Lists.transform(addedReviewers, PatchSetApproval::accountId),
addedCCs,
addedReviewersByEmail,
addedCCsByEmail,
@@ -249,7 +255,7 @@ public class AddReviewersOp implements BatchUpdateOp {
if (!addedReviewers.isEmpty()) {
List<AccountState> reviewers =
addedReviewers.stream()
- .map(r -> accountCache.get(r.getAccountId()))
+ .map(r -> accountCache.get(r.accountId()))
.flatMap(Streams::stream)
.collect(toList());
reviewerAdded.fire(change, patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
diff --git a/java/com/google/gerrit/server/change/ArchiveFormat.java b/java/com/google/gerrit/server/change/ArchiveFormat.java
index 0316c5f36a..d895a66d48 100644
--- a/java/com/google/gerrit/server/change/ArchiveFormat.java
+++ b/java/com/google/gerrit/server/change/ArchiveFormat.java
@@ -35,7 +35,9 @@ public enum ArchiveFormat {
TXZ("application/x-xz", new TxzFormat()),
ZIP("application/x-zip", new ZipFormat());
+ @SuppressWarnings("ImmutableEnumChecker") // ArchiveCommand.Format is effectively immutable.
private final ArchiveCommand.Format<?> format;
+
private final String mimeType;
ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
diff --git a/java/com/google/gerrit/server/change/BatchAbandon.java b/java/com/google/gerrit/server/change/BatchAbandon.java
index bc29c5dc54..e0a72ac42d 100644
--- a/java/com/google/gerrit/server/change/BatchAbandon.java
+++ b/java/com/google/gerrit/server/change/BatchAbandon.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.ChangeCleanupConfig;
diff --git a/java/com/google/gerrit/server/change/ChangeETagComputation.java b/java/com/google/gerrit/server/change/ChangeETagComputation.java
new file mode 100644
index 0000000000..a5b7d49f80
--- /dev/null
+++ b/java/com/google/gerrit/server/change/ChangeETagComputation.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/**
+ * Allows plugins to contribute a value to the change ETag computation.
+ *
+ * <p>Plugins can affect the result of the get change / get change details REST endpoints by:
+ *
+ * <ul>
+ * <li>providing plugin defined attributes to {@link
+ * com.google.gerrit.extensions.common.ChangeInfo#plugins} (see {@link
+ * ChangeAttributeFactory})
+ * <li>implementing a {@link com.google.gerrit.server.rules.SubmitRule} which affects the
+ * computation of {@link com.google.gerrit.extensions.common.ChangeInfo#submittable}
+ * </ul>
+ *
+ * <p>If the plugin defined part of {@link com.google.gerrit.extensions.common.ChangeInfo} depends
+ * on plugin specific data, callers that use the change ETags to avoid unneeded recomputations of
+ * ChangeInfos may see outdated plugin attributes and/or outdated submittable information, because a
+ * ChangeInfo is only reloaded if the change ETag changes.
+ *
+ * <p>By implementating this interface plugins can contribute to the change ETag computation and
+ * thus ensure that the ETag changes when the plugin data was changed. This way it is ensured that
+ * callers do not see outdated ChangeInfos.
+ *
+ * @see ChangeResource#getETag()
+ */
+@ExtensionPoint
+public interface ChangeETagComputation {
+ /**
+ * Computes an ETag of plugin-specific data for the given change.
+ *
+ * <p><strong>Note:</strong> Change ETags are computed very frequently and the computation must be
+ * cheap. Take good care to not perform any expensive computations when implementing this.
+ *
+ * <p>If an error is encountered during the ETag computation the plugin can indicate this by
+ * throwing any RuntimeException. In this case no value will be included in the change ETag
+ * computation. This means if the error is transient, the ETag will differ when the computation
+ * succeeds on a follow-up run.
+ *
+ * @param projectName the name of the project that contains the change
+ * @param changeId ID of the change for which the ETag should be computed
+ * @return the ETag
+ */
+ String getETag(Project.NameKey projectName, Change.Id changeId);
+}
diff --git a/java/com/google/gerrit/server/change/ChangeFinder.java b/java/com/google/gerrit/server/change/ChangeFinder.java
index 751d4b89b8..8d2d83dc79 100644
--- a/java/com/google/gerrit/server/change/ChangeFinder.java
+++ b/java/com/google/gerrit/server/change/ChangeFinder.java
@@ -19,17 +19,18 @@ import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -91,7 +92,8 @@ public class ChangeFinder {
new Description("Total number of API calls per identifier type.")
.setRate()
.setUnit("requests"),
- Field.ofEnum(ChangeIdType.class, "change_id_type"));
+ Field.ofEnum(ChangeIdType.class, "change_id_type", Metadata.Builder::changeIdType)
+ .build());
}
public ChangeNotes findOne(String id) {
@@ -129,7 +131,7 @@ public class ChangeFinder {
Integer n = Ints.tryParse(id);
if (n != null) {
changeIdCounter.increment(ChangeIdType.NUMERIC_ID);
- return find(new Change.Id(n));
+ return find(Change.id(n));
}
}
@@ -138,7 +140,7 @@ public class ChangeFinder {
InternalChangeQuery query = queryProvider.get().noFields();
// Try commit hash
- if (id.matches("^([0-9a-fA-F]{" + RevId.ABBREV_LEN + "," + RevId.LEN + "})$")) {
+ if (id.matches("^([0-9a-fA-F]{" + ObjectIds.ABBREV_STR_LEN + "," + ObjectIds.STR_LEN + "})$")) {
changeIdCounter.increment(ChangeIdType.COMMIT_HASH);
return asChangeNotes(query.byCommit(id));
}
@@ -162,7 +164,7 @@ public class ChangeFinder {
}
private List<ChangeNotes> fromProjectNumber(String project, int changeNumber) {
- Change.Id cId = new Change.Id(changeNumber);
+ Change.Id cId = Change.id(changeNumber);
try {
return ImmutableList.of(
changeNotesFactory.createChecked(Project.NameKey.parse(project), cId));
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index a3b5db0e4e..a00f1f8500 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
+import static com.google.gerrit.entities.Change.INITIAL_PATCH_SET_ID;
import static com.google.gerrit.server.change.ReviewerAdder.newAddReviewerInputFromCommitIdentity;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.Objects.requireNonNull;
@@ -30,18 +30,18 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
@@ -168,7 +168,7 @@ public class ChangeInserter implements InsertChangeOp {
this.reviewerAdder = reviewerAdder;
this.changeId = changeId;
- this.psId = new PatchSet.Id(changeId, INITIAL_PATCH_SET_ID);
+ this.psId = PatchSet.id(changeId, INITIAL_PATCH_SET_ID);
this.commitId = commitId.copy();
this.refName = refName;
this.reviewerInputs = ImmutableList.of();
@@ -185,7 +185,7 @@ public class ChangeInserter implements InsertChangeOp {
getChangeKey(ctx.getRevWalk(), commitId),
changeId,
ctx.getAccountId(),
- new Branch.NameKey(ctx.getProject(), refName),
+ BranchNameKey.create(ctx.getProject(), refName),
ctx.getWhen());
change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
change.setTopic(topic);
@@ -201,7 +201,7 @@ public class ChangeInserter implements InsertChangeOp {
rw.parseBody(commit);
List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
if (!idList.isEmpty()) {
- return new Change.Key(idList.get(idList.size() - 1).trim());
+ return Change.key(idList.get(idList.size() - 1).trim());
}
// A Change-Id is generated for the review, but not appended to the commit message.
// This can happen if requireChangeId is false.
@@ -370,7 +370,7 @@ public class ChangeInserter implements InsertChangeOp {
ChangeUpdate update = ctx.getUpdate(psId);
update.setChangeId(change.getKey().get());
update.setSubjectForCommit("Create change");
- update.setBranch(change.getDest().get());
+ update.setBranch(change.getDest().branch());
update.setTopic(change.getTopic());
update.setPsDescription(patchSetDescription);
update.setPrivate(isPrivate);
@@ -419,9 +419,9 @@ public class ChangeInserter implements InsertChangeOp {
if (message != null) {
changeMessage =
ChangeMessagesUtil.newMessage(
- patchSet.getId(),
+ patchSet.id(),
ctx.getUser(),
- patchSet.getCreatedOn(),
+ patchSet.createdOn(),
message,
ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
cmUtil.addChangeMessage(update, changeMessage);
@@ -446,7 +446,7 @@ public class ChangeInserter implements InsertChangeOp {
cm.setNotify(notify);
cm.addReviewers(
reviewerAdditions.flattenResults(AddReviewersOp.Result::addedReviewers).stream()
- .map(PatchSetApproval::getAccountId)
+ .map(PatchSetApproval::accountId)
.collect(toImmutableSet()));
cm.addReviewersByEmail(
reviewerAdditions.flattenResults(AddReviewersOp.Result::addedReviewersByEmail));
@@ -511,14 +511,14 @@ public class ChangeInserter implements InsertChangeOp {
new CommitReceivedEvent(
cmd,
projectState.getProject(),
- change.getDest().get(),
+ change.getDest().branch(),
ctx.getRevWalk().getObjectReader(),
commitId,
ctx.getIdentifiedUser())) {
commitValidatorsFactory
.forGerritCommits(
permissionBackend.user(ctx.getUser()).project(ctx.getProject()),
- new Branch.NameKey(ctx.getProject(), refName),
+ BranchNameKey.create(ctx.getProject(), refName),
ctx.getIdentifiedUser(),
new NoSshInfo(),
ctx.getRevWalk(),
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 8d57992ec8..27e5347d8a 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -28,6 +28,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
+import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_DIFFSTAT;
import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_MERGEABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
@@ -48,6 +49,12 @@ import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRecord.Status;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ListChangesOption;
@@ -69,12 +76,6 @@ import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GpgException;
@@ -288,7 +289,7 @@ public class ChangeJson {
public ChangeInfo format(RevisionResource rsrc) {
ChangeData cd = changeDataFactory.create(rsrc.getNotes());
- return format(cd, Optional.of(rsrc.getPatchSet().getId()), true, ChangeInfo::new);
+ return format(cd, Optional.of(rsrc.getPatchSet().id()), true, ChangeInfo::new);
}
public List<List<ChangeInfo>> format(List<QueryResult<ChangeData>> in)
@@ -436,7 +437,7 @@ public class ChangeJson {
info = format(cd, Optional.empty(), false, ChangeInfo::new);
changeInfos.add(info);
if (isCacheable) {
- cache.put(new Change.Id(info._number), info);
+ cache.put(Change.id(info._number), info);
}
} catch (RuntimeException e) {
logger.atWarning().withCause(e).log(
@@ -467,7 +468,7 @@ public class ChangeJson {
Change c = result.change();
if (c != null) {
info.project = c.getProject().get();
- info.branch = c.getDest().getShortName();
+ info.branch = c.getDest().shortName();
info.topic = c.getTopic();
info.changeId = c.getKey().get();
info.subject = c.getSubject();
@@ -516,7 +517,7 @@ public class ChangeJson {
Change in = cd.change();
out.project = in.getProject().get();
- out.branch = in.getDest().getShortName();
+ out.branch = in.getDest().shortName();
out.topic = in.getTopic();
out.assignee = in.getAssignee() != null ? accountLoader.get(in.getAssignee()) : null;
out.hashtags = cd.hashtags();
@@ -533,10 +534,12 @@ public class ChangeJson {
out.submittable = submittable(cd);
}
}
- Optional<ChangedLines> changedLines = cd.changedLines();
- if (changedLines.isPresent()) {
- out.insertions = changedLines.get().insertions;
- out.deletions = changedLines.get().deletions;
+ if (!has(SKIP_DIFFSTAT)) {
+ Optional<ChangedLines> changedLines = cd.changedLines();
+ if (changedLines.isPresent()) {
+ out.insertions = changedLines.get().insertions;
+ out.deletions = changedLines.get().deletions;
+ }
}
out.isPrivate = in.isPrivate() ? true : null;
out.workInProgress = in.isWorkInProgress() ? true : null;
@@ -673,8 +676,8 @@ public class ChangeJson {
if (!s.isPresent()) {
return;
}
- out.submitted = s.get().getGranted();
- out.submitter = accountLoader.get(s.get().getAccountId());
+ out.submitted = s.get().granted();
+ out.submitter = accountLoader.get(s.get().accountId());
}
private Collection<ChangeMessageInfo> messages(ChangeData cd) {
@@ -716,7 +719,7 @@ public class ChangeJson {
continue;
}
for (ApprovalInfo ai : label.all) {
- Account.Id id = new Account.Id(ai._accountId);
+ Account.Id id = Account.id(ai._accountId);
if (canRemoveAnyReviewer
|| removeReviewerControl.testRemoveReviewer(
@@ -736,7 +739,7 @@ public class ChangeJson {
if (ccs != null) {
for (AccountInfo ai : ccs) {
if (ai._accountId != null) {
- Account.Id id = new Account.Id(ai._accountId);
+ Account.Id id = Account.id(ai._accountId);
if (canRemoveAnyReviewer
|| removeReviewerControl.testRemoveReviewer(cd, userProvider.get(), id, 0)) {
removable.add(id);
@@ -801,7 +804,7 @@ public class ChangeJson {
}
Map<PatchSet.Id, PatchSet> map = Maps.newHashMapWithExpectedSize(src.size());
for (PatchSet patchSet : src) {
- map.put(patchSet.getId(), patchSet);
+ map.put(patchSet.id(), patchSet);
}
return map;
}
diff --git a/java/com/google/gerrit/server/change/ChangeKeyAdapter.java b/java/com/google/gerrit/server/change/ChangeKeyAdapter.java
new file mode 100644
index 0000000000..0db4ceab52
--- /dev/null
+++ b/java/com/google/gerrit/server/change/ChangeKeyAdapter.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.entities.Change;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+
+/**
+ * Adapter that serializes {@link com.google.gerrit.entities.Change.Key}'s {@code key} field as
+ * {@code id}, for backwards compatibility in stream-events.
+ */
+// TODO(dborowitz): auto-value-gson should support this directly using @SerializedName on the
+// AutoValue method.
+public class ChangeKeyAdapter implements JsonSerializer<Change.Key>, JsonDeserializer<Change.Key> {
+ @Override
+ public JsonElement serialize(Change.Key src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("id", src.get());
+ return obj;
+ }
+
+ @Override
+ public Change.Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonElement keyJson = json.getAsJsonObject().get("id");
+ if (keyJson == null || !keyJson.isJsonPrimitive() || !keyJson.getAsJsonPrimitive().isString()) {
+ throw new JsonParseException("Key is not a string: " + keyJson);
+ }
+ String key = keyJson.getAsJsonPrimitive().getAsString();
+ return Change.key(key);
+ }
+}
diff --git a/java/com/google/gerrit/server/change/ChangeKindCache.java b/java/com/google/gerrit/server/change/ChangeKindCache.java
index 44da4d6094..9bd7ad74de 100644
--- a/java/com/google/gerrit/server/change/ChangeKindCache.java
+++ b/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ChangeKind;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.query.change.ChangeData;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index c20c0a27d0..1e149548db 100644
--- a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -23,12 +23,12 @@ import com.google.common.cache.Weigher;
import com.google.common.collect.FluentIterable;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
@@ -374,13 +374,13 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
ChangeKind kind = ChangeKind.REWORK;
// Trivial case: if we're on the first patch, we don't need to use
// the repository.
- if (patch.getId().get() > 1) {
+ if (patch.id().get() > 1) {
try {
Collection<PatchSet> patchSetCollection = change.patchSets();
PatchSet priorPs = patch;
for (PatchSet ps : patchSetCollection) {
- if (ps.getId().get() < patch.getId().get()
- && (ps.getId().get() > priorPs.getId().get() || priorPs == patch)) {
+ if (ps.id().get() < patch.id().get()
+ && (ps.id().get() > priorPs.id().get() || priorPs == patch)) {
// We only want the previous patch set, so walk until the last one
priorPs = ps;
}
@@ -393,22 +393,17 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
if (priorPs != patch) {
kind =
cache.getChangeKind(
- change.project(),
- rw,
- repoConfig,
- ObjectId.fromString(priorPs.getRevision().get()),
- ObjectId.fromString(patch.getRevision().get()));
+ change.project(), rw, repoConfig, priorPs.commitId(), patch.commitId());
}
} catch (StorageException e) {
// Do nothing; assume we have a complex change
logger.atWarning().withCause(e).log(
"Unable to get change kind for patchSet %s of change %s",
- patch.getPatchSetId(), change.getId());
+ patch.number(), change.getId());
}
}
logger.atFine().log(
- "Change kind for patchSet %s of change %s: %s",
- patch.getPatchSetId(), change.getId(), kind);
+ "Change kind for patchSet %s of change %s: %s", patch.number(), change.getId(), kind);
return kind;
}
@@ -422,7 +417,7 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
ChangeKind kind = ChangeKind.REWORK;
// Trivial case: if we're on the first patch, we don't need to open
// the repository.
- if (patch.getId().get() > 1) {
+ if (patch.id().get() > 1) {
try (Repository repo = repoManager.openRepository(change.getProject());
RevWalk rw = new RevWalk(repo)) {
kind =
@@ -432,12 +427,11 @@ public class ChangeKindCacheImpl implements ChangeKindCache {
// Do nothing; assume we have a complex change
logger.atWarning().withCause(e).log(
"Unable to get change kind for patchSet %s of change %s",
- patch.getPatchSetId(), change.getChangeId());
+ patch.number(), change.getChangeId());
}
}
logger.atFine().log(
- "Change kind for patchSet %s of change %s: %s",
- patch.getPatchSetId(), change.getChangeId(), kind);
+ "Change kind for patchSet %s of change %s: %s", patch.number(), change.getChangeId(), kind);
return kind;
}
}
diff --git a/java/com/google/gerrit/server/change/ChangeMessageResource.java b/java/com/google/gerrit/server/change/ChangeMessageResource.java
index 3c9ef34a28..25f952d0b1 100644
--- a/java/com/google/gerrit/server/change/ChangeMessageResource.java
+++ b/java/com/google/gerrit/server/change/ChangeMessageResource.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.inject.TypeLiteral;
/** A change message resource. */
diff --git a/java/com/google/gerrit/server/change/ChangeResource.java b/java/com/google/gerrit/server/change/ChangeResource.java
index 98b728f167..8b8ce54745 100644
--- a/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/java/com/google/gerrit/server/change/ChangeResource.java
@@ -21,23 +21,27 @@ import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestResource.HasETag;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
@@ -75,6 +79,7 @@ public class ChangeResource implements RestResource, HasETag {
private final PermissionBackend permissionBackend;
private final StarredChangesUtil starredChangesUtil;
private final ProjectCache projectCache;
+ private final PluginSetContext<ChangeETagComputation> changeETagComputation;
private final ChangeNotes notes;
private final CurrentUser user;
@@ -86,6 +91,7 @@ public class ChangeResource implements RestResource, HasETag {
PermissionBackend permissionBackend,
StarredChangesUtil starredChangesUtil,
ProjectCache projectCache,
+ PluginSetContext<ChangeETagComputation> changeETagComputation,
@Assisted ChangeNotes notes,
@Assisted CurrentUser user) {
this.accountCache = accountCache;
@@ -94,6 +100,7 @@ public class ChangeResource implements RestResource, HasETag {
this.permissionBackend = permissionBackend;
this.starredChangesUtil = starredChangesUtil;
this.projectCache = projectCache;
+ this.changeETagComputation = changeETagComputation;
this.notes = notes;
this.user = user;
}
@@ -149,7 +156,7 @@ public class ChangeResource implements RestResource, HasETag {
accounts.add(getChange().getAssignee());
}
try {
- patchSetUtil.byChange(notes).stream().map(PatchSet::getUploader).forEach(accounts::add);
+ patchSetUtil.byChange(notes).stream().map(PatchSet::uploader).forEach(accounts::add);
// It's intentional to include the states for *all* reviewers into the ETag computation.
// We need the states of all current reviewers and CCs because they are part of ChangeInfo.
@@ -193,16 +200,32 @@ public class ChangeResource implements RestResource, HasETag {
for (ProjectState p : projectStateTree) {
hashObjectId(h, p.getConfig().getRevision(), buf);
}
+
+ changeETagComputation.runEach(
+ c -> {
+ String pluginETag = c.getETag(notes.getProjectName(), notes.getChangeId());
+ if (pluginETag != null) {
+ h.putString(pluginETag, UTF_8);
+ }
+ });
}
@Override
public String getETag() {
- Hasher h = Hashing.murmur3_128().newHasher();
- if (user.isIdentifiedUser()) {
- h.putString(starredChangesUtil.getObjectId(user.getAccountId(), getId()).name(), UTF_8);
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Compute change ETag",
+ Metadata.builder()
+ .changeId(notes.getChangeId().get())
+ .projectName(notes.getProjectName().get())
+ .build())) {
+ Hasher h = Hashing.murmur3_128().newHasher();
+ if (user.isIdentifiedUser()) {
+ h.putString(starredChangesUtil.getObjectId(user.getAccountId(), getId()).name(), UTF_8);
+ }
+ prepareETag(h, user);
+ return h.hash().toString();
}
- prepareETag(h, user);
- return h.hash().toString();
}
private void hashObjectId(Hasher h, ObjectId id, byte[] buf) {
@@ -211,9 +234,8 @@ public class ChangeResource implements RestResource, HasETag {
}
private void hashAccount(Hasher h, AccountState accountState, byte[] buf) {
- h.putInt(accountState.getAccount().getId().get());
- h.putString(
- MoreObjects.firstNonNull(accountState.getAccount().getMetaId(), ZERO_ID_STRING), UTF_8);
- accountState.getExternalIds().stream().forEach(e -> hashObjectId(h, e.blobId(), buf));
+ h.putInt(accountState.account().id().get());
+ h.putString(MoreObjects.firstNonNull(accountState.account().metaId(), ZERO_ID_STRING), UTF_8);
+ accountState.externalIds().stream().forEach(e -> hashObjectId(h, e.blobId(), buf));
}
}
diff --git a/java/com/google/gerrit/server/change/ChangeTriplet.java b/java/com/google/gerrit/server/change/ChangeTriplet.java
index e4e6870250..10743022e5 100644
--- a/java/com/google/gerrit/server/change/ChangeTriplet.java
+++ b/java/com/google/gerrit/server/change/ChangeTriplet.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.change;
import com.google.auto.value.AutoValue;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.Optional;
@AutoValue
@@ -27,8 +27,8 @@ public abstract class ChangeTriplet {
return format(change.getDest(), change.getKey());
}
- private static String format(Branch.NameKey branch, Change.Key change) {
- return branch.getParentKey().get() + "~" + branch.getShortName() + "~" + change.get();
+ private static String format(BranchNameKey branch, Change.Key change) {
+ return branch.project().get() + "~" + branch.shortName() + "~" + change.get();
}
/**
@@ -53,14 +53,14 @@ public abstract class ChangeTriplet {
String changeId = Url.decode(triplet.substring(z + 1));
return Optional.of(
new AutoValue_ChangeTriplet(
- new Branch.NameKey(new Project.NameKey(project), branch), new Change.Key(changeId)));
+ BranchNameKey.create(Project.nameKey(project), branch), Change.key(changeId)));
}
public final Project.NameKey project() {
- return branch().getParentKey();
+ return branch().project();
}
- public abstract Branch.NameKey branch();
+ public abstract BranchNameKey branch();
public abstract Change.Key id();
diff --git a/java/com/google/gerrit/server/change/CommentResource.java b/java/com/google/gerrit/server/change/CommentResource.java
index 1b7cbf801a..dbe7a7642c 100644
--- a/java/com/google/gerrit/server/change/CommentResource.java
+++ b/java/com/google/gerrit/server/change/CommentResource.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.inject.TypeLiteral;
public class CommentResource implements RestResource {
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 80b7190df7..0374a1c0db 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
import static com.google.gerrit.server.ChangeUtil.PS_ID_ORDER;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
@@ -30,14 +30,14 @@ import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.extensions.common.ProblemInfo.Status;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -238,7 +238,7 @@ public class ConsistencyChecker {
}
private boolean openRepo() {
- Project.NameKey project = change().getDest().getParentKey();
+ Project.NameKey project = change().getDest().project();
try {
repo = repoManager.openRepository(project);
oi = repo.newObjectInserter();
@@ -265,7 +265,7 @@ public class ConsistencyChecker {
try {
refs =
repo.getRefDatabase()
- .exactRef(all.stream().map(ps -> ps.getId().toRefName()).toArray(String[]::new));
+ .exactRef(all.stream().map(ps -> ps.id().toRefName()).toArray(String[]::new));
} catch (IOException e) {
error("error reading refs", e);
refs = Collections.emptyMap();
@@ -274,12 +274,9 @@ public class ConsistencyChecker {
List<DeletePatchSetFromDbOp> deletePatchSetOps = new ArrayList<>();
for (PatchSet ps : all) {
// Check revision format.
- int psNum = ps.getId().get();
- String refName = ps.getId().toRefName();
- ObjectId objId = parseObjectId(ps.getRevision().get(), "patch set " + psNum);
- if (objId == null) {
- continue;
- }
+ int psNum = ps.id().get();
+ String refName = ps.id().toRefName();
+ ObjectId objId = ps.commitId();
patchSetsBySha.put(objId, ps);
// Check ref existence.
@@ -299,13 +296,13 @@ public class ConsistencyChecker {
RevCommit psCommit = parseCommit(objId, String.format("patch set %d", psNum));
if (psCommit == null) {
if (fix != null && fix.deletePatchSetIfCommitMissing) {
- deletePatchSetOps.add(new DeletePatchSetFromDbOp(lastProblem(), ps.getId()));
+ deletePatchSetOps.add(new DeletePatchSetFromDbOp(lastProblem(), ps.id()));
}
continue;
} else if (refProblem != null && fix != null) {
fixPatchSetRef(refProblem, ps);
}
- if (ps.getId().equals(change().currentPatchSetId())) {
+ if (ps.id().equals(change().currentPatchSetId())) {
currPsCommit = psCommit;
}
}
@@ -319,7 +316,7 @@ public class ConsistencyChecker {
problem(
String.format(
"Multiple patch sets pointing to %s: %s",
- e.getKey().name(), Collections2.transform(e.getValue(), PatchSet::getPatchSetId)));
+ e.getKey().name(), Collections2.transform(e.getValue(), PatchSet::number)));
}
}
@@ -327,7 +324,7 @@ public class ConsistencyChecker {
}
private void checkMerged() {
- String refName = change().getDest().get();
+ String refName = change().getDest().branch();
Ref dest;
try {
dest = repo.getRefDatabase().exactRef(refName);
@@ -351,15 +348,15 @@ public class ConsistencyChecker {
try {
merged = rw.isMergedInto(currPsCommit, tip);
} catch (IOException e) {
- problem("Error checking whether patch set " + currPs.getId().get() + " is merged");
+ problem("Error checking whether patch set " + currPs.id().get() + " is merged");
return;
}
- checkMergedBitMatchesStatus(currPs.getId(), currPsCommit, merged);
+ checkMergedBitMatchesStatus(currPs.id(), currPsCommit, merged);
}
}
private ProblemInfo wrongChangeStatus(PatchSet.Id psId, RevCommit commit) {
- String refName = change().getDest().get();
+ String refName = change().getDest().branch();
return problem(
formatProblemMessage(
"Patch set %d (%s) is merged into destination ref %s (%s), but change"
@@ -368,7 +365,7 @@ public class ConsistencyChecker {
}
private void checkMergedBitMatchesStatus(PatchSet.Id psId, RevCommit commit, boolean merged) {
- String refName = change().getDest().get();
+ String refName = change().getDest().branch();
if (merged && !change().isMerged()) {
ProblemInfo p = wrongChangeStatus(psId, commit);
if (fix != null) {
@@ -379,7 +376,7 @@ public class ConsistencyChecker {
formatProblemMessage(
"Patch set %d (%s) is not merged into"
+ " destination ref %s (%s), but change status is %s",
- currPs.getId().get(), commit.name(), refName, tip.name()));
+ currPs.id().get(), commit.name(), refName, tip.name()));
}
}
@@ -395,7 +392,11 @@ public class ConsistencyChecker {
}
private void checkExpectMergedAs() {
- ObjectId objId = parseObjectId(fix.expectMergedAs, "expected merged commit");
+ if (!ObjectId.isId(fix.expectMergedAs)) {
+ problem("Invalid revision on expected merged commit: " + fix.expectMergedAs);
+ return;
+ }
+ ObjectId objId = ObjectId.fromString(fix.expectMergedAs);
RevCommit commit = parseCommit(objId, "expected merged commit");
if (commit == null) {
return;
@@ -406,7 +407,7 @@ public class ConsistencyChecker {
problem(
String.format(
"Expected merged commit %s is not merged into destination ref %s (%s)",
- commit.name(), change().getDest().get(), tip.name()));
+ commit.name(), change().getDest().branch(), tip.name()));
return;
}
@@ -420,8 +421,7 @@ public class ConsistencyChecker {
continue;
}
try {
- Change c =
- notesFactory.createChecked(change().getProject(), psId.getParentKey()).getChange();
+ Change c = notesFactory.createChecked(change().getProject(), psId.changeId()).getChange();
if (!c.getDest().equals(change().getDest())) {
continue;
}
@@ -601,9 +601,9 @@ public class ConsistencyChecker {
private void fixPatchSetRef(ProblemInfo p, PatchSet ps) {
try {
- RefUpdate ru = repo.updateRef(ps.getId().toRefName());
+ RefUpdate ru = repo.updateRef(ps.id().toRefName());
ru.setForceUpdate(true);
- ru.setNewObjectId(ObjectId.fromString(ps.getRevision().get()));
+ ru.setNewObjectId(ps.commitId());
ru.setRefLogIdent(newRefLogIdent());
ru.setRefLogMessage("Repair patch set ref", true);
RefUpdate.Result result = ru.update();
@@ -630,7 +630,7 @@ public class ConsistencyChecker {
}
} catch (IOException e) {
String msg = "Error fixing patch set ref";
- logger.atWarning().withCause(e).log("%s %s", msg, ps.getId().toRefName());
+ logger.atWarning().withCause(e).log("%s %s", msg, ps.id().toRefName());
p.status = Status.FIX_FAILED;
p.outcome = msg;
}
@@ -640,7 +640,7 @@ public class ConsistencyChecker {
try (BatchUpdate bu = newBatchUpdate()) {
bu.setRepository(repo, rw, oi);
for (DeletePatchSetFromDbOp op : ops) {
- checkArgument(op.psId.getParentKey().equals(notes.getChangeId()));
+ checkArgument(op.psId.changeId().equals(notes.getChangeId()));
bu.addOp(notes.getChangeId(), op);
}
bu.addOp(notes.getChangeId(), new UpdateCurrentPatchSetOp(ops));
@@ -652,7 +652,7 @@ public class ConsistencyChecker {
}
} catch (UpdateException | RestApiException e) {
String msg = "Error deleting patch set";
- logger.atWarning().withCause(e).log("%s of change %s", msg, ops.get(0).psId.getParentKey());
+ logger.atWarning().withCause(e).log("%s of change %s", msg, ops.get(0).psId.changeId());
for (DeletePatchSetFromDbOp op : ops) {
// Overwrite existing statuses that were set before the transaction was
// rolled back.
@@ -714,8 +714,8 @@ public class ConsistencyChecker {
// and whether they are seen by this op; we are already given the full set
// of patch sets that will eventually be deleted in this update.
for (PatchSet ps : psUtil.byChange(ctx.getNotes())) {
- if (!toDelete.contains(ps.getId())) {
- all.add(ps.getId());
+ if (!toDelete.contains(ps.id())) {
+ all.add(ps.id());
}
}
if (all.isEmpty()) {
@@ -734,15 +734,6 @@ public class ConsistencyChecker {
return serverIdent.get();
}
- private ObjectId parseObjectId(String objIdStr, String desc) {
- try {
- return ObjectId.fromString(objIdStr);
- } catch (IllegalArgumentException e) {
- problem(String.format("Invalid revision on %s: %s", desc, objIdStr));
- return null;
- }
- }
-
private RevCommit parseCommit(ObjectId objId, String desc) {
try {
return rw.parseCommit(objId);
diff --git a/java/com/google/gerrit/server/change/DeleteChangeOp.java b/java/com/google/gerrit/server/change/DeleteChangeOp.java
index ec20e87093..14298d5149 100644
--- a/java/com/google/gerrit/server/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/change/DeleteChangeOp.java
@@ -14,16 +14,19 @@
package com.google.gerrit.server.change;
+import static com.google.common.flogger.LazyArgs.lazy;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.extensions.events.ChangeDeleted;
import com.google.gerrit.server.plugincontext.PluginItemContext;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.RepoContext;
@@ -37,6 +40,8 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevWalk;
public class DeleteChangeOp implements BatchUpdateOp {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
DeleteChangeOp create(Change.Id id);
}
@@ -71,8 +76,19 @@ public class DeleteChangeOp implements BatchUpdateOp {
ensureDeletable(ctx, id, patchSets);
// Cleaning up is only possible as long as the change and its elements are
// still part of the database.
- cleanUpReferences(ctx, id);
+ cleanUpReferences(id);
+ logger.atFine().log(
+ "Deleting change %s, current patch set %d is commit %s",
+ id,
+ ctx.getChange().currentPatchSetId().get(),
+ lazy(
+ () ->
+ patchSets.stream()
+ .filter(p -> p.number() == ctx.getChange().currentPatchSetId().get())
+ .findAny()
+ .map(p -> p.commitId().name())
+ .orElse("n/a")));
ctx.deleteChange();
changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen());
return true;
@@ -87,37 +103,50 @@ public class DeleteChangeOp implements BatchUpdateOp {
if (isPatchSetMerged(ctx, patchSet)) {
throw new ResourceConflictException(
String.format(
- "Cannot delete change %s: patch set %s is already merged",
- id, patchSet.getPatchSetId()));
+ "Cannot delete change %s: patch set %s is already merged", id, patchSet.number()));
}
}
}
private boolean isPatchSetMerged(ChangeContext ctx, PatchSet patchSet) throws IOException {
- Optional<ObjectId> destId = ctx.getRepoView().getRef(ctx.getChange().getDest().get());
+ Optional<ObjectId> destId = ctx.getRepoView().getRef(ctx.getChange().getDest().branch());
if (!destId.isPresent()) {
return false;
}
RevWalk revWalk = ctx.getRevWalk();
- ObjectId objectId = ObjectId.fromString(patchSet.getRevision().get());
- return revWalk.isMergedInto(revWalk.parseCommit(objectId), revWalk.parseCommit(destId.get()));
+ return revWalk.isMergedInto(
+ revWalk.parseCommit(patchSet.commitId()), revWalk.parseCommit(destId.get()));
}
- private void cleanUpReferences(ChangeContext ctx, Change.Id id) throws NoSuchChangeException {
+ private void cleanUpReferences(Change.Id id) throws IOException {
accountPatchReviewStore.run(s -> s.clearReviewed(id));
- // Non-atomic operation on Accounts table; not much we can do to make it
- // atomic.
- starredChangesUtil.unstarAll(ctx.getChange().getProject(), id);
+ // Non-atomic operation on All-Users refs; not much we can do to make it atomic.
+ starredChangesUtil.unstarAllForChangeDeletion(id);
}
@Override
public void updateRepo(RepoContext ctx) throws IOException {
- String prefix = new PatchSet.Id(id, 1).toRefName();
- prefix = prefix.substring(0, prefix.length() - 1);
+ String changeRefPrefix = RefNames.changeRefPrefix(id);
+ for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(changeRefPrefix).entrySet()) {
+ removeRef(ctx, e, changeRefPrefix);
+ }
+ removeUserEdits(ctx);
+ }
+
+ private void removeUserEdits(RepoContext ctx) throws IOException {
+ String prefix = RefNames.REFS_USERS;
+ String editRef = String.format("/edit-%s/", id);
for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(prefix).entrySet()) {
- ctx.addRefUpdate(e.getValue(), ObjectId.zeroId(), prefix + e.getKey());
+ if (e.getKey().contains(editRef)) {
+ removeRef(ctx, e, prefix);
+ }
}
}
+
+ private void removeRef(RepoContext ctx, Map.Entry<String, ObjectId> entry, String prefix)
+ throws IOException {
+ ctx.addRefUpdate(entry.getValue(), ObjectId.zeroId(), prefix + entry.getKey());
+ }
}
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java b/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
index 4b5572b6bb..3bc9324c36 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.change;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -55,7 +55,7 @@ public class DeleteReviewerByEmailOp implements BatchUpdateOp {
String msg = "Removed reviewer " + reviewer;
changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(change.getId(), ChangeUtil.messageUuid()),
+ ChangeMessage.key(change.getId(), ChangeUtil.messageUuid()),
ctx.getAccountId(),
ctx.getWhen(),
psId);
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
index 29458a864b..c4de02c812 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
@@ -18,17 +18,17 @@ import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -108,7 +108,7 @@ public class DeleteReviewerOp implements BatchUpdateOp {
@Override
public boolean updateChange(ChangeContext ctx)
throws AuthException, ResourceNotFoundException, PermissionBackendException, IOException {
- Account.Id reviewerId = reviewer.getAccount().getId();
+ Account.Id reviewerId = reviewer.account().id();
// Check of removing this reviewer (even if there is no vote processed by the loop below) is OK
removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), reviewerId);
@@ -125,7 +125,7 @@ public class DeleteReviewerOp implements BatchUpdateOp {
}
StringBuilder msg = new StringBuilder();
- msg.append("Removed reviewer " + reviewer.getAccount().getFullName());
+ msg.append("Removed reviewer " + reviewer.account().fullName());
StringBuilder removedVotesMsg = new StringBuilder();
removedVotesMsg.append(" with the following votes:\n\n");
List<PatchSetApproval> del = new ArrayList<>();
@@ -134,14 +134,14 @@ public class DeleteReviewerOp implements BatchUpdateOp {
// Check if removing this vote is OK
removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), a);
del.add(a);
- if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) {
- oldApprovals.put(a.getLabel(), a.getValue());
+ if (a.patchSetId().equals(currPs.id()) && a.value() != 0) {
+ oldApprovals.put(a.label(), a.value());
removedVotesMsg
.append("* ")
- .append(a.getLabel())
- .append(formatLabelValue(a.getValue()))
+ .append(a.label())
+ .append(formatLabelValue(a.value()))
.append(" by ")
- .append(userFactory.create(a.getAccountId()).getNameEmail())
+ .append(userFactory.create(a.accountId()).getNameEmail())
.append("\n");
votesRemoved = true;
}
@@ -152,7 +152,7 @@ public class DeleteReviewerOp implements BatchUpdateOp {
} else {
msg.append(".");
}
- ChangeUpdate update = ctx.getUpdate(currPs.getId());
+ ChangeUpdate update = ctx.getUpdate(currPs.id());
update.removeReviewer(reviewerId);
changeMessage =
@@ -195,7 +195,7 @@ public class DeleteReviewerOp implements BatchUpdateOp {
private Iterable<PatchSetApproval> approvals(ChangeContext ctx, Account.Id accountId) {
Iterable<PatchSetApproval> approvals;
approvals = approvalsUtil.byChange(ctx.getNotes()).values();
- return Iterables.filter(approvals, psa -> accountId.equals(psa.getAccountId()));
+ return Iterables.filter(approvals, psa -> accountId.equals(psa.accountId()));
}
private String formatLabelValue(short value) {
@@ -206,16 +206,19 @@ public class DeleteReviewerOp implements BatchUpdateOp {
}
private void emailReviewers(
- NameKey projectName, Change change, ChangeMessage changeMessage, NotifyResolver.Result notify)
+ Project.NameKey projectName,
+ Change change,
+ ChangeMessage changeMessage,
+ NotifyResolver.Result notify)
throws EmailException {
Account.Id userId = user.get().getAccountId();
- if (userId.equals(reviewer.getAccount().getId())) {
+ if (userId.equals(reviewer.account().id())) {
// The user knows they removed themselves, don't bother emailing them.
return;
}
DeleteReviewerSender cm = deleteReviewerSenderFactory.create(projectName, change.getId());
cm.setFrom(userId);
- cm.addReviewers(Collections.singleton(reviewer.getAccount().getId()));
+ cm.addReviewers(Collections.singleton(reviewer.account().id()));
cm.setChangeMessage(changeMessage.getMessage(), changeMessage.getWrittenOn());
cm.setNotify(notify);
cm.send();
diff --git a/java/com/google/gerrit/server/change/DraftCommentResource.java b/java/com/google/gerrit/server/change/DraftCommentResource.java
index ef317251f1..3d3e8f9da8 100644
--- a/java/com/google/gerrit/server/change/DraftCommentResource.java
+++ b/java/com/google/gerrit/server/change/DraftCommentResource.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
diff --git a/java/com/google/gerrit/server/change/EmailReviewComments.java b/java/com/google/gerrit/server/change/EmailReviewComments.java
index 8353501f93..f7e45e70d7 100644
--- a/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -18,9 +18,9 @@ import static com.google.gerrit.server.CommentsUtil.COMMENT_ORDER;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SendEmailExecutor;
@@ -129,7 +129,7 @@ public class EmailReviewComments implements Runnable, RequestContext {
cm.setNotify(notify);
cm.send();
} catch (Exception e) {
- logger.atSevere().withCause(e).log("Cannot email comments for %s", patchSet.getId());
+ logger.atSevere().withCause(e).log("Cannot email comments for %s", patchSet.id());
} finally {
requestContext.setContext(old);
}
diff --git a/java/com/google/gerrit/server/change/FileContentUtil.java b/java/com/google/gerrit/server/change/FileContentUtil.java
index a806f94653..5c7946cc69 100644
--- a/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -21,10 +21,10 @@ import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PatchScript.FileMode;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.project.ProjectState;
diff --git a/java/com/google/gerrit/server/change/FileInfoJson.java b/java/com/google/gerrit/server/change/FileInfoJson.java
index 56cc8df4d6..a82397545a 100644
--- a/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.common.FileInfo;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -44,27 +43,20 @@ public class FileInfoJson {
public Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
- return toFileInfoMap(change, patchSet.getRevision(), null);
- }
-
- public Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, @Nullable PatchSet base)
- throws PatchListNotAvailableException {
- ObjectId objectId = ObjectId.fromString(revision.get());
- return toFileInfoMap(change, objectId, base);
+ return toFileInfoMap(change, patchSet.commitId(), null);
}
public Map<String, FileInfo> toFileInfoMap(
Change change, ObjectId objectId, @Nullable PatchSet base)
throws PatchListNotAvailableException {
- ObjectId a = (base == null) ? null : ObjectId.fromString(base.getRevision().get());
+ ObjectId a = base != null ? base.commitId() : null;
return toFileInfoMap(change, PatchListKey.againstCommit(a, objectId, Whitespace.IGNORE_NONE));
}
- public Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, int parent)
+ public Map<String, FileInfo> toFileInfoMap(Change change, ObjectId objectId, int parent)
throws PatchListNotAvailableException {
- ObjectId b = ObjectId.fromString(revision.get());
return toFileInfoMap(
- change, PatchListKey.againstParentNum(parent + 1, b, Whitespace.IGNORE_NONE));
+ change, PatchListKey.againstParentNum(parent + 1, objectId, Whitespace.IGNORE_NONE));
}
private Map<String, FileInfo> toFileInfoMap(Change change, PatchListKey key)
diff --git a/java/com/google/gerrit/server/change/FileResource.java b/java/com/google/gerrit/server/change/FileResource.java
index bd7557f2c2..5402338574 100644
--- a/java/com/google/gerrit/server/change/FileResource.java
+++ b/java/com/google/gerrit/server/change/FileResource.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.inject.TypeLiteral;
public class FileResource implements RestResource {
@@ -29,7 +29,7 @@ public class FileResource implements RestResource {
public FileResource(RevisionResource rev, String name) {
this.rev = rev;
- this.key = new Patch.Key(rev.getPatchSet().getId(), name);
+ this.key = Patch.key(rev.getPatchSet().id(), name);
}
public Patch.Key getPatchKey() {
diff --git a/java/com/google/gerrit/server/change/FixResource.java b/java/com/google/gerrit/server/change/FixResource.java
index 08e278542f..b6b5894f10 100644
--- a/java/com/google/gerrit/server/change/FixResource.java
+++ b/java/com/google/gerrit/server/change/FixResource.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.FixReplacement;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.FixReplacement;
import com.google.inject.TypeLiteral;
import java.util.List;
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index 3ac6959484..a0b64dab34 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -14,22 +14,33 @@
package com.google.gerrit.server.change;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.config.ExternalIncludedIn;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -37,17 +48,21 @@ import org.eclipse.jgit.revwalk.RevWalk;
@Singleton
public class IncludedIn {
private final GitRepositoryManager repoManager;
+ private final PermissionBackend permissionBackend;
private final PluginSetContext<ExternalIncludedIn> externalIncludedIn;
@Inject
IncludedIn(
- GitRepositoryManager repoManager, PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
+ GitRepositoryManager repoManager,
+ PermissionBackend permissionBackend,
+ PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
this.repoManager = repoManager;
+ this.permissionBackend = permissionBackend;
this.externalIncludedIn = externalIncludedIn;
}
public IncludedInInfo apply(Project.NameKey project, String revisionId)
- throws RestApiException, IOException {
+ throws RestApiException, IOException, PermissionBackendException {
try (Repository r = repoManager.openRepository(project);
RevWalk rw = new RevWalk(r)) {
rw.setRetainBody(false);
@@ -61,18 +76,45 @@ public class IncludedIn {
}
IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
+
+ // Filter branches and tags according to their visbility by the user
+ ImmutableSortedSet<String> filteredBranches =
+ sortedShortNames(filterReadableRefs(project, d.branches()).keySet());
+ ImmutableSortedSet<String> filteredTags =
+ sortedShortNames(filterReadableRefs(project, d.tags()).keySet());
+
ListMultimap<String, String> external = MultimapBuilder.hashKeys().arrayListValues().build();
externalIncludedIn.runEach(
ext -> {
ListMultimap<String, String> extIncludedIns =
- ext.getIncludedIn(project.get(), rev.name(), d.tags(), d.branches());
+ ext.getIncludedIn(project.get(), rev.name(), filteredBranches, filteredTags);
if (extIncludedIns != null) {
external.putAll(extIncludedIns);
}
});
return new IncludedInInfo(
- d.branches(), d.tags(), (!external.isEmpty() ? external.asMap() : null));
+ filteredBranches, filteredTags, (!external.isEmpty() ? external.asMap() : null));
+ }
+ }
+
+ /**
+ * Filter readable branches or tags according to the caller's refs visibility.
+ *
+ * @param project specific Gerrit project.
+ * @param inputRefs a list of branches (in short name) as strings
+ */
+ private Map<String, Ref> filterReadableRefs(Project.NameKey project, ImmutableList<Ref> inputRefs)
+ throws IOException, PermissionBackendException {
+ PermissionBackend.ForProject perm = permissionBackend.currentUser().project(project);
+ try (Repository repo = repoManager.openRepository(project)) {
+ return perm.filter(inputRefs, repo, RefFilterOptions.defaults());
}
}
+
+ private ImmutableSortedSet<String> sortedShortNames(Collection<String> refs) {
+ return refs.stream()
+ .map(Repository::shortenRefName)
+ .collect(toImmutableSortedSet(naturalOrder()));
+ }
}
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
index 09ca258103..38917006e1 100644
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -14,13 +14,11 @@
package com.google.gerrit.server.change;
-import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static java.util.Comparator.comparing;
-import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.toList;
import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -171,13 +169,12 @@ public class IncludedInResolver {
* Returns the short names of refs which are as well in the matchingRefs list as well as in the
* allRef list.
*/
- private static ImmutableSortedSet<String> getMatchingRefNames(
+ private static ImmutableList<Ref> getMatchingRefNames(
Set<String> matchingRefs, Collection<Ref> allRefs) {
return allRefs.stream()
- .map(Ref::getName)
- .filter(matchingRefs::contains)
- .map(Repository::shortenRefName)
- .collect(toImmutableSortedSet(naturalOrder()));
+ .filter(r -> matchingRefs.contains(r.getName()))
+ .distinct()
+ .collect(ImmutableList.toImmutableList());
}
/** Parse commit of ref and store the relation between ref and commit. */
@@ -211,8 +208,8 @@ public class IncludedInResolver {
@AutoValue
public abstract static class Result {
- public abstract ImmutableSortedSet<String> branches();
+ public abstract ImmutableList<Ref> branches();
- public abstract ImmutableSortedSet<String> tags();
+ public abstract ImmutableList<Ref> tags();
}
}
diff --git a/java/com/google/gerrit/server/change/LabelNormalizer.java b/java/com/google/gerrit/server/change/LabelNormalizer.java
index 1ec17176f3..1ef3aee0e6 100644
--- a/java/com/google/gerrit/server/change/LabelNormalizer.java
+++ b/java/com/google/gerrit/server/change/LabelNormalizer.java
@@ -24,8 +24,8 @@ import com.google.common.collect.Lists;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
@@ -88,24 +88,23 @@ public class LabelNormalizer {
List<PatchSetApproval> deleted = Lists.newArrayListWithCapacity(approvals.size());
LabelTypes labelTypes = projectCache.checkedGet(notes.getProjectName()).getLabelTypes(notes);
for (PatchSetApproval psa : approvals) {
- Change.Id changeId = psa.getKey().getParentKey().getParentKey();
+ Change.Id changeId = psa.key().patchSetId().changeId();
checkArgument(
changeId.equals(notes.getChangeId()),
"Approval %s does not match change %s",
- psa.getKey(),
+ psa.key(),
notes.getChange().getKey());
if (psa.isLegacySubmit()) {
unchanged.add(psa);
continue;
}
- LabelType label = labelTypes.byLabel(psa.getLabelId());
+ LabelType label = labelTypes.byLabel(psa.labelId());
if (label == null) {
deleted.add(psa);
continue;
}
- PatchSetApproval copy = copy(psa);
- applyTypeFloor(label, copy);
- if (copy.getValue() != psa.getValue()) {
+ PatchSetApproval copy = applyTypeFloor(label, psa);
+ if (copy.value() != psa.value()) {
updated.add(copy);
} else {
unchanged.add(psa);
@@ -114,18 +113,16 @@ public class LabelNormalizer {
return Result.create(unchanged, updated, deleted);
}
- private PatchSetApproval copy(PatchSetApproval src) {
- return new PatchSetApproval(src.getPatchSetId(), src);
- }
-
- private void applyTypeFloor(LabelType lt, PatchSetApproval a) {
+ private PatchSetApproval applyTypeFloor(LabelType lt, PatchSetApproval a) {
+ PatchSetApproval.Builder b = a.toBuilder();
LabelValue atMin = lt.getMin();
- if (atMin != null && a.getValue() < atMin.getValue()) {
- a.setValue(atMin.getValue());
+ if (atMin != null && a.value() < atMin.getValue()) {
+ b.value(atMin.getValue());
}
LabelValue atMax = lt.getMax();
- if (atMax != null && a.getValue() > atMax.getValue()) {
- a.setValue(atMax.getValue());
+ if (atMax != null && a.value() > atMax.getValue()) {
+ b.value(atMax.getValue());
}
+ return b.build();
}
}
diff --git a/java/com/google/gerrit/server/change/LabelsJson.java b/java/com/google/gerrit/server/change/LabelsJson.java
index 6fde5a5892..c6f49696ca 100644
--- a/java/com/google/gerrit/server/change/LabelsJson.java
+++ b/java/com/google/gerrit/server/change/LabelsJson.java
@@ -36,12 +36,12 @@ import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.VotingRangeInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.account.AccountLoader;
@@ -206,8 +206,8 @@ public class LabelsJson {
if (standard) {
for (PatchSetApproval psa : cd.currentApprovals()) {
if (type.matches(psa)) {
- short val = psa.getValue();
- Account.Id accountId = psa.getAccountId();
+ short val = psa.value();
+ Account.Id accountId = psa.accountId();
setLabelScores(accountLoader, type, e.getValue(), val, accountId);
}
}
@@ -260,7 +260,7 @@ public class LabelsJson {
accountId,
null,
null)) {
- result.put(psa.getLabel(), psa.getValue());
+ result.put(psa.label(), psa.value());
}
return result;
}
@@ -279,7 +279,7 @@ public class LabelsJson {
// we aren't including 0 votes for all users below, so we can just look at
// the latest patch set (in the next loop).
for (PatchSetApproval psa : cd.approvals().values()) {
- allUsers.add(psa.getAccountId());
+ allUsers.add(psa.accountId());
}
}
@@ -287,13 +287,13 @@ public class LabelsJson {
SetMultimap<Account.Id, PatchSetApproval> current =
MultimapBuilder.hashKeys().hashSetValues().build();
for (PatchSetApproval a : cd.currentApprovals()) {
- allUsers.add(a.getAccountId());
- LabelType type = labelTypes.byLabel(a.getLabelId());
+ allUsers.add(a.accountId());
+ LabelType type = labelTypes.byLabel(a.labelId());
if (type != null) {
labelNames.add(type.getName());
// Not worth the effort to distinguish between votable/non-votable for 0
// values on closed changes, since they can't vote anyway.
- current.put(a.getAccountId(), a);
+ current.put(a.accountId(), a);
}
}
@@ -335,19 +335,19 @@ public class LabelsJson {
}
}
for (PatchSetApproval psa : current.get(accountId)) {
- LabelType type = labelTypes.byLabel(psa.getLabelId());
+ LabelType type = labelTypes.byLabel(psa.labelId());
if (type == null) {
continue;
}
- short val = psa.getValue();
+ short val = psa.value();
ApprovalInfo info = byLabel.get(type.getName());
if (info != null) {
info.value = Integer.valueOf(val);
info.permittedVotingRange = pvr.getOrDefault(type.getName(), null);
- info.date = psa.getGranted();
- info.tag = psa.getTag();
- if (psa.isPostSubmit()) {
+ info.date = psa.granted();
+ info.tag = psa.tag().orElse(null);
+ if (psa.postSubmit()) {
info.postSubmit = true;
}
}
@@ -441,13 +441,13 @@ public class LabelsJson {
Set<Account.Id> allUsers = new HashSet<>();
allUsers.addAll(cd.reviewers().byState(ReviewerStateInternal.REVIEWER));
for (PatchSetApproval psa : cd.approvals().values()) {
- allUsers.add(psa.getAccountId());
+ allUsers.add(psa.accountId());
}
Table<Account.Id, String, PatchSetApproval> current =
HashBasedTable.create(allUsers.size(), cd.getLabelTypes().getLabelTypes().size());
for (PatchSetApproval psa : cd.currentApprovals()) {
- current.put(psa.getAccountId(), psa.getLabel(), psa);
+ current.put(psa.accountId(), psa.label(), psa);
}
LabelTypes labelTypes = cd.getLabelTypes();
@@ -467,16 +467,16 @@ public class LabelsJson {
Timestamp date = null;
PatchSetApproval psa = current.get(accountId, lt.getName());
if (psa != null) {
- value = Integer.valueOf(psa.getValue());
+ value = Integer.valueOf(psa.value());
if (value == 0) {
// This may be a dummy approval that was inserted when the reviewer
// was added. Explicitly check whether the user can vote on this
// label.
value = perm.test(new LabelPermission(lt)) ? 0 : null;
}
- tag = psa.getTag();
- date = psa.getGranted();
- if (psa.isPostSubmit()) {
+ tag = psa.tag().orElse(null);
+ date = psa.granted();
+ if (psa.postSubmit()) {
logger.atWarning().log("unexpected post-submit approval on open change: %s", psa);
}
} else {
diff --git a/java/com/google/gerrit/server/change/MergeabilityCache.java b/java/com/google/gerrit/server/change/MergeabilityCache.java
index 3a7f3ab2c2..b432bc9bf7 100644
--- a/java/com/google/gerrit/server/change/MergeabilityCache.java
+++ b/java/com/google/gerrit/server/change/MergeabilityCache.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Branch;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -29,7 +29,7 @@ public interface MergeabilityCache {
Ref intoRef,
SubmitType submitType,
String mergeStrategy,
- Branch.NameKey dest,
+ BranchNameKey dest,
Repository repo) {
throw new UnsupportedOperationException("Mergeability checking disabled");
}
@@ -46,7 +46,7 @@ public interface MergeabilityCache {
Ref intoRef,
SubmitType submitType,
String mergeStrategy,
- Branch.NameKey dest,
+ BranchNameKey dest,
Repository repo);
Boolean getIfPresent(ObjectId commit, Ref intoRef, SubmitType submitType, String mergeStrategy);
diff --git a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index d4085191dc..44af1e41e6 100644
--- a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -24,9 +24,9 @@ import com.google.common.cache.Cache;
import com.google.common.cache.Weigher;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
import com.google.gerrit.server.cache.serialize.BooleanCacheSerializer;
@@ -191,7 +191,7 @@ public class MergeabilityCacheImpl implements MergeabilityCache {
Ref intoRef,
SubmitType submitType,
String mergeStrategy,
- Branch.NameKey dest,
+ BranchNameKey dest,
Repository repo) {
ObjectId into = intoRef != null ? intoRef.getObjectId() : ObjectId.zeroId();
EntryKey key = new EntryKey(commit, into, submitType, mergeStrategy);
diff --git a/java/com/google/gerrit/server/change/NotifyResolver.java b/java/com/google/gerrit/server/change/NotifyResolver.java
index 5b156846a5..27951cad8e 100644
--- a/java/com/google/gerrit/server/change/NotifyResolver.java
+++ b/java/com/google/gerrit/server/change/NotifyResolver.java
@@ -21,12 +21,12 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.NotifyInfo;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountResolver;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -99,7 +99,7 @@ public class NotifyResolver {
List<String> problems = new ArrayList<>(inputs.size());
for (String nameOrEmail : inputs) {
try {
- r.add(accountResolver.resolve(nameOrEmail).asUnique().getAccount().getId());
+ r.add(accountResolver.resolve(nameOrEmail).asUnique().account().id());
} catch (UnprocessableEntityException e) {
problems.add(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index d3649f660d..71c54b1e8d 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -20,13 +20,13 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
@@ -208,7 +208,7 @@ public class PatchSetInserter implements BatchUpdateOp {
if (newGroups.isEmpty()) {
PatchSet prevPs = psUtil.current(ctx.getNotes());
if (prevPs != null) {
- newGroups = prevPs.getGroups();
+ newGroups = prevPs.groups();
}
}
patchSet =
@@ -222,7 +222,7 @@ public class PatchSetInserter implements BatchUpdateOp {
if (message != null) {
changeMessage =
ChangeMessagesUtil.newMessage(
- patchSet.getId(),
+ patchSet.id(),
ctx.getUser(),
ctx.getWhen(),
message,
@@ -288,7 +288,7 @@ public class PatchSetInserter implements BatchUpdateOp {
commitId,
refName.substring(0, refName.lastIndexOf('/') + 1) + "new"),
projectCache.checkedGet(origNotes.getProjectName()).getProject(),
- origNotes.getChange().getDest().get(),
+ origNotes.getChange().getDest().branch(),
ctx.getRevWalk().getObjectReader(),
commitId,
ctx.getIdentifiedUser())) {
diff --git a/java/com/google/gerrit/server/change/PureRevert.java b/java/com/google/gerrit/server/change/PureRevert.java
index acb3dd7879..cb632dc79c 100644
--- a/java/com/google/gerrit/server/change/PureRevert.java
+++ b/java/com/google/gerrit/server/change/PureRevert.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.git.PureRevertCache;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
@@ -54,8 +54,6 @@ public class PureRevert {
}
return pureRevertCache.isPureRevert(
- notes.getProjectName(),
- ObjectId.fromString(notes.getCurrentPatchSet().getRevision().get()),
- claimedOriginalObjectId);
+ notes.getProjectName(), notes.getCurrentPatchSet().commitId(), claimedOriginalObjectId);
}
}
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index fccda7c138..4723af8514 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -16,11 +16,10 @@ package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -151,10 +150,8 @@ public class RebaseChangeOp implements BatchUpdateOp {
NoSuchChangeException, PermissionBackendException {
// Ok that originalPatchSet was not read in a transaction, since we just
// need its revision.
- RevId oldRev = originalPatchSet.getRevision();
-
RevWalk rw = ctx.getRevWalk();
- RevCommit original = rw.parseCommit(ObjectId.fromString(oldRev.get()));
+ RevCommit original = rw.parseCommit(originalPatchSet.commitId());
rw.parseBody(original);
RevCommit baseCommit = rw.parseCommit(baseCommitId);
CurrentUser changeOwner = identifiedUserFactory.create(notes.getChange().getOwner());
@@ -164,7 +161,7 @@ public class RebaseChangeOp implements BatchUpdateOp {
rw.parseBody(baseCommit);
newCommitMessage =
newMergeUtil()
- .createCommitMessageOnSubmit(original, baseCommit, notes, originalPatchSet.getId());
+ .createCommitMessageOnSubmit(original, baseCommit, notes, originalPatchSet.id());
} else {
newCommitMessage = original.getFullMessage();
}
@@ -178,9 +175,7 @@ public class RebaseChangeOp implements BatchUpdateOp {
rebasedPatchSetId =
ChangeUtil.nextPatchSetIdFromChangeRefs(
- ctx.getRepoView()
- .getRefs(originalPatchSet.getId().getParentKey().toRefPrefix())
- .keySet(),
+ ctx.getRepoView().getRefs(originalPatchSet.id().changeId().toRefPrefix()).keySet(),
notes.getChange().currentPatchSetId());
patchSetInserter =
patchSetInserterFactory
@@ -195,14 +190,14 @@ public class RebaseChangeOp implements BatchUpdateOp {
"Patch Set "
+ rebasedPatchSetId.get()
+ ": Patch Set "
- + originalPatchSet.getId().get()
+ + originalPatchSet.id().get()
+ " was rebased");
}
if (base != null && !base.notes().getChange().isMerged()) {
if (!base.notes().getChange().isMerged()) {
// Add to end of relation chain for open base change.
- patchSetInserter.setGroups(base.patchSet().getGroups());
+ patchSetInserter.setGroups(base.patchSet().groups());
} else {
// If the base is merged, start a new relation chain.
patchSetInserter.setGroups(GroupCollector.getDefaultGroups(rebasedCommit));
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index a4cf5ba5d5..2d36df204a 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -17,14 +17,14 @@ package com.google.gerrit.server.change;
import com.google.auto.value.AutoValue;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.query.change.ChangeData;
@@ -56,7 +56,7 @@ public class RebaseUtil {
this.psUtil = psUtil;
}
- public boolean canRebase(PatchSet patchSet, Branch.NameKey dest, Repository git, RevWalk rw) {
+ public boolean canRebase(PatchSet patchSet, BranchNameKey dest, Repository git, RevWalk rw) {
try {
findBaseRevision(patchSet, dest, git, rw);
return true;
@@ -64,7 +64,7 @@ public class RebaseUtil {
return false;
} catch (StorageException | IOException e) {
logger.atWarning().withCause(e).log(
- "Error checking if patch set %s on %s can be rebased", patchSet.getId(), dest);
+ "Error checking if patch set %s on %s can be rebased", patchSet.id(), dest);
return false;
}
}
@@ -87,18 +87,18 @@ public class RebaseUtil {
// Try parsing the base as a ref string.
PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
if (basePatchSetId != null) {
- Change.Id baseChangeId = basePatchSetId.getParentKey();
+ Change.Id baseChangeId = basePatchSetId.changeId();
ChangeNotes baseNotes = notesFor(rsrc, baseChangeId);
if (baseNotes != null) {
return Base.create(
- notesFor(rsrc, basePatchSetId.getParentKey()), psUtil.get(baseNotes, basePatchSetId));
+ notesFor(rsrc, basePatchSetId.changeId()), psUtil.get(baseNotes, basePatchSetId));
}
}
// Try parsing base as a change number (assume current patch set).
Integer baseChangeId = Ints.tryParse(base);
if (baseChangeId != null) {
- ChangeNotes baseNotes = notesFor(rsrc, new Change.Id(baseChangeId));
+ ChangeNotes baseNotes = notesFor(rsrc, Change.id(baseChangeId));
if (baseNotes != null) {
return Base.create(baseNotes, psUtil.current(baseNotes));
}
@@ -108,10 +108,10 @@ public class RebaseUtil {
Base ret = null;
for (ChangeData cd : queryProvider.get().byProjectCommit(rsrc.getProject(), base)) {
for (PatchSet ps : cd.patchSets()) {
- if (!ps.getRevision().matches(base)) {
+ if (!ObjectIds.matchesAbbreviation(ps.commitId(), base)) {
continue;
}
- if (ret == null || ret.patchSet().getId().get() < ps.getId().get()) {
+ if (ret == null || ret.patchSet().id().get() < ps.id().get()) {
ret = Base.create(cd.notes(), ps);
}
}
@@ -141,10 +141,10 @@ public class RebaseUtil {
* @throws IOException if accessing the repository fails.
*/
public ObjectId findBaseRevision(
- PatchSet patchSet, Branch.NameKey destBranch, Repository git, RevWalk rw)
+ PatchSet patchSet, BranchNameKey destBranch, Repository git, RevWalk rw)
throws RestApiException, IOException {
- String baseRev = null;
- RevCommit commit = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ ObjectId baseId = null;
+ RevCommit commit = rw.parseCommit(patchSet.commitId());
if (commit.getParentCount() > 1) {
throw new UnprocessableEntityException("Cannot rebase a change with multiple parents.");
@@ -153,12 +153,12 @@ public class RebaseUtil {
"Cannot rebase a change without any parents (is this the initial commit?).");
}
- RevId parentRev = new RevId(commit.getParent(0).name());
+ ObjectId parentId = commit.getParent(0);
CHANGES:
- for (ChangeData cd : queryProvider.get().byBranchCommit(destBranch, parentRev.get())) {
+ for (ChangeData cd : queryProvider.get().byBranchCommit(destBranch, parentId.name())) {
for (PatchSet depPatchSet : cd.patchSets()) {
- if (!depPatchSet.getRevision().equals(parentRev)) {
+ if (!depPatchSet.commitId().equals(parentId)) {
continue;
}
Change depChange = cd.change();
@@ -168,29 +168,29 @@ public class RebaseUtil {
}
if (depChange.isNew()) {
- if (depPatchSet.getId().equals(depChange.currentPatchSetId())) {
+ if (depPatchSet.id().equals(depChange.currentPatchSetId())) {
throw new ResourceConflictException(
"Change is already based on the latest patch set of the dependent change.");
}
- baseRev = cd.currentPatchSet().getRevision().get();
+ baseId = cd.currentPatchSet().commitId();
}
break CHANGES;
}
}
- if (baseRev == null) {
+ if (baseId == null) {
// We are dependent on a merged PatchSet or have no PatchSet
// dependencies at all.
- Ref destRef = git.getRefDatabase().exactRef(destBranch.get());
+ Ref destRef = git.getRefDatabase().exactRef(destBranch.branch());
if (destRef == null) {
throw new UnprocessableEntityException(
- "The destination branch does not exist: " + destBranch.get());
+ "The destination branch does not exist: " + destBranch.branch());
}
- baseRev = destRef.getObjectId().getName();
- if (baseRev.equals(parentRev.get())) {
+ baseId = destRef.getObjectId();
+ if (baseId.equals(parentId)) {
throw new ResourceConflictException("Change is already up to date.");
}
}
- return ObjectId.fromString(baseRev);
+ return baseId;
}
}
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index a6ad559fac..ba6ba21009 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -31,6 +31,13 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -41,13 +48,6 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -237,7 +237,8 @@ public class ReviewerAdder {
revision.getUser(),
ImmutableSet.of(user.getAccountId()),
null,
- true);
+ true,
+ false);
}
@Nullable
@@ -260,7 +261,13 @@ public class ReviewerAdder {
if (isValidReviewer(notes.getChange().getDest(), reviewerUser.getAccount())) {
return new ReviewerAddition(
- input, notes, user, ImmutableSet.of(reviewerUser.getAccountId()), null, exactMatchFound);
+ input,
+ notes,
+ user,
+ ImmutableSet.of(reviewerUser.getAccountId()),
+ null,
+ exactMatchFound,
+ false);
}
return fail(
input,
@@ -340,11 +347,11 @@ public class ReviewerAdder {
for (Account member : members) {
if (isValidReviewer(notes.getChange().getDest(), member)) {
- reviewers.add(member.getId());
+ reviewers.add(member.id());
}
}
- return new ReviewerAddition(input, notes, user, reviewers, null, true);
+ return new ReviewerAddition(input, notes, user, reviewers, null, true, true);
}
@Nullable
@@ -366,16 +373,16 @@ public class ReviewerAdder {
FailureType.NOT_FOUND,
MessageFormat.format(ChangeMessages.get().reviewerInvalid, input.reviewer));
}
- return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), true);
+ return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), true, false);
}
- private boolean isValidReviewer(Branch.NameKey branch, Account member)
+ private boolean isValidReviewer(BranchNameKey branch, Account member)
throws PermissionBackendException {
try {
// Check ref permission instead of change permission, since change permissions take into
// account the private bit, whereas adding a user as a reviewer is explicitly allowing them to
// see private changes.
- permissionBackend.absentUser(member.getId()).ref(branch).check(RefPermission.READ);
+ permissionBackend.absentUser(member.id()).ref(branch).check(RefPermission.READ);
return true;
} catch (AuthException e) {
return false;
@@ -421,7 +428,8 @@ public class ReviewerAdder {
CurrentUser caller,
@Nullable Iterable<Account.Id> reviewers,
@Nullable Iterable<Address> reviewersByEmail,
- boolean exactMatchFound) {
+ boolean exactMatchFound,
+ boolean forGroup) {
checkArgument(
reviewers != null || reviewersByEmail != null,
"must have either reviewers or reviewersByEmail");
@@ -435,7 +443,7 @@ public class ReviewerAdder {
this.reviewersByEmail =
reviewersByEmail == null ? ImmutableSet.of() : ImmutableSet.copyOf(reviewersByEmail);
this.caller = caller.asIdentifiedUser();
- op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state());
+ op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state(), forGroup);
this.exactMatchFound = exactMatchFound;
}
@@ -469,8 +477,8 @@ public class ReviewerAdder {
// New reviewers have value 0, don't bother normalizing.
result.reviewers.add(
json.format(
- new ReviewerInfo(psa.getAccountId().get()),
- psa.getAccountId(),
+ new ReviewerInfo(psa.accountId().get()),
+ psa.accountId(),
cd,
ImmutableList.of(psa)));
}
diff --git a/java/com/google/gerrit/server/change/ReviewerJson.java b/java/com/google/gerrit/server/change/ReviewerJson.java
index 2742bb944d..6686ed8808 100644
--- a/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -21,12 +21,12 @@ import com.google.common.collect.Lists;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.permissions.LabelPermission;
@@ -111,9 +111,9 @@ public class ReviewerJson {
out.approvals = new TreeMap<>(labelTypes.nameComparator());
for (PatchSetApproval ca : approvals) {
- LabelType at = labelTypes.byLabel(ca.getLabelId());
+ LabelType at = labelTypes.byLabel(ca.labelId());
if (at != null) {
- out.approvals.put(at.getName(), formatValue(ca.getValue()));
+ out.approvals.put(at.getName(), formatValue(ca.value()));
}
}
diff --git a/java/com/google/gerrit/server/change/ReviewerResource.java b/java/com/google/gerrit/server/change/ReviewerResource.java
index 52f35853bf..df0a03f72d 100644
--- a/java/com/google/gerrit/server/change/ReviewerResource.java
+++ b/java/com/google/gerrit/server/change/ReviewerResource.java
@@ -17,11 +17,11 @@ package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
diff --git a/java/com/google/gerrit/server/change/ReviewerSuggestion.java b/java/com/google/gerrit/server/change/ReviewerSuggestion.java
index 198a5fde04..1b2a0086a4 100644
--- a/java/com/google/gerrit/server/change/ReviewerSuggestion.java
+++ b/java/com/google/gerrit/server/change/ReviewerSuggestion.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.Set;
/**
@@ -35,9 +35,8 @@ public interface ReviewerSuggestion {
* @param changeId The changeId that the suggestion is for. Can be {@code null}.
* @param query The query as typed by the user. Can be {@code null}.
* @param candidates A set of candidates for the ranking. Can be empty.
- * @return Set of {@link SuggestedReviewer}s. The {@link
- * com.google.gerrit.reviewdb.client.Account.Id}s listed here don't have to be included in
- * {@code candidates}.
+ * @return Set of {@link SuggestedReviewer}s. The {@link com.google.gerrit.entities.Account.Id}s
+ * listed here don't have to be included in {@code candidates}.
*/
Set<SuggestedReviewer> suggestReviewers(
Project.NameKey project,
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index aa733cdfe3..fbd14c4428 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -28,10 +28,15 @@ import static com.google.gerrit.extensions.client.ListChangesOption.PUSH_CERTIFI
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
import static com.google.gerrit.server.CommonConverters.toGitPerson;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommitInfo;
@@ -44,10 +49,6 @@ import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GpgException;
@@ -71,7 +72,6 @@ import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jgit.lib.ObjectId;
@@ -182,7 +182,7 @@ public class RevisionJson {
info.message = commit.getFullMessage();
if (addLinks) {
- List<WebLinkInfo> links = webLinks.getPatchSetLinks(project, commit.name());
+ ImmutableList<WebLinkInfo> links = webLinks.getPatchSetLinks(project, commit.name());
info.webLinks = links.isEmpty() ? null : links;
}
@@ -192,7 +192,7 @@ public class RevisionJson {
i.commit = parent.name();
i.subject = parent.getShortMessage();
if (addLinks) {
- List<WebLinkInfo> parentLinks = webLinks.getParentLinks(project, parent.name());
+ ImmutableList<WebLinkInfo> parentLinks = webLinks.getParentLinks(project, parent.name());
i.webLinks = parentLinks.isEmpty() ? null : parentLinks;
}
info.parents.add(i);
@@ -216,7 +216,7 @@ public class RevisionJson {
try (Repository repo = openRepoIfNecessary(cd.project());
RevWalk rw = newRevWalk(repo)) {
for (PatchSet in : map.values()) {
- PatchSet.Id id = in.getId();
+ PatchSet.Id id = in.id();
boolean want;
if (has(ALL_REVISIONS)) {
want = true;
@@ -227,7 +227,7 @@ public class RevisionJson {
}
if (want) {
res.put(
- in.getRevision().get(),
+ in.commitId().name(),
toRevisionInfo(accountLoader, cd, in, repo, rw, false, changeInfo));
}
}
@@ -251,7 +251,7 @@ public class RevisionJson {
String projectName = cd.project().get();
String url = scheme.getUrl(projectName);
- String refName = in.getRefName();
+ String refName = in.refName();
FetchInfo fetchInfo = new FetchInfo(url, refName);
r.put(schemeName, fetchInfo);
@@ -275,14 +275,14 @@ public class RevisionJson {
throws PatchListNotAvailableException, GpgException, IOException, PermissionBackendException {
Change c = cd.change();
RevisionInfo out = new RevisionInfo();
- out.isCurrent = in.getId().equals(c.currentPatchSetId());
- out._number = in.getId().get();
- out.ref = in.getRefName();
- out.created = in.getCreatedOn();
- out.uploader = accountLoader.get(in.getUploader());
+ out.isCurrent = in.id().equals(c.currentPatchSetId());
+ out._number = in.id().get();
+ out.ref = in.refName();
+ out.created = in.createdOn();
+ out.uploader = accountLoader.get(in.uploader());
out.fetch = makeFetchMap(cd, in);
out.kind = changeKindCache.getChangeKind(rw, repo != null ? repo.getConfig() : null, cd, in);
- out.description = in.getDescription();
+ out.description = in.description().orElse(null);
boolean setCommit = has(ALL_COMMITS) || (out.isCurrent && has(CURRENT_COMMIT));
boolean addFooters = out.isCurrent && has(COMMIT_FOOTERS);
@@ -290,14 +290,14 @@ public class RevisionJson {
checkState(rw != null);
checkState(repo != null);
Project.NameKey project = c.getProject();
- String rev = in.getRevision().get();
+ String rev = in.commitId().name();
RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
rw.parseBody(commit);
if (setCommit) {
out.commit = getCommitInfo(project, rw, commit, has(WEB_LINKS), fillCommit);
}
if (addFooters) {
- Ref ref = repo.exactRef(cd.change().getDest().get());
+ Ref ref = repo.exactRef(cd.change().getDest().branch());
RevCommit mergeTip = null;
if (ref != null) {
mergeTip = rw.parseCommit(ref.getObjectId());
@@ -306,7 +306,7 @@ public class RevisionJson {
out.commitWithFooters =
mergeUtilFactory
.create(projectCache.get(project))
- .createCommitMessageOnSubmit(commit, mergeTip, cd.notes(), in.getId());
+ .createCommitMessageOnSubmit(commit, mergeTip, cd.notes(), in.id());
}
}
@@ -324,10 +324,10 @@ public class RevisionJson {
}
if (gpgApi.isEnabled() && has(PUSH_CERTIFICATES)) {
- if (in.getPushCertificate() != null) {
+ if (in.pushCertificate().isPresent()) {
out.pushCertificate =
gpgApi.checkPushCertificate(
- in.getPushCertificate(), userFactory.create(in.getUploader()));
+ in.pushCertificate().get(), userFactory.create(in.uploader()));
} else {
out.pushCertificate = new PushCertificateInfo();
}
diff --git a/java/com/google/gerrit/server/change/RevisionResource.java b/java/com/google/gerrit/server/change/RevisionResource.java
index caafe24a57..30fa593b09 100644
--- a/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/java/com/google/gerrit/server/change/RevisionResource.java
@@ -16,15 +16,18 @@ package com.google.gerrit.server.change;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestResource.HasETag;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.edit.ChangeEdit;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.inject.TypeLiteral;
@@ -89,9 +92,18 @@ public class RevisionResource implements RestResource, HasETag {
@Override
public String getETag() {
- Hasher h = Hashing.murmur3_128().newHasher();
- prepareETag(h, getUser());
- return h.hash().toString();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Compute revision ETag",
+ Metadata.builder()
+ .changeId(change.getId().get())
+ .patchSetId(ps.number())
+ .projectName(change.getProject().get())
+ .build())) {
+ Hasher h = Hashing.murmur3_128().newHasher();
+ prepareETag(h, getUser());
+ return h.hash().toString();
+ }
}
public void prepareETag(Hasher h, CurrentUser user) {
@@ -114,7 +126,7 @@ public class RevisionResource implements RestResource, HasETag {
@Override
public String toString() {
- String s = ps.getId().toString();
+ String s = ps.id().toString();
if (edit.isPresent()) {
s = "edit:" + s;
}
@@ -122,6 +134,6 @@ public class RevisionResource implements RestResource, HasETag {
}
public boolean isCurrent() {
- return ps.getId().equals(getChange().currentPatchSetId());
+ return ps.id().equals(getChange().currentPatchSetId());
}
}
diff --git a/java/com/google/gerrit/server/change/RobotCommentResource.java b/java/com/google/gerrit/server/change/RobotCommentResource.java
index c4fab582e9..b12727d21a 100644
--- a/java/com/google/gerrit/server/change/RobotCommentResource.java
+++ b/java/com/google/gerrit/server/change/RobotCommentResource.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.inject.TypeLiteral;
public class RobotCommentResource implements RestResource {
diff --git a/java/com/google/gerrit/server/change/SetAssigneeOp.java b/java/com/google/gerrit/server/change/SetAssigneeOp.java
index 8d350c32bd..9848150cd6 100644
--- a/java/com/google/gerrit/server/change/SetAssigneeOp.java
+++ b/java/com/google/gerrit/server/change/SetAssigneeOp.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.change;
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.AssigneeChanged;
diff --git a/java/com/google/gerrit/server/change/SetHashtagsOp.java b/java/com/google/gerrit/server/change/SetHashtagsOp.java
index abc4eeec14..712e1f31fc 100644
--- a/java/com/google/gerrit/server/change/SetHashtagsOp.java
+++ b/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -21,12 +21,12 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.change.HashtagsUtil.InvalidHashtagException;
import com.google.gerrit.server.extensions.events.HashtagsEdited;
diff --git a/java/com/google/gerrit/server/change/SetPrivateOp.java b/java/com/google/gerrit/server/change/SetPrivateOp.java
index 1600fd592a..28d178dd0f 100644
--- a/java/com/google/gerrit/server/change/SetPrivateOp.java
+++ b/java/com/google/gerrit/server/change/SetPrivateOp.java
@@ -16,11 +16,11 @@ package com.google.gerrit.server.change;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
diff --git a/java/com/google/gerrit/server/change/SuggestedReviewer.java b/java/com/google/gerrit/server/change/SuggestedReviewer.java
index 353bf3b084..1b30199737 100644
--- a/java/com/google/gerrit/server/change/SuggestedReviewer.java
+++ b/java/com/google/gerrit/server/change/SuggestedReviewer.java
@@ -13,7 +13,7 @@
// limitations under the License.
package com.google.gerrit.server.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
public class SuggestedReviewer {
diff --git a/java/com/google/gerrit/server/change/WalkSorter.java b/java/com/google/gerrit/server/change/WalkSorter.java
index 5945a0cb9d..816a9042d4 100644
--- a/java/com/google/gerrit/server/change/WalkSorter.java
+++ b/java/com/google/gerrit/server/change/WalkSorter.java
@@ -24,9 +24,9 @@ import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
@@ -41,7 +41,6 @@ import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
@@ -223,27 +222,26 @@ public class WalkSorter {
for (ChangeData cd : in) {
PatchSet maxPs = null;
for (PatchSet ps : cd.patchSets()) {
- if (shouldInclude(ps) && (maxPs == null || ps.getId().get() > maxPs.getId().get())) {
+ if (shouldInclude(ps) && (maxPs == null || ps.id().get() > maxPs.id().get())) {
maxPs = ps;
}
}
if (maxPs == null) {
continue; // No patch sets matched.
}
- ObjectId id = ObjectId.fromString(maxPs.getRevision().get());
try {
- RevCommit c = rw.parseCommit(id);
+ RevCommit c = rw.parseCommit(maxPs.commitId());
byCommit.put(c, PatchSetData.create(cd, maxPs, c));
} catch (MissingObjectException | IncorrectObjectTypeException e) {
logger.atWarning().withCause(e).log(
- "missing commit %s for patch set %s", id.name(), maxPs.getId());
+ "missing commit %s for patch set %s", maxPs.commitId().name(), maxPs.id());
}
}
return byCommit;
}
private boolean shouldInclude(PatchSet ps) {
- return includePatchSets.isEmpty() || includePatchSets.contains(ps.getId());
+ return includePatchSets.isEmpty() || includePatchSets.contains(ps.id());
}
private static void markStart(RevWalk rw, Iterable<RevCommit> commits) throws IOException {
diff --git a/java/com/google/gerrit/server/change/WorkInProgressOp.java b/java/com/google/gerrit/server/change/WorkInProgressOp.java
index f3f1a2974d..78edadab82 100644
--- a/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.change;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
diff --git a/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java b/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java
new file mode 100644
index 0000000000..344b9b3024
--- /dev/null
+++ b/java/com/google/gerrit/server/change/testing/TestChangeETagComputation.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change.testing;
+
+import com.google.gerrit.server.change.ChangeETagComputation;
+
+public class TestChangeETagComputation {
+
+ public static ChangeETagComputation withETag(String etag) {
+ return (p, id) -> etag;
+ }
+
+ public static ChangeETagComputation withException(RuntimeException e) {
+ return (p, id) -> {
+ throw e;
+ };
+ }
+}
diff --git a/java/com/google/gerrit/server/config/AllProjectsName.java b/java/com/google/gerrit/server/config/AllProjectsName.java
index 7719e38dbb..6d5525c96f 100644
--- a/java/com/google/gerrit/server/config/AllProjectsName.java
+++ b/java/com/google/gerrit/server/config/AllProjectsName.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
/** Special name of the project that all projects derive from. */
public class AllProjectsName extends Project.NameKey {
diff --git a/java/com/google/gerrit/server/config/AllUsersName.java b/java/com/google/gerrit/server/config/AllUsersName.java
index 22d29a4fef..aa92db899d 100644
--- a/java/com/google/gerrit/server/config/AllUsersName.java
+++ b/java/com/google/gerrit/server/config/AllUsersName.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
/** Special name of the project in which meta data for all users is stored. */
public class AllUsersName extends Project.NameKey {
diff --git a/java/com/google/gerrit/server/config/DownloadConfig.java b/java/com/google/gerrit/server/config/DownloadConfig.java
index e9d5e5e83e..6dea07d660 100644
--- a/java/com/google/gerrit/server/config/DownloadConfig.java
+++ b/java/com/google/gerrit/server/config/DownloadConfig.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.config;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.CoreDownloadSchemes;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
-import com.google.gerrit.reviewdb.client.CoreDownloadSchemes;
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 5dad6a80f8..25f2b20269 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -30,6 +30,7 @@ import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.config.ExternalIncludedIn;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.config.PluginProjectPermissionDefinition;
+import com.google.gerrit.extensions.events.AccountActivationListener;
import com.google.gerrit.extensions.events.AccountIndexedListener;
import com.google.gerrit.extensions.events.AgreementSignupListener;
import com.google.gerrit.extensions.events.AssigneeChangedListener;
@@ -61,6 +62,7 @@ import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.extensions.webui.BranchWebLink;
import com.google.gerrit.extensions.webui.DiffWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
@@ -76,7 +78,10 @@ import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CmdLineParserModule;
import com.google.gerrit.server.CreateGroupPermissionSyncer;
import com.google.gerrit.server.DynamicOptions;
+import com.google.gerrit.server.ExceptionHook;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.RequestListener;
+import com.google.gerrit.server.TraceRequestListener;
import com.google.gerrit.server.account.AccountCacheImpl;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountDeactivator;
@@ -98,6 +103,7 @@ import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.change.AbandonOp;
import com.google.gerrit.server.change.AccountPatchReviewStore;
import com.google.gerrit.server.change.ChangeAttributeFactory;
+import com.google.gerrit.server.change.ChangeETagComputation;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeKindCacheImpl;
@@ -135,6 +141,7 @@ import com.google.gerrit.server.git.validators.UploadValidationListener;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.group.db.GroupDbModule;
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.mail.AutoReplyMailFilter;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.ListMailFilter;
@@ -142,7 +149,8 @@ import com.google.gerrit.server.mail.MailFilter;
import com.google.gerrit.server.mail.send.FromAddressGenerator;
import com.google.gerrit.server.mail.send.FromAddressGeneratorProvider;
import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
-import com.google.gerrit.server.mail.send.MailSoyTofuProvider;
+import com.google.gerrit.server.mail.send.MailSoySauceProvider;
+import com.google.gerrit.server.mail.send.MailSoyTemplateProvider;
import com.google.gerrit.server.mail.send.MailTemplates;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
@@ -190,7 +198,7 @@ import com.google.gitiles.blame.cache.BlameCacheImpl;
import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
-import com.google.template.soy.tofu.SoyTofu;
+import com.google.template.soy.jbcsrc.api.SoySauce;
import java.util.List;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.PostReceiveHook;
@@ -281,7 +289,7 @@ public class GerritGlobalModule extends FactoryModule {
bind(ApprovalsUtil.class);
- bind(SoyTofu.class).annotatedWith(MailTemplates.class).toProvider(MailSoyTofuProvider.class);
+ bind(SoySauce.class).annotatedWith(MailTemplates.class).toProvider(MailSoySauceProvider.class);
bind(FromAddressGenerator.class).toProvider(FromAddressGeneratorProvider.class).in(SINGLETON);
bind(Boolean.class)
.annotatedWith(EnableReverseDnsLookup.class)
@@ -325,6 +333,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), PostReceiveHook.class);
DynamicSet.setOf(binder(), PreUploadHook.class);
DynamicSet.setOf(binder(), PostUploadHook.class);
+ DynamicSet.setOf(binder(), AccountActivationListener.class);
DynamicSet.setOf(binder(), AccountIndexedListener.class);
DynamicSet.setOf(binder(), ChangeIndexedListener.class);
DynamicSet.setOf(binder(), GroupIndexedListener.class);
@@ -341,6 +350,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.bind(binder(), EventListener.class).to(EventsMetrics.class);
DynamicSet.setOf(binder(), UserScopedEventListener.class);
DynamicSet.setOf(binder(), CommitValidationListener.class);
+ DynamicSet.setOf(binder(), CommentValidator.class);
DynamicSet.setOf(binder(), ChangeMessageModifier.class);
DynamicSet.setOf(binder(), RefOperationValidationListener.class);
DynamicSet.setOf(binder(), OnSubmitValidationListener.class);
@@ -380,6 +390,12 @@ public class GerritGlobalModule extends FactoryModule {
DynamicItem.itemOf(binder(), ProjectNameLockManager.class);
DynamicSet.setOf(binder(), SubmitRule.class);
DynamicSet.setOf(binder(), QuotaEnforcer.class);
+ DynamicSet.setOf(binder(), PerformanceLogger.class);
+ DynamicSet.setOf(binder(), RequestListener.class);
+ DynamicSet.bind(binder(), RequestListener.class).to(TraceRequestListener.class);
+ DynamicSet.setOf(binder(), ChangeETagComputation.class);
+ DynamicSet.setOf(binder(), ExceptionHook.class);
+ DynamicSet.setOf(binder(), MailSoyTemplateProvider.class);
DynamicMap.mapOf(binder(), MailFilter.class);
bind(MailFilter.class).annotatedWith(Exports.named("ListMailFilter")).to(ListMailFilter.class);
diff --git a/java/com/google/gerrit/server/config/GerritIsReplica.java b/java/com/google/gerrit/server/config/GerritIsReplica.java
new file mode 100644
index 0000000000..154fdcd36d
--- /dev/null
+++ b/java/com/google/gerrit/server/config/GerritIsReplica.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+/* Marker on {@link Boolean} indicating whether Gerrit is run as a read-only replica. */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface GerritIsReplica {}
diff --git a/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java b/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java
new file mode 100644
index 0000000000..bd07f7df8f
--- /dev/null
+++ b/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Provides {@link Boolean} annotated with {@link GerritIsReplica}.
+ *
+ * <p>The returned boolean indicates whether Gerrit is run as a read-only replica.
+ */
+@Singleton
+public final class GerritIsReplicaProvider implements Provider<Boolean> {
+ public static final String CONFIG_SECTION = "container";
+ public static final String REPLICA_KEY = "replica";
+ public static final String DEPRECATED_REPLICA_KEY = "slave";
+
+ public final boolean isReplica;
+
+ @Inject
+ public GerritIsReplicaProvider(@GerritServerConfig Config config) {
+ this.isReplica =
+ config.getBoolean(CONFIG_SECTION, REPLICA_KEY, false)
+ || config.getBoolean(CONFIG_SECTION, DEPRECATED_REPLICA_KEY, false);
+ }
+
+ @Override
+ public Boolean get() {
+ return isReplica;
+ }
+}
diff --git a/java/com/google/gerrit/server/config/GerritServerConfigModule.java b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
index 25ee7599f3..3777a55e24 100644
--- a/java/com/google/gerrit/server/config/GerritServerConfigModule.java
+++ b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
@@ -78,5 +78,8 @@ public class GerritServerConfigModule extends AbstractModule {
.annotatedWith(GerritServerConfig.class)
.toProvider(GerritServerConfigProvider.class);
bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
+ bind(Boolean.class)
+ .annotatedWith(GerritIsReplica.class)
+ .toProvider(GerritIsReplicaProvider.class);
}
}
diff --git a/java/com/google/gerrit/server/config/GroupSetProvider.java b/java/com/google/gerrit/server/config/GroupSetProvider.java
index 2255a67d16..7f487e1b89 100644
--- a/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.config;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.util.RequestContext;
diff --git a/java/com/google/gerrit/server/config/PluginConfigFactory.java b/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 2164a86d1f..9e4570186c 100644
--- a/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.config;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.project.NoSuchProjectException;
diff --git a/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index 92ae10ade5..fcfa5e9d49 100644
--- a/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -17,14 +17,14 @@ package com.google.gerrit.server.config;
import static java.util.stream.Collectors.toList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.api.projects.ConfigValue;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
@@ -316,7 +316,7 @@ public class ProjectConfigEntry {
@Override
public void onGitReferenceUpdated(Event event) {
- Project.NameKey p = new Project.NameKey(event.getProjectName());
+ Project.NameKey p = Project.nameKey(event.getProjectName());
if (!event.getRefName().equals(RefNames.REFS_CONFIG)) {
return;
}
diff --git a/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index a2e0356683..c2538ac44e 100644
--- a/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.util.ServerRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
diff --git a/java/com/google/gerrit/server/config/RepositoryConfig.java b/java/com/google/gerrit/server/config/RepositoryConfig.java
index d8c8468854..f7223216e7 100644
--- a/java/com/google/gerrit/server/config/RepositoryConfig.java
+++ b/java/com/google/gerrit/server/config/RepositoryConfig.java
@@ -19,8 +19,8 @@ import static java.util.Comparator.comparing;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.nio.file.Path;
diff --git a/java/com/google/gerrit/server/config/SitePaths.java b/java/com/google/gerrit/server/config/SitePaths.java
index 47b6336ff3..ee95c6f73f 100644
--- a/java/com/google/gerrit/server/config/SitePaths.java
+++ b/java/com/google/gerrit/server/config/SitePaths.java
@@ -54,6 +54,8 @@ public final class SitePaths {
public final Path secure_config;
public final Path notedb_config;
+ public final Path jgit_config;
+
public final Path ssl_keystore;
public final Path ssh_key;
public final Path ssh_rsa;
@@ -99,6 +101,8 @@ public final class SitePaths {
secure_config = etc_dir.resolve("secure.config");
notedb_config = etc_dir.resolve("notedb.config");
+ jgit_config = etc_dir.resolve("jgit.config");
+
ssl_keystore = etc_dir.resolve("keystore");
ssh_key = etc_dir.resolve("ssh_host_key");
ssh_rsa = etc_dir.resolve("ssh_host_rsa_key");
diff --git a/java/com/google/gerrit/server/config/UrlFormatter.java b/java/com/google/gerrit/server/config/UrlFormatter.java
index eb1e0b256b..6b2510e480 100644
--- a/java/com/google/gerrit/server/config/UrlFormatter.java
+++ b/java/com/google/gerrit/server/config/UrlFormatter.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.config;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import java.util.Optional;
/**
diff --git a/java/com/google/gerrit/server/data/ChangeAttribute.java b/java/com/google/gerrit/server/data/ChangeAttribute.java
index fde592200f..a6da2b98bc 100644
--- a/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.data;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.common.PluginDefinedInfo;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gson.annotations.SerializedName;
import java.util.List;
diff --git a/java/com/google/gerrit/server/data/PatchAttribute.java b/java/com/google/gerrit/server/data/PatchAttribute.java
index 22f18afaae..2428c54775 100644
--- a/java/com/google/gerrit/server/data/PatchAttribute.java
+++ b/java/com/google/gerrit/server/data/PatchAttribute.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.data;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
+import com.google.gerrit.entities.Patch.ChangeType;
public class PatchAttribute {
public String file;
diff --git a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index 5d3d1f2013..59ae6f872f 100644
--- a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -80,8 +80,7 @@ public class QueryDocumentationExecutor {
}
Query query = parser.parse(q);
try {
- // TODO(fishywang): Currently as we don't have much documentation, we just use MAX_VALUE here
- // and skipped paging. Maybe add paging later.
+ // We don't have much documentation, so we just use MAX_VALUE here and skip paging.
TopDocs results = searcher.search(query, Integer.MAX_VALUE);
ScoreDoc[] hits = results.scoreDocs;
long totalHits = results.totalHits;
diff --git a/java/com/google/gerrit/server/edit/ChangeEdit.java b/java/com/google/gerrit/server/edit/ChangeEdit.java
index 11dc380151..c652289a68 100644
--- a/java/com/google/gerrit/server/edit/ChangeEdit.java
+++ b/java/com/google/gerrit/server/edit/ChangeEdit.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.edit;
import static java.util.Objects.requireNonNull;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import org.eclipse.jgit.revwalk.RevCommit;
/**
diff --git a/java/com/google/gerrit/server/edit/ChangeEditJson.java b/java/com/google/gerrit/server/edit/ChangeEditJson.java
index 55e0aefbbf..25dcae06a9 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditJson.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -51,8 +51,8 @@ public class ChangeEditJson {
public EditInfo toEditInfo(ChangeEdit edit, boolean downloadCommands) {
EditInfo out = new EditInfo();
out.commit = fillCommit(edit.getEditCommit());
- out.baseRevision = edit.getBasePatchSet().getRevision().get();
- out.basePatchSetNumber = edit.getBasePatchSet().getPatchSetId();
+ out.baseRevision = edit.getBasePatchSet().commitId().name();
+ out.basePatchSetNumber = edit.getBasePatchSet().number();
out.ref = edit.getRefName();
if (downloadCommands) {
out.fetch = fillFetchMap(edit);
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 9620b49dab..bd0b96fddd 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -15,16 +15,16 @@
package com.google.gerrit.server.edit;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -127,7 +127,7 @@ public class ChangeEditModifier {
}
PatchSet currentPatchSet = lookupCurrentPatchSet(notes);
- ObjectId patchSetCommitId = getPatchSetCommitId(currentPatchSet);
+ ObjectId patchSetCommitId = currentPatchSet.commitId();
createEdit(repository, notes, currentPatchSet, patchSetCommitId, TimeUtil.nowTs());
}
@@ -160,7 +160,7 @@ public class ChangeEditModifier {
throw new InvalidChangeOperationException(
String.format(
"Change edit for change %s is already based on latest patch set %s",
- notes.getChangeId(), currentPatchSet.getId()));
+ notes.getChangeId(), currentPatchSet.id()));
}
rebase(repository, changeEdit, currentPatchSet);
@@ -383,7 +383,7 @@ public class ChangeEditModifier {
if (optionalChangeEdit.isPresent()) {
ChangeEdit changeEdit = optionalChangeEdit.get();
newTreeId = merge(repository, changeEdit, newTreeId);
- if (ObjectId.equals(newTreeId, changeEdit.getEditCommit().getTree())) {
+ if (ObjectId.isEqual(newTreeId, changeEdit.getEditCommit().getTree())) {
// Modifications are already contained in the change edit.
return changeEdit;
}
@@ -433,10 +433,10 @@ public class ChangeEditModifier {
String.format(
"Only the patch set %s on which the existing change edit is based may be modified "
+ "(specified patch set: %s)",
- changeEdit.getBasePatchSet().getId(), patchSet.getId()));
+ changeEdit.getBasePatchSet().id(), patchSet.id()));
}
} else {
- PatchSet.Id patchSetId = patchSet.getId();
+ PatchSet.Id patchSetId = patchSet.id();
PatchSet.Id currentPatchSetId = notes.getChange().currentPatchSetId();
if (!patchSetId.equals(currentPatchSetId)) {
throw new InvalidChangeOperationException(
@@ -463,12 +463,12 @@ public class ChangeEditModifier {
private static boolean isBasedOn(ChangeEdit changeEdit, PatchSet patchSet) {
PatchSet editBasePatchSet = changeEdit.getBasePatchSet();
- return editBasePatchSet.getId().equals(patchSet.getId());
+ return editBasePatchSet.id().equals(patchSet.id());
}
private static RevCommit lookupCommit(Repository repository, PatchSet patchSet)
throws IOException {
- ObjectId patchSetCommitId = getPatchSetCommitId(patchSet);
+ ObjectId patchSetCommitId = patchSet.commitId();
return lookupCommit(repository, patchSetCommitId);
}
@@ -491,7 +491,7 @@ public class ChangeEditModifier {
throw new BadRequestException(e.getMessage());
}
- if (ObjectId.equals(newTreeId, baseCommit.getTree())) {
+ if (ObjectId.isEqual(newTreeId, baseCommit.getTree())) {
throw new InvalidChangeOperationException("no changes were made");
}
return newTreeId;
@@ -500,7 +500,7 @@ public class ChangeEditModifier {
private static ObjectId merge(Repository repository, ChangeEdit changeEdit, ObjectId newTreeId)
throws IOException, MergeConflictException {
PatchSet basePatchSet = changeEdit.getBasePatchSet();
- ObjectId basePatchSetCommitId = getPatchSetCommitId(basePatchSet);
+ ObjectId basePatchSetCommitId = basePatchSet.commitId();
ObjectId editCommitId = changeEdit.getEditCommit();
ThreeWayMerger threeWayMerger = MergeStrategy.RESOLVE.newMerger(repository, true);
@@ -539,10 +539,6 @@ public class ChangeEditModifier {
return user.newCommitterIdent(commitTimestamp, tz);
}
- private static ObjectId getPatchSetCommitId(PatchSet patchSet) {
- return ObjectId.fromString(patchSet.getRevision().get());
- }
-
private ChangeEdit createEdit(
Repository repository,
ChangeNotes notes,
@@ -561,7 +557,7 @@ public class ChangeEditModifier {
private String getEditRefName(Change change, PatchSet basePatchSet) {
IdentifiedUser me = currentUser.get().asIdentifiedUser();
- return RefNames.refsEdit(me.getAccountId(), change.getId(), basePatchSet.getId());
+ return RefNames.refsEdit(me.getAccountId(), change.getId(), basePatchSet.id());
}
private ChangeEdit updateEdit(
diff --git a/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index ef1d8803dd..ea34b768fc 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -16,14 +16,14 @@ package com.google.gerrit.server.edit;
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -122,7 +122,7 @@ public class ChangeEditUtil {
String[] refNames = new String[n];
for (int i = n; i > 0; i--) {
refNames[i - 1] =
- RefNames.refsEdit(u.getAccountId(), change.getId(), new PatchSet.Id(change.getId(), i));
+ RefNames.refsEdit(u.getAccountId(), change.getId(), PatchSet.id(change.getId(), i));
}
Ref ref = repo.getRefDatabase().firstExactRef(refNames);
if (ref == null) {
@@ -162,7 +162,7 @@ public class ChangeEditUtil {
ObjectReader reader = oi.newReader();
RevWalk rw = new RevWalk(reader)) {
PatchSet basePatchSet = edit.getBasePatchSet();
- if (!basePatchSet.getId().equals(change.currentPatchSetId())) {
+ if (!basePatchSet.id().equals(change.currentPatchSetId())) {
throw new ResourceConflictException("only edit for current patch set can be published");
}
@@ -177,17 +177,14 @@ public class ChangeEditUtil {
new StringBuilder("Patch Set ").append(inserter.getPatchSetId().get()).append(": ");
// Previously checked that the base patch set is the current patch set.
- ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get());
+ ObjectId prior = basePatchSet.commitId();
ChangeKind kind =
changeKindCache.getChangeKind(change.getProject(), rw, repo.getConfig(), prior, squashed);
if (kind == ChangeKind.NO_CODE_CHANGE) {
message.append("Commit message was updated.");
inserter.setDescription("Edit commit message");
} else {
- message
- .append("Published edit on patch set ")
- .append(basePatchSet.getPatchSetId())
- .append(".");
+ message.append("Published edit on patch set ").append(basePatchSet.number()).append(".");
}
try (BatchUpdate bu = updateFactory.create(change.getProject(), user, TimeUtil.nowTs())) {
@@ -226,7 +223,7 @@ public class ChangeEditUtil {
int pos = ref.getName().lastIndexOf('/');
checkArgument(pos > 0, "invalid edit ref: %s", ref.getName());
String psId = ref.getName().substring(pos + 1);
- return psUtil.get(notes, new PatchSet.Id(notes.getChange().getId(), Integer.parseInt(psId)));
+ return psUtil.get(notes, PatchSet.id(notes.getChange().getId(), Integer.parseInt(psId)));
} catch (StorageException | NumberFormatException e) {
throw new IOException(e);
}
@@ -235,7 +232,7 @@ public class ChangeEditUtil {
private RevCommit squashEdit(
RevWalk rw, ObjectInserter inserter, RevCommit edit, PatchSet basePatchSet)
throws IOException, ResourceConflictException {
- RevCommit parent = rw.parseCommit(ObjectId.fromString(basePatchSet.getRevision().get()));
+ RevCommit parent = rw.parseCommit(basePatchSet.commitId());
if (parent.getTree().equals(edit.getTree())
&& edit.getFullMessage().equals(parent.getFullMessage())) {
throw new ResourceConflictException("identical tree and message");
diff --git a/java/com/google/gerrit/server/events/AssigneeChangedEvent.java b/java/com/google/gerrit/server/events/AssigneeChangedEvent.java
index 60a0935008..490d6d1435 100644
--- a/java/com/google/gerrit/server/events/AssigneeChangedEvent.java
+++ b/java/com/google/gerrit/server/events/AssigneeChangedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class AssigneeChangedEvent extends ChangeEvent {
diff --git a/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java b/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
index 32b5b021c0..066f398375 100644
--- a/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
+++ b/java/com/google/gerrit/server/events/ChangeAbandonedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class ChangeAbandonedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/ChangeDeletedEvent.java b/java/com/google/gerrit/server/events/ChangeDeletedEvent.java
index 63142fd149..017a839bc5 100644
--- a/java/com/google/gerrit/server/events/ChangeDeletedEvent.java
+++ b/java/com/google/gerrit/server/events/ChangeDeletedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class ChangeDeletedEvent extends ChangeEvent {
diff --git a/java/com/google/gerrit/server/events/ChangeEvent.java b/java/com/google/gerrit/server/events/ChangeEvent.java
index 6029dedc0d..d39099cc03 100644
--- a/java/com/google/gerrit/server/events/ChangeEvent.java
+++ b/java/com/google/gerrit/server/events/ChangeEvent.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.data.ChangeAttribute;
public abstract class ChangeEvent extends RefEvent {
@@ -29,7 +29,7 @@ public abstract class ChangeEvent extends RefEvent {
protected ChangeEvent(String type, Change change) {
super(type);
this.project = change.getProject();
- this.refName = RefNames.fullName(change.getDest().get());
+ this.refName = RefNames.fullName(change.getDest().branch());
this.changeKey = change.getKey();
}
diff --git a/java/com/google/gerrit/server/events/ChangeMergedEvent.java b/java/com/google/gerrit/server/events/ChangeMergedEvent.java
index 3fb2ac8b03..3b9632863b 100644
--- a/java/com/google/gerrit/server/events/ChangeMergedEvent.java
+++ b/java/com/google/gerrit/server/events/ChangeMergedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class ChangeMergedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/ChangeRestoredEvent.java b/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
index 7c86d70722..48100a1fb1 100644
--- a/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
+++ b/java/com/google/gerrit/server/events/ChangeRestoredEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class ChangeRestoredEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/CommentAddedEvent.java b/java/com/google/gerrit/server/events/CommentAddedEvent.java
index bb1ac4d7e7..dbbebe8931 100644
--- a/java/com/google/gerrit/server/events/CommentAddedEvent.java
+++ b/java/com/google/gerrit/server/events/CommentAddedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.ApprovalAttribute;
diff --git a/java/com/google/gerrit/server/events/CommitReceivedEvent.java b/java/com/google/gerrit/server/events/CommitReceivedEvent.java
index c0f9c29785..6e43621c6e 100644
--- a/java/com/google/gerrit/server/events/CommitReceivedEvent.java
+++ b/java/com/google/gerrit/server/events/CommitReceivedEvent.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.IdentifiedUser;
import java.io.IOException;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/events/EventBroker.java b/java/com/google/gerrit/server/events/EventBroker.java
index d5f548ff4b..a6db081e4f 100644
--- a/java/com/google/gerrit/server/events/EventBroker.java
+++ b/java/com/google/gerrit/server/events/EventBroker.java
@@ -15,13 +15,13 @@
package com.google.gerrit.server.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -34,6 +34,7 @@ import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -47,6 +48,8 @@ public class EventBroker implements EventDispatcher {
protected void configure() {
DynamicItem.itemOf(binder(), EventDispatcher.class);
DynamicItem.bind(binder(), EventDispatcher.class).to(EventBroker.class);
+
+ bind(Gson.class).annotatedWith(EventGson.class).toProvider(EventGsonProvider.class);
}
}
@@ -81,7 +84,7 @@ public class EventBroker implements EventDispatcher {
}
@Override
- public void postEvent(Branch.NameKey branchName, RefEvent event)
+ public void postEvent(BranchNameKey branchName, RefEvent event)
throws PermissionBackendException {
fireEvent(branchName, event);
}
@@ -120,7 +123,7 @@ public class EventBroker implements EventDispatcher {
fireEventForUnrestrictedListeners(event);
}
- protected void fireEvent(Branch.NameKey branchName, RefEvent event)
+ protected void fireEvent(BranchNameKey branchName, RefEvent event)
throws PermissionBackendException {
for (PluginSetEntryContext<UserScopedEventListener> c : listeners) {
CurrentUser user = c.call(UserScopedEventListener::getUser);
@@ -174,9 +177,9 @@ public class EventBroker implements EventDispatcher {
}
}
- protected boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user)
+ protected boolean isVisibleTo(BranchNameKey branchName, CurrentUser user)
throws PermissionBackendException {
- ProjectState pe = projectCache.get(branchName.getParentKey());
+ ProjectState pe = projectCache.get(branchName.project());
if (pe == null || !pe.statePermitsRead()) {
return false;
}
@@ -194,13 +197,13 @@ public class EventBroker implements EventDispatcher {
RefEvent refEvent = (RefEvent) event;
String ref = refEvent.getRefName();
if (PatchSet.isChangeRef(ref)) {
- Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey();
+ Change.Id cid = PatchSet.Id.fromRef(ref).changeId();
try {
Change change = notesFactory.createChecked(refEvent.getProjectNameKey(), cid).getChange();
return isVisibleTo(change, user);
} catch (NoSuchChangeException e) {
logger.atFine().log(
- "Change %s cannot be found, falling back on ref visibility check", cid.id);
+ "Change %s cannot be found, falling back on ref visibility check", cid.get());
}
}
return isVisibleTo(refEvent.getBranchNameKey(), user);
diff --git a/java/com/google/gerrit/server/events/EventDispatcher.java b/java/com/google/gerrit/server/events/EventDispatcher.java
index e6735f2960..a358aee451 100644
--- a/java/com/google/gerrit/server/events/EventDispatcher.java
+++ b/java/com/google/gerrit/server/events/EventDispatcher.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
/** Interface for posting (dispatching) Events */
@@ -37,7 +37,7 @@ public interface EventDispatcher {
* @param event The event to post
* @throws PermissionBackendException on failure of permission checks
*/
- void postEvent(Branch.NameKey branchName, RefEvent event) throws PermissionBackendException;
+ void postEvent(BranchNameKey branchName, RefEvent event) throws PermissionBackendException;
/**
* Post a stream event that is related to a project.
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index efd9bb9cea..3f22d7f8ab 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -24,18 +24,18 @@ import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.UserIdentity;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.index.IndexConfig;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
@@ -68,7 +68,6 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -127,7 +126,7 @@ public class EventFactory {
public ChangeAttribute asChangeAttribute(Change change) {
ChangeAttribute a = new ChangeAttribute();
a.project = change.getProject().get();
- a.branch = change.getDest().getShortName();
+ a.branch = change.getDest().shortName();
a.topic = change.getTopic();
a.id = change.getKey().get();
a.number = change.getId().get();
@@ -174,12 +173,12 @@ public class EventFactory {
* @return object suitable for serialization to JSON
*/
public RefUpdateAttribute asRefUpdateAttribute(
- ObjectId oldId, ObjectId newId, Branch.NameKey refName) {
+ ObjectId oldId, ObjectId newId, BranchNameKey refName) {
RefUpdateAttribute ru = new RefUpdateAttribute();
ru.newRev = newId != null ? newId.getName() : ObjectId.zeroId().getName();
ru.oldRev = oldId != null ? oldId.getName() : ObjectId.zeroId().getName();
- ru.project = refName.getParentKey().get();
- ru.refName = refName.get();
+ ru.project = refName.project().get();
+ ru.refName = refName.branch();
return ru;
}
@@ -285,7 +284,7 @@ public class EventFactory {
private void addDependsOn(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
throws IOException {
- RevCommit commit = rw.parseCommit(ObjectId.fromString(currentPs.getRevision().get()));
+ RevCommit commit = rw.parseCommit(currentPs.commitId());
final List<String> parentNames = new ArrayList<>(commit.getParentCount());
for (RevCommit p : commit.getParents()) {
parentNames.add(p.name());
@@ -296,7 +295,7 @@ public class EventFactory {
for (ChangeData cd : queryProvider.get().byProjectCommits(change.getProject(), parentNames)) {
for (PatchSet ps : cd.patchSets()) {
for (String p : parentNames) {
- if (!ps.getRevision().get().equals(p)) {
+ if (!ps.commitId().name().equals(p)) {
continue;
}
ca.dependsOn.add(newDependsOn(requireNonNull(cd.change()), ps));
@@ -318,18 +317,18 @@ public class EventFactory {
private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
throws IOException {
- if (currentPs.getGroups().isEmpty()) {
+ if (currentPs.groups().isEmpty()) {
return;
}
- String rev = currentPs.getRevision().get();
+ String rev = currentPs.commitId().name();
// Find changes in the same related group as this patch set, having a patch
// set whose parent matches this patch set's revision.
for (ChangeData cd :
InternalChangeQuery.byProjectGroups(
- queryProvider, indexConfig, change.getProject(), currentPs.getGroups())) {
+ queryProvider, indexConfig, change.getProject(), currentPs.groups())) {
PATCH_SETS:
for (PatchSet ps : cd.patchSets()) {
- RevCommit commit = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ RevCommit commit = rw.parseCommit(ps.commitId());
for (RevCommit p : commit.getParents()) {
if (!p.name().equals(rev)) {
continue;
@@ -343,7 +342,7 @@ public class EventFactory {
private DependencyAttribute newDependsOn(Change c, PatchSet ps) {
DependencyAttribute d = newDependencyAttribute(c, ps);
- d.isCurrentPatchSet = ps.getId().equals(c.currentPatchSetId());
+ d.isCurrentPatchSet = ps.id().equals(c.currentPatchSetId());
return d;
}
@@ -355,8 +354,8 @@ public class EventFactory {
DependencyAttribute d = new DependencyAttribute();
d.number = c.getId().get();
d.id = c.getKey().toString();
- d.revision = ps.getRevision().get();
- d.ref = ps.getRefName();
+ d.revision = ps.commitId().name();
+ d.ref = ps.refName();
return d;
}
@@ -400,7 +399,7 @@ public class EventFactory {
for (PatchSet p : ps) {
PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p);
if (approvals != null) {
- addApprovals(psa, p.getId(), approvals, labelTypes);
+ addApprovals(psa, p.id(), approvals, labelTypes);
}
ca.patchSets.add(psa);
if (includeFiles) {
@@ -463,12 +462,12 @@ public class EventFactory {
*/
public PatchSetAttribute asPatchSetAttribute(RevWalk revWalk, Change change, PatchSet patchSet) {
PatchSetAttribute p = new PatchSetAttribute();
- p.revision = patchSet.getRevision().get();
- p.number = patchSet.getPatchSetId();
- p.ref = patchSet.getRefName();
- p.uploader = asAccountAttribute(patchSet.getUploader());
- p.createdOn = patchSet.getCreatedOn().getTime() / 1000L;
- PatchSet.Id pId = patchSet.getId();
+ p.revision = patchSet.commitId().name();
+ p.number = patchSet.number();
+ p.ref = patchSet.refName();
+ p.uploader = asAccountAttribute(patchSet.uploader());
+ p.createdOn = patchSet.createdOn().getTime() / 1000L;
+ PatchSet.Id pId = patchSet.id();
try {
p.parents = new ArrayList<>();
RevCommit c = revWalk.parseCommit(ObjectId.fromString(p.revision));
@@ -476,7 +475,7 @@ public class EventFactory {
p.parents.add(parent.name());
}
- UserIdentity author = toUserIdentity(c.getAuthorIdent());
+ UserIdentity author = emails.toUserIdentity(c.getAuthorIdent());
if (author.getAccount() == null) {
p.author = new AccountAttribute();
p.author.email = author.getEmail();
@@ -495,7 +494,7 @@ public class EventFactory {
}
p.kind = changeKindCache.getChangeKind(change, patchSet);
} catch (IOException | StorageException e) {
- logger.atSevere().withCause(e).log("Cannot load patch set data for %s", patchSet.getId());
+ logger.atSevere().withCause(e).log("Cannot load patch set data for %s", patchSet.id());
} catch (PatchListObjectTooLargeException e) {
logger.atWarning().log("Cannot get size information for %s: %s", pId, e.getMessage());
} catch (PatchListNotAvailableException e) {
@@ -504,26 +503,6 @@ public class EventFactory {
return p;
}
- // TODO: The same method exists in PatchSetInfoFactory, find a common place
- // for it
- private UserIdentity toUserIdentity(PersonIdent who) throws IOException {
- UserIdentity u = new UserIdentity();
- u.setName(who.getName());
- u.setEmail(who.getEmailAddress());
- u.setDate(new Timestamp(who.getWhen().getTime()));
- u.setTimeZone(who.getTimeZoneOffset());
-
- // If only one account has access to this email address, select it
- // as the identity of the user.
- //
- Set<Account.Id> a = emails.getAccountFor(u.getEmail());
- if (a.size() == 1) {
- u.setAccount(a.iterator().next());
- }
-
- return u;
- }
-
public void addApprovals(
PatchSetAttribute p,
PatchSet.Id id,
@@ -540,7 +519,7 @@ public class EventFactory {
if (!list.isEmpty()) {
p.approvals = new ArrayList<>(list.size());
for (PatchSetApproval a : list) {
- if (a.getValue() != 0) {
+ if (a.value() != 0) {
p.approvals.add(asApprovalAttribute(a, labelTypes));
}
}
@@ -571,9 +550,9 @@ public class EventFactory {
*/
public AccountAttribute asAccountAttribute(AccountState accountState) {
AccountAttribute who = new AccountAttribute();
- who.name = accountState.getAccount().getFullName();
- who.email = accountState.getAccount().getPreferredEmail();
- who.username = accountState.getUserName().orElse(null);
+ who.name = accountState.account().fullName();
+ who.email = accountState.account().preferredEmail();
+ who.username = accountState.userName().orElse(null);
return who;
}
@@ -599,13 +578,13 @@ public class EventFactory {
*/
public ApprovalAttribute asApprovalAttribute(PatchSetApproval approval, LabelTypes labelTypes) {
ApprovalAttribute a = new ApprovalAttribute();
- a.type = approval.getLabelId().get();
- a.value = Short.toString(approval.getValue());
- a.by = asAccountAttribute(approval.getAccountId());
- a.grantedOn = approval.getGranted().getTime() / 1000L;
+ a.type = approval.labelId().get();
+ a.value = Short.toString(approval.value());
+ a.by = asAccountAttribute(approval.accountId());
+ a.grantedOn = approval.granted().getTime() / 1000L;
a.oldValue = null;
- LabelType lt = labelTypes.byLabel(approval.getLabelId());
+ LabelType lt = labelTypes.byLabel(approval.labelId());
if (lt != null) {
a.description = lt.getName();
}
diff --git a/java/com/google/gerrit/server/events/EventGson.java b/java/com/google/gerrit/server/events/EventGson.java
new file mode 100644
index 0000000000..87b45f6581
--- /dev/null
+++ b/java/com/google/gerrit/server/events/EventGson.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.events;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+@Target({PARAMETER, FIELD})
+public @interface EventGson {}
diff --git a/java/com/google/gerrit/server/events/EventGsonProvider.java b/java/com/google/gerrit/server/events/EventGsonProvider.java
new file mode 100644
index 0000000000..688507bfe3
--- /dev/null
+++ b/java/com/google/gerrit/server/events/EventGsonProvider.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.change.ChangeKeyAdapter;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Provider;
+
+public class EventGsonProvider implements Provider<Gson> {
+ @Override
+ public Gson get() {
+ return new GsonBuilder()
+ .registerTypeAdapter(Event.class, new EventDeserializer())
+ .registerTypeAdapter(Supplier.class, new SupplierSerializer())
+ .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
+ .registerTypeAdapter(Change.Key.class, new ChangeKeyAdapter())
+ .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeyAdapter())
+ .create();
+ }
+}
diff --git a/java/com/google/gerrit/server/events/EventsMetrics.java b/java/com/google/gerrit/server/events/EventsMetrics.java
index f73d6de081..3c87cca27b 100644
--- a/java/com/google/gerrit/server/events/EventsMetrics.java
+++ b/java/com/google/gerrit/server/events/EventsMetrics.java
@@ -18,6 +18,7 @@ import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -31,7 +32,7 @@ public class EventsMetrics implements EventListener {
metricMaker.newCounter(
"events",
new Description("Triggered events").setRate().setUnit("triggered events"),
- Field.ofString("type"));
+ Field.ofString("type", Metadata.Builder::eventType).build());
}
@Override
diff --git a/java/com/google/gerrit/server/events/HashtagsChangedEvent.java b/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
index 1de0fc3c19..d9e0445ecf 100644
--- a/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
+++ b/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class HashtagsChangedEvent extends ChangeEvent {
diff --git a/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java b/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
index 8cea85602b..24f4709013 100644
--- a/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
+++ b/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class PatchSetCreatedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/PatchSetEvent.java b/java/com/google/gerrit/server/events/PatchSetEvent.java
index f9dde66d94..c8e45910ed 100644
--- a/java/com/google/gerrit/server/events/PatchSetEvent.java
+++ b/java/com/google/gerrit/server/events/PatchSetEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.PatchSetAttribute;
public class PatchSetEvent extends ChangeEvent {
diff --git a/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
index d03eda4655..2e6fe835ea 100644
--- a/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
+++ b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class PrivateStateChangedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/ProjectCreatedEvent.java b/java/com/google/gerrit/server/events/ProjectCreatedEvent.java
index dc979cae1e..d092b3934b 100644
--- a/java/com/google/gerrit/server/events/ProjectCreatedEvent.java
+++ b/java/com/google/gerrit/server/events/ProjectCreatedEvent.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
public class ProjectCreatedEvent extends ProjectEvent {
static final String TYPE = "project-created";
@@ -27,7 +27,7 @@ public class ProjectCreatedEvent extends ProjectEvent {
@Override
public Project.NameKey getProjectNameKey() {
- return new Project.NameKey(projectName);
+ return Project.nameKey(projectName);
}
public String getHeadName() {
diff --git a/java/com/google/gerrit/server/events/ProjectEvent.java b/java/com/google/gerrit/server/events/ProjectEvent.java
index cba8e90ffc..77085e6182 100644
--- a/java/com/google/gerrit/server/events/ProjectEvent.java
+++ b/java/com/google/gerrit/server/events/ProjectEvent.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
public abstract class ProjectEvent extends Event {
protected ProjectEvent(String type) {
diff --git a/java/com/google/gerrit/server/events/ProjectNameKeySerializer.java b/java/com/google/gerrit/server/events/ProjectNameKeyAdapter.java
index 743b3145d8..eeaa2386b3 100644
--- a/java/com/google/gerrit/server/events/ProjectNameKeySerializer.java
+++ b/java/com/google/gerrit/server/events/ProjectNameKeyAdapter.java
@@ -14,17 +14,31 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
-public class ProjectNameKeySerializer implements JsonSerializer<Project.NameKey> {
+public class ProjectNameKeyAdapter
+ implements JsonSerializer<Project.NameKey>, JsonDeserializer<Project.NameKey> {
@Override
public JsonElement serialize(
Project.NameKey project, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(project.get());
}
+
+ @Override
+ public Project.NameKey deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonPrimitive() || !json.getAsJsonPrimitive().isString()) {
+ throw new JsonParseException("Key is not a string: " + json);
+ }
+ return Project.nameKey(json.getAsString());
+ }
}
diff --git a/java/com/google/gerrit/server/events/RefEvent.java b/java/com/google/gerrit/server/events/RefEvent.java
index 951940f0ec..7a6bf6f330 100644
--- a/java/com/google/gerrit/server/events/RefEvent.java
+++ b/java/com/google/gerrit/server/events/RefEvent.java
@@ -14,15 +14,15 @@
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.entities.BranchNameKey;
public abstract class RefEvent extends ProjectEvent {
protected RefEvent(String type) {
super(type);
}
- public Branch.NameKey getBranchNameKey() {
- return new Branch.NameKey(getProjectNameKey(), getRefName());
+ public BranchNameKey getBranchNameKey() {
+ return BranchNameKey.create(getProjectNameKey(), getRefName());
}
public abstract String getRefName();
diff --git a/java/com/google/gerrit/server/events/RefReceivedEvent.java b/java/com/google/gerrit/server/events/RefReceivedEvent.java
index aa02d11b9f..18783aaca5 100644
--- a/java/com/google/gerrit/server/events/RefReceivedEvent.java
+++ b/java/com/google/gerrit/server/events/RefReceivedEvent.java
@@ -13,7 +13,7 @@
// limitations under the License.
package com.google.gerrit.server.events;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.IdentifiedUser;
import org.eclipse.jgit.transport.ReceiveCommand;
diff --git a/java/com/google/gerrit/server/events/RefUpdatedEvent.java b/java/com/google/gerrit/server/events/RefUpdatedEvent.java
index d74054344e..1ca63924e8 100644
--- a/java/com/google/gerrit/server/events/RefUpdatedEvent.java
+++ b/java/com/google/gerrit/server/events/RefUpdatedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
@@ -30,7 +30,7 @@ public class RefUpdatedEvent extends RefEvent {
@Override
public Project.NameKey getProjectNameKey() {
- return new Project.NameKey(refUpdate.get().project);
+ return Project.nameKey(refUpdate.get().project);
}
@Override
diff --git a/java/com/google/gerrit/server/events/ReviewerAddedEvent.java b/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
index ea6bda3e74..5d2b7a51ed 100644
--- a/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
+++ b/java/com/google/gerrit/server/events/ReviewerAddedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class ReviewerAddedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java b/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
index 02f9d438c2..2b0b6ac368 100644
--- a/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
+++ b/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.ApprovalAttribute;
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 5826dc7e39..1769f7d3ca 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -20,6 +20,10 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
@@ -42,10 +46,6 @@ import com.google.gerrit.extensions.events.TopicEditedListener;
import com.google.gerrit.extensions.events.VoteDeletedListener;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.cache.PerThreadCache.ReadonlyRequestWindow;
@@ -139,7 +139,7 @@ public class StreamEventsApiListener
private ChangeNotes getNotes(ChangeInfo info) {
try {
- return changeNotesFactory.createChecked(new Change.Id(info._number));
+ return changeNotesFactory.createChecked(Change.id(info._number));
} catch (NoSuchChangeException e) {
throw new StorageException(e);
}
@@ -164,7 +164,7 @@ public class StreamEventsApiListener
return Suppliers.memoize(
() ->
account != null
- ? eventFactory.asAccountAttribute(new Account.Id(account._accountId))
+ ? eventFactory.asAccountAttribute(Account.id(account._accountId))
: null);
}
@@ -367,7 +367,7 @@ public class StreamEventsApiListener
if (ev.getUpdater() != null) {
event.submitter = accountAttributeSupplier(ev.getUpdater());
}
- final Branch.NameKey refName = new Branch.NameKey(ev.getProjectName(), ev.getRefName());
+ final BranchNameKey refName = BranchNameKey.create(ev.getProjectName(), ev.getRefName());
event.refUpdate =
Suppliers.memoize(
() ->
diff --git a/java/com/google/gerrit/server/events/TopicChangedEvent.java b/java/com/google/gerrit/server/events/TopicChangedEvent.java
index 0b6ecc5c96..b9cce6643c 100644
--- a/java/com/google/gerrit/server/events/TopicChangedEvent.java
+++ b/java/com/google/gerrit/server/events/TopicChangedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class TopicChangedEvent extends ChangeEvent {
diff --git a/java/com/google/gerrit/server/events/VoteDeletedEvent.java b/java/com/google/gerrit/server/events/VoteDeletedEvent.java
index 87c4c055e3..180b4a8f3c 100644
--- a/java/com/google/gerrit/server/events/VoteDeletedEvent.java
+++ b/java/com/google/gerrit/server/events/VoteDeletedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.ApprovalAttribute;
diff --git a/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
index 5e52c7b337..74f997b2c7 100644
--- a/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
+++ b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.events;
import com.google.common.base.Supplier;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class WorkInProgressStateChangedEvent extends PatchSetEvent {
diff --git a/java/com/google/gerrit/server/extensions/events/AssigneeChanged.java b/java/com/google/gerrit/server/extensions/events/AssigneeChanged.java
index fdce1da8f6..a76b69bdbb 100644
--- a/java/com/google/gerrit/server/extensions/events/AssigneeChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/AssigneeChanged.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.AssigneeChangedListener;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java b/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
index 252129b1e9..ccf58cf2eb 100644
--- a/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
+++ b/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.ChangeAbandonedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java b/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java
index acb212acb5..a24873cb8c 100644
--- a/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java
+++ b/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.ChangeDeletedListener;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.cache.PerThreadCache.ReadonlyRequestWindow;
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeMerged.java b/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
index 1645333661..5ce38ff8f0 100644
--- a/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
+++ b/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.ChangeMergedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeRestored.java b/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
index e8bed56854..fa91dbd380 100644
--- a/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
+++ b/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.ChangeRestoredListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeReverted.java b/java/com/google/gerrit/server/extensions/events/ChangeReverted.java
index ccb17d5cfd..5fb004f65e 100644
--- a/java/com/google/gerrit/server/extensions/events/ChangeReverted.java
+++ b/java/com/google/gerrit/server/extensions/events/ChangeReverted.java
@@ -15,11 +15,11 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.ChangeRevertedListener;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/extensions/events/CommentAdded.java b/java/com/google/gerrit/server/extensions/events/CommentAdded.java
index e5d626f16c..e72b4d4331 100644
--- a/java/com/google/gerrit/server/extensions/events/CommentAdded.java
+++ b/java/com/google/gerrit/server/extensions/events/CommentAdded.java
@@ -15,6 +15,8 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -22,8 +24,6 @@ import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.CommentAddedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
diff --git a/java/com/google/gerrit/server/extensions/events/EventUtil.java b/java/com/google/gerrit/server/extensions/events/EventUtil.java
index d00eb31721..3dcf3b8228 100644
--- a/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -16,19 +16,21 @@ package com.google.gerrit.server.extensions.events;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.RevisionJson;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -39,42 +41,51 @@ import java.sql.Timestamp;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.jgit.lib.Config;
+/**
+ * Formats change and revision info objects to serve as payload for Gerrit events.
+ *
+ * <p>Uses configurable options ({@code event.payload.listChangeOptions}) to decide which fields to
+ * populate.
+ */
@Singleton
public class EventUtil {
- private static final ImmutableSet<ListChangesOption> CHANGE_OPTIONS;
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private static final ImmutableSet<ListChangesOption> DEFAULT_CHANGE_OPTIONS;
static {
EnumSet<ListChangesOption> opts = EnumSet.allOf(ListChangesOption.class);
-
// Some options, like actions, are expensive to compute because they potentially have to walk
// lots of history and inspect lots of other changes.
opts.remove(ListChangesOption.CHANGE_ACTIONS);
opts.remove(ListChangesOption.CURRENT_ACTIONS);
-
// CHECK suppresses some exceptions on corrupt changes, which is not appropriate for passing
// through the event system as we would rather let them propagate.
opts.remove(ListChangesOption.CHECK);
-
- CHANGE_OPTIONS = Sets.immutableEnumSet(opts);
+ DEFAULT_CHANGE_OPTIONS = Sets.immutableEnumSet(opts);
}
private final ChangeData.Factory changeDataFactory;
private final ChangeJson.Factory changeJsonFactory;
private final RevisionJson.Factory revisionJsonFactory;
+ private final ImmutableSet<ListChangesOption> changeOptions;
@Inject
EventUtil(
ChangeJson.Factory changeJsonFactory,
RevisionJson.Factory revisionJsonFactory,
- ChangeData.Factory changeDataFactory) {
+ ChangeData.Factory changeDataFactory,
+ @GerritServerConfig Config gerritConfig) {
this.changeDataFactory = changeDataFactory;
this.changeJsonFactory = changeJsonFactory;
this.revisionJsonFactory = revisionJsonFactory;
+ this.changeOptions = parseChangeListOptions(gerritConfig);
}
public ChangeInfo changeInfo(Change change) {
- return changeJsonFactory.create(CHANGE_OPTIONS).format(change);
+ return changeJsonFactory.create(changeOptions).format(change);
}
public RevisionInfo revisionInfo(Project project, PatchSet ps)
@@ -84,19 +95,19 @@ public class EventUtil {
public RevisionInfo revisionInfo(Project.NameKey project, PatchSet ps)
throws PatchListNotAvailableException, GpgException, IOException, PermissionBackendException {
- ChangeData cd = changeDataFactory.create(project, ps.getId().getParentKey());
- return revisionJsonFactory.create(CHANGE_OPTIONS).getRevisionInfo(cd, ps);
+ ChangeData cd = changeDataFactory.create(project, ps.id().changeId());
+ return revisionJsonFactory.create(changeOptions).getRevisionInfo(cd, ps);
}
public AccountInfo accountInfo(AccountState accountState) {
- if (accountState == null || accountState.getAccount().getId() == null) {
+ if (accountState == null || accountState.account().id() == null) {
return null;
}
- Account account = accountState.getAccount();
- AccountInfo accountInfo = new AccountInfo(account.getId().get());
- accountInfo.email = account.getPreferredEmail();
- accountInfo.name = account.getFullName();
- accountInfo.username = accountState.getUserName().orElse(null);
+ Account account = accountState.account();
+ AccountInfo accountInfo = new AccountInfo(account.id().get());
+ accountInfo.email = account.preferredEmail();
+ accountInfo.name = account.fullName();
+ accountInfo.username = accountState.userName().orElse(null);
return accountInfo;
}
@@ -106,9 +117,25 @@ public class EventUtil {
for (Map.Entry<String, Short> e : approvals.entrySet()) {
Integer value = e.getValue() != null ? Integer.valueOf(e.getValue()) : null;
result.put(
- e.getKey(),
- new ApprovalInfo(accountState.getAccount().getId().get(), value, null, null, ts));
+ e.getKey(), new ApprovalInfo(accountState.account().id().get(), value, null, null, ts));
}
return result;
}
+
+ private static ImmutableSet<ListChangesOption> parseChangeListOptions(Config gerritConfig) {
+ String[] config = gerritConfig.getStringList("event", "payload", "listChangeOptions");
+ if (config.length == 0) {
+ return DEFAULT_CHANGE_OPTIONS;
+ }
+
+ ImmutableSet.Builder<ListChangesOption> result = ImmutableSet.builder();
+ for (String c : config) {
+ try {
+ result.add(ListChangesOption.valueOf(c));
+ } catch (IllegalArgumentException e) {
+ logger.atWarning().withCause(e).log("could not parse list change option %s", c);
+ }
+ }
+ return result.build();
+ }
}
diff --git a/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java b/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
index bae17e7b8d..99f105ec9b 100644
--- a/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
+++ b/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.extensions.events;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java b/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java
index 65f5b8bfa2..df60ec08cc 100644
--- a/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java
+++ b/java/com/google/gerrit/server/extensions/events/HashtagsEdited.java
@@ -16,12 +16,12 @@ package com.google.gerrit.server.extensions.events;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
index 49a609124e..72adff7670 100644
--- a/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java b/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
index f9f67f6c4b..1af428cb8c 100644
--- a/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
+++ b/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
@@ -16,14 +16,14 @@ package com.google.gerrit.server.extensions.events;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java b/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
index b92f3e6937..61632f2be7 100644
--- a/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
+++ b/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
@@ -15,6 +15,8 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -22,8 +24,6 @@ import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.ReviewerDeletedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/events/RevisionCreated.java b/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
index f34e0ac608..50fbaa15f5 100644
--- a/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
+++ b/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
diff --git a/java/com/google/gerrit/server/extensions/events/TopicEdited.java b/java/com/google/gerrit/server/extensions/events/TopicEdited.java
index 9e1ae44560..cb982a1c8b 100644
--- a/java/com/google/gerrit/server/extensions/events/TopicEdited.java
+++ b/java/com/google/gerrit/server/extensions/events/TopicEdited.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.TopicEditedListener;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/extensions/events/VoteDeleted.java b/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
index bd6873a65c..8533a650df 100644
--- a/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
+++ b/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
@@ -15,6 +15,8 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -22,8 +24,6 @@ import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.VoteDeletedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
index 785d6feae8..16c5e25e3b 100644
--- a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
+++ b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
@@ -15,14 +15,14 @@
package com.google.gerrit.server.extensions.events;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/java/com/google/gerrit/server/extensions/webui/UiActions.java b/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 3ca2bdb93d..0bc3d5c9ac 100644
--- a/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -19,7 +19,6 @@ import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
import static java.util.stream.Collectors.toList;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicate;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
@@ -38,6 +37,7 @@ import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendCondition;
@@ -50,6 +50,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Predicate;
@Singleton
public class UiActions {
@@ -71,7 +72,7 @@ public class UiActions {
new com.google.gerrit.metrics.Description("Latency for RestView#getDescription calls")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- Field.ofString("view"));
+ Field.ofString("view", Metadata.Builder::restViewName).build());
}
public <R extends RestResource> Iterable<UiAction.Description> from(
@@ -143,7 +144,7 @@ public class UiActions {
String name = e.getExportName().substring(d + 1);
UiAction.Description dsc;
- try (Timer1.Context ignored = uiActionLatency.start(name)) {
+ try (Timer1.Context<String> ignored = uiActionLatency.start(name)) {
dsc = ((UiAction<R>) view).getDescription(resource);
}
diff --git a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
index 65f14db27a..a1682fe4be 100644
--- a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
+++ b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
@@ -18,11 +18,11 @@ import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.FixReplacement;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.FixReplacement;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.edit.tree.ChangeFileContentModification;
import com.google.gerrit.server.edit.tree.TreeModification;
diff --git a/java/com/google/gerrit/server/git/BanCommit.java b/java/com/google/gerrit/server/git/BanCommit.java
index d8aeeceb86..4473ab710b 100644
--- a/java/com/google/gerrit/server/git/BanCommit.java
+++ b/java/com/google/gerrit/server/git/BanCommit.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.git;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_REJECT_COMMITS;
+import static com.google.gerrit.entities.RefNames.REFS_REJECT_COMMITS;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
diff --git a/java/com/google/gerrit/server/git/BranchOrderSection.java b/java/com/google/gerrit/server/git/BranchOrderSection.java
index d4b537e268..4c77b61122 100644
--- a/java/com/google/gerrit/server/git/BranchOrderSection.java
+++ b/java/com/google/gerrit/server/git/BranchOrderSection.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import java.util.List;
public class BranchOrderSection {
diff --git a/java/com/google/gerrit/server/git/ChangeMessageModifier.java b/java/com/google/gerrit/server/git/ChangeMessageModifier.java
index 7d4edcf317..580c0b9cce 100644
--- a/java/com/google/gerrit/server/git/ChangeMessageModifier.java
+++ b/java/com/google/gerrit/server/git/ChangeMessageModifier.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Branch;
import org.eclipse.jgit.revwalk.RevCommit;
/**
@@ -47,5 +47,5 @@ public interface ChangeMessageModifier {
* @return a new not null commit message.
*/
String onSubmit(
- String newCommitMessage, RevCommit original, RevCommit mergeTip, Branch.NameKey destination);
+ String newCommitMessage, RevCommit original, RevCommit mergeTip, BranchNameKey destination);
}
diff --git a/java/com/google/gerrit/server/git/ChangeReportFormatter.java b/java/com/google/gerrit/server/git/ChangeReportFormatter.java
index 3ce6b2b085..f897a1dedf 100644
--- a/java/com/google/gerrit/server/git/ChangeReportFormatter.java
+++ b/java/com/google/gerrit/server/git/ChangeReportFormatter.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.git;
import com.google.auto.value.AutoValue;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
public interface ChangeReportFormatter {
@AutoValue
diff --git a/java/com/google/gerrit/server/git/CodeReviewCommit.java b/java/com/google/gerrit/server/git/CodeReviewCommit.java
index c210dcdaaa..d7538ba3fb 100644
--- a/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -19,8 +19,8 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.submit.CommitMergeStatus;
import java.io.IOException;
@@ -48,7 +48,7 @@ public class CodeReviewCommit extends RevCommit {
Ordering.natural()
.onResultOf(
(CodeReviewCommit c) ->
- c.getPatchsetId() != null ? c.getPatchsetId().getParentKey().get() : null)
+ c.getPatchsetId() != null ? c.getPatchsetId().changeId().get() : null)
.nullsFirst();
public static CodeReviewRevWalk newRevWalk(Repository repo) {
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java
index b0f10f2bc8..476037bd1a 100644
--- a/java/com/google/gerrit/server/git/CommitUtil.java
+++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -14,16 +14,49 @@
package com.google.gerrit.server.git;
+import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.CommonConverters;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.change.ChangeMessages;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.io.IOException;
+import java.sql.Timestamp;
+import java.text.MessageFormat;
import java.util.ArrayList;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.ChangeIdUtil;
/** Static utilities for working with {@link RevCommit}s. */
+@Singleton
public class CommitUtil {
+ private final GitRepositoryManager repoManager;
+ private final Provider<PersonIdent> serverIdent;
+
+ @Inject
+ CommitUtil(
+ GitRepositoryManager repoManager, @GerritPersonIdent Provider<PersonIdent> serverIdent) {
+ this.repoManager = repoManager;
+ this.serverIdent = serverIdent;
+ }
+
public static CommitInfo toCommitInfo(RevCommit commit) throws IOException {
return toCommitInfo(commit, null);
}
@@ -47,5 +80,84 @@ public class CommitUtil {
return info;
}
- private CommitUtil() {}
+ /**
+ * Allows creating a revert commit.
+ *
+ * @param message Commit message for the revert commit.
+ * @param notes ChangeNotes of the change being reverted.
+ * @param user Current User performing the revert.
+ * @return ObjectId that represents the newly created commit.
+ * @throws ResourceConflictException Can't revert the initial commit.
+ * @throws IOException Thrown in case of I/O errors.
+ */
+ public ObjectId createRevertCommit(String message, ChangeNotes notes, CurrentUser user)
+ throws ResourceConflictException, IOException {
+ message = Strings.emptyToNull(message);
+
+ Project.NameKey project = notes.getProjectName();
+ try (Repository git = repoManager.openRepository(project);
+ ObjectInserter oi = git.newObjectInserter();
+ ObjectReader reader = oi.newReader();
+ RevWalk revWalk = new RevWalk(reader)) {
+ return createRevertCommit(message, notes, user, null, TimeUtil.nowTs(), oi, revWalk);
+ }
+ }
+
+ /**
+ * @param message Commit message for the revert commit.
+ * @param notes ChangeNotes of the change being reverted.
+ * @param user Current User performing the revert.
+ * @param generatedChangeId The changeId for the commit message, can be null since it is not
+ * needed for commits, only for changes.
+ * @param ts Timestamp of creation for the commit.
+ * @param oi ObjectInserter for inserting the newly created commit.
+ * @param revWalk Used for parsing the original commit.
+ * @return ObjectId that represents the newly created commit.
+ * @throws ResourceConflictException Can't revert the initial commit.
+ * @throws IOException Thrown in case of I/O errors.
+ */
+ public ObjectId createRevertCommit(
+ String message,
+ ChangeNotes notes,
+ CurrentUser user,
+ @Nullable ObjectId generatedChangeId,
+ Timestamp ts,
+ ObjectInserter oi,
+ RevWalk revWalk)
+ throws ResourceConflictException, IOException {
+
+ PatchSet patch = notes.getCurrentPatchSet();
+ RevCommit commitToRevert = revWalk.parseCommit(patch.commitId());
+ if (commitToRevert.getParentCount() == 0) {
+ throw new ResourceConflictException("Cannot revert initial commit");
+ }
+
+ PersonIdent committerIdent = serverIdent.get();
+ PersonIdent authorIdent =
+ user.asIdentifiedUser().newCommitterIdent(ts, committerIdent.getTimeZone());
+
+ RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
+ revWalk.parseHeaders(parentToCommitToRevert);
+
+ CommitBuilder revertCommitBuilder = new CommitBuilder();
+ revertCommitBuilder.addParentId(commitToRevert);
+ revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+ revertCommitBuilder.setAuthor(authorIdent);
+ revertCommitBuilder.setCommitter(authorIdent);
+
+ Change changeToRevert = notes.getChange();
+ if (message == null) {
+ message =
+ MessageFormat.format(
+ ChangeMessages.get().revertChangeDefaultMessage,
+ changeToRevert.getSubject(),
+ patch.commitId().name());
+ }
+ if (generatedChangeId != null) {
+ revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, generatedChangeId, true));
+ }
+ ObjectId id = oi.insert(revertCommitBuilder);
+ oi.flush();
+ return id;
+ }
}
diff --git a/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java
deleted file mode 100644
index a8594e7622..0000000000
--- a/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.git;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook;
-import org.eclipse.jgit.transport.ServiceMayNotContinueException;
-
-/**
- * Wrapper around {@link com.google.gerrit.server.permissions.PermissionBackend.ForProject} that
- * implements {@link org.eclipse.jgit.transport.AdvertiseRefsHook}.
- */
-public class DefaultAdvertiseRefsHook extends AbstractAdvertiseRefsHook {
- private final PermissionBackend.ForProject perm;
- private final PermissionBackend.RefFilterOptions opts;
-
- public DefaultAdvertiseRefsHook(
- PermissionBackend.ForProject perm, PermissionBackend.RefFilterOptions opts) {
- this.perm = perm;
- this.opts = opts;
- }
-
- @Override
- protected Map<String, Ref> getAdvertisedRefs(Repository repo, RevWalk revWalk)
- throws ServiceMayNotContinueException {
- try {
- List<String> prefixes =
- !opts.prefixes().isEmpty() ? opts.prefixes() : ImmutableList.of(RefDatabase.ALL);
- return perm.filter(
- repo.getRefDatabase().getRefsByPrefix(prefixes.toArray(new String[0])), repo, opts);
- } catch (IOException | PermissionBackendException e) {
- ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
- ex.initCause(e);
- throw ex;
- }
- }
-}
diff --git a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
index 5b9dffce4e..5866c578f4 100644
--- a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
+++ b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.git;
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.UrlFormatter;
import com.google.inject.Inject;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/git/DelegateRefDatabase.java b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
new file mode 100644
index 0000000000..34dd6a9a67
--- /dev/null
+++ b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Wrapper around {@link RefDatabase} that delegates all calls to the wrapped {@link RefDatabase}.
+ */
+public class DelegateRefDatabase extends RefDatabase {
+
+ private Repository delegate;
+
+ DelegateRefDatabase(Repository delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void create() throws IOException {
+ delegate.getRefDatabase().create();
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ @Override
+ public boolean isNameConflicting(String name) throws IOException {
+ return delegate.getRefDatabase().isNameConflicting(name);
+ }
+
+ @Override
+ public RefUpdate newUpdate(String name, boolean detach) throws IOException {
+ return delegate.getRefDatabase().newUpdate(name, detach);
+ }
+
+ @Override
+ public RefRename newRename(String fromName, String toName) throws IOException {
+ return delegate.getRefDatabase().newRename(fromName, toName);
+ }
+
+ @Override
+ public Ref exactRef(String name) throws IOException {
+ return delegate.getRefDatabase().exactRef(name);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Map<String, Ref> getRefs(String prefix) throws IOException {
+ return delegate.getRefDatabase().getRefs(prefix);
+ }
+
+ @Override
+ public List<Ref> getAdditionalRefs() throws IOException {
+ return delegate.getRefDatabase().getAdditionalRefs();
+ }
+
+ @Override
+ public Ref peel(Ref ref) throws IOException {
+ return delegate.getRefDatabase().peel(ref);
+ }
+
+ Repository getDelegate() {
+ return delegate;
+ }
+}
diff --git a/java/com/google/gerrit/server/git/DelegateRepository.java b/java/com/google/gerrit/server/git/DelegateRepository.java
new file mode 100644
index 0000000000..800490d4c4
--- /dev/null
+++ b/java/com/google/gerrit/server/git/DelegateRepository.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import java.io.IOException;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.lib.BaseRepositoryBuilder;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+
+/** Wrapper around {@link Repository} that delegates all calls to the wrapped {@link Repository}. */
+class DelegateRepository extends Repository {
+
+ private final Repository delegate;
+
+ DelegateRepository(Repository delegate) {
+ super(toBuilder(delegate));
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void create(boolean bare) throws IOException {
+ delegate.create(bare);
+ }
+
+ @Override
+ public String getIdentifier() {
+ return delegate.getIdentifier();
+ }
+
+ @Override
+ public ObjectDatabase getObjectDatabase() {
+ return delegate.getObjectDatabase();
+ }
+
+ @Override
+ public RefDatabase getRefDatabase() {
+ return delegate.getRefDatabase();
+ }
+
+ @Override
+ public StoredConfig getConfig() {
+ return delegate.getConfig();
+ }
+
+ @Override
+ public AttributesNodeProvider createAttributesNodeProvider() {
+ return delegate.createAttributesNodeProvider();
+ }
+
+ @Override
+ public void scanForRepoChanges() throws IOException {
+ delegate.scanForRepoChanges();
+ }
+
+ @Override
+ public void notifyIndexChanged(boolean internal) {
+ delegate.notifyIndexChanged(internal);
+ }
+
+ @Override
+ public ReflogReader getReflogReader(String refName) throws IOException {
+ return delegate.getReflogReader(refName);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static BaseRepositoryBuilder toBuilder(Repository repo) {
+ if (!repo.isBare()) {
+ throw new IllegalArgumentException("non-bare repository is not supported");
+ }
+
+ return new BaseRepositoryBuilder<>().setFS(repo.getFS()).setGitDir(repo.getDirectory());
+ }
+}
diff --git a/java/com/google/gerrit/server/git/GarbageCollection.java b/java/com/google/gerrit/server/git/GarbageCollection.java
index 75c90126ef..090d4397aa 100644
--- a/java/com/google/gerrit/server/git/GarbageCollection.java
+++ b/java/com/google/gerrit/server/git/GarbageCollection.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.git;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GarbageCollectionResult;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.GarbageCollectorListener;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GcConfig;
import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
import com.google.gerrit.server.plugincontext.PluginSetContext;
diff --git a/java/com/google/gerrit/server/git/GarbageCollectionQueue.java b/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
index 31cd880d1e..e3a923b5c2 100644
--- a/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
+++ b/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.inject.Singleton;
import java.util.Collection;
import java.util.HashSet;
diff --git a/java/com/google/gerrit/server/git/GitRepositoryManager.java b/java/com/google/gerrit/server/git/GitRepositoryManager.java
index 01bbe89abd..e4d06965fc 100644
--- a/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.inject.ImplementedBy;
import com.google.inject.Singleton;
import java.io.IOException;
diff --git a/java/com/google/gerrit/server/git/GroupCollector.java b/java/com/google/gerrit/server/git/GroupCollector.java
index e4fd803b27..dd3919870b 100644
--- a/java/com/google/gerrit/server/git/GroupCollector.java
+++ b/java/com/google/gerrit/server/git/GroupCollector.java
@@ -28,8 +28,8 @@ import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -82,9 +82,9 @@ public class GroupCollector {
if (rsrc.getEdit().isPresent()) {
// Groups for an edit are just the base revision's groups, since they have
// the same parent.
- return rsrc.getEdit().get().getBasePatchSet().getGroups();
+ return rsrc.getEdit().get().getBasePatchSet().groups();
}
- return rsrc.getPatchSet().getGroups();
+ return rsrc.getPatchSet().groups();
}
private interface Lookup {
@@ -107,9 +107,9 @@ public class GroupCollector {
transformRefs(changeRefsById),
psId -> {
// TODO(dborowitz): Reuse open repository from caller.
- ChangeNotes notes = notesFactory.createChecked(project, psId.getParentKey());
+ ChangeNotes notes = notesFactory.createChecked(project, psId.changeId());
PatchSet ps = psUtil.get(notes, psId);
- return ps != null ? ps.getGroups() : null;
+ return ps != null ? ps.groups() : null;
});
}
diff --git a/java/com/google/gerrit/server/git/HookUtil.java b/java/com/google/gerrit/server/git/HookUtil.java
index 3bef7cccb2..fd29c8debc 100644
--- a/java/com/google/gerrit/server/git/HookUtil.java
+++ b/java/com/google/gerrit/server/git/HookUtil.java
@@ -19,8 +19,9 @@ import static java.util.stream.Collectors.toMap;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.BaseReceivePack;
+import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.UploadPack;
/** Static utilities for writing git protocol hooks. */
public class HookUtil {
@@ -32,8 +33,7 @@ public class HookUtil {
* @return map of refs that were advertised.
* @throws ServiceMayNotContinueException if a problem occurred.
*/
- @SuppressWarnings("deprecation")
- public static Map<String, Ref> ensureAllRefsAdvertised(BaseReceivePack rp)
+ public static Map<String, Ref> ensureAllRefsAdvertised(ReceivePack rp)
throws ServiceMayNotContinueException {
Map<String, Ref> refs = rp.getAdvertisedRefs();
if (refs != null) {
@@ -52,5 +52,32 @@ public class HookUtil {
return refs;
}
+ /**
+ * Scan and advertise all refs in the repo if refs have not already been advertised; otherwise,
+ * just return the advertised map.
+ *
+ * @param up upload-pack handler.
+ * @return map of refs that were advertised.
+ * @throws ServiceMayNotContinueException if a problem occurred.
+ */
+ public static Map<String, Ref> ensureAllRefsAdvertised(UploadPack up)
+ throws ServiceMayNotContinueException {
+ Map<String, Ref> refs = up.getAdvertisedRefs();
+ if (refs != null) {
+ return refs;
+ }
+ try {
+ refs =
+ up.getRepository().getRefDatabase().getRefs().stream()
+ .collect(toMap(Ref::getName, r -> r));
+ } catch (ServiceMayNotContinueException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new ServiceMayNotContinueException(e);
+ }
+ up.setAdvertisedRefs(refs);
+ return refs;
+ }
+
private HookUtil() {}
}
diff --git a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 2c0fdca83f..6ccb73c6ce 100644
--- a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.git;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -268,7 +268,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
int newLen = projectName.length() - Constants.DOT_GIT_EXT.length();
projectName = projectName.substring(0, newLen);
}
- return new Project.NameKey(projectName);
+ return Project.nameKey(projectName);
}
protected class ProjectVisitor extends SimpleFileVisitor<Path> {
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index c1d8ddd1b4..ad878433fa 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -17,19 +17,30 @@ package com.google.gerrit.server.git;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Comparator.naturalOrder;
import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
-import com.google.common.base.Joiner;
import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.exceptions.InvalidMergeStrategyException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -38,13 +49,6 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -67,7 +71,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -117,6 +120,13 @@ import org.eclipse.jgit.util.TemporaryBuffer;
public class MergeUtil {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ /**
+ * Length of abbreviated hex SHA-1s in merged filenames.
+ *
+ * <p>This is a constant so output is stable over time even if the SHA-1 prefix becomes ambiguous.
+ */
+ private static final int NAME_ABBREV_LEN = 6;
+
static class PluggableCommitMessageGenerator {
private final DynamicSet<ChangeMessageModifier> changeMessageModifiers;
@@ -126,7 +136,7 @@ public class MergeUtil {
}
public String generate(
- RevCommit original, RevCommit mergeTip, Branch.NameKey dest, String originalMessage) {
+ RevCommit original, RevCommit mergeTip, BranchNameKey dest, String originalMessage) {
requireNonNull(original.getRawBuffer());
if (mergeTip != null) {
requireNonNull(mergeTip.getRawBuffer());
@@ -256,7 +266,8 @@ public class MergeUtil {
boolean ignoreIdenticalTree,
boolean allowConflicts)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
- MergeIdenticalTreeException, MergeConflictException, MethodNotAllowedException {
+ MergeIdenticalTreeException, MergeConflictException, MethodNotAllowedException,
+ InvalidMergeStrategyException {
ThreeWayMerger m = newThreeWayMerger(inserter, repoConfig);
m.setBase(originalCommit.getParent(parentIndex));
@@ -337,13 +348,13 @@ public class MergeUtil {
String.format(
"%0$-" + nameLength + "s (%s %s)",
oursName,
- ours.abbreviate(6).name(),
+ abbreviateName(ours, NAME_ABBREV_LEN),
oursMsg.substring(0, Math.min(oursMsg.length(), 60)));
String theirsNameFormatted =
String.format(
"%0$-" + nameLength + "s (%s %s)",
theirsName,
- theirs.abbreviate(6).name(),
+ abbreviateName(theirs, NAME_ABBREV_LEN),
theirsMsg.substring(0, Math.min(theirsMsg.length(), 60)));
MergeFormatter fmt = new MergeFormatter();
@@ -422,7 +433,8 @@ public class MergeUtil {
PersonIdent committerIndent,
String commitMsg,
RevWalk rw)
- throws IOException, MergeIdenticalTreeException, MergeConflictException {
+ throws IOException, MergeIdenticalTreeException, MergeConflictException,
+ InvalidMergeStrategyException {
if (!MergeStrategy.THEIRS.getName().equals(mergeStrategy)
&& rw.isMergedInto(originalCommit, mergeTip)) {
@@ -450,7 +462,7 @@ public class MergeUtil {
}
public static String createConflictMessage(List<String> conflicts) {
- StringBuilder sb = new StringBuilder("merge conflict(s)");
+ StringBuilder sb = new StringBuilder("merge conflict(s):");
for (String c : conflicts) {
sb.append('\n').append(c);
}
@@ -514,7 +526,7 @@ public class MergeUtil {
PatchSetApproval submitAudit = null;
for (PatchSetApproval a : safeGetApprovals(notes, psId)) {
- if (a.getValue() <= 0) {
+ if (a.value() <= 0) {
// Negative votes aren't counted.
continue;
}
@@ -522,29 +534,29 @@ public class MergeUtil {
if (a.isLegacySubmit()) {
// Submit is treated specially, below (becomes committer)
//
- if (submitAudit == null || a.getGranted().compareTo(submitAudit.getGranted()) > 0) {
+ if (submitAudit == null || a.granted().compareTo(submitAudit.granted()) > 0) {
submitAudit = a;
}
continue;
}
- final Account acc = identifiedUserFactory.create(a.getAccountId()).getAccount();
+ final Account acc = identifiedUserFactory.create(a.accountId()).getAccount();
final StringBuilder identbuf = new StringBuilder();
- if (acc.getFullName() != null && acc.getFullName().length() > 0) {
+ if (acc.fullName() != null && acc.fullName().length() > 0) {
if (identbuf.length() > 0) {
identbuf.append(' ');
}
- identbuf.append(acc.getFullName());
+ identbuf.append(acc.fullName());
}
- if (acc.getPreferredEmail() != null && acc.getPreferredEmail().length() > 0) {
- if (isSignedOffBy(footers, acc.getPreferredEmail())) {
+ if (acc.preferredEmail() != null && acc.preferredEmail().length() > 0) {
+ if (isSignedOffBy(footers, acc.preferredEmail())) {
continue;
}
if (identbuf.length() > 0) {
identbuf.append(' ');
}
identbuf.append('<');
- identbuf.append(acc.getPreferredEmail());
+ identbuf.append(acc.preferredEmail());
identbuf.append('>');
}
if (identbuf.length() == 0) {
@@ -553,12 +565,12 @@ public class MergeUtil {
}
final String tag;
- if (isCodeReview(a.getLabelId())) {
+ if (isCodeReview(a.labelId())) {
tag = "Reviewed-by";
- } else if (isVerified(a.getLabelId())) {
+ } else if (isVerified(a.labelId())) {
tag = "Tested-by";
} else {
- final LabelType lt = project.getLabelTypes().byLabel(a.getLabelId());
+ final LabelType lt = project.getLabelTypes().byLabel(a.labelId());
if (lt == null) {
continue;
}
@@ -733,10 +745,10 @@ public class MergeUtil {
CodeReviewRevWalk rw,
ObjectInserter inserter,
Config repoConfig,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
CodeReviewCommit mergeTip,
CodeReviewCommit n)
- throws IntegrationException {
+ throws IntegrationException, InvalidMergeStrategyException {
ThreeWayMerger m = newThreeWayMerger(inserter, repoConfig);
try {
if (m.merge(mergeTip, n)) {
@@ -789,7 +801,7 @@ public class MergeUtil {
PersonIdent committer,
CodeReviewRevWalk rw,
ObjectInserter inserter,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
CodeReviewCommit mergeTip,
ObjectId treeId,
CodeReviewCommit n)
@@ -806,9 +818,9 @@ public class MergeUtil {
}
StringBuilder msgbuf = new StringBuilder().append(summarize(rw, merged));
- if (!R_HEADS_MASTER.equals(destBranch.get())) {
+ if (!R_HEADS_MASTER.equals(destBranch.branch())) {
msgbuf.append(" into ");
- msgbuf.append(destBranch.getShortName());
+ msgbuf.append(destBranch.shortName());
}
if (merged.size() > 1) {
@@ -840,29 +852,26 @@ public class MergeUtil {
return String.format("Merge \"%s\"", c.getShortMessage());
}
- LinkedHashSet<String> topics = new LinkedHashSet<>(4);
- for (CodeReviewCommit c : merged) {
- if (!Strings.isNullOrEmpty(c.change().getTopic())) {
- topics.add(c.change().getTopic());
- }
- }
+ ImmutableSortedSet<String> topics =
+ merged.stream()
+ .map(c -> c.change().getTopic())
+ .filter(t -> !Strings.isNullOrEmpty(t))
+ .map(t -> "\"" + t + "\"")
+ .collect(toImmutableSortedSet(naturalOrder()));
- if (topics.size() == 1) {
- return String.format("Merge changes from topic \"%s\"", Iterables.getFirst(topics, null));
- } else if (topics.size() > 1) {
- return String.format("Merge changes from topics \"%s\"", Joiner.on("\", \"").join(topics));
- } else {
+ if (!topics.isEmpty()) {
return String.format(
- "Merge changes %s%s",
- FluentIterable.from(merged)
- .limit(5)
- .transform(c -> c.change().getKey().abbreviate())
- .join(Joiner.on(',')),
- merged.size() > 5 ? ", ..." : "");
+ "Merge changes from topic%s %s",
+ topics.size() > 1 ? "s" : "", topics.stream().collect(joining(", ")));
}
+ return merged.stream()
+ .limit(5)
+ .map(c -> c.change().getKey().abbreviate())
+ .collect(joining(",", "Merge changes ", merged.size() > 5 ? ", ..." : ""));
}
- public ThreeWayMerger newThreeWayMerger(ObjectInserter inserter, Config repoConfig) {
+ public ThreeWayMerger newThreeWayMerger(ObjectInserter inserter, Config repoConfig)
+ throws InvalidMergeStrategyException {
return newThreeWayMerger(inserter, repoConfig, mergeStrategyName());
}
@@ -886,7 +895,8 @@ public class MergeUtil {
}
public static ThreeWayMerger newThreeWayMerger(
- ObjectInserter inserter, Config repoConfig, String strategyName) {
+ ObjectInserter inserter, Config repoConfig, String strategyName)
+ throws InvalidMergeStrategyException {
Merger m = newMerger(inserter, repoConfig, strategyName);
checkArgument(
m instanceof ThreeWayMerger,
@@ -895,9 +905,12 @@ public class MergeUtil {
return (ThreeWayMerger) m;
}
- public static Merger newMerger(ObjectInserter inserter, Config repoConfig, String strategyName) {
+ public static Merger newMerger(ObjectInserter inserter, Config repoConfig, String strategyName)
+ throws InvalidMergeStrategyException {
MergeStrategy strategy = MergeStrategy.get(strategyName);
- checkArgument(strategy != null, "invalid merge strategy: %s", strategyName);
+ if (strategy == null) {
+ throw new InvalidMergeStrategyException(strategyName);
+ }
return strategy.newMerger(
new ObjectInserter.Filter() {
@Override
@@ -975,7 +988,7 @@ public class MergeUtil {
if (c.getPatchsetId() == null) {
continue;
}
- Change.Id id = c.getPatchsetId().getParentKey();
+ Change.Id id = c.getPatchsetId().changeId();
if (!expected.contains(id)) {
continue;
}
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index 3d64b82542..858a55aa5c 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -17,13 +17,11 @@ package com.google.gerrit.server.git;
import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.config.SendEmailExecutor;
@@ -42,7 +40,6 @@ import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -112,7 +109,7 @@ public class MergedByPushOp implements BatchUpdateOp {
@Override
public boolean updateChange(ChangeContext ctx) throws IOException {
change = ctx.getChange();
- correctBranch = refName.equals(change.getDest().get());
+ correctBranch = refName.equals(change.getDest().branch());
if (!correctBranch) {
return false;
}
@@ -145,7 +142,7 @@ public class MergedByPushOp implements BatchUpdateOp {
}
StringBuilder msgBuf = new StringBuilder();
msgBuf.append("Change has been successfully pushed");
- if (!refName.equals(change.getDest().get())) {
+ if (!refName.equals(change.getDest().branch())) {
msgBuf.append(" into ");
if (refName.startsWith(Constants.R_HEADS)) {
msgBuf.append("branch ");
@@ -159,12 +156,7 @@ public class MergedByPushOp implements BatchUpdateOp {
ChangeMessagesUtil.newMessage(
psId, ctx.getUser(), ctx.getWhen(), msgBuf.toString(), ChangeMessagesUtil.TAG_MERGED);
cmUtil.addChangeMessage(update, msg);
-
- PatchSetApproval submitter =
- ApprovalsUtil.newApproval(
- change.currentPatchSetId(), ctx.getUser(), LabelId.legacySubmit(), 1, ctx.getWhen());
- update.putApproval(submitter.getLabel(), submitter.getValue());
-
+ update.putApproval(LabelId.legacySubmit().get(), (short) 1);
return true;
}
@@ -182,7 +174,7 @@ public class MergedByPushOp implements BatchUpdateOp {
public void run() {
try {
MergedSender cm =
- mergedSenderFactory.create(ctx.getProject(), psId.getParentKey());
+ mergedSenderFactory.create(ctx.getProject(), psId.changeId());
cm.setFrom(ctx.getAccountId());
cm.setPatchSet(patchSet, info);
cm.send();
@@ -203,8 +195,7 @@ public class MergedByPushOp implements BatchUpdateOp {
private PatchSetInfo getPatchSetInfo(ChangeContext ctx) throws IOException {
RevWalk rw = ctx.getRevWalk();
- RevCommit commit =
- rw.parseCommit(ObjectId.fromString(requireNonNull(patchSet).getRevision().get()));
+ RevCommit commit = rw.parseCommit(requireNonNull(patchSet).commitId());
return patchSetInfoFactory.get(rw, commit, psId);
}
}
diff --git a/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java b/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
index 01e85cf332..32b5bb83c1 100644
--- a/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.git;
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.RepositoryConfig;
import com.google.gerrit.server.config.SitePaths;
diff --git a/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index b72ea92c0c..bfc51350b0 100644
--- a/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -346,6 +346,8 @@ public class MultiProgressMonitor {
out.write(Constants.encode(s.toString()));
out.flush();
} catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Sending progress to client failed. Stop sending updates for task %s", taskName);
write = false;
}
}
diff --git a/java/com/google/gerrit/server/git/NotesBranchUtil.java b/java/com/google/gerrit/server/git/NotesBranchUtil.java
index 1636b85571..8cb2b40634 100644
--- a/java/com/google/gerrit/server/git/NotesBranchUtil.java
+++ b/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.git;
import static com.google.common.base.MoreObjects.firstNonNull;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/git/NotifyConfig.java b/java/com/google/gerrit/server/git/NotifyConfig.java
index 7f74f54983..429f15a95f 100644
--- a/java/com/google/gerrit/server/git/NotifyConfig.java
+++ b/java/com/google/gerrit/server/git/NotifyConfig.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.git;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.mail.Address;
@@ -113,6 +114,13 @@ public class NotifyConfig implements Comparable<NotifyConfig> {
@Override
public String toString() {
- return "NotifyConfig[" + name + " = " + addresses + " + " + groups + "]";
+ return MoreObjects.toStringHelper(this)
+ .add("name", name)
+ .add("addresses", addresses)
+ .add("groups", groups)
+ .add("header", header)
+ .add("types", types)
+ .add("filter", filter)
+ .toString();
}
}
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
new file mode 100644
index 0000000000..c68842de02
--- /dev/null
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -0,0 +1,185 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Wrapper around {@link DelegateRefDatabase} that filters all refs using {@link
+ * com.google.gerrit.server.permissions.PermissionBackend}.
+ */
+public class PermissionAwareReadOnlyRefDatabase extends DelegateRefDatabase {
+
+ private final PermissionBackend.ForProject forProject;
+
+ @Inject
+ PermissionAwareReadOnlyRefDatabase(
+ Repository delegateRepository, PermissionBackend.ForProject forProject) {
+ super(delegateRepository);
+ this.forProject = forProject;
+ }
+
+ @Override
+ public boolean isNameConflicting(String name) {
+ throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
+ }
+
+ @Override
+ public RefUpdate newUpdate(String name, boolean detach) {
+ throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
+ }
+
+ @Override
+ public RefRename newRename(String fromName, String toName) {
+ throw new UnsupportedOperationException("PermissionAwareReadOnlyRefDatabase is read-only");
+ }
+
+ @Override
+ public Ref exactRef(String name) throws IOException {
+ Ref ref = getDelegate().getRefDatabase().exactRef(name);
+ if (ref == null) {
+ return null;
+ }
+
+ Map<String, Ref> result;
+ try {
+ result =
+ forProject.filter(ImmutableMap.of(name, ref), getDelegate(), RefFilterOptions.defaults());
+ } catch (PermissionBackendException e) {
+ if (e.getCause() instanceof IOException) {
+ throw (IOException) e.getCause();
+ }
+ throw new IOException(e);
+ }
+ if (result.isEmpty()) {
+ return null;
+ }
+
+ Preconditions.checkState(
+ result.size() == 1, "Only one element expected, but was: " + result.size());
+ return Iterables.getOnlyElement(result.values());
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Map<String, Ref> getRefs(String prefix) throws IOException {
+ Map<String, Ref> refs = getDelegate().getRefDatabase().getRefs(prefix);
+ if (refs.isEmpty()) {
+ return refs;
+ }
+
+ Map<String, Ref> result;
+ try {
+ // The security filtering assumes to receive the same refMap, independently from the ref
+ // prefix offset
+ result =
+ forProject.filter(
+ prefixIndependentRefMap(prefix, refs), getDelegate(), RefFilterOptions.defaults());
+ } catch (PermissionBackendException e) {
+ throw new IOException("", e);
+ }
+ return applyPrefixRefMap(prefix, result);
+ }
+
+ private Map<String, Ref> prefixIndependentRefMap(String prefix, Map<String, Ref> refs) {
+ if (prefix.length() > 0) {
+ return refs.values().stream().collect(Collectors.toMap(Ref::getName, Function.identity()));
+ }
+
+ return refs;
+ }
+
+ private Map<String, Ref> applyPrefixRefMap(String prefix, Map<String, Ref> refs) {
+ int prefixSlashPos = prefix.lastIndexOf('/') + 1;
+ if (prefixSlashPos > 0) {
+ return refs.values().stream()
+ .collect(
+ Collectors.toMap(
+ (Ref ref) -> ref.getName().substring(prefixSlashPos), Function.identity()));
+ }
+
+ return refs;
+ }
+
+ @Override
+ public List<Ref> getRefsByPrefix(String prefix) throws IOException {
+ Map<String, Ref> coarseRefs;
+ int lastSlash = prefix.lastIndexOf('/');
+ if (lastSlash == -1) {
+ coarseRefs = getRefs(ALL);
+ } else {
+ coarseRefs = getRefs(prefix.substring(0, lastSlash + 1));
+ }
+
+ List<Ref> result;
+ if (lastSlash + 1 == prefix.length()) {
+ result = coarseRefs.values().stream().collect(toList());
+ } else {
+ String p = prefix.substring(lastSlash + 1);
+ result =
+ coarseRefs.entrySet().stream()
+ .filter(e -> e.getKey().startsWith(p))
+ .map(e -> e.getValue())
+ .collect(toList());
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ @Override
+ @NonNull
+ public Map<String, Ref> exactRef(String... refs) throws IOException {
+ Map<String, Ref> result = new HashMap<>(refs.length);
+ for (String name : refs) {
+ Ref ref = exactRef(name);
+ if (ref != null) {
+ result.put(name, ref);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ @Nullable
+ public Ref firstExactRef(String... refs) throws IOException {
+ for (String name : refs) {
+ Ref ref = exactRef(name);
+ if (ref != null) {
+ return ref;
+ }
+ }
+ return null;
+ }
+}
diff --git a/java/com/google/gerrit/server/git/PermissionAwareRepository.java b/java/com/google/gerrit/server/git/PermissionAwareRepository.java
new file mode 100644
index 0000000000..bb80cb59a7
--- /dev/null
+++ b/java/com/google/gerrit/server/git/PermissionAwareRepository.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.server.permissions.PermissionBackend;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Wrapper around {@link DelegateRepository} that overwrites {@link #getRefDatabase()} to return a
+ * {@link PermissionAwareReadOnlyRefDatabase}.
+ */
+public class PermissionAwareRepository extends DelegateRepository {
+
+ private final PermissionAwareReadOnlyRefDatabase permissionAwareReadOnlyRefDatabase;
+
+ public PermissionAwareRepository(Repository delegate, PermissionBackend.ForProject forProject) {
+ super(delegate);
+ this.permissionAwareReadOnlyRefDatabase =
+ new PermissionAwareReadOnlyRefDatabase(delegate, forProject);
+ }
+
+ @Override
+ public RefDatabase getRefDatabase() {
+ return permissionAwareReadOnlyRefDatabase;
+ }
+}
diff --git a/java/com/google/gerrit/server/git/PermissionAwareRepositoryManager.java b/java/com/google/gerrit/server/git/PermissionAwareRepositoryManager.java
new file mode 100644
index 0000000000..b11aa4967e
--- /dev/null
+++ b/java/com/google/gerrit/server/git/PermissionAwareRepositoryManager.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.base.Preconditions;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Wraps and unwraps existing repositories and makes them permission-aware by returning a {@link
+ * PermissionAwareReadOnlyRefDatabase}.
+ */
+public class PermissionAwareRepositoryManager {
+ public static Repository wrap(Repository delegate, PermissionBackend.ForProject forProject) {
+ Preconditions.checkState(
+ !(delegate instanceof PermissionAwareRepository),
+ "Cannot wrap PermissionAwareRepository instance");
+ return new PermissionAwareRepository(delegate, forProject);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/ProjectRunnable.java b/java/com/google/gerrit/server/git/ProjectRunnable.java
index 23d2326dee..e74bf2d1ca 100644
--- a/java/com/google/gerrit/server/git/ProjectRunnable.java
+++ b/java/com/google/gerrit/server/git/ProjectRunnable.java
@@ -13,7 +13,7 @@
// limitations under the License.
package com.google.gerrit.server.git;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
/** Used to retrieve the project name from an operation * */
public interface ProjectRunnable extends Runnable {
diff --git a/java/com/google/gerrit/server/git/PureRevertCache.java b/java/com/google/gerrit/server/git/PureRevertCache.java
index 16ac87ff38..9f9530c2df 100644
--- a/java/com/google/gerrit/server/git/PureRevertCache.java
+++ b/java/com/google/gerrit/server/git/PureRevertCache.java
@@ -18,15 +18,16 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache;
import com.google.gerrit.server.cache.proto.Cache.PureRevertKeyProto;
import com.google.gerrit.server.cache.serialize.BooleanCacheSerializer;
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.cache.serialize.ProtobufSerializer;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
@@ -35,7 +36,6 @@ import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.protobuf.ByteString;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -49,6 +49,7 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
/** Computes and caches if a change is a pure revert of another change. */
@Singleton
@@ -97,8 +98,8 @@ public class PureRevertCache {
claimedRevert.getProjectName(), claimedRevert.getChange().getRevertOf());
return isPureRevert(
claimedRevert.getProjectName(),
- ObjectId.fromString(claimedRevert.getCurrentPatchSet().getRevision().get()),
- ObjectId.fromString(claimedOriginal.getCurrentPatchSet().getRevision().get()));
+ claimedRevert.getCurrentPatchSet().commitId(),
+ claimedOriginal.getCurrentPatchSet().commitId());
}
/**
@@ -151,10 +152,12 @@ public class PureRevertCache {
@Override
public Boolean load(PureRevertKeyProto key) throws BadRequestException, IOException {
try (TraceContext.TraceTimer ignored =
- TraceContext.newTimer("Loading pure revert for %s", key)) {
+ TraceContext.newTimer(
+ "Loading pure revert",
+ Metadata.builder().cacheKey(key.toString()).projectName(key.getProject()).build())) {
ObjectId original = ObjectIdConverter.create().fromByteString(key.getClaimedOriginal());
ObjectId revert = ObjectIdConverter.create().fromByteString(key.getClaimedRevert());
- Project.NameKey project = new Project.NameKey(key.getProject());
+ Project.NameKey project = Project.nameKey(key.getProject());
try (Repository repo = repoManager.openRepository(project);
ObjectInserter oi = repo.newObjectInserter();
@@ -185,9 +188,8 @@ public class PureRevertCache {
}
// Any differences between claimed original's parent and the rebase result indicate that
- // the
- // claimedRevert is not a pure revert but made content changes
- try (DiffFormatter df = new DiffFormatter(new ByteArrayOutputStream())) {
+ // the claimedRevert is not a pure revert but made content changes
+ try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
df.setReader(oi.newReader(), repo.getConfig());
List<DiffEntry> entries =
df.scan(claimedOriginalCommit.getParent(0), merger.getResultTreeId());
diff --git a/java/com/google/gerrit/server/git/ReceivePackInitializer.java b/java/com/google/gerrit/server/git/ReceivePackInitializer.java
index f3742156e3..d7af2802b6 100644
--- a/java/com/google/gerrit/server/git/ReceivePackInitializer.java
+++ b/java/com/google/gerrit/server/git/ReceivePackInitializer.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.transport.ReceivePack;
@ExtensionPoint
diff --git a/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java b/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
index 3a7a1256c2..8535cd2607 100644
--- a/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
+++ b/java/com/google/gerrit/server/git/RepositoryCaseMismatchException.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
/**
diff --git a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
index d7f8982e40..196fc61f10 100644
--- a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
+++ b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -20,14 +20,15 @@ import com.google.common.cache.LoadingCache;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.query.change.ChangeData;
@@ -136,7 +137,7 @@ public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
@Override
public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
- cache.invalidate(new Project.NameKey(event.getProjectName()));
+ cache.invalidate(Project.nameKey(event.getProjectName()));
}
}
@@ -152,7 +153,9 @@ public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
@Override
public List<CachedChange> load(Project.NameKey key) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading changes of project %s", key);
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading changes of project", Metadata.builder().projectName(key.get()).build());
ManualRequestContext ctx = requestContext.open()) {
List<ChangeData> cds =
queryProvider
diff --git a/java/com/google/gerrit/server/git/SystemReaderInstaller.java b/java/com/google/gerrit/server/git/SystemReaderInstaller.java
new file mode 100644
index 0000000000..b6cc108bdf
--- /dev/null
+++ b/java/com/google/gerrit/server/git/SystemReaderInstaller.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.git.DelegateSystemReader;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
+
+@Singleton
+public class SystemReaderInstaller implements LifecycleListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final SitePaths site;
+
+ @Inject
+ SystemReaderInstaller(SitePaths site) {
+ this.site = site;
+ }
+
+ @Override
+ public void start() {
+ SystemReader.setInstance(customReader());
+ logger.atInfo().log("Set JGit's SystemReader to read system config from %s", site.jgit_config);
+ }
+
+ @Override
+ public void stop() {}
+
+ private SystemReader customReader() {
+ SystemReader current = SystemReader.getInstance();
+
+ return new DelegateSystemReader(current) {
+ @Override
+ public FileBasedConfig openSystemConfig(Config parent, FS fs) {
+ return new FileBasedConfig(parent, site.jgit_config.toFile(), FS.DETECTED);
+ }
+ };
+ }
+}
diff --git a/java/com/google/gerrit/server/git/TagCache.java b/java/com/google/gerrit/server/git/TagCache.java
index 535644d8f8..daf5ea5fbe 100644
--- a/java/com/google/gerrit/server/git/TagCache.java
+++ b/java/com/google/gerrit/server/git/TagCache.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.cache.Cache;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/git/TagSet.java b/java/com/google/gerrit/server/git/TagSet.java
index 57637c897b..43483bfb38 100644
--- a/java/com/google/gerrit/server/git/TagSet.java
+++ b/java/com/google/gerrit/server/git/TagSet.java
@@ -18,9 +18,9 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.CachedRefProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.TagProto;
@@ -232,7 +232,7 @@ class TagSet {
new Tag(
idConverter.fromByteString(t.getId()),
BitSet.valueOf(t.getFlags().asReadOnlyByteBuffer()))));
- return new TagSet(new Project.NameKey(proto.getProjectName()), refs, tags);
+ return new TagSet(Project.nameKey(proto.getProjectName()), refs, tags);
}
TagSetProto toProto() {
diff --git a/java/com/google/gerrit/server/git/TagSetHolder.java b/java/com/google/gerrit/server/git/TagSetHolder.java
index 194283e7af..a574308525 100644
--- a/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.git;
import static java.util.stream.Collectors.toList;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
import java.util.Collection;
@@ -117,7 +117,7 @@ public class TagSetHolder {
@Override
public TagSetHolder deserialize(byte[] in) {
TagSetHolderProto proto = Protos.parseUnchecked(TagSetHolderProto.parser(), in);
- TagSetHolder holder = new TagSetHolder(new Project.NameKey(proto.getProjectName()));
+ TagSetHolder holder = new TagSetHolder(Project.nameKey(proto.getProjectName()));
if (proto.hasTags()) {
holder.tags = TagSet.fromProto(proto.getTags());
}
diff --git a/java/com/google/gerrit/server/git/TracingHook.java b/java/com/google/gerrit/server/git/TracingHook.java
new file mode 100644
index 0000000000..56eded05cd
--- /dev/null
+++ b/java/com/google/gerrit/server/git/TracingHook.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.server.logging.TraceContext;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.transport.FetchV2Request;
+import org.eclipse.jgit.transport.LsRefsV2Request;
+import org.eclipse.jgit.transport.ProtocolV2Hook;
+
+/**
+ * Git hook for ls-refs and fetch that enables Gerrit request tracing if the user sets the 'trace'
+ * server option.
+ *
+ * <p>This hook is only invoked if Git protocol v2 is used.
+ *
+ * <p>If the 'trace' server option is specified without value, this means without providing a trace
+ * ID, a trace ID is generated, but it's not returned to the client. Hence users are advised to
+ * always provide a trace ID.
+ */
+public class TracingHook implements ProtocolV2Hook, AutoCloseable {
+ private TraceContext traceContext;
+
+ @Override
+ public void onLsRefs(LsRefsV2Request req) {
+ maybeStartTrace(req.getServerOptions());
+ }
+
+ @Override
+ public void onFetch(FetchV2Request req) {
+ maybeStartTrace(req.getServerOptions());
+ }
+
+ @Override
+ public void close() {
+ if (traceContext != null) {
+ traceContext.close();
+ }
+ }
+
+ /**
+ * Starts request tracing if 'trace' server option is set.
+ *
+ * @param serverOptionList list of provided server options
+ */
+ private void maybeStartTrace(List<String> serverOptionList) {
+ if (traceContext != null) {
+ // Trace was already started
+ return;
+ }
+
+ Optional<String> traceOption = parseTraceOption(serverOptionList);
+ traceContext =
+ TraceContext.newTrace(
+ traceOption.isPresent(),
+ traceOption.orElse(null),
+ (tagName, traceId) -> {
+ // TODO(ekempin): Return trace ID to client
+ });
+ }
+
+ private Optional<String> parseTraceOption(List<String> serverOptionList) {
+ if (serverOptionList == null || serverOptionList.isEmpty()) {
+ return Optional.empty();
+ }
+
+ Optional<String> traceOption =
+ serverOptionList.stream().filter(o -> o.startsWith("trace")).findAny();
+ if (!traceOption.isPresent()) {
+ return Optional.empty();
+ }
+
+ int e = traceOption.get().indexOf('=');
+ if (e > 0) {
+ // trace option was specified with trace ID: "--trace=<trace-ID>"
+ return Optional.of(traceOption.get().substring(e + 1));
+ }
+
+ // trace option was specified without trace ID: "--trace",
+ // return an empty string so that a trace ID is generated
+ return Optional.of("");
+ }
+}
diff --git a/java/com/google/gerrit/server/git/UploadPackInitializer.java b/java/com/google/gerrit/server/git/UploadPackInitializer.java
index b63c5b34e4..7e97d872a9 100644
--- a/java/com/google/gerrit/server/git/UploadPackInitializer.java
+++ b/java/com/google/gerrit/server/git/UploadPackInitializer.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.transport.UploadPack;
@ExtensionPoint
diff --git a/java/com/google/gerrit/server/git/UploadPackMetricsHook.java b/java/com/google/gerrit/server/git/UploadPackMetricsHook.java
index aa02fba99a..4afff2bdf9 100644
--- a/java/com/google/gerrit/server/git/UploadPackMetricsHook.java
+++ b/java/com/google/gerrit/server/git/UploadPackMetricsHook.java
@@ -23,6 +23,7 @@ import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.storage.pack.PackStatistics;
@@ -43,14 +44,15 @@ public class UploadPackMetricsHook implements PostUploadHook {
@Inject
UploadPackMetricsHook(MetricMaker metricMaker) {
- Field<Operation> operation = Field.ofEnum(Operation.class, "operation");
+ Field<Operation> operationField =
+ Field.ofEnum(Operation.class, "operation", Metadata.Builder::gitOperation).build();
requestCount =
metricMaker.newCounter(
"git/upload-pack/request_count",
new Description("Total number of git-upload-pack requests")
.setRate()
.setUnit("requests"),
- operation);
+ operationField);
counting =
metricMaker.newTimer(
@@ -58,7 +60,7 @@ public class UploadPackMetricsHook implements PostUploadHook {
new Description("Time spent in the 'Counting...' phase")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- operation);
+ operationField);
compressing =
metricMaker.newTimer(
@@ -66,7 +68,7 @@ public class UploadPackMetricsHook implements PostUploadHook {
new Description("Time spent in the 'Compressing...' phase")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- operation);
+ operationField);
writing =
metricMaker.newTimer(
@@ -74,7 +76,7 @@ public class UploadPackMetricsHook implements PostUploadHook {
new Description("Time spent transferring bytes to client")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- operation);
+ operationField);
packBytes =
metricMaker.newHistogram(
@@ -82,7 +84,7 @@ public class UploadPackMetricsHook implements PostUploadHook {
new Description("Distribution of sizes of packs sent to clients")
.setCumulative()
.setUnit(Units.BYTES),
- operation);
+ operationField);
}
@Override
diff --git a/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java
new file mode 100644
index 0000000000..6c1879e1e5
--- /dev/null
+++ b/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Map;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.UploadPack;
+
+/**
+ * Advertises {@code refs/users/self} for authenticated users when interacting with the {@code
+ * All-Users} repository.
+ */
+@Singleton
+public class UsersSelfAdvertiseRefsHook implements AdvertiseRefsHook {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ public UsersSelfAdvertiseRefsHook(Provider<CurrentUser> userProvider) {
+ this.userProvider = userProvider;
+ }
+
+ @Override
+ public void advertiseRefs(UploadPack uploadPack) throws ServiceMayNotContinueException {
+ CurrentUser user = userProvider.get();
+ if (!user.isIdentifiedUser()) {
+ return;
+ }
+
+ addSelfSymlinkIfNecessary(
+ uploadPack.getRepository().getRefDatabase(),
+ HookUtil.ensureAllRefsAdvertised(uploadPack),
+ user.getAccountId());
+ }
+
+ @Override
+ public void advertiseRefs(ReceivePack receivePack) throws ServiceMayNotContinueException {
+ CurrentUser user = userProvider.get();
+ if (!user.isIdentifiedUser()) {
+ return;
+ }
+
+ addSelfSymlinkIfNecessary(
+ receivePack.getRepository().getRefDatabase(),
+ HookUtil.ensureAllRefsAdvertised(receivePack),
+ user.getAccountId());
+ }
+
+ private static void addSelfSymlinkIfNecessary(
+ RefDatabase refDatabase, Map<String, Ref> advertisedRefs, Account.Id accountId)
+ throws ServiceMayNotContinueException {
+ String refName = RefNames.refsUsers(accountId);
+ try {
+ Ref r = refDatabase.exactRef(refName);
+ if (r == null) {
+ logger.atWarning().log("User ref %s not found", refName);
+ return;
+ }
+
+ SymbolicRef s = new SymbolicRef(RefNames.REFS_USERS_SELF, r);
+ advertisedRefs.put(s.getName(), s);
+ logger.atFinest().log("Added %s as alias for user ref %s", RefNames.REFS_USERS_SELF, refName);
+ } catch (IOException e) {
+ throw new ServiceMayNotContinueException(e);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index 4522b0d80f..f2a0ff1a8c 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -18,11 +18,11 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.CaseFormat;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ScheduleConfig.Schedule;
import com.google.gerrit.server.logging.LoggingContext;
diff --git a/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java b/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
index 97beefdeb2..e90f58b203 100644
--- a/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
+++ b/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git.meta;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index 5cda23808f..d588441fc5 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -19,8 +19,11 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.MoreObjects;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.git.GitUpdateFailureException;
import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.git.ObjectIds;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.util.CommitMessageUtil;
@@ -114,7 +117,7 @@ public abstract class VersionedMetaData {
/** @return revision of the metadata that was loaded. */
@Nullable
public ObjectId getRevision() {
- return revision != null ? revision.copy() : null;
+ return ObjectIds.copyOrNull(revision);
}
/**
@@ -487,18 +490,18 @@ public abstract class VersionedMetaData {
case REJECTED_MISSING_OBJECT:
case REJECTED_OTHER_REASON:
default:
- throw new IOException(errorMsg(ru, db.getDirectory()));
+ throw new GitUpdateFailureException(errorMsg(ru, db.getDirectory()), ru);
}
}
-
- private String errorMsg(RefUpdate ru, File location) {
- return String.format(
- "Cannot update %s in %s: %s (%s)",
- ru.getName(), location, ru.getResult(), ru.getRefLogMessage());
- }
};
}
+ private String errorMsg(RefUpdate ru, File location) {
+ return String.format(
+ "Cannot update %s in %s: %s (%s)",
+ ru.getName(), location, ru.getResult(), ru.getRefLogMessage());
+ }
+
protected DirCache readTree(RevTree tree)
throws IOException, MissingObjectException, IncorrectObjectTypeException {
DirCache dc = DirCache.newInCore();
@@ -548,8 +551,13 @@ public abstract class VersionedMetaData {
try (TraceTimer timer =
TraceContext.newTimer(
- "Read file '%s' from ref '%s' of project '%s' from revision '%s'",
- fileName, getRefName(), projectName, revision.name());
+ "Read file",
+ Metadata.builder()
+ .projectName(projectName.get())
+ .noteDbRefName(getRefName())
+ .revision(revision.name())
+ .noteDbFilePath(fileName)
+ .build());
TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree())) {
if (tw != null) {
ObjectLoader obj = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
@@ -624,7 +632,12 @@ public abstract class VersionedMetaData {
protected void saveFile(String fileName, byte[] raw) throws IOException {
try (TraceTimer timer =
TraceContext.newTimer(
- "Save file '%s' in ref '%s' of project '%s'", fileName, getRefName(), projectName)) {
+ "Save file",
+ Metadata.builder()
+ .projectName(projectName.get())
+ .noteDbRefName(getRefName())
+ .noteDbFilePath(fileName)
+ .build())) {
DirCacheEditor editor = newTree.editor();
if (raw != null && 0 < raw.length) {
final ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, raw);
diff --git a/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java b/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
index c092c432d5..13ae54aecb 100644
--- a/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
+++ b/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
@@ -20,7 +20,7 @@ import com.google.gerrit.server.git.HookUtil;
import java.util.Map;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
-import org.eclipse.jgit.transport.BaseReceivePack;
+import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
@@ -34,7 +34,7 @@ class AllRefsWatcher implements AdvertiseRefsHook {
private Map<String, Ref> allRefs;
@Override
- public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException {
+ public void advertiseRefs(ReceivePack rp) throws ServiceMayNotContinueException {
allRefs = HookUtil.ensureAllRefsAdvertised(rp);
}
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 4475465784..c68ed30871 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -21,6 +21,8 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.metrics.Counter0;
@@ -30,19 +32,19 @@ import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.cache.PerThreadCache;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ReceiveCommitsExecutor;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
import com.google.gerrit.server.git.MultiProgressMonitor;
+import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
import com.google.gerrit.server.git.ProjectRunnable;
import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
@@ -63,7 +65,6 @@ import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Named;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -71,8 +72,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.AdvertiseRefsHook;
-import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
@@ -204,11 +203,20 @@ public class AsyncReceiveCommits implements PreReceiveHook {
@Inject
Metrics(MetricMaker metricMaker) {
+ // For the changes metric the push type field is never set to PushType.NORMAL, hence it is not
+ // mentioned in the field description.
changes =
metricMaker.newHistogram(
"receivecommits/changes_per_push",
new Description("number of changes uploaded in a single push.").setCumulative(),
- Field.ofEnum(PushType.class, "type", "type of push (create/replace, autoclose)"));
+ Field.ofEnum(PushType.class, "type", Metadata.Builder::pushType)
+ .description("type of push (create/replace, autoclose)")
+ .build());
+
+ Field<PushType> pushTypeField =
+ Field.ofEnum(PushType.class, "type", Metadata.Builder::pushType)
+ .description("type of push (create/replace, autoclose, normal)")
+ .build();
latencyPerChange =
metricMaker.newTimer(
@@ -218,7 +226,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
+ "(Only includes pushes which contain changes.)")
.setUnit(Units.MILLISECONDS)
.setCumulative(),
- Field.ofEnum(PushType.class, "type", "type of push (create/replace, autoclose)"));
+ pushTypeField);
latencyPerPush =
metricMaker.newTimer(
@@ -226,8 +234,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
new Description("processing delay for a processing single push")
.setUnit(Units.MILLISECONDS)
.setCumulative(),
- Field.ofEnum(
- PushType.class, "type", "type of push (create/replace, autoclose, normal)"));
+ pushTypeField);
timeouts =
metricMaker.newCounter(
@@ -263,6 +270,8 @@ public class AsyncReceiveCommits implements PreReceiveHook {
ContributorAgreementsChecker contributorAgreements,
Metrics metrics,
QuotaBackend quotaBackend,
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
+ AllUsersName allUsersName,
@Named(TIMEOUT_NAME) long timeoutMillis,
@Assisted ProjectState projectState,
@Assisted IdentifiedUser user,
@@ -278,9 +287,12 @@ public class AsyncReceiveCommits implements PreReceiveHook {
this.user = user;
this.repo = repo;
this.metrics = metrics;
-
+ // If the user lacks READ permission, some references may be filtered and hidden from view.
+ // Check objects mentioned inside the incoming pack file are reachable from visible refs.
Project.NameKey projectName = projectState.getNameKey();
- receivePack = new ReceivePack(repo);
+ this.perm = permissionBackend.user(user).project(projectName);
+
+ receivePack = new ReceivePack(PermissionAwareRepositoryManager.wrap(repo, perm));
receivePack.setAllowCreates(true);
receivePack.setAllowDeletes(true);
receivePack.setAllowNonFastForwards(true);
@@ -293,9 +305,6 @@ public class AsyncReceiveCommits implements PreReceiveHook {
receivePack.setPreReceiveHook(this);
receivePack.setPostReceiveHook(lazyPostReceive.create(user, projectName));
- // If the user lacks READ permission, some references may be filtered and hidden from view.
- // Check objects mentioned inside the incoming pack file are reachable from visible refs.
- this.perm = permissionBackend.user(user).project(projectName);
try {
projectState.checkStatePermitsRead();
this.perm.check(ProjectPermission.READ);
@@ -304,19 +313,14 @@ public class AsyncReceiveCommits implements PreReceiveHook {
receiveConfig.checkReferencedObjectsAreReachable);
}
- List<AdvertiseRefsHook> advHooks = new ArrayList<>(4);
allRefsWatcher = new AllRefsWatcher();
- advHooks.add(allRefsWatcher);
- advHooks.add(
- new DefaultAdvertiseRefsHook(perm, RefFilterOptions.builder().setFilterMeta(true).build()));
- advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
- advHooks.add(new HackPushNegotiateHook());
- receivePack.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
-
+ receivePack.setAdvertiseRefsHook(
+ ReceiveCommitsAdvertiseRefsHookChain.create(
+ allRefsWatcher, usersSelfAdvertiseRefsHook, allUsersName, queryProvider, projectName));
resultChangeIds = new ResultChangeIds();
receiveCommits =
factory.create(
- projectState, user, receivePack, allRefsWatcher, messageSender, resultChangeIds);
+ projectState, user, receivePack, repo, allRefsWatcher, messageSender, resultChangeIds);
receiveCommits.init();
QuotaResponse.Aggregated availableTokens =
quotaBackend.user(user).project(projectName).availableTokens(REPOSITORY_SIZE_GROUP);
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index b1bf933374..d89bb6358a 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -7,21 +7,22 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/util/cli",
"//lib:args4j",
"//lib:guava",
+ "//lib:jgit",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
index a2d8e94406..7b5f90bdc0 100644
--- a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
+++ b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
@@ -14,27 +14,29 @@
package com.google.gerrit.server.git.receive;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
-import java.util.List;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -48,12 +50,29 @@ public class BranchCommitValidator {
private final IdentifiedUser user;
private final PermissionBackend.ForProject permissions;
private final Project project;
- private final Branch.NameKey branch;
+ private final BranchNameKey branch;
private final SshInfo sshInfo;
interface Factory {
BranchCommitValidator create(
- ProjectState projectState, Branch.NameKey branch, IdentifiedUser user);
+ ProjectState projectState, BranchNameKey branch, IdentifiedUser user);
+ }
+
+ /** A boolean validation status and a list of additional messages. */
+ @AutoValue
+ abstract static class Result {
+ static Result create(boolean isValid, ImmutableList<CommitValidationMessage> messages) {
+ return new AutoValue_BranchCommitValidator_Result(isValid, messages);
+ }
+
+ /** Whether the commit is valid. */
+ abstract boolean isValid();
+
+ /**
+ * A list of messages related to the validation. Messages may be present regardless of the
+ * {@link #isValid()} status.
+ */
+ abstract ImmutableList<CommitValidationMessage> messages();
}
@Inject
@@ -62,7 +81,7 @@ public class BranchCommitValidator {
PermissionBackend permissionBackend,
SshInfo sshInfo,
@Assisted ProjectState projectState,
- @Assisted Branch.NameKey branch,
+ @Assisted BranchNameKey branch,
@Assisted IdentifiedUser user) {
this.sshInfo = sshInfo;
this.user = user;
@@ -80,17 +99,17 @@ public class BranchCommitValidator {
* @param commit the commit being validated.
* @param isMerged whether this is a merge commit created by magicBranch --merge option
* @param change the change for which this is a new patchset.
+ * @return The validation {@link Result}.
*/
- public boolean validCommit(
+ Result validateCommit(
ObjectReader objectReader,
ReceiveCommand cmd,
RevCommit commit,
boolean isMerged,
- List<ValidationMessage> messages,
NoteMap rejectCommits,
@Nullable Change change)
throws IOException {
- return validCommit(objectReader, cmd, commit, isMerged, messages, rejectCommits, change, false);
+ return validateCommit(objectReader, cmd, commit, isMerged, rejectCommits, change, false);
}
/**
@@ -102,55 +121,63 @@ public class BranchCommitValidator {
* @param isMerged whether this is a merge commit created by magicBranch --merge option
* @param change the change for which this is a new patchset.
* @param skipValidation whether 'skip-validation' was requested.
+ * @return The validation {@link Result}.
*/
- public boolean validCommit(
+ Result validateCommit(
ObjectReader objectReader,
ReceiveCommand cmd,
RevCommit commit,
boolean isMerged,
- List<ValidationMessage> messages,
NoteMap rejectCommits,
@Nullable Change change,
boolean skipValidation)
throws IOException {
- try (CommitReceivedEvent receiveEvent =
- new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) {
- CommitValidators validators;
- if (isMerged) {
- validators =
- commitValidatorsFactory.forMergedCommits(permissions, branch, user.asIdentifiedUser());
- } else {
- validators =
- commitValidatorsFactory.forReceiveCommits(
- permissions,
- branch,
- user.asIdentifiedUser(),
- sshInfo,
- rejectCommits,
- receiveEvent.revWalk,
- change,
- skipValidation);
- }
+ try (TraceTimer traceTimer = TraceContext.newTimer("BranchCommitValidator#validateCommit")) {
+ ImmutableList.Builder<CommitValidationMessage> messages = new ImmutableList.Builder<>();
+ try (CommitReceivedEvent receiveEvent =
+ new CommitReceivedEvent(cmd, project, branch.branch(), objectReader, commit, user)) {
+ CommitValidators validators;
+ if (isMerged) {
+ validators =
+ commitValidatorsFactory.forMergedCommits(
+ permissions, branch, user.asIdentifiedUser());
+ } else {
+ validators =
+ commitValidatorsFactory.forReceiveCommits(
+ permissions,
+ branch,
+ user.asIdentifiedUser(),
+ sshInfo,
+ rejectCommits,
+ receiveEvent.revWalk,
+ change,
+ skipValidation);
+ }
- for (CommitValidationMessage m : validators.validate(receiveEvent)) {
- messages.add(
- new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.getType()));
+ for (CommitValidationMessage m : validators.validate(receiveEvent)) {
+ messages.add(
+ new CommitValidationMessage(
+ messageForCommit(commit, m.getMessage(), objectReader), m.getType()));
+ }
+ } catch (CommitValidationException e) {
+ logger.atFine().log("Commit validation failed on %s", commit.name());
+ for (CommitValidationMessage m : e.getMessages()) {
+ // The non-error messages may contain background explanation for the
+ // fatal error, so have to preserve all messages.
+ messages.add(
+ new CommitValidationMessage(
+ messageForCommit(commit, m.getMessage(), objectReader), m.getType()));
+ }
+ cmd.setResult(
+ REJECTED_OTHER_REASON, messageForCommit(commit, e.getMessage(), objectReader));
+ return Result.create(false, messages.build());
}
- } catch (CommitValidationException e) {
- logger.atFine().log("Commit validation failed on %s", commit.name());
- for (CommitValidationMessage m : e.getMessages()) {
- // The non-error messages may contain background explanation for the
- // fatal error, so have to preserve all messages.
- messages.add(
- new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.getType()));
- }
- cmd.setResult(REJECTED_OTHER_REASON, messageForCommit(commit, e.getMessage()));
- return false;
+ return Result.create(true, messages.build());
}
- return true;
}
- private String messageForCommit(RevCommit c, String msg) {
- return String.format("commit %s: %s", c.abbreviate(RevId.ABBREV_LEN).name(), msg);
+ private String messageForCommit(RevCommit c, String msg, ObjectReader objectReader)
+ throws IOException {
+ return String.format("commit %s: %s", abbreviateName(c, objectReader), msg);
}
}
diff --git a/java/com/google/gerrit/server/git/receive/HackPushNegotiateHook.java b/java/com/google/gerrit/server/git/receive/HackPushNegotiateHook.java
index 36d0eb7add..72483af881 100644
--- a/java/com/google/gerrit/server/git/receive/HackPushNegotiateHook.java
+++ b/java/com/google/gerrit/server/git/receive/HackPushNegotiateHook.java
@@ -18,18 +18,17 @@ import static java.util.stream.Collectors.toMap;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.git.ObjectIds;
import java.io.IOException;
import java.util.Collection;
-import java.util.Collections;
import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
-import org.eclipse.jgit.transport.BaseReceivePack;
+import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
@@ -49,7 +48,7 @@ public class HackPushNegotiateHook implements AdvertiseRefsHook {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Size of an additional ".have" line. */
- private static final int HAVE_LINE_LEN = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;
+ private static final int HAVE_LINE_LEN = 4 + ObjectIds.STR_LEN + 1 + 5 + 1;
/**
* Maximum number of bytes to "waste" in the advertisement with a peek at this repository's
@@ -73,9 +72,8 @@ public class HackPushNegotiateHook implements AdvertiseRefsHook {
throw new UnsupportedOperationException("HackPushNegotiateHook cannot be used for UploadPack");
}
- @SuppressWarnings("deprecation")
@Override
- public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException {
+ public void advertiseRefs(ReceivePack rp) throws ServiceMayNotContinueException {
Map<String, Ref> r = rp.getAdvertisedRefs();
if (r == null) {
try {
@@ -91,38 +89,32 @@ public class HackPushNegotiateHook implements AdvertiseRefsHook {
rp.setAdvertisedRefs(r, history(r.values(), rp));
}
- private Set<ObjectId> history(Collection<Ref> refs, BaseReceivePack rp) {
+ private Set<ObjectId> history(Collection<Ref> refs, ReceivePack rp) {
Set<ObjectId> alreadySending = rp.getAdvertisedObjects();
- if (alreadySending.isEmpty()) {
- alreadySending = idsOf(refs);
- }
-
- int max = MAX_HISTORY - Math.max(0, alreadySending.size() - refs.size());
- if (max <= 0) {
- return Collections.emptySet();
+ if (MAX_HISTORY <= alreadySending.size()) {
+ return alreadySending;
}
// Scan history until the advertisement is full.
- @SuppressWarnings("deprecation")
RevWalk rw = rp.getRevWalk();
rw.reset();
try {
- for (Ref ref : refs) {
+ Set<ObjectId> tips = idsOf(refs);
+ for (ObjectId tip : tips) {
try {
- if (ref.getObjectId() != null) {
- rw.markStart(rw.parseCommit(ref.getObjectId()));
- }
+ rw.markStart(rw.parseCommit(tip));
} catch (IOException badCommit) {
continue;
}
}
- Set<ObjectId> history = Sets.newHashSetWithExpectedSize(max);
+ Set<ObjectId> history = Sets.newHashSetWithExpectedSize(MAX_HISTORY);
+ history.addAll(alreadySending);
try {
int stepCnt = 0;
- for (RevCommit c; history.size() < max && (c = rw.next()) != null; ) {
+ for (RevCommit c; history.size() < MAX_HISTORY && (c = rw.next()) != null; ) {
if (c.getParentCount() <= 1
- && !alreadySending.contains(c)
+ && !tips.contains(c)
&& (history.size() < BASE_COMMITS || (++stepCnt % STEP_COMMITS) == 0)) {
history.add(c);
}
diff --git a/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java b/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
index 17d4a38173..a19dbac486 100644
--- a/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
+++ b/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.git.receive;
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.quota.QuotaBackend;
@@ -61,7 +61,7 @@ public class LazyPostReceiveHookChain implements PostReceiveHook {
@Override
public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
hooks.runEach(h -> h.onPostReceive(rp, commands));
- if (affectsSize(rp, commands)) {
+ if (affectsSize(rp)) {
QuotaResponse.Aggregated a =
quotaBackend
.user(user)
@@ -78,21 +78,7 @@ public class LazyPostReceiveHookChain implements PostReceiveHook {
}
}
- public static boolean affectsSize(ReceivePack rp, Collection<ReceiveCommand> commands) {
- long packSize;
- try {
- packSize = rp.getPackSize();
- } catch (IllegalStateException e) {
- // No pack was received, i.e. ref deletion or wind back
- return false;
- }
- if (packSize > 0L) {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getType() != ReceiveCommand.Type.DELETE) {
- return true;
- }
- }
- }
- return false;
+ public static boolean affectsSize(ReceivePack rp) {
+ return rp.hasReceivedPack() && rp.getPackSize() > 0L;
}
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 2de5dbc5d6..012ba437fb 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -15,17 +15,17 @@
package com.google.gerrit.server.git.receive;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.flogger.LazyArgs.lazy;
-import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.change.HashtagsUtil.cleanupHashtag;
import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
import static com.google.gerrit.server.git.receive.ReceiveConstants.COMMAND_REJECTION_MESSAGE_FOOTER;
-import static com.google.gerrit.server.git.receive.ReceiveConstants.ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP;
+import static com.google.gerrit.server.git.receive.ReceiveConstants.ONLY_USERS_WITH_TOGGLE_WIP_STATE_PERM_CAN_MODIFY_WIP;
import static com.google.gerrit.server.git.receive.ReceiveConstants.PUSH_OPTION_SKIP_VALIDATION;
import static com.google.gerrit.server.git.receive.ReceiveConstants.SAME_CHANGE_ID_IN_MULTIPLE_CHANGES;
import static com.google.gerrit.server.git.validators.CommitValidators.NEW_PATCHSET_PATTERN;
@@ -40,7 +40,6 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
-import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
@@ -48,6 +47,7 @@ import com.google.common.base.Throwables;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
@@ -62,37 +62,48 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.NotifyInfo;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CreateGroupPermissionSyncer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.PublishCommentUtil;
+import com.google.gerrit.server.RequestInfo;
+import com.google.gerrit.server.RequestListener;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.NotifyResolver;
@@ -118,8 +129,12 @@ import com.google.gerrit.server.git.validators.RefOperationValidationException;
import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogContext;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.Sequences;
@@ -180,11 +195,12 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
-import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -223,9 +239,6 @@ import org.kohsuke.args4j.Option;
class ReceiveCommits {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private static final String CODE_REVIEW_ERROR =
- "You need 'Push' rights to upload code review requests.\n"
- + "Verify that you are pushing to the right branch.";
private static final String CANNOT_DELETE_CHANGES = "Cannot delete from '" + REFS_CHANGES + "'";
private static final String CANNOT_DELETE_CONFIG =
"Cannot delete project configuration from '" + RefNames.REFS_CONFIG + "'";
@@ -236,6 +249,7 @@ class ReceiveCommits {
ProjectState projectState,
IdentifiedUser user,
ReceivePack receivePack,
+ Repository repository,
AllRefsWatcher allRefsWatcher,
MessageSender messageSender,
ResultChangeIds resultChangeIds);
@@ -276,16 +290,14 @@ class ReceiveCommits {
}
}
- private static final Function<Exception, RestApiException> INSERT_EXCEPTION =
- input -> {
- if (input instanceof RestApiException) {
- return (RestApiException) input;
- } else if ((input instanceof ExecutionException)
- && (input.getCause() instanceof RestApiException)) {
- return (RestApiException) input.getCause();
- }
- return new RestApiException("Error inserting change/patchset", input);
- };
+ private static RestApiException asRestApiException(Exception e) {
+ if (e instanceof RestApiException) {
+ return (RestApiException) e;
+ } else if ((e instanceof ExecutionException) && (e.getCause() instanceof RestApiException)) {
+ return (RestApiException) e.getCause();
+ }
+ return new RestApiException("Error inserting change/patchset", e);
+ }
// ReceiveCommits has a lot of fields, sorry. Here and in the constructor they are split up
// somewhat, and kept sorted lexicographically within sections, except where later assignments
@@ -301,7 +313,10 @@ class ReceiveCommits {
private final ChangeNotes.Factory notesFactory;
private final ChangeReportFormatter changeFormatter;
private final CmdLineParser.Factory optionParserFactory;
+ private final CommentsUtil commentsUtil;
+ private final PluginSetContext<CommentValidator> commentValidators;
private final BranchCommitValidator.Factory commitValidatorFactory;
+ private final Config config;
private final CreateGroupPermissionSyncer createGroupPermissionSyncer;
private final CreateRefControl createRefControl;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
@@ -309,6 +324,7 @@ class ReceiveCommits {
private final MergedByPushOp.Factory mergedByPushOpFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
+ private final DynamicSet<PerformanceLogger> performanceLoggers;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
private final Provider<InternalChangeQuery> queryProvider;
@@ -317,6 +333,7 @@ class ReceiveCommits {
private final ReceiveConfig receiveConfig;
private final RefOperationValidators.Factory refValidatorsFactory;
private final ReplaceOp.Factory replaceOpFactory;
+ private final PluginSetContext<RequestListener> requestListeners;
private final RetryHelper retryHelper;
private final RequestScopePropagator requestScopePropagator;
private final Sequences seq;
@@ -334,7 +351,6 @@ class ReceiveCommits {
// Immutable fields derived from constructor arguments.
private final boolean allowProjectOwnersToChangeParent;
- private final boolean allowPushToRefsChanges;
private final LabelTypes labelTypes;
private final NoteMap rejectCommits;
private final PermissionBackend.ForProject permissions;
@@ -343,7 +359,7 @@ class ReceiveCommits {
// Collections populated during processing.
private final List<UpdateGroupsRequest> updateGroups;
- private final List<ValidationMessage> messages;
+ private final Queue<ValidationMessage> messages;
/** Multimap of error text to refnames that produced that error. */
private final ListMultimap<String, String> errors;
@@ -363,6 +379,7 @@ class ReceiveCommits {
private MessageSender messageSender;
private ResultChangeIds resultChangeIds;
+ private ImmutableMap<String, String> loggingTags;
@Inject
ReceiveCommits(
@@ -370,21 +387,24 @@ class ReceiveCommits {
AllProjectsName allProjectsName,
BatchUpdate.Factory batchUpdateFactory,
ProjectConfig.Factory projectConfigFactory,
- @GerritServerConfig Config cfg,
+ @GerritServerConfig Config config,
ChangeEditUtil editUtil,
ChangeIndexer indexer,
ChangeInserter.Factory changeInserterFactory,
ChangeNotes.Factory notesFactory,
DynamicItem<ChangeReportFormatter> changeFormatterProvider,
CmdLineParser.Factory optionParserFactory,
+ CommentsUtil commentsUtil,
BranchCommitValidator.Factory commitValidatorFactory,
CreateGroupPermissionSyncer createGroupPermissionSyncer,
CreateRefControl createRefControl,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginSetContext<ReceivePackInitializer> initializers,
+ PluginSetContext<CommentValidator> commentValidators,
MergedByPushOp.Factory mergedByPushOpFactory,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
+ DynamicSet<PerformanceLogger> performanceLoggers,
PermissionBackend permissionBackend,
ProjectCache projectCache,
Provider<InternalChangeQuery> queryProvider,
@@ -393,6 +413,7 @@ class ReceiveCommits {
ReceiveConfig receiveConfig,
RefOperationValidators.Factory refValidatorsFactory,
ReplaceOp.Factory replaceOpFactory,
+ PluginSetContext<RequestListener> requestListeners,
RetryHelper retryHelper,
RequestScopePropagator requestScopePropagator,
Sequences seq,
@@ -403,6 +424,7 @@ class ReceiveCommits {
@Assisted ProjectState projectState,
@Assisted IdentifiedUser user,
@Assisted ReceivePack rp,
+ @Assisted Repository repository,
@Assisted AllRefsWatcher allRefsWatcher,
@Nullable @Assisted MessageSender messageSender,
@Assisted ResultChangeIds resultChangeIds)
@@ -413,7 +435,10 @@ class ReceiveCommits {
this.batchUpdateFactory = batchUpdateFactory;
this.changeFormatter = changeFormatterProvider.get();
this.changeInserterFactory = changeInserterFactory;
+ this.commentsUtil = commentsUtil;
+ this.commentValidators = commentValidators;
this.commitValidatorFactory = commitValidatorFactory;
+ this.config = config;
this.createRefControl = createRefControl;
this.createGroupPermissionSyncer = createGroupPermissionSyncer;
this.editUtil = editUtil;
@@ -430,10 +455,12 @@ class ReceiveCommits {
this.pluginConfigEntries = pluginConfigEntries;
this.projectCache = projectCache;
this.psUtil = psUtil;
+ this.performanceLoggers = performanceLoggers;
this.queryProvider = queryProvider;
this.receiveConfig = receiveConfig;
this.refValidatorsFactory = refValidatorsFactory;
this.replaceOpFactory = replaceOpFactory;
+ this.requestListeners = requestListeners;
this.retryHelper = retryHelper;
this.requestScopePropagator = requestScopePropagator;
this.seq = seq;
@@ -447,24 +474,25 @@ class ReceiveCommits {
this.projectState = projectState;
this.user = user;
this.receivePack = rp;
+ // This repository instance in unwrapped, while the repository instance in
+ // receivePack.getRepo() is wrapped in PermissionAwareRepository instance.
+ this.repo = repository;
// Immutable fields derived from constructor arguments.
- allowPushToRefsChanges = cfg.getBoolean("receive", "allowPushToRefsChanges", false);
- repo = rp.getRepository();
project = projectState.getProject();
labelTypes = projectState.getLabelTypes();
permissions = permissionBackend.user(user).project(project.getNameKey());
- rejectCommits = BanCommit.loadRejectCommitsMap(rp.getRepository(), rp.getRevWalk());
+ rejectCommits = BanCommit.loadRejectCommitsMap(repo, rp.getRevWalk());
// Collections populated during processing.
errors = MultimapBuilder.linkedHashKeys().arrayListValues().build();
- messages = new ArrayList<>();
+ messages = new ConcurrentLinkedQueue<>();
pushOptions = LinkedListMultimap.create();
replaceByChange = new LinkedHashMap<>();
updateGroups = new ArrayList<>();
this.allowProjectOwnersToChangeParent =
- cfg.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
+ config.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
// Other settings populated during processing.
newChangeForAllNotInTarget =
@@ -473,6 +501,7 @@ class ReceiveCommits {
// Handles for outputting back over the wire to the end user.
this.messageSender = messageSender != null ? messageSender : new ReceivePackMessageSender();
this.resultChangeIds = resultChangeIds;
+ this.loggingTags = ImmutableMap.of();
}
void init() {
@@ -499,125 +528,148 @@ class ReceiveCommits {
addMessage(error, ValidationMessage.Type.ERROR);
}
+ /**
+ * Sends all messages which have been collected while processing the push to the client.
+ *
+ * <p><strong>Attention:</strong>{@link AsyncReceiveCommits} may call this method while {@link
+ * #processCommands(Collection, MultiProgressMonitor)} is still running (if the execution of
+ * processCommands takes too long and AsyncReceiveCommits gets a timeout). This means that local
+ * variables that are accessed in this method must be thread-safe (otherwise we may hit a {@link
+ * java.util.ConcurrentModificationException} if we read a variable here that at the same time is
+ * updated by the background thread that still executes processCommands).
+ */
void sendMessages() {
- for (ValidationMessage m : messages) {
- String msg = m.getType().getPrefix() + m.getMessage();
+ try (TraceContext traceContext =
+ TraceContext.newTrace(
+ loggingTags.containsKey(RequestId.Type.TRACE_ID.name()),
+ loggingTags.get(RequestId.Type.TRACE_ID.name()),
+ (tagName, traceId) -> {})) {
+ loggingTags.forEach((tagName, tagValue) -> traceContext.addTag(tagName, tagValue));
+
+ for (ValidationMessage m : messages) {
+ String msg = m.getType().getPrefix() + m.getMessage();
+ logger.atFine().log("Sending message: %s", msg);
- // Avoid calling sendError which will add its own error: prefix.
- messageSender.sendMessage(msg);
+ // Avoid calling sendError which will add its own error: prefix.
+ messageSender.sendMessage(msg);
+ }
}
}
void processCommands(Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
- Task commandProgress = progress.beginSubTask("refs", UNKNOWN);
- commands = commands.stream().map(c -> wrapReceiveCommand(c, commandProgress)).collect(toList());
- processCommandsUnsafe(commands, progress);
- rejectRemaining(commands, INTERNAL_SERVER_ERROR);
-
- // This sends error messages before the 'done' string of the progress monitor is sent.
- // Currently, the test framework relies on this ordering to understand if pushes completed
- // successfully.
- sendErrorMessages();
-
- commandProgress.end();
- progress.end();
- }
-
- // Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
- private void processCommandsUnsafe(
- Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
parsePushOptions();
+ int commandCount = commands.size();
try (TraceContext traceContext =
- TraceContext.newTrace(
- tracePushOption.isPresent(),
- tracePushOption.orElse(null),
- (tagName, traceId) -> addMessage(tagName + ": " + traceId))) {
+ TraceContext.newTrace(
+ tracePushOption.isPresent(),
+ tracePushOption.orElse(null),
+ (tagName, traceId) -> addMessage(tagName + ": " + traceId));
+ TraceTimer traceTimer =
+ newTimer("processCommands", Metadata.builder().resourceCount(commandCount));
+ PerformanceLogContext performanceLogContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ RequestInfo requestInfo =
+ RequestInfo.builder(RequestInfo.RequestType.GIT_RECEIVE, user, traceContext)
+ .project(project.getNameKey())
+ .build();
+ requestListeners.runEach(l -> l.onRequest(requestInfo));
traceContext.addTag(RequestId.Type.RECEIVE_ID, new RequestId(project.getNameKey().get()));
- logger.atFinest().log("Calling user: %s", user.getLoggableName());
-
// Log the push options here, rather than in parsePushOptions(), so that they are included
// into the trace if tracing is enabled.
logger.atFine().log("push options: %s", receivePack.getPushOptions());
- if (!projectState.getProject().getState().permitsWrite()) {
- for (ReceiveCommand cmd : commands) {
- reject(cmd, "prohibited by Gerrit: project state does not permit write");
- }
- return;
- }
+ Task commandProgress = progress.beginSubTask("refs", UNKNOWN);
+ commands =
+ commands.stream().map(c -> wrapReceiveCommand(c, commandProgress)).collect(toList());
+ processCommandsUnsafe(commands, progress);
+ rejectRemaining(commands, INTERNAL_SERVER_ERROR);
+
+ // This sends error messages before the 'done' string of the progress monitor is sent.
+ // Currently, the test framework relies on this ordering to understand if pushes completed
+ // successfully.
+ sendErrorMessages();
- logger.atFine().log("Parsing %d commands", commands.size());
+ commandProgress.end();
+ progress.end();
+ loggingTags = traceContext.getTags();
+ logger.atFine().log("Processing commands done.");
+ }
+ }
- List<ReceiveCommand> magicCommands = new ArrayList<>();
- List<ReceiveCommand> directPatchSetPushCommands = new ArrayList<>();
- List<ReceiveCommand> regularCommands = new ArrayList<>();
+ // Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
+ private void processCommandsUnsafe(
+ Collection<ReceiveCommand> commands, MultiProgressMonitor progress) {
+ logger.atFine().log("Calling user: %s", user.getLoggableName());
+ logger.atFine().log("Groups: %s", user.getEffectiveGroups().getKnownGroups());
+ if (!projectState.getProject().getState().permitsWrite()) {
for (ReceiveCommand cmd : commands) {
- if (MagicBranch.isMagicBranch(cmd.getRefName())) {
- magicCommands.add(cmd);
- } else if (isDirectChangesPush(cmd.getRefName())) {
- directPatchSetPushCommands.add(cmd);
- } else {
- regularCommands.add(cmd);
- }
+ reject(cmd, "prohibited by Gerrit: project state does not permit write");
}
+ return;
+ }
- int commandTypes =
- (magicCommands.isEmpty() ? 0 : 1)
- + (directPatchSetPushCommands.isEmpty() ? 0 : 1)
- + (regularCommands.isEmpty() ? 0 : 1);
+ logger.atFine().log("Parsing %d commands", commands.size());
- if (commandTypes > 1) {
- rejectRemaining(commands, "cannot combine normal pushes and magic pushes");
- return;
+ List<ReceiveCommand> magicCommands = new ArrayList<>();
+ List<ReceiveCommand> regularCommands = new ArrayList<>();
+
+ for (ReceiveCommand cmd : commands) {
+ if (MagicBranch.isMagicBranch(cmd.getRefName())) {
+ magicCommands.add(cmd);
+ } else {
+ regularCommands.add(cmd);
}
+ }
- try {
- if (!regularCommands.isEmpty()) {
- handleRegularCommands(regularCommands, progress);
- return;
- }
+ int commandTypes = (magicCommands.isEmpty() ? 0 : 1) + (regularCommands.isEmpty() ? 0 : 1);
- for (ReceiveCommand cmd : directPatchSetPushCommands) {
- parseDirectChangesPush(cmd);
- }
+ if (commandTypes > 1) {
+ rejectRemaining(commands, "cannot combine normal pushes and magic pushes");
+ return;
+ }
- boolean first = true;
- for (ReceiveCommand cmd : magicCommands) {
- if (first) {
- parseMagicBranch(cmd);
- first = false;
- } else {
- reject(cmd, "duplicate request");
- }
- }
- } catch (PermissionBackendException | NoSuchProjectException | IOException err) {
- logger.atSevere().withCause(err).log("Failed to process refs in %s", project.getName());
+ try {
+ if (!regularCommands.isEmpty()) {
+ handleRegularCommands(regularCommands, progress);
return;
}
- Task newProgress = progress.beginSubTask("new", UNKNOWN);
- Task replaceProgress = progress.beginSubTask("updated", UNKNOWN);
-
- List<CreateRequest> newChanges = Collections.emptyList();
- if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
- newChanges = selectNewAndReplacedChangesFromMagicBranch(newProgress);
+ boolean first = true;
+ for (ReceiveCommand cmd : magicCommands) {
+ if (first) {
+ parseMagicBranch(cmd);
+ first = false;
+ } else {
+ reject(cmd, "duplicate request");
+ }
}
+ } catch (PermissionBackendException | NoSuchProjectException | IOException err) {
+ logger.atSevere().withCause(err).log("Failed to process refs in %s", project.getName());
+ return;
+ }
- // Commit validation has already happened, so any changes without Change-Id are for the
- // deprecated feature.
- warnAboutMissingChangeId(newChanges);
- preparePatchSetsForReplace(newChanges);
- insertChangesAndPatchSets(newChanges, replaceProgress);
- newProgress.end();
- replaceProgress.end();
- queueSuccessMessages(newChanges);
+ Task newProgress = progress.beginSubTask("new", UNKNOWN);
+ Task replaceProgress = progress.beginSubTask("updated", UNKNOWN);
- logger.atFine().log(
- "Command results: %s",
- lazy(() -> commands.stream().map(ReceiveCommits::commandToString).collect(joining(","))));
+ List<CreateRequest> newChanges = Collections.emptyList();
+ if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
+ newChanges = selectNewAndReplacedChangesFromMagicBranch(newProgress);
}
+
+ // Commit validation has already happened, so any changes without Change-Id are for the
+ // deprecated feature.
+ warnAboutMissingChangeId(newChanges);
+ preparePatchSetsForReplace(newChanges);
+ insertChangesAndPatchSets(newChanges, replaceProgress);
+ newProgress.end();
+ replaceProgress.end();
+ queueSuccessMessages(newChanges);
+
+ logger.atFine().log(
+ "Command results: %s",
+ lazy(() -> commands.stream().map(ReceiveCommits::commandToString).collect(joining(","))));
}
private void sendErrorMessages() {
@@ -633,67 +685,70 @@ class ReceiveCommits {
private void handleRegularCommands(List<ReceiveCommand> cmds, MultiProgressMonitor progress)
throws PermissionBackendException, IOException, NoSuchProjectException {
- resultChangeIds.setMagicPush(false);
- for (ReceiveCommand cmd : cmds) {
- parseRegularCommand(cmd);
- }
-
- try (BatchUpdate bu =
- batchUpdateFactory.create(
- project.getNameKey(), user.materializedCopy(), TimeUtil.nowTs());
- ObjectInserter ins = repo.newObjectInserter();
- ObjectReader reader = ins.newReader();
- RevWalk rw = new RevWalk(reader)) {
- bu.setRepository(repo, rw, ins);
- bu.setRefLogMessage("push");
-
- int added = 0;
+ try (TraceTimer traceTimer =
+ newTimer("handleRegularCommands", Metadata.builder().resourceCount(cmds.size()))) {
+ resultChangeIds.setMagicPush(false);
for (ReceiveCommand cmd : cmds) {
- if (cmd.getResult() == NOT_ATTEMPTED) {
- bu.addRepoOnlyOp(new UpdateOneRefOp(cmd));
- added++;
+ parseRegularCommand(cmd);
+ }
+
+ try (BatchUpdate bu =
+ batchUpdateFactory.create(
+ project.getNameKey(), user.materializedCopy(), TimeUtil.nowTs());
+ ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo, rw, ins);
+ bu.setRefLogMessage("push");
+
+ int added = 0;
+ for (ReceiveCommand cmd : cmds) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ bu.addRepoOnlyOp(new UpdateOneRefOp(cmd));
+ added++;
+ }
}
+ logger.atFine().log("Added %d additional ref updates", added);
+ bu.execute();
+ } catch (UpdateException | RestApiException e) {
+ rejectRemaining(cmds, INTERNAL_SERVER_ERROR);
+ logger.atSevere().withCause(e).log("update failed:");
}
- logger.atFine().log("Added %d additional ref updates", added);
- bu.execute();
- } catch (UpdateException | RestApiException e) {
- rejectRemaining(cmds, INTERNAL_SERVER_ERROR);
- logger.atSevere().withCause(e).log("update failed:");
- }
- Set<Branch.NameKey> branches = new HashSet<>();
- for (ReceiveCommand c : cmds) {
- // Most post-update steps should happen in UpdateOneRefOp#postUpdate. The only steps that
- // should happen in this loop are things that can't happen within one BatchUpdate because
- // they involve kicking off an additional BatchUpdate.
- if (c.getResult() != OK) {
- continue;
- }
- if (isHead(c) || isConfig(c)) {
- switch (c.getType()) {
- case CREATE:
- case UPDATE:
- case UPDATE_NONFASTFORWARD:
- Task closeProgress = progress.beginSubTask("closed", UNKNOWN);
- autoCloseChanges(c, closeProgress);
- closeProgress.end();
- branches.add(new Branch.NameKey(project.getNameKey(), c.getRefName()));
- break;
+ Set<BranchNameKey> branches = new HashSet<>();
+ for (ReceiveCommand c : cmds) {
+ // Most post-update steps should happen in UpdateOneRefOp#postUpdate. The only steps that
+ // should happen in this loop are things that can't happen within one BatchUpdate because
+ // they involve kicking off an additional BatchUpdate.
+ if (c.getResult() != OK) {
+ continue;
+ }
+ if (isHead(c) || isConfig(c)) {
+ switch (c.getType()) {
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ Task closeProgress = progress.beginSubTask("closed", UNKNOWN);
+ autoCloseChanges(c, closeProgress);
+ closeProgress.end();
+ branches.add(BranchNameKey.create(project.getNameKey(), c.getRefName()));
+ break;
- case DELETE:
- break;
+ case DELETE:
+ break;
+ }
}
}
- }
- // Update superproject gitlinks if required.
- if (!branches.isEmpty()) {
- try (MergeOpRepoManager orm = ormProvider.get()) {
- orm.setContext(TimeUtil.nowTs(), user, NotifyResolver.Result.none());
- SubmoduleOp op = subOpFactory.create(branches, orm);
- op.updateSuperProjects();
- } catch (SubmoduleException e) {
- logger.atSevere().withCause(e).log("Can't update the superprojects");
+ // Update superproject gitlinks if required.
+ if (!branches.isEmpty()) {
+ try (MergeOpRepoManager orm = ormProvider.get()) {
+ orm.setContext(TimeUtil.nowTs(), user, NotifyResolver.Result.none());
+ SubmoduleOp op = subOpFactory.create(branches, orm);
+ op.updateSuperProjects();
+ } catch (SubmoduleException e) {
+ logger.atSevere().withCause(e).log("Can't update the superprojects");
+ }
}
}
}
@@ -755,7 +810,7 @@ class ReceiveCommits {
Boolean isPrivate = null;
Boolean wip = null;
if (!updated.isEmpty()) {
- edit = magicBranch != null && (magicBranch.edit || magicBranch.draft);
+ edit = magicBranch != null && magicBranch.edit;
if (magicBranch != null) {
if (magicBranch.isPrivate) {
isPrivate = true;
@@ -814,97 +869,102 @@ class ReceiveCommits {
}
private void insertChangesAndPatchSets(List<CreateRequest> newChanges, Task replaceProgress) {
- ReceiveCommand magicBranchCmd = magicBranch != null ? magicBranch.cmd : null;
- if (magicBranchCmd != null && magicBranchCmd.getResult() != NOT_ATTEMPTED) {
- logger.atWarning().log(
- "Skipping change updates on %s because ref update failed: %s %s",
- project.getName(),
- magicBranchCmd.getResult(),
- Strings.nullToEmpty(magicBranchCmd.getMessage()));
- return;
- }
-
- try (BatchUpdate bu =
- batchUpdateFactory.create(
- project.getNameKey(), user.materializedCopy(), TimeUtil.nowTs());
- ObjectInserter ins = repo.newObjectInserter();
- ObjectReader reader = ins.newReader();
- RevWalk rw = new RevWalk(reader)) {
- bu.setRepository(repo, rw, ins);
- bu.setRefLogMessage("push");
- if (magicBranch != null) {
- bu.setNotify(magicBranch.getNotifyForNewChange());
+ try (TraceTimer traceTimer =
+ newTimer(
+ "insertChangesAndPatchSets", Metadata.builder().resourceCount(newChanges.size()))) {
+ ReceiveCommand magicBranchCmd = magicBranch != null ? magicBranch.cmd : null;
+ if (magicBranchCmd != null && magicBranchCmd.getResult() != NOT_ATTEMPTED) {
+ logger.atWarning().log(
+ "Skipping change updates on %s because ref update failed: %s %s",
+ project.getName(),
+ magicBranchCmd.getResult(),
+ Strings.nullToEmpty(magicBranchCmd.getMessage()));
+ return;
}
- logger.atFine().log("Adding %d replace requests", newChanges.size());
- for (ReplaceRequest replace : replaceByChange.values()) {
+ try (BatchUpdate bu =
+ batchUpdateFactory.create(
+ project.getNameKey(), user.materializedCopy(), TimeUtil.nowTs());
+ ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo, rw, ins);
+ bu.setRefLogMessage("push");
if (magicBranch != null) {
- bu.setNotifyHandling(replace.ontoChange, magicBranch.getNotifyHandling(replace.notes));
+ bu.setNotify(magicBranch.getNotifyForNewChange());
}
- replace.addOps(bu, replaceProgress);
- }
- logger.atFine().log("Adding %d create requests", newChanges.size());
- for (CreateRequest create : newChanges) {
- create.addOps(bu);
- }
+ logger.atFine().log("Adding %d replace requests", newChanges.size());
+ for (ReplaceRequest replace : replaceByChange.values()) {
+ if (magicBranch != null) {
+ bu.setNotifyHandling(replace.ontoChange, magicBranch.getNotifyHandling(replace.notes));
+ }
+ replace.addOps(bu, replaceProgress);
+ }
- logger.atFine().log("Adding %d group update requests", newChanges.size());
- updateGroups.forEach(r -> r.addOps(bu));
+ logger.atFine().log("Adding %d create requests", newChanges.size());
+ for (CreateRequest create : newChanges) {
+ create.addOps(bu);
+ }
- logger.atFine().log("Executing batch");
- try {
- bu.execute();
- } catch (UpdateException e) {
- throw INSERT_EXCEPTION.apply(e);
- }
+ logger.atFine().log("Adding %d group update requests", newChanges.size());
+ updateGroups.forEach(r -> r.addOps(bu));
- replaceByChange.values().stream()
- .forEach(req -> resultChangeIds.add(ResultChangeIds.Key.REPLACED, req.ontoChange));
- newChanges.stream()
- .forEach(req -> resultChangeIds.add(ResultChangeIds.Key.CREATED, req.changeId));
+ logger.atFine().log("Executing batch");
+ try {
+ bu.execute();
+ } catch (UpdateException e) {
+ throw asRestApiException(e);
+ }
- if (magicBranchCmd != null) {
- magicBranchCmd.setResult(OK);
- }
- for (ReplaceRequest replace : replaceByChange.values()) {
- String rejectMessage = replace.getRejectMessage();
- if (rejectMessage == null) {
- if (replace.inputCommand.getResult() == NOT_ATTEMPTED) {
- // Not necessarily the magic branch, so need to set OK on the original value.
- replace.inputCommand.setResult(OK);
+ replaceByChange.values().stream()
+ .forEach(req -> resultChangeIds.add(ResultChangeIds.Key.REPLACED, req.ontoChange));
+ newChanges.stream()
+ .forEach(req -> resultChangeIds.add(ResultChangeIds.Key.CREATED, req.changeId));
+
+ if (magicBranchCmd != null) {
+ magicBranchCmd.setResult(OK);
+ }
+ for (ReplaceRequest replace : replaceByChange.values()) {
+ String rejectMessage = replace.getRejectMessage();
+ if (rejectMessage == null) {
+ if (replace.inputCommand.getResult() == NOT_ATTEMPTED) {
+ // Not necessarily the magic branch, so need to set OK on the original value.
+ replace.inputCommand.setResult(OK);
+ }
+ } else {
+ logger.atFine().log("Rejecting due to message from ReplaceOp");
+ reject(replace.inputCommand, rejectMessage);
}
- } else {
- logger.atFine().log("Rejecting due to message from ReplaceOp");
- reject(replace.inputCommand, rejectMessage);
}
- }
-
- } catch (ResourceConflictException e) {
- addError(e.getMessage());
- reject(magicBranchCmd, "conflict");
- } catch (BadRequestException | UnprocessableEntityException | AuthException e) {
- logger.atFine().withCause(e).log("Rejecting due to client error");
- reject(magicBranchCmd, e.getMessage());
- } catch (RestApiException | IOException e) {
- logger.atSevere().withCause(e).log("Can't insert change/patch set for %s", project.getName());
- reject(magicBranchCmd, String.format("%s: %s", INTERNAL_SERVER_ERROR, e.getMessage()));
- }
- if (magicBranch != null && magicBranch.submit) {
- try {
- submit(newChanges, replaceByChange.values());
} catch (ResourceConflictException e) {
addError(e.getMessage());
reject(magicBranchCmd, "conflict");
- } catch (RestApiException
- | StorageException
- | UpdateException
- | IOException
- | ConfigInvalidException
- | PermissionBackendException e) {
- logger.atSevere().withCause(e).log("Error submitting changes to %s", project.getName());
- reject(magicBranchCmd, "error during submit");
+ } catch (BadRequestException | UnprocessableEntityException | AuthException e) {
+ logger.atFine().withCause(e).log("Rejecting due to client error");
+ reject(magicBranchCmd, e.getMessage());
+ } catch (RestApiException | IOException e) {
+ logger.atSevere().withCause(e).log(
+ "Can't insert change/patch set for %s", project.getName());
+ reject(magicBranchCmd, String.format("%s: %s", INTERNAL_SERVER_ERROR, e.getMessage()));
+ }
+
+ if (magicBranch != null && magicBranch.submit) {
+ try {
+ submit(newChanges, replaceByChange.values());
+ } catch (ResourceConflictException e) {
+ addError(e.getMessage());
+ reject(magicBranchCmd, "conflict");
+ } catch (RestApiException
+ | StorageException
+ | UpdateException
+ | IOException
+ | ConfigInvalidException
+ | PermissionBackendException e) {
+ logger.atSevere().withCause(e).log("Error submitting changes to %s", project.getName());
+ reject(magicBranchCmd, "error during submit");
+ }
}
}
}
@@ -938,7 +998,7 @@ class ReceiveCommits {
if (!noteDbValues.isEmpty()) {
// These semantics for duplicates/errors are somewhat arbitrary and may not match e.g. the
// CmdLineParser behavior used by MagicBranchInput.
- String value = noteDbValues.get(noteDbValues.size() - 1);
+ String value = Iterables.getLast(noteDbValues);
noteDbPushOption = NoteDbPushOption.parse(value);
if (!noteDbPushOption.isPresent()) {
addError("Invalid value in -o " + NoteDbPushOption.OPTION_NAME + "=" + value);
@@ -949,32 +1009,12 @@ class ReceiveCommits {
List<String> traceValues = pushOptions.get("trace");
if (!traceValues.isEmpty()) {
- String value = traceValues.get(traceValues.size() - 1);
- tracePushOption = Optional.of(value);
+ tracePushOption = Optional.of(Iterables.getLast(traceValues));
} else {
tracePushOption = Optional.empty();
}
}
- private static boolean isDirectChangesPush(String refname) {
- Matcher m = NEW_PATCHSET_PATTERN.matcher(refname);
- return m.matches();
- }
-
- private void parseDirectChangesPush(ReceiveCommand cmd) {
- Matcher m = NEW_PATCHSET_PATTERN.matcher(cmd.getRefName());
- checkArgument(m.matches());
-
- if (allowPushToRefsChanges) {
- // The referenced change must exist and must still be open.
- Change.Id changeId = Change.Id.parse(m.group(1));
- parseReplaceCommand(cmd, changeId);
- messages.add(new ValidationMessage("warning: pushes to refs/changes are deprecated", false));
- } else {
- reject(cmd, "upload to refs/changes not allowed");
- }
- }
-
// Wrap ReceiveCommand so the progress counter works automatically.
private ReceiveCommand wrapReceiveCommand(ReceiveCommand cmd, Task progress) {
String refname = cmd.getRefName();
@@ -1007,161 +1047,166 @@ class ReceiveCommits {
*/
private void parseRegularCommand(ReceiveCommand cmd)
throws PermissionBackendException, NoSuchProjectException, IOException {
- if (cmd.getResult() != NOT_ATTEMPTED) {
- // Already rejected by the core receive process.
- logger.atFine().log("Already processed by core: %s %s", cmd.getResult(), cmd);
- return;
- }
-
- if (!Repository.isValidRefName(cmd.getRefName()) || cmd.getRefName().contains("//")) {
- reject(cmd, "not valid ref");
- return;
- }
- if (RefNames.isNoteDbMetaRef(cmd.getRefName())) {
- // Reject pushes to NoteDb refs without a special option and permission. Note that this
- // prohibition doesn't depend on NoteDb being enabled in any way, since all sites will
- // migrate to NoteDb eventually, and we don't want garbage data waiting there when the
- // migration finishes.
- logger.atFine().log(
- "%s NoteDb ref %s with %s=%s",
- cmd.getType(), cmd.getRefName(), NoteDbPushOption.OPTION_NAME, noteDbPushOption);
- if (!Optional.of(NoteDbPushOption.ALLOW).equals(noteDbPushOption)) {
- // Only reject this command, not the whole push. This supports the use case of "git clone
- // --mirror" followed by "git push --mirror", when the user doesn't really intend to clone
- // or mirror the NoteDb data; there is no single refspec that describes all refs *except*
- // NoteDb refs.
- reject(
- cmd,
- "NoteDb update requires -o "
- + NoteDbPushOption.OPTION_NAME
- + "="
- + NoteDbPushOption.ALLOW.value());
+ try (TraceTimer traceTimer = newTimer("parseRegularCommand")) {
+ if (cmd.getResult() != NOT_ATTEMPTED) {
+ // Already rejected by the core receive process.
+ logger.atFine().log("Already processed by core: %s %s", cmd.getResult(), cmd);
return;
}
- try {
- permissionBackend.user(user).check(GlobalPermission.ACCESS_DATABASE);
- } catch (AuthException e) {
- reject(cmd, "NoteDb update requires access database permission");
+
+ if (!Repository.isValidRefName(cmd.getRefName()) || cmd.getRefName().contains("//")) {
+ reject(cmd, "not valid ref");
return;
}
- }
+ if (RefNames.isNoteDbMetaRef(cmd.getRefName())) {
+ // Reject pushes to NoteDb refs without a special option and permission. Note that this
+ // prohibition doesn't depend on NoteDb being enabled in any way, since all sites will
+ // migrate to NoteDb eventually, and we don't want garbage data waiting there when the
+ // migration finishes.
+ logger.atFine().log(
+ "%s NoteDb ref %s with %s=%s",
+ cmd.getType(), cmd.getRefName(), NoteDbPushOption.OPTION_NAME, noteDbPushOption);
+ if (!Optional.of(NoteDbPushOption.ALLOW).equals(noteDbPushOption)) {
+ // Only reject this command, not the whole push. This supports the use case of "git clone
+ // --mirror" followed by "git push --mirror", when the user doesn't really intend to clone
+ // or mirror the NoteDb data; there is no single refspec that describes all refs *except*
+ // NoteDb refs.
+ reject(
+ cmd,
+ "NoteDb update requires -o "
+ + NoteDbPushOption.OPTION_NAME
+ + "="
+ + NoteDbPushOption.ALLOW.value());
+ return;
+ }
+ try {
+ permissionBackend.user(user).check(GlobalPermission.ACCESS_DATABASE);
+ } catch (AuthException e) {
+ reject(cmd, "NoteDb update requires access database permission");
+ return;
+ }
+ }
- switch (cmd.getType()) {
- case CREATE:
- parseCreate(cmd);
- break;
+ switch (cmd.getType()) {
+ case CREATE:
+ parseCreate(cmd);
+ break;
- case UPDATE:
- parseUpdate(cmd);
- break;
+ case UPDATE:
+ parseUpdate(cmd);
+ break;
- case DELETE:
- parseDelete(cmd);
- break;
+ case DELETE:
+ parseDelete(cmd);
+ break;
- case UPDATE_NONFASTFORWARD:
- parseRewind(cmd);
- break;
+ case UPDATE_NONFASTFORWARD:
+ parseRewind(cmd);
+ break;
- default:
- reject(cmd, "prohibited by Gerrit: unknown command type " + cmd.getType());
- return;
- }
+ default:
+ reject(cmd, "prohibited by Gerrit: unknown command type " + cmd.getType());
+ return;
+ }
- if (cmd.getResult() != NOT_ATTEMPTED) {
- return;
- }
+ if (cmd.getResult() != NOT_ATTEMPTED) {
+ return;
+ }
- if (isConfig(cmd)) {
- validateConfigPush(cmd);
+ if (isConfig(cmd)) {
+ validateConfigPush(cmd);
+ }
}
}
/** Validates a push to refs/meta/config, and reject the command if it fails. */
private void validateConfigPush(ReceiveCommand cmd) throws PermissionBackendException {
- logger.atFine().log("Processing %s command", cmd.getRefName());
- try {
- permissions.check(ProjectPermission.WRITE_CONFIG);
- } catch (AuthException e) {
- reject(
- cmd,
- String.format(
- "must be either project owner or have %s permission",
- ProjectPermission.WRITE_CONFIG.describeForException()));
- return;
- }
+ try (TraceTimer traceTimer = newTimer("validateConfigPush")) {
+ logger.atFine().log("Processing %s command", cmd.getRefName());
+ try {
+ permissions.check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ reject(
+ cmd,
+ String.format(
+ "must be either project owner or have %s permission",
+ ProjectPermission.WRITE_CONFIG.describeForException()));
+ return;
+ }
- switch (cmd.getType()) {
- case CREATE:
- case UPDATE:
- case UPDATE_NONFASTFORWARD:
- try {
- ProjectConfig cfg = projectConfigFactory.create(project.getNameKey());
- cfg.load(project.getNameKey(), receivePack.getRevWalk(), cmd.getNewId());
- if (!cfg.getValidationErrors().isEmpty()) {
- addError("Invalid project configuration:");
- for (ValidationError err : cfg.getValidationErrors()) {
- addError(" " + err.getMessage());
- }
- reject(cmd, "invalid project configuration");
- logger.atSevere().log(
- "User %s tried to push invalid project configuration %s for %s",
- user.getLoggableName(), cmd.getNewId().name(), project.getName());
- return;
- }
- Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
- Project.NameKey oldParent = project.getParent(allProjectsName);
- if (oldParent == null) {
- // update of the 'All-Projects' project
- if (newParent != null) {
- reject(cmd, "invalid project configuration: root project cannot have parent");
+ switch (cmd.getType()) {
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ try {
+ ProjectConfig cfg = projectConfigFactory.create(project.getNameKey());
+ cfg.load(project.getNameKey(), receivePack.getRevWalk(), cmd.getNewId());
+ if (!cfg.getValidationErrors().isEmpty()) {
+ addError("Invalid project configuration:");
+ for (ValidationError err : cfg.getValidationErrors()) {
+ addError(" " + err.getMessage());
+ }
+ reject(cmd, "invalid project configuration");
+ logger.atSevere().log(
+ "User %s tried to push invalid project configuration %s for %s",
+ user.getLoggableName(), cmd.getNewId().name(), project.getName());
return;
}
- } else {
- if (!oldParent.equals(newParent)) {
- if (allowProjectOwnersToChangeParent) {
- try {
- permissionBackend
- .user(user)
- .project(project.getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
- } catch (AuthException e) {
- reject(cmd, "invalid project configuration: only project owners can set parent");
- return;
- }
- } else {
- try {
- permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
- } catch (AuthException e) {
- reject(cmd, "invalid project configuration: only Gerrit admin can set parent");
- return;
+ Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
+ Project.NameKey oldParent = project.getParent(allProjectsName);
+ if (oldParent == null) {
+ // update of the 'All-Projects' project
+ if (newParent != null) {
+ reject(cmd, "invalid project configuration: root project cannot have parent");
+ return;
+ }
+ } else {
+ if (!oldParent.equals(newParent)) {
+ if (allowProjectOwnersToChangeParent) {
+ try {
+ permissionBackend
+ .user(user)
+ .project(project.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ reject(
+ cmd, "invalid project configuration: only project owners can set parent");
+ return;
+ }
+ } else {
+ try {
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (AuthException e) {
+ reject(cmd, "invalid project configuration: only Gerrit admin can set parent");
+ return;
+ }
}
}
- }
- if (projectCache.get(newParent) == null) {
- reject(cmd, "invalid project configuration: parent does not exist");
- return;
+ if (projectCache.get(newParent) == null) {
+ reject(cmd, "invalid project configuration: parent does not exist");
+ return;
+ }
}
+ validatePluginConfig(cmd, cfg);
+ } catch (Exception e) {
+ reject(cmd, "invalid project configuration");
+ logger.atSevere().withCause(e).log(
+ "User %s tried to push invalid project configuration %s for %s",
+ user.getLoggableName(), cmd.getNewId().name(), project.getName());
+ return;
}
- validatePluginConfig(cmd, cfg);
- } catch (Exception e) {
- reject(cmd, "invalid project configuration");
- logger.atSevere().withCause(e).log(
- "User %s tried to push invalid project configuration %s for %s",
- user.getLoggableName(), cmd.getNewId().name(), project.getName());
- return;
- }
- break;
+ break;
- case DELETE:
- break;
+ case DELETE:
+ break;
- default:
- reject(
- cmd,
- "prohibited by Gerrit: don't know how to handle config update of type "
- + cmd.getType());
+ default:
+ reject(
+ cmd,
+ "prohibited by Gerrit: don't know how to handle config update of type "
+ + cmd.getType());
+ }
}
}
@@ -1212,52 +1257,59 @@ class ReceiveCommits {
private void parseCreate(ReceiveCommand cmd)
throws PermissionBackendException, NoSuchProjectException, IOException {
- RevObject obj;
- try {
- obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
- } catch (IOException err) {
- logger.atSevere().withCause(err).log(
- "Invalid object %s for %s creation", cmd.getNewId().name(), cmd.getRefName());
- reject(cmd, "invalid object");
- return;
- }
- logger.atFine().log("Creating %s", cmd);
+ try (TraceTimer traceTimer = newTimer("parseCreate")) {
+ RevObject obj;
+ try {
+ obj = receivePack.getRevWalk().parseAny(cmd.getNewId());
+ } catch (IOException err) {
+ logger.atSevere().withCause(err).log(
+ "Invalid object %s for %s creation", cmd.getNewId().name(), cmd.getRefName());
+ reject(cmd, "invalid object");
+ return;
+ }
+ logger.atFine().log("Creating %s", cmd);
- if (isHead(cmd) && !isCommit(cmd)) {
- return;
- }
+ if (isHead(cmd) && !isCommit(cmd)) {
+ return;
+ }
- Branch.NameKey branch = new Branch.NameKey(project.getName(), cmd.getRefName());
- try {
- // Must pass explicit user instead of injecting a provider into CreateRefControl, since
- // Provider<CurrentUser> within ReceiveCommits will always return anonymous.
- createRefControl.checkCreateRef(Providers.of(user), receivePack.getRepository(), branch, obj);
- } catch (AuthException denied) {
- rejectProhibited(cmd, denied);
- return;
- } catch (ResourceConflictException denied) {
- reject(cmd, "prohibited by Gerrit: " + denied.getMessage());
- return;
- }
+ BranchNameKey branch = BranchNameKey.create(project.getName(), cmd.getRefName());
+ try {
+ // Must pass explicit user instead of injecting a provider into CreateRefControl, since
+ // Provider<CurrentUser> within ReceiveCommits will always return anonymous.
+ createRefControl.checkCreateRef(
+ Providers.of(user), receivePack.getRepository(), branch, obj);
+ } catch (AuthException denied) {
+ rejectProhibited(cmd, denied);
+ return;
+ } catch (ResourceConflictException denied) {
+ reject(cmd, "prohibited by Gerrit: " + denied.getMessage());
+ return;
+ }
- if (validRefOperation(cmd)) {
- validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
+ if (validRefOperation(cmd)) {
+ validateRegularPushCommits(
+ BranchNameKey.create(project.getNameKey(), cmd.getRefName()), cmd);
+ }
}
}
private void parseUpdate(ReceiveCommand cmd) throws PermissionBackendException {
- logger.atFine().log("Updating %s", cmd);
- Optional<AuthException> err = checkRefPermission(cmd, RefPermission.UPDATE);
- if (!err.isPresent()) {
- if (isHead(cmd) && !isCommit(cmd)) {
- reject(cmd, "head must point to commit");
- return;
- }
- if (validRefOperation(cmd)) {
- validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
+ try (TraceTimer traceTimer = TraceContext.newTimer("parseUpdate")) {
+ logger.atFine().log("Updating %s", cmd);
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.UPDATE);
+ if (!err.isPresent()) {
+ if (isHead(cmd) && !isCommit(cmd)) {
+ reject(cmd, "head must point to commit");
+ return;
+ }
+ if (validRefOperation(cmd)) {
+ validateRegularPushCommits(
+ BranchNameKey.create(project.getNameKey(), cmd.getRefName()), cmd);
+ }
+ } else {
+ rejectProhibited(cmd, err.get());
}
- } else {
- rejectProhibited(cmd, err.get());
}
}
@@ -1280,45 +1332,50 @@ class ReceiveCommits {
}
private void parseDelete(ReceiveCommand cmd) throws PermissionBackendException {
- logger.atFine().log("Deleting %s", cmd);
- if (cmd.getRefName().startsWith(REFS_CHANGES)) {
- errors.put(CANNOT_DELETE_CHANGES, cmd.getRefName());
- reject(cmd, "cannot delete changes");
- } else if (isConfigRef(cmd.getRefName())) {
- errors.put(CANNOT_DELETE_CONFIG, cmd.getRefName());
- reject(cmd, "cannot delete project configuration");
- }
-
- Optional<AuthException> err = checkRefPermission(cmd, RefPermission.DELETE);
- if (!err.isPresent()) {
- validRefOperation(cmd);
-
- } else {
- rejectProhibited(cmd, err.get());
+ try (TraceTimer traceTimer = newTimer("parseDelete")) {
+ logger.atFine().log("Deleting %s", cmd);
+ if (cmd.getRefName().startsWith(REFS_CHANGES)) {
+ errors.put(CANNOT_DELETE_CHANGES, cmd.getRefName());
+ reject(cmd, "cannot delete changes");
+ } else if (isConfigRef(cmd.getRefName())) {
+ errors.put(CANNOT_DELETE_CONFIG, cmd.getRefName());
+ reject(cmd, "cannot delete project configuration");
+ }
+
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.DELETE);
+ if (!err.isPresent()) {
+ validRefOperation(cmd);
+ } else {
+ rejectProhibited(cmd, err.get());
+ }
}
}
private void parseRewind(ReceiveCommand cmd) throws PermissionBackendException {
- try {
- receivePack.getRevWalk().parseCommit(cmd.getNewId());
- } catch (IOException err) {
- logger.atSevere().withCause(err).log(
- "Invalid object %s for %s forced update", cmd.getNewId().name(), cmd.getRefName());
- reject(cmd, "invalid object");
- return;
- }
- logger.atFine().log("Rewinding %s", cmd);
+ try (TraceTimer traceTimer = newTimer("parseRewind")) {
+ try {
+ receivePack.getRevWalk().parseCommit(cmd.getNewId());
+ } catch (IOException err) {
+ logger.atSevere().withCause(err).log(
+ "Invalid object %s for %s forced update", cmd.getNewId().name(), cmd.getRefName());
+ reject(cmd, "invalid object");
+ return;
+ }
+ logger.atFine().log("Rewinding %s", cmd);
- if (!validRefOperation(cmd)) {
- return;
- }
- validateRegularPushCommits(new Branch.NameKey(project.getNameKey(), cmd.getRefName()), cmd);
- if (cmd.getResult() != NOT_ATTEMPTED) {
- return;
- }
+ if (!validRefOperation(cmd)) {
+ return;
+ }
+ validateRegularPushCommits(BranchNameKey.create(project.getNameKey(), cmd.getRefName()), cmd);
+ if (cmd.getResult() != NOT_ATTEMPTED) {
+ return;
+ }
- checkRefPermission(cmd, RefPermission.FORCE_UPDATE)
- .ifPresent((AuthException err) -> rejectProhibited(cmd, err));
+ Optional<AuthException> err = checkRefPermission(cmd, RefPermission.FORCE_UPDATE);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
+ }
+ }
}
private Optional<AuthException> checkRefPermission(ReceiveCommand cmd, RefPermission perm)
@@ -1358,11 +1415,21 @@ class ReceiveCommits {
static class MagicBranchInput {
private static final Splitter COMMAS = Splitter.on(',').omitEmptyStrings();
+ private final IdentifiedUser user;
+ private final ProjectState projectState;
+ private final boolean defaultPublishComments;
+
boolean deprecatedTopicSeen;
final ReceiveCommand cmd;
final LabelTypes labelTypes;
- private final boolean defaultPublishComments;
- Branch.NameKey dest;
+ /**
+ * Result of running {@link CommentValidator}-s on drafts that are published with the commit
+ * (which happens iff {@code --publish-comments} is set). Remains {@code true} if none are
+ * installed.
+ */
+ private boolean commentsValid = true;
+
+ BranchNameKey dest;
PermissionBackend.ForRef perm;
Set<String> reviewer = Sets.newLinkedHashSet();
Set<String> cc = Sets.newLinkedHashSet();
@@ -1381,13 +1448,6 @@ class ReceiveCommits {
@Option(name = "--topic", metaVar = "NAME", usage = "attach topic to changes")
String topic;
- @Option(
- name = "--draft",
- usage =
- "Will be removed. Before that, this option will be mapped to '--private'"
- + "for new changes and '--edit' for existing changes")
- boolean draft;
-
@Option(name = "--private", usage = "mark new/updated change as private")
boolean isPrivate;
@@ -1511,18 +1571,23 @@ class ReceiveCommits {
if (!hashtag.isEmpty()) {
hashtags.add(hashtag);
}
- // TODO(dpursehouse): validate hashtags
}
- MagicBranchInput(IdentifiedUser user, ReceiveCommand cmd, LabelTypes labelTypes) {
+ @UsedAt(UsedAt.Project.GOOGLE)
+ @Option(name = "--create-cod-token", usage = "create a token for consistency-on-demand")
+ private boolean createCodToken;
+
+ MagicBranchInput(
+ IdentifiedUser user, ProjectState projectState, ReceiveCommand cmd, LabelTypes labelTypes) {
+ this.user = user;
+ this.projectState = projectState;
this.deprecatedTopicSeen = false;
this.cmd = cmd;
- this.draft = cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
this.labelTypes = labelTypes;
- GeneralPreferencesInfo prefs = user.state().getGeneralPreferences();
+ GeneralPreferencesInfo prefs = user.state().generalPreferences();
this.defaultPublishComments =
prefs != null
- ? firstNonNull(user.state().getGeneralPreferences().publishCommentsOnPush, false)
+ ? firstNonNull(user.state().generalPreferences().publishCommentsOnPush, false)
: false;
}
@@ -1560,7 +1625,15 @@ class ReceiveCommits {
.collect(toImmutableSet());
}
+ void setCommentsValid(boolean commentsValid) {
+ this.commentsValid = commentsValid;
+ }
+
boolean shouldPublishComments() {
+ if (!commentsValid) {
+ // Validation messages of type WARNING have already been added, now withhold the comments.
+ return false;
+ }
if (publishComments) {
return true;
} else if (noPublishComments) {
@@ -1619,9 +1692,24 @@ class ReceiveCommits {
return ref.substring(0, split);
}
+ public boolean shouldSetWorkInProgressOnNewChanges() {
+ // When wip or ready explicitly provided, leave it as is.
+ if (workInProgress) {
+ return true;
+ }
+ if (ready) {
+ return false;
+ }
+
+ return projectState.is(BooleanProjectConfig.WORK_IN_PROGRESS_BY_DEFAULT)
+ || firstNonNull(user.state().generalPreferences().workInProgressByDefault, false);
+ }
+
NotifyResolver.Result getNotifyForNewChange() {
return NotifyResolver.Result.create(
- firstNonNull(notifyHandling, workInProgress ? NotifyHandling.OWNER : NotifyHandling.ALL),
+ firstNonNull(
+ notifyHandling,
+ shouldSetWorkInProgressOnNewChanges() ? NotifyHandling.OWNER : NotifyHandling.ALL),
ImmutableSetMultimap.<RecipientType, Account.Id>builder()
.putAll(RecipientType.TO, notifyTo)
.putAll(RecipientType.CC, notifyCc)
@@ -1648,202 +1736,198 @@ class ReceiveCommits {
* <p>Assumes we are handling a magic branch here.
*/
private void parseMagicBranch(ReceiveCommand cmd) throws PermissionBackendException {
- logger.atFine().log("Found magic branch %s", cmd.getRefName());
- MagicBranchInput magicBranch = new MagicBranchInput(user, cmd, labelTypes);
+ try (TraceTimer traceTimer = newTimer("parseMagicBranch")) {
+ logger.atFine().log("Found magic branch %s", cmd.getRefName());
+ MagicBranchInput magicBranch = new MagicBranchInput(user, projectState, cmd, labelTypes);
- String ref;
- magicBranch.cmdLineParser = optionParserFactory.create(magicBranch);
+ String ref;
+ magicBranch.cmdLineParser = optionParserFactory.create(magicBranch);
- try {
- ref = magicBranch.parse(repo, receivePack.getAdvertisedRefs().keySet(), pushOptions);
- } catch (CmdLineException e) {
- if (!magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
- logger.atFine().log("Invalid branch syntax");
- reject(cmd, e.getMessage());
- return;
+ try {
+ ref = magicBranch.parse(repo, receivePack.getAdvertisedRefs().keySet(), pushOptions);
+ } catch (CmdLineException e) {
+ if (!magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
+ logger.atFine().log("Invalid branch syntax");
+ reject(cmd, e.getMessage());
+ return;
+ }
+ ref = null; // never happens
}
- ref = null; // never happens
- }
-
- if (magicBranch.topic != null && magicBranch.topic.length() > ChangeUtil.TOPIC_MAX_LENGTH) {
- reject(
- cmd, String.format("topic length exceeds the limit (%d)", ChangeUtil.TOPIC_MAX_LENGTH));
- }
- if (magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
- StringWriter w = new StringWriter();
- w.write("\nHelp for refs/for/branch:\n\n");
- magicBranch.cmdLineParser.printUsage(w, null);
- addMessage(w.toString());
- reject(cmd, "see help");
- return;
- }
- if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(ref)) {
- logger.atFine().log("Handling %s", RefNames.REFS_USERS_SELF);
- ref = RefNames.refsUsers(user.getAccountId());
- }
- // Pushing changes for review usually requires that the target branch exists, but there is an
- // exception for the branch to which HEAD points to and for refs/meta/config. Pushing for
- // review to these branches is allowed even if the branch does not exist yet. This allows to
- // push initial code for review to an empty repository and to review an initial project
- // configuration.
- if (!receivePack.getAdvertisedRefs().containsKey(ref)
- && !ref.equals(readHEAD(repo))
- && !ref.equals(RefNames.REFS_CONFIG)) {
- logger.atFine().log("Ref %s not found", ref);
- if (ref.startsWith(Constants.R_HEADS)) {
- String n = ref.substring(Constants.R_HEADS.length());
- reject(cmd, "branch " + n + " not found");
- } else {
- reject(cmd, ref + " not found");
+ if (magicBranch.topic != null && magicBranch.topic.length() > ChangeUtil.TOPIC_MAX_LENGTH) {
+ reject(
+ cmd, String.format("topic length exceeds the limit (%d)", ChangeUtil.TOPIC_MAX_LENGTH));
}
- return;
- }
-
- magicBranch.dest = new Branch.NameKey(project.getNameKey(), ref);
- magicBranch.perm = permissions.ref(ref);
-
- Optional<AuthException> err = checkRefPermission(magicBranch.perm, RefPermission.CREATE_CHANGE);
- if (err.isPresent()) {
- rejectProhibited(cmd, err.get());
- return;
- }
- // TODO(davido): Remove legacy support for drafts magic branch option
- // after repo-tool supports private and work-in-progress changes.
- if (magicBranch.draft && !receiveConfig.allowDrafts) {
- errors.put(CODE_REVIEW_ERROR, ref);
- reject(cmd, "draft workflow is disabled");
- return;
- }
+ if (magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
+ StringWriter w = new StringWriter();
+ w.write("\nHelp for refs/for/branch:\n\n");
+ magicBranch.cmdLineParser.printUsage(w, null);
+ addMessage(w.toString());
+ reject(cmd, "see help");
+ return;
+ }
+ if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(ref)) {
+ logger.atFine().log("Handling %s", RefNames.REFS_USERS_SELF);
+ ref = RefNames.refsUsers(user.getAccountId());
+ }
+ // Pushing changes for review usually requires that the target branch exists, but there is an
+ // exception for the branch to which HEAD points to and for refs/meta/config. Pushing for
+ // review to these branches is allowed even if the branch does not exist yet. This allows to
+ // push initial code for review to an empty repository and to review an initial project
+ // configuration.
+ if (!receivePack.getAdvertisedRefs().containsKey(ref)
+ && !ref.equals(readHEAD(repo))
+ && !ref.equals(RefNames.REFS_CONFIG)) {
+ logger.atFine().log("Ref %s not found", ref);
+ if (ref.startsWith(Constants.R_HEADS)) {
+ String n = ref.substring(Constants.R_HEADS.length());
+ reject(cmd, "branch " + n + " not found");
+ } else {
+ reject(cmd, ref + " not found");
+ }
+ return;
+ }
- if (magicBranch.isPrivate && magicBranch.removePrivate) {
- reject(cmd, "the options 'private' and 'remove-private' are mutually exclusive");
- return;
- }
+ magicBranch.dest = BranchNameKey.create(project.getNameKey(), ref);
+ magicBranch.perm = permissions.ref(ref);
- boolean privateByDefault =
- projectCache.get(project.getNameKey()).is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
- setChangeAsPrivate =
- magicBranch.draft
- || magicBranch.isPrivate
- || (privateByDefault && !magicBranch.removePrivate);
+ Optional<AuthException> err =
+ checkRefPermission(magicBranch.perm, RefPermission.CREATE_CHANGE);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
+ return;
+ }
- if (receiveConfig.disablePrivateChanges && setChangeAsPrivate) {
- reject(cmd, "private changes are disabled");
- return;
- }
+ if (magicBranch.isPrivate && magicBranch.removePrivate) {
+ reject(cmd, "the options 'private' and 'remove-private' are mutually exclusive");
+ return;
+ }
- if (magicBranch.workInProgress && magicBranch.ready) {
- reject(cmd, "the options 'wip' and 'ready' are mutually exclusive");
- return;
- }
- if (magicBranch.publishComments && magicBranch.noPublishComments) {
- reject(
- cmd, "the options 'publish-comments' and 'no-publish-comments' are mutually exclusive");
- return;
- }
+ boolean privateByDefault =
+ projectCache.get(project.getNameKey()).is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
+ setChangeAsPrivate =
+ magicBranch.isPrivate || (privateByDefault && !magicBranch.removePrivate);
- if (magicBranch.submit) {
- err = checkRefPermission(magicBranch.perm, RefPermission.UPDATE_BY_SUBMIT);
- if (err.isPresent()) {
- rejectProhibited(cmd, err.get());
+ if (receiveConfig.disablePrivateChanges && setChangeAsPrivate) {
+ reject(cmd, "private changes are disabled");
return;
}
- }
- RevWalk walk = receivePack.getRevWalk();
- RevCommit tip;
- try {
- tip = walk.parseCommit(magicBranch.cmd.getNewId());
- logger.atFine().log("Tip of push: %s", tip.name());
- } catch (IOException ex) {
- magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
- logger.atSevere().withCause(ex).log("Invalid pack upload; one or more objects weren't sent");
- return;
- }
+ if (magicBranch.workInProgress && magicBranch.ready) {
+ reject(cmd, "the options 'wip' and 'ready' are mutually exclusive");
+ return;
+ }
+ if (magicBranch.publishComments && magicBranch.noPublishComments) {
+ reject(
+ cmd, "the options 'publish-comments' and 'no-publish-comments' are mutually exclusive");
+ return;
+ }
- String destBranch = magicBranch.dest.get();
- try {
- if (magicBranch.merged) {
- if (magicBranch.base != null) {
- reject(cmd, "cannot use merged with base");
- return;
- }
- RevCommit branchTip = readBranchTip(magicBranch.dest);
- if (branchTip == null) {
- reject(cmd, magicBranch.dest.get() + " not found");
- return;
- }
- if (!walk.isMergedInto(tip, branchTip)) {
- reject(cmd, "not merged into branch");
+ if (magicBranch.submit) {
+ err = checkRefPermission(magicBranch.perm, RefPermission.UPDATE_BY_SUBMIT);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
return;
}
}
- // If tip is a merge commit, or the root commit or
- // if %base or %merged was specified, ignore newChangeForAllNotInTarget.
- if (tip.getParentCount() > 1
- || magicBranch.base != null
- || magicBranch.merged
- || tip.getParentCount() == 0) {
- logger.atFine().log("Forcing newChangeForAllNotInTarget = false");
- newChangeForAllNotInTarget = false;
+ RevWalk walk = receivePack.getRevWalk();
+ RevCommit tip;
+ try {
+ tip = walk.parseCommit(magicBranch.cmd.getNewId());
+ logger.atFine().log("Tip of push: %s", tip.name());
+ } catch (IOException ex) {
+ magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
+ logger.atSevere().withCause(ex).log(
+ "Invalid pack upload; one or more objects weren't sent");
+ return;
}
- if (magicBranch.base != null) {
- logger.atFine().log("Handling %%base: %s", magicBranch.base);
- magicBranch.baseCommit = Lists.newArrayListWithCapacity(magicBranch.base.size());
- for (ObjectId id : magicBranch.base) {
- try {
- magicBranch.baseCommit.add(walk.parseCommit(id));
- } catch (IncorrectObjectTypeException notCommit) {
- reject(cmd, "base must be a commit");
+ String destBranch = magicBranch.dest.branch();
+ try {
+ if (magicBranch.merged) {
+ if (magicBranch.base != null) {
+ reject(cmd, "cannot use merged with base");
return;
- } catch (MissingObjectException e) {
- reject(cmd, "base not found");
+ }
+ RevCommit branchTip = readBranchTip(magicBranch.dest);
+ if (branchTip == null) {
+ reject(cmd, magicBranch.dest.branch() + " not found");
return;
- } catch (IOException e) {
- logger.atWarning().withCause(e).log(
- "Project %s cannot read %s", project.getName(), id.name());
- reject(cmd, INTERNAL_SERVER_ERROR);
+ }
+ if (!walk.isMergedInto(tip, branchTip)) {
+ reject(cmd, "not merged into branch");
return;
}
}
- } else if (newChangeForAllNotInTarget) {
- RevCommit branchTip = readBranchTip(magicBranch.dest);
- if (branchTip != null) {
- magicBranch.baseCommit = Collections.singletonList(branchTip);
- logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
- } else {
- // The target branch does not exist. Usually pushing changes for review requires that the
- // target branch exists, but there is an exception for the branch to which HEAD points to
- // and for refs/meta/config. Pushing for review to these branches is allowed even if the
- // branch does not exist yet. This allows to push initial code for review to an empty
- // repository and to review an initial project configuration.
- if (!ref.equals(readHEAD(repo)) && !ref.equals(RefNames.REFS_CONFIG)) {
- reject(cmd, magicBranch.dest.get() + " not found");
- return;
+
+ // If tip is a merge commit, or the root commit or
+ // if %base or %merged was specified, ignore newChangeForAllNotInTarget.
+ if (tip.getParentCount() > 1
+ || magicBranch.base != null
+ || magicBranch.merged
+ || tip.getParentCount() == 0) {
+ logger.atFine().log("Forcing newChangeForAllNotInTarget = false");
+ newChangeForAllNotInTarget = false;
+ }
+
+ if (magicBranch.base != null) {
+ logger.atFine().log("Handling %%base: %s", magicBranch.base);
+ magicBranch.baseCommit = Lists.newArrayListWithCapacity(magicBranch.base.size());
+ for (ObjectId id : magicBranch.base) {
+ try {
+ magicBranch.baseCommit.add(walk.parseCommit(id));
+ } catch (IncorrectObjectTypeException notCommit) {
+ reject(cmd, "base must be a commit");
+ return;
+ } catch (MissingObjectException e) {
+ reject(cmd, "base not found");
+ return;
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Project %s cannot read %s", project.getName(), id.name());
+ reject(cmd, INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+ } else if (newChangeForAllNotInTarget) {
+ RevCommit branchTip = readBranchTip(magicBranch.dest);
+ if (branchTip != null) {
+ magicBranch.baseCommit = Collections.singletonList(branchTip);
+ logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
+ } else {
+ // The target branch does not exist. Usually pushing changes for review requires that
+ // the
+ // target branch exists, but there is an exception for the branch to which HEAD points
+ // to
+ // and for refs/meta/config. Pushing for review to these branches is allowed even if the
+ // branch does not exist yet. This allows to push initial code for review to an empty
+ // repository and to review an initial project configuration.
+ if (!ref.equals(readHEAD(repo)) && !ref.equals(RefNames.REFS_CONFIG)) {
+ reject(cmd, magicBranch.dest.branch() + " not found");
+ return;
+ }
}
}
+ } catch (IOException ex) {
+ logger.atWarning().withCause(ex).log(
+ "Error walking to %s in project %s", destBranch, project.getName());
+ reject(cmd, INTERNAL_SERVER_ERROR);
+ return;
}
- } catch (IOException ex) {
- logger.atWarning().withCause(ex).log(
- "Error walking to %s in project %s", destBranch, project.getName());
- reject(cmd, INTERNAL_SERVER_ERROR);
- return;
- }
- if (magicBranch.deprecatedTopicSeen) {
- messages.add(
- new ValidationMessage(
- "WARNING: deprecated topic syntax. Use -o topic=TOPIC instead", false));
- logger.atInfo().log("deprecated topic push seen for project %s", project.getName());
- }
+ if (magicBranch.deprecatedTopicSeen) {
+ messages.add(
+ new ValidationMessage(
+ "WARNING: deprecated topic syntax. Use -o topic=TOPIC instead", false));
+ logger.atInfo().log("deprecated topic push seen for project %s", project.getName());
+ }
- if (validateConnected(magicBranch.cmd, magicBranch.dest, tip)) {
- this.magicBranch = magicBranch;
- this.resultChangeIds.setMagicPush(true);
+ if (validateConnected(magicBranch.cmd, magicBranch.dest, tip)) {
+ this.magicBranch = magicBranch;
+ this.resultChangeIds.setMagicPush(true);
+ }
}
}
@@ -1851,42 +1935,45 @@ class ReceiveCommits {
// branch. If they aren't, we want to abort. We do this check by
// looking to see if we can compute a merge base between the new
// commits and the target branch head.
- private boolean validateConnected(ReceiveCommand cmd, Branch.NameKey dest, RevCommit tip) {
- RevWalk walk = receivePack.getRevWalk();
- try {
- Ref targetRef = receivePack.getAdvertisedRefs().get(dest.get());
- if (targetRef == null || targetRef.getObjectId() == null) {
- // The destination branch does not yet exist. Assume the
- // history being sent for review will start it and thus
- // is "connected" to the branch.
- logger.atFine().log("Branch is unborn");
-
- // This is not an error condition.
- return true;
- }
-
- RevCommit h = walk.parseCommit(targetRef.getObjectId());
- logger.atFine().log("Current branch tip: %s", h.name());
- RevFilter oldRevFilter = walk.getRevFilter();
+ private boolean validateConnected(ReceiveCommand cmd, BranchNameKey dest, RevCommit tip) {
+ try (TraceTimer traceTimer =
+ newTimer("validateConnected", Metadata.builder().branchName(dest.branch()))) {
+ RevWalk walk = receivePack.getRevWalk();
try {
- walk.reset();
- walk.setRevFilter(RevFilter.MERGE_BASE);
- walk.markStart(tip);
- walk.markStart(h);
- if (walk.next() == null) {
- reject(cmd, "no common ancestry");
- return false;
+ Ref targetRef = receivePack.getAdvertisedRefs().get(dest.branch());
+ if (targetRef == null || targetRef.getObjectId() == null) {
+ // The destination branch does not yet exist. Assume the
+ // history being sent for review will start it and thus
+ // is "connected" to the branch.
+ logger.atFine().log("Branch is unborn");
+
+ // This is not an error condition.
+ return true;
}
- } finally {
- walk.reset();
- walk.setRevFilter(oldRevFilter);
+
+ RevCommit h = walk.parseCommit(targetRef.getObjectId());
+ logger.atFine().log("Current branch tip: %s", h.name());
+ RevFilter oldRevFilter = walk.getRevFilter();
+ try {
+ walk.reset();
+ walk.setRevFilter(RevFilter.MERGE_BASE);
+ walk.markStart(tip);
+ walk.markStart(h);
+ if (walk.next() == null) {
+ reject(cmd, "no common ancestry");
+ return false;
+ }
+ } finally {
+ walk.reset();
+ walk.setRevFilter(oldRevFilter);
+ }
+ } catch (IOException e) {
+ cmd.setResult(REJECTED_MISSING_OBJECT);
+ logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
+ return false;
}
- } catch (IOException e) {
- cmd.setResult(REJECTED_MISSING_OBJECT);
- logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
- return false;
+ return true;
}
- return true;
}
private static String readHEAD(Repository repo) {
@@ -1900,89 +1987,64 @@ class ReceiveCommits {
}
}
- private RevCommit readBranchTip(Branch.NameKey branch) throws IOException {
- Ref r = allRefs().get(branch.get());
+ private RevCommit readBranchTip(BranchNameKey branch) throws IOException {
+ Ref r = allRefs().get(branch.branch());
if (r == null) {
return null;
}
return receivePack.getRevWalk().parseCommit(r.getObjectId());
}
- // Handle an upload to refs/changes/XX/CHANGED-NUMBER.
- private void parseReplaceCommand(ReceiveCommand cmd, Change.Id changeId) {
- logger.atFine().log("Parsing replace command");
- if (cmd.getType() != ReceiveCommand.Type.CREATE) {
- reject(cmd, "invalid usage");
- return;
- }
-
- RevCommit newCommit;
- try {
- newCommit = receivePack.getRevWalk().parseCommit(cmd.getNewId());
- logger.atFine().log("Replacing with %s", newCommit);
- } catch (IOException e) {
- logger.atSevere().withCause(e).log("Cannot parse %s as commit", cmd.getNewId().name());
- reject(cmd, "invalid commit");
- return;
- }
-
- Change changeEnt;
- try {
- changeEnt = notesFactory.createChecked(project.getNameKey(), changeId).getChange();
- } catch (NoSuchChangeException e) {
- logger.atSevere().withCause(e).log("Change not found %s", changeId);
- reject(cmd, "change " + changeId + " not found");
- return;
- } catch (StorageException e) {
- logger.atSevere().withCause(e).log("Cannot lookup existing change %s", changeId);
- reject(cmd, "database error");
- return;
- }
- if (!project.getNameKey().equals(changeEnt.getProject())) {
- reject(cmd, "change " + changeId + " does not belong to project " + project.getName());
- return;
- }
-
- BranchCommitValidator validator =
- commitValidatorFactory.create(projectState, changeEnt.getDest(), user);
- try {
- if (validator.validCommit(
- receivePack.getRevWalk().getObjectReader(),
- cmd,
- newCommit,
- false,
- messages,
- rejectCommits,
- changeEnt)) {
- logger.atFine().log("Replacing change %s", changeEnt.getId());
- requestReplace(cmd, true, changeEnt, newCommit);
- }
- } catch (IOException e) {
- reject(cmd, "I/O exception validating commit");
- }
- }
-
/**
- * Add an update for an existing change. Returns true if it succeeded; rejects the command if it
- * failed.
+ * Update an existing change. If draft comments are to be published, these are validated and may
+ * be withheld.
+ *
+ * @return True if the command succeeded, false if it was rejected.
*/
- private boolean requestReplace(
+ private boolean requestReplaceAndValidateComments(
ReceiveCommand cmd, boolean checkMergedInto, Change change, RevCommit newCommit) {
- if (change.isClosed()) {
- reject(
- cmd,
- changeFormatter.changeClosed(
- ChangeReportFormatter.Input.builder().setChange(change).build()));
- return false;
- }
+ try (TraceTimer traceTimer = newTimer("requestReplaceAndValidateComments")) {
+ if (change.isClosed()) {
+ reject(
+ cmd,
+ changeFormatter.changeClosed(
+ ChangeReportFormatter.Input.builder().setChange(change).build()));
+ return false;
+ }
- ReplaceRequest req = new ReplaceRequest(change.getId(), newCommit, cmd, checkMergedInto);
- if (replaceByChange.containsKey(req.ontoChange)) {
- reject(cmd, "duplicate request");
- return false;
+ ReplaceRequest req = new ReplaceRequest(change.getId(), newCommit, cmd, checkMergedInto);
+ if (replaceByChange.containsKey(req.ontoChange)) {
+ reject(cmd, "duplicate request");
+ return false;
+ }
+
+ if (magicBranch != null && magicBranch.shouldPublishComments()) {
+ List<Comment> drafts =
+ commentsUtil.draftByChangeAuthor(
+ notesFactory.createChecked(change), user.getAccountId());
+ ImmutableList<CommentForValidation> draftsForValidation =
+ drafts.stream()
+ .map(
+ comment ->
+ CommentForValidation.create(
+ comment.lineNbr > 0
+ ? CommentType.INLINE_COMMENT
+ : CommentType.FILE_COMMENT,
+ comment.message))
+ .collect(toImmutableList());
+ ImmutableList<CommentValidationFailure> commentValidationFailures =
+ PublishCommentUtil.findInvalidComments(commentValidators, draftsForValidation);
+ magicBranch.setCommentsValid(commentValidationFailures.isEmpty());
+ commentValidationFailures.forEach(
+ failure ->
+ addMessage(
+ "Comment validation failure: " + failure.getMessage(),
+ ValidationMessage.Type.WARNING));
+ }
+
+ replaceByChange.put(req.ontoChange, req);
+ return true;
}
- replaceByChange.put(req.ontoChange, req);
- return true;
}
private void warnAboutMissingChangeId(List<CreateRequest> newChanges) {
@@ -2003,351 +2065,367 @@ class ReceiveCommits {
}
private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress) {
- logger.atFine().log("Finding new and replaced changes");
- List<CreateRequest> newChanges = new ArrayList<>();
-
- ListMultimap<ObjectId, Ref> existing = changeRefsById();
- GroupCollector groupCollector =
- GroupCollector.create(changeRefsById(), psUtil, notesFactory, project.getNameKey());
+ try (TraceTimer traceTimer = newTimer("selectNewAndReplacedChangesFromMagicBranch")) {
+ logger.atFine().log("Finding new and replaced changes");
+ List<CreateRequest> newChanges = new ArrayList<>();
- BranchCommitValidator validator =
- commitValidatorFactory.create(projectState, magicBranch.dest, user);
-
- try {
- RevCommit start = setUpWalkForSelectingChanges();
- if (start == null) {
- return Collections.emptyList();
- }
+ ListMultimap<ObjectId, Ref> existing = changeRefsById();
+ GroupCollector groupCollector =
+ GroupCollector.create(changeRefsById(), psUtil, notesFactory, project.getNameKey());
- LinkedHashMap<RevCommit, ChangeLookup> pending = new LinkedHashMap<>();
- Set<Change.Key> newChangeIds = new HashSet<>();
- int maxBatchChanges = receiveConfig.getEffectiveMaxBatchChangesLimit(user);
- int total = 0;
- int alreadyTracked = 0;
- boolean rejectImplicitMerges =
- start.getParentCount() == 1
- && projectCache
- .get(project.getNameKey())
- .is(BooleanProjectConfig.REJECT_IMPLICIT_MERGES)
- // Don't worry about implicit merges when creating changes for
- // already-merged commits; they're already in history, so it's too
- // late.
- && !magicBranch.merged;
- Set<RevCommit> mergedParents;
- if (rejectImplicitMerges) {
- mergedParents = new HashSet<>();
- } else {
- mergedParents = null;
- }
+ BranchCommitValidator validator =
+ commitValidatorFactory.create(projectState, magicBranch.dest, user);
- for (; ; ) {
- RevCommit c = receivePack.getRevWalk().next();
- if (c == null) {
- break;
+ try {
+ RevCommit start = setUpWalkForSelectingChanges();
+ if (start == null) {
+ return Collections.emptyList();
}
- total++;
- receivePack.getRevWalk().parseBody(c);
- String name = c.name();
- groupCollector.visit(c);
- Collection<Ref> existingRefs = existing.get(c);
+ LinkedHashMap<RevCommit, ChangeLookup> pending = new LinkedHashMap<>();
+ Set<Change.Key> newChangeIds = new HashSet<>();
+ int maxBatchChanges = receiveConfig.getEffectiveMaxBatchChangesLimit(user);
+ int total = 0;
+ int alreadyTracked = 0;
+ boolean rejectImplicitMerges =
+ start.getParentCount() == 1
+ && projectCache
+ .get(project.getNameKey())
+ .is(BooleanProjectConfig.REJECT_IMPLICIT_MERGES)
+ // Don't worry about implicit merges when creating changes for
+ // already-merged commits; they're already in history, so it's too
+ // late.
+ && !magicBranch.merged;
+ Set<RevCommit> mergedParents;
if (rejectImplicitMerges) {
- Collections.addAll(mergedParents, c.getParents());
- mergedParents.remove(c);
- }
-
- boolean commitAlreadyTracked = !existingRefs.isEmpty();
- if (commitAlreadyTracked) {
- alreadyTracked++;
- // Corner cases where an existing commit might need a new group:
- // A) Existing commit has a null group; wasn't assigned during schema
- // upgrade, or schema upgrade is performed on a running server.
- // B) Let A<-B<-C, then:
- // 1. Push A to refs/heads/master
- // 2. Push B to refs/for/master
- // 3. Force push A~ to refs/heads/master
- // 4. Push C to refs/for/master.
- // B will be in existing so we aren't replacing the patch set. It
- // used to have its own group, but now needs to to be changed to
- // A's group.
- // C) Commit is a PatchSet of a pre-existing change uploaded with a
- // different target branch.
- for (Ref ref : existingRefs) {
- updateGroups.add(new UpdateGroupsRequest(ref, c));
- }
- if (!(newChangeForAllNotInTarget || magicBranch.base != null)) {
- continue;
- }
- }
-
- List<String> idList = c.getFooterLines(CHANGE_ID);
- if (!idList.isEmpty()) {
- pending.put(
- c, lookupByChangeKey(c, new Change.Key(idList.get(idList.size() - 1).trim())));
+ mergedParents = new HashSet<>();
} else {
- pending.put(c, lookupByCommit(c));
+ mergedParents = null;
}
- int n = pending.size() + newChanges.size();
- if (maxBatchChanges != 0 && n > maxBatchChanges) {
- logger.atFine().log("%d changes exceeds limit of %d", n, maxBatchChanges);
- reject(
- magicBranch.cmd,
- "the number of pushed changes in a batch exceeds the max limit " + maxBatchChanges);
- return Collections.emptyList();
- }
+ for (; ; ) {
+ RevCommit c = receivePack.getRevWalk().next();
+ if (c == null) {
+ break;
+ }
+ total++;
+ receivePack.getRevWalk().parseBody(c);
+ String name = c.name();
+ groupCollector.visit(c);
+ Collection<Ref> existingRefs = existing.get(c);
+
+ if (rejectImplicitMerges) {
+ Collections.addAll(mergedParents, c.getParents());
+ mergedParents.remove(c);
+ }
- if (commitAlreadyTracked) {
- boolean changeExistsOnDestBranch = false;
- for (ChangeData cd : pending.get(c).destChanges) {
- if (cd.change().getDest().equals(magicBranch.dest)) {
- changeExistsOnDestBranch = true;
- break;
+ boolean commitAlreadyTracked = !existingRefs.isEmpty();
+ if (commitAlreadyTracked) {
+ alreadyTracked++;
+ // Corner cases where an existing commit might need a new group:
+ // A) Existing commit has a null group; wasn't assigned during schema
+ // upgrade, or schema upgrade is performed on a running server.
+ // B) Let A<-B<-C, then:
+ // 1. Push A to refs/heads/master
+ // 2. Push B to refs/for/master
+ // 3. Force push A~ to refs/heads/master
+ // 4. Push C to refs/for/master.
+ // B will be in existing so we aren't replacing the patch set. It
+ // used to have its own group, but now needs to to be changed to
+ // A's group.
+ // C) Commit is a PatchSet of a pre-existing change uploaded with a
+ // different target branch.
+ for (Ref ref : existingRefs) {
+ updateGroups.add(new UpdateGroupsRequest(ref, c));
+ }
+ if (!(newChangeForAllNotInTarget || magicBranch.base != null)) {
+ continue;
}
- }
- if (changeExistsOnDestBranch) {
- continue;
}
- logger.atFine().log("Creating new change for %s even though it is already tracked", name);
- }
+ List<String> idList = c.getFooterLines(FooterConstants.CHANGE_ID);
+ if (!idList.isEmpty()) {
+ pending.put(c, lookupByChangeKey(c, Change.key(idList.get(idList.size() - 1).trim())));
+ } else {
+ pending.put(c, lookupByCommit(c));
+ }
- if (!validator.validCommit(
- receivePack.getRevWalk().getObjectReader(),
- magicBranch.cmd,
- c,
- magicBranch.merged,
- messages,
- rejectCommits,
- null)) {
- // Not a change the user can propose? Abort as early as possible.
- logger.atFine().log("Aborting early due to invalid commit");
- return Collections.emptyList();
- }
+ int n = pending.size() + newChanges.size();
+ if (maxBatchChanges != 0 && n > maxBatchChanges) {
+ logger.atFine().log("%d changes exceeds limit of %d", n, maxBatchChanges);
+ reject(
+ magicBranch.cmd,
+ "the number of pushed changes in a batch exceeds the max limit " + maxBatchChanges);
+ return Collections.emptyList();
+ }
- // Don't allow merges to be uploaded in commit chain via all-not-in-target
- if (newChangeForAllNotInTarget && c.getParentCount() > 1) {
- reject(
- magicBranch.cmd,
- "Pushing merges in commit chains with 'all not in target' is not allowed,\n"
- + "to override please set the base manually");
- logger.atFine().log("Rejecting merge commit %s with newChangeForAllNotInTarget", name);
- // TODO(dborowitz): Should we early return here?
- }
+ if (commitAlreadyTracked) {
+ boolean changeExistsOnDestBranch = false;
+ for (ChangeData cd : pending.get(c).destChanges) {
+ if (cd.change().getDest().equals(magicBranch.dest)) {
+ changeExistsOnDestBranch = true;
+ break;
+ }
+ }
+ if (changeExistsOnDestBranch) {
+ continue;
+ }
- if (idList.isEmpty()) {
- newChanges.add(new CreateRequest(c, magicBranch.dest.get(), newProgress));
- continue;
- }
- }
- logger.atFine().log(
- "Finished initial RevWalk with %d commits total: %d already"
- + " tracked, %d new changes with no Change-Id, and %d deferred"
- + " lookups",
- total, alreadyTracked, newChanges.size(), pending.size());
+ logger.atFine().log(
+ "Creating new change for %s even though it is already tracked", name);
+ }
- if (rejectImplicitMerges) {
- rejectImplicitMerges(mergedParents);
- }
+ BranchCommitValidator.Result validationResult =
+ validator.validateCommit(
+ receivePack.getRevWalk().getObjectReader(),
+ magicBranch.cmd,
+ c,
+ magicBranch.merged,
+ rejectCommits,
+ null);
+ messages.addAll(validationResult.messages());
+ if (!validationResult.isValid()) {
+ // Not a change the user can propose? Abort as early as possible.
+ logger.atFine().log("Aborting early due to invalid commit");
+ return Collections.emptyList();
+ }
- for (Iterator<ChangeLookup> itr = pending.values().iterator(); itr.hasNext(); ) {
- ChangeLookup p = itr.next();
- if (p.changeKey == null) {
- continue;
- }
+ // Don't allow merges to be uploaded in commit chain via all-not-in-target
+ if (newChangeForAllNotInTarget && c.getParentCount() > 1) {
+ reject(
+ magicBranch.cmd,
+ "Pushing merges in commit chains with 'all not in target' is not allowed,\n"
+ + "to override please set the base manually");
+ logger.atFine().log("Rejecting merge commit %s with newChangeForAllNotInTarget", name);
+ // TODO(dborowitz): Should we early return here?
+ }
- if (newChangeIds.contains(p.changeKey)) {
- logger.atFine().log("Multiple commits with Change-Id %s", p.changeKey);
- reject(magicBranch.cmd, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
- return Collections.emptyList();
+ if (idList.isEmpty()) {
+ newChanges.add(new CreateRequest(c, magicBranch.dest.branch(), newProgress));
+ continue;
+ }
}
+ logger.atFine().log(
+ "Finished initial RevWalk with %d commits total: %d already"
+ + " tracked, %d new changes with no Change-Id, and %d deferred"
+ + " lookups",
+ total, alreadyTracked, newChanges.size(), pending.size());
- List<ChangeData> changes = p.destChanges;
- if (changes.size() > 1) {
- logger.atFine().log(
- "Multiple changes in branch %s with Change-Id %s: %s",
- magicBranch.dest,
- p.changeKey,
- changes.stream().map(cd -> cd.getId().toString()).collect(joining()));
- // WTF, multiple changes in this branch have the same key?
- // Since the commit is new, the user should recreate it with
- // a different Change-Id. In practice, we should never see
- // this error message as Change-Id should be unique per branch.
- //
- reject(magicBranch.cmd, p.changeKey.get() + " has duplicates");
- return Collections.emptyList();
+ if (rejectImplicitMerges) {
+ rejectImplicitMerges(mergedParents);
}
- if (changes.size() == 1) {
- // Schedule as a replacement to this one matching change.
- //
+ for (Iterator<ChangeLookup> itr = pending.values().iterator(); itr.hasNext(); ) {
+ ChangeLookup p = itr.next();
+ if (p.changeKey == null) {
+ continue;
+ }
- RevId currentPs = changes.get(0).currentPatchSet().getRevision();
- // If Commit is already current PatchSet of target Change.
- if (p.commit.name().equals(currentPs.get())) {
- if (pending.size() == 1) {
- // There are no commits left to check, all commits in pending were already
- // current PatchSet of the corresponding target changes.
- reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
- } else {
- // Commit is already current PatchSet.
- // Remove from pending and try next commit.
- itr.remove();
- continue;
- }
+ if (newChangeIds.contains(p.changeKey)) {
+ logger.atFine().log("Multiple commits with Change-Id %s", p.changeKey);
+ reject(magicBranch.cmd, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
+ return Collections.emptyList();
}
- if (requestReplace(magicBranch.cmd, false, changes.get(0).change(), p.commit)) {
- continue;
+
+ List<ChangeData> changes = p.destChanges;
+ if (changes.size() > 1) {
+ logger.atFine().log(
+ "Multiple changes in branch %s with Change-Id %s: %s",
+ magicBranch.dest,
+ p.changeKey,
+ changes.stream().map(cd -> cd.getId().toString()).collect(joining()));
+ // WTF, multiple changes in this branch have the same key?
+ // Since the commit is new, the user should recreate it with
+ // a different Change-Id. In practice, we should never see
+ // this error message as Change-Id should be unique per branch.
+ //
+ reject(magicBranch.cmd, p.changeKey.get() + " has duplicates");
+ return Collections.emptyList();
}
- return Collections.emptyList();
- }
- if (changes.isEmpty()) {
- if (!isValidChangeId(p.changeKey.get())) {
- reject(magicBranch.cmd, "invalid Change-Id");
+ if (changes.size() == 1) {
+ // Schedule as a replacement to this one matching change.
+ //
+
+ ObjectId currentPs = changes.get(0).currentPatchSet().commitId();
+ // If Commit is already current PatchSet of target Change.
+ if (p.commit.equals(currentPs)) {
+ if (pending.size() == 1) {
+ // There are no commits left to check, all commits in pending were already
+ // current PatchSet of the corresponding target changes.
+ reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
+ } else {
+ // Commit is already current PatchSet.
+ // Remove from pending and try next commit.
+ itr.remove();
+ continue;
+ }
+ }
+ if (requestReplaceAndValidateComments(
+ magicBranch.cmd, false, changes.get(0).change(), p.commit)) {
+ continue;
+ }
return Collections.emptyList();
}
- // In case the change look up from the index failed,
- // double check against the existing refs
- if (foundInExistingRef(existing.get(p.commit))) {
- if (pending.size() == 1) {
- reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
+ if (changes.isEmpty()) {
+ if (!isValidChangeId(p.changeKey.get())) {
+ reject(magicBranch.cmd, "invalid Change-Id");
return Collections.emptyList();
}
- itr.remove();
- continue;
+
+ // In case the change look up from the index failed,
+ // double check against the existing refs
+ if (foundInExistingRef(existing.get(p.commit))) {
+ if (pending.size() == 1) {
+ reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
+ return Collections.emptyList();
+ }
+ itr.remove();
+ continue;
+ }
+ newChangeIds.add(p.changeKey);
}
- newChangeIds.add(p.changeKey);
+ newChanges.add(new CreateRequest(p.commit, magicBranch.dest.branch(), newProgress));
}
- newChanges.add(new CreateRequest(p.commit, magicBranch.dest.get(), newProgress));
+ logger.atFine().log(
+ "Finished deferred lookups with %d updates and %d new changes",
+ replaceByChange.size(), newChanges.size());
+ } catch (IOException e) {
+ // Should never happen, the core receive process would have
+ // identified the missing object earlier before we got control.
+ //
+ magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
+ logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
+ return Collections.emptyList();
+ } catch (StorageException e) {
+ logger.atSevere().withCause(e).log("Cannot query database to locate prior changes");
+ reject(magicBranch.cmd, "database error");
+ return Collections.emptyList();
}
- logger.atFine().log(
- "Finished deferred lookups with %d updates and %d new changes",
- replaceByChange.size(), newChanges.size());
- } catch (IOException e) {
- // Should never happen, the core receive process would have
- // identified the missing object earlier before we got control.
- //
- magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
- logger.atSevere().withCause(e).log("Invalid pack upload; one or more objects weren't sent");
- return Collections.emptyList();
- } catch (StorageException e) {
- logger.atSevere().withCause(e).log("Cannot query database to locate prior changes");
- reject(magicBranch.cmd, "database error");
- return Collections.emptyList();
- }
- if (newChanges.isEmpty() && replaceByChange.isEmpty()) {
- reject(magicBranch.cmd, "no new changes");
- return Collections.emptyList();
- }
- if (!newChanges.isEmpty() && magicBranch.edit) {
- reject(magicBranch.cmd, "edit is not supported for new changes");
- return newChanges;
- }
-
- try {
- SortedSetMultimap<ObjectId, String> groups = groupCollector.getGroups();
- List<Integer> newIds = seq.nextChangeIds(newChanges.size());
- for (int i = 0; i < newChanges.size(); i++) {
- CreateRequest create = newChanges.get(i);
- create.setChangeId(newIds.get(i));
- create.groups = ImmutableList.copyOf(groups.get(create.commit));
+ if (newChanges.isEmpty() && replaceByChange.isEmpty()) {
+ reject(magicBranch.cmd, "no new changes");
+ return Collections.emptyList();
}
- for (ReplaceRequest replace : replaceByChange.values()) {
- replace.groups = ImmutableList.copyOf(groups.get(replace.newCommitId));
+ if (!newChanges.isEmpty() && magicBranch.edit) {
+ reject(magicBranch.cmd, "edit is not supported for new changes");
+ return newChanges;
}
- for (UpdateGroupsRequest update : updateGroups) {
- update.groups = ImmutableList.copyOf((groups.get(update.commit)));
+
+ try {
+ SortedSetMultimap<ObjectId, String> groups = groupCollector.getGroups();
+ List<Integer> newIds = seq.nextChangeIds(newChanges.size());
+ for (int i = 0; i < newChanges.size(); i++) {
+ CreateRequest create = newChanges.get(i);
+ create.setChangeId(newIds.get(i));
+ create.groups = ImmutableList.copyOf(groups.get(create.commit));
+ }
+ for (ReplaceRequest replace : replaceByChange.values()) {
+ replace.groups = ImmutableList.copyOf(groups.get(replace.newCommitId));
+ }
+ for (UpdateGroupsRequest update : updateGroups) {
+ update.groups = ImmutableList.copyOf((groups.get(update.commit)));
+ }
+ logger.atFine().log("Finished updating groups from GroupCollector");
+ } catch (StorageException e) {
+ logger.atSevere().withCause(e).log("Error collecting groups for changes");
+ reject(magicBranch.cmd, INTERNAL_SERVER_ERROR);
}
- logger.atFine().log("Finished updating groups from GroupCollector");
- } catch (StorageException e) {
- logger.atSevere().withCause(e).log("Error collecting groups for changes");
- reject(magicBranch.cmd, INTERNAL_SERVER_ERROR);
+ return newChanges;
}
- return newChanges;
}
private boolean foundInExistingRef(Collection<Ref> existingRefs) {
- for (Ref ref : existingRefs) {
- ChangeNotes notes =
- notesFactory.create(project.getNameKey(), Change.Id.fromRef(ref.getName()));
- Change change = notes.getChange();
- if (change.getDest().equals(magicBranch.dest)) {
- logger.atFine().log("Found change %s from existing refs.", change.getKey());
- // Reindex the change asynchronously, ignoring errors.
- @SuppressWarnings("unused")
- Future<?> possiblyIgnoredError = indexer.indexAsync(project.getNameKey(), change.getId());
- return true;
+ try (TraceTimer traceTimer = newTimer("foundInExistingRef")) {
+ for (Ref ref : existingRefs) {
+ ChangeNotes notes =
+ notesFactory.create(project.getNameKey(), Change.Id.fromRef(ref.getName()));
+ Change change = notes.getChange();
+ if (change.getDest().equals(magicBranch.dest)) {
+ logger.atFine().log("Found change %s from existing refs.", change.getKey());
+ // Reindex the change asynchronously, ignoring errors.
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError = indexer.indexAsync(project.getNameKey(), change.getId());
+ return true;
+ }
}
+ return false;
}
- return false;
}
private RevCommit setUpWalkForSelectingChanges() throws IOException {
- RevWalk rw = receivePack.getRevWalk();
- RevCommit start = rw.parseCommit(magicBranch.cmd.getNewId());
-
- rw.reset();
- rw.sort(RevSort.TOPO);
- rw.sort(RevSort.REVERSE, true);
- receivePack.getRevWalk().markStart(start);
- if (magicBranch.baseCommit != null) {
- markExplicitBasesUninteresting();
- } else if (magicBranch.merged) {
- logger.atFine().log("Marking parents of merged commit %s uninteresting", start.name());
- for (RevCommit c : start.getParents()) {
- rw.markUninteresting(c);
+ try (TraceTimer traceTimer = newTimer("setUpWalkForSelectingChanges")) {
+ RevWalk rw = receivePack.getRevWalk();
+ RevCommit start = rw.parseCommit(magicBranch.cmd.getNewId());
+
+ rw.reset();
+ rw.sort(RevSort.TOPO);
+ rw.sort(RevSort.REVERSE, true);
+ receivePack.getRevWalk().markStart(start);
+ if (magicBranch.baseCommit != null) {
+ markExplicitBasesUninteresting();
+ } else if (magicBranch.merged) {
+ logger.atFine().log("Marking parents of merged commit %s uninteresting", start.name());
+ for (RevCommit c : start.getParents()) {
+ rw.markUninteresting(c);
+ }
+ } else {
+ markHeadsAsUninteresting(rw, magicBranch.dest != null ? magicBranch.dest.branch() : null);
}
- } else {
- markHeadsAsUninteresting(rw, magicBranch.dest != null ? magicBranch.dest.get() : null);
+ return start;
}
- return start;
}
private void markExplicitBasesUninteresting() throws IOException {
- logger.atFine().log("Marking %d base commits uninteresting", magicBranch.baseCommit.size());
- for (RevCommit c : magicBranch.baseCommit) {
- receivePack.getRevWalk().markUninteresting(c);
- }
- Ref targetRef = allRefs().get(magicBranch.dest.get());
- if (targetRef != null) {
- logger.atFine().log(
- "Marking target ref %s (%s) uninteresting",
- magicBranch.dest.get(), targetRef.getObjectId().name());
- receivePack
- .getRevWalk()
- .markUninteresting(receivePack.getRevWalk().parseCommit(targetRef.getObjectId()));
+ try (TraceTimer traceTimer = newTimer("markExplicitBasesUninteresting")) {
+ logger.atFine().log("Marking %d base commits uninteresting", magicBranch.baseCommit.size());
+ for (RevCommit c : magicBranch.baseCommit) {
+ receivePack.getRevWalk().markUninteresting(c);
+ }
+ Ref targetRef = allRefs().get(magicBranch.dest.branch());
+ if (targetRef != null) {
+ logger.atFine().log(
+ "Marking target ref %s (%s) uninteresting",
+ magicBranch.dest.branch(), targetRef.getObjectId().name());
+ receivePack
+ .getRevWalk()
+ .markUninteresting(receivePack.getRevWalk().parseCommit(targetRef.getObjectId()));
+ }
}
}
private void rejectImplicitMerges(Set<RevCommit> mergedParents) throws IOException {
- if (!mergedParents.isEmpty()) {
- Ref targetRef = allRefs().get(magicBranch.dest.get());
- if (targetRef != null) {
- RevWalk rw = receivePack.getRevWalk();
- RevCommit tip = rw.parseCommit(targetRef.getObjectId());
- boolean containsImplicitMerges = true;
- for (RevCommit p : mergedParents) {
- containsImplicitMerges &= !rw.isMergedInto(p, tip);
- }
-
- if (containsImplicitMerges) {
- rw.reset();
+ try (TraceTimer traceTimer = newTimer("rejectImplicitMerges")) {
+ if (!mergedParents.isEmpty()) {
+ Ref targetRef = allRefs().get(magicBranch.dest.branch());
+ if (targetRef != null) {
+ RevWalk rw = receivePack.getRevWalk();
+ RevCommit tip = rw.parseCommit(targetRef.getObjectId());
+ boolean containsImplicitMerges = true;
for (RevCommit p : mergedParents) {
- rw.markStart(p);
+ containsImplicitMerges &= !rw.isMergedInto(p, tip);
}
- rw.markUninteresting(tip);
- RevCommit c;
- while ((c = rw.next()) != null) {
- rw.parseBody(c);
- messages.add(
- new CommitValidationMessage(
- "Implicit Merge of " + c.abbreviate(7).name() + " " + c.getShortMessage(),
- ValidationMessage.Type.ERROR));
+
+ if (containsImplicitMerges) {
+ rw.reset();
+ for (RevCommit p : mergedParents) {
+ rw.markStart(p);
+ }
+ rw.markUninteresting(tip);
+ RevCommit c;
+ while ((c = rw.next()) != null) {
+ rw.parseBody(c);
+ messages.add(
+ new CommitValidationMessage(
+ "Implicit Merge of "
+ + abbreviateName(c, rw.getObjectReader())
+ + " "
+ + c.getShortMessage(),
+ ValidationMessage.Type.ERROR));
+ }
+ reject(magicBranch.cmd, "implicit merges detected");
}
- reject(magicBranch.cmd, "implicit merges detected");
}
}
}
@@ -2356,20 +2434,23 @@ class ReceiveCommits {
// Mark all branch tips as uninteresting in the given revwalk,
// so we get only the new commits when walking rw.
private void markHeadsAsUninteresting(RevWalk rw, @Nullable String forRef) {
- int i = 0;
- for (Ref ref : allRefs().values()) {
- if ((ref.getName().startsWith(R_HEADS) || ref.getName().equals(forRef))
- && ref.getObjectId() != null) {
- try {
- rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
- i++;
- } catch (IOException e) {
- logger.atWarning().withCause(e).log(
- "Invalid ref %s in %s", ref.getName(), project.getName());
+ try (TraceTimer traceTimer =
+ newTimer("markHeadsAsUninteresting", Metadata.builder().branchName(forRef))) {
+ int i = 0;
+ for (Ref ref : allRefs().values()) {
+ if ((ref.getName().startsWith(R_HEADS) || ref.getName().equals(forRef))
+ && ref.getObjectId() != null) {
+ try {
+ rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
+ i++;
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Invalid ref %s in %s", ref.getName(), project.getName());
+ }
}
}
+ logger.atFine().log("Marked %d heads as uninteresting", i);
}
- logger.atFine().log("Marked %d heads as uninteresting", i);
}
private static boolean isValidChangeId(String idStr) {
@@ -2390,12 +2471,16 @@ class ReceiveCommits {
}
private ChangeLookup lookupByChangeKey(RevCommit c, Change.Key key) {
- return new ChangeLookup(c, key, queryProvider.get().byBranchKey(magicBranch.dest, key));
+ try (TraceTimer traceTimer = newTimer("lookupByChangeKey")) {
+ return new ChangeLookup(c, key, queryProvider.get().byBranchKey(magicBranch.dest, key));
+ }
}
private ChangeLookup lookupByCommit(RevCommit c) {
- return new ChangeLookup(
- c, null, queryProvider.get().byBranchCommit(magicBranch.dest, c.getName()));
+ try (TraceTimer traceTimer = newTimer("lookupByCommit")) {
+ return new ChangeLookup(
+ c, null, queryProvider.get().byBranchCommit(magicBranch.dest, c.getName()));
+ }
}
/** Represents a commit for which a Change should be created. */
@@ -2418,103 +2503,97 @@ class ReceiveCommits {
}
private void setChangeId(int id) {
- possiblyOverrideWorkInProgress();
-
- changeId = new Change.Id(id);
- ins =
- changeInserterFactory
- .create(changeId, commit, refName)
- .setTopic(magicBranch.topic)
- .setPrivate(setChangeAsPrivate)
- .setWorkInProgress(magicBranch.workInProgress)
- // Changes already validated in validateNewCommits.
- .setValidate(false);
-
- if (magicBranch.merged) {
- ins.setStatus(Change.Status.MERGED);
- }
- cmd = new ReceiveCommand(ObjectId.zeroId(), commit, ins.getPatchSetId().toRefName());
- if (receivePack.getPushCertificate() != null) {
- ins.setPushCertificate(receivePack.getPushCertificate().toTextWithSignature());
- }
- }
-
- private void possiblyOverrideWorkInProgress() {
- // When wip or ready explicitly provided, leave it as is.
- if (magicBranch.workInProgress || magicBranch.ready) {
- return;
+ try (TraceTimer traceTimer = newTimer(CreateRequest.class, "setChangeId")) {
+ changeId = Change.id(id);
+ ins =
+ changeInserterFactory
+ .create(changeId, commit, refName)
+ .setTopic(magicBranch.topic)
+ .setPrivate(setChangeAsPrivate)
+ .setWorkInProgress(magicBranch.shouldSetWorkInProgressOnNewChanges())
+ // Changes already validated in validateNewCommits.
+ .setValidate(false);
+
+ if (magicBranch.merged) {
+ ins.setStatus(Change.Status.MERGED);
+ }
+ cmd = new ReceiveCommand(ObjectId.zeroId(), commit, ins.getPatchSetId().toRefName());
+ if (receivePack.getPushCertificate() != null) {
+ ins.setPushCertificate(receivePack.getPushCertificate().toTextWithSignature());
+ }
}
- magicBranch.workInProgress =
- projectState.is(BooleanProjectConfig.WORK_IN_PROGRESS_BY_DEFAULT)
- || firstNonNull(user.state().getGeneralPreferences().workInProgressByDefault, false);
}
private void addOps(BatchUpdate bu) throws RestApiException {
- checkState(changeId != null, "must call setChangeId before addOps");
- try {
- RevWalk rw = receivePack.getRevWalk();
- rw.parseBody(commit);
- final PatchSet.Id psId = ins.setGroups(groups).getPatchSetId();
- Account.Id me = user.getAccountId();
- List<FooterLine> footerLines = commit.getFooterLines();
- requireNonNull(magicBranch);
-
- // TODO(dborowitz): Support reviewers by email from footers? Maybe not: kernel developers
- // with AOSP accounts already complain about these notifications, and that would make it
- // worse. Might be better to get rid of the feature entirely:
- // https://groups.google.com/d/topic/repo-discuss/tIFxY7L4DXk/discussion
- MailRecipients fromFooters = getRecipientsFromFooters(accountResolver, footerLines);
- fromFooters.remove(me);
-
- Map<String, Short> approvals = magicBranch.labels;
- StringBuilder msg =
- new StringBuilder(
- ApprovalsUtil.renderMessageWithApprovals(
- psId.get(), approvals, Collections.emptyMap()));
- msg.append('.');
- if (!Strings.isNullOrEmpty(magicBranch.message)) {
- msg.append("\n").append(magicBranch.message);
- }
-
- bu.setNotify(magicBranch.getNotifyForNewChange());
- bu.insertChange(
- ins.setReviewersAndCcsAsStrings(
- magicBranch.getCombinedReviewers(fromFooters),
- magicBranch.getCombinedCcs(fromFooters))
- .setApprovals(approvals)
- .setMessage(msg.toString())
- .setRequestScopePropagator(requestScopePropagator)
- .setSendMail(true)
- .setPatchSetDescription(magicBranch.message));
- if (!magicBranch.hashtags.isEmpty()) {
- // Any change owner is allowed to add hashtags when creating a change.
- bu.addOp(
- changeId,
- hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags)).setFireEvent(false));
- }
- if (!Strings.isNullOrEmpty(magicBranch.topic)) {
+ try (TraceTimer traceTimer = newTimer(CreateRequest.class, "addOps")) {
+ checkState(changeId != null, "must call setChangeId before addOps");
+ try {
+ RevWalk rw = receivePack.getRevWalk();
+ rw.parseBody(commit);
+ final PatchSet.Id psId = ins.setGroups(groups).getPatchSetId();
+ Account.Id me = user.getAccountId();
+ List<FooterLine> footerLines = commit.getFooterLines();
+ requireNonNull(magicBranch);
+
+ // TODO(dborowitz): Support reviewers by email from footers? Maybe not: kernel developers
+ // with AOSP accounts already complain about these notifications, and that would make it
+ // worse. Might be better to get rid of the feature entirely:
+ // https://groups.google.com/d/topic/repo-discuss/tIFxY7L4DXk/discussion
+ MailRecipients fromFooters = getRecipientsFromFooters(accountResolver, footerLines);
+ fromFooters.remove(me);
+
+ Map<String, Short> approvals = magicBranch.labels;
+ StringBuilder msg =
+ new StringBuilder(
+ ApprovalsUtil.renderMessageWithApprovals(
+ psId.get(), approvals, Collections.emptyMap()));
+ msg.append('.');
+ if (!Strings.isNullOrEmpty(magicBranch.message)) {
+ msg.append("\n").append(magicBranch.message);
+ }
+
+ bu.setNotify(magicBranch.getNotifyForNewChange());
+ bu.insertChange(
+ ins.setReviewersAndCcsAsStrings(
+ magicBranch.getCombinedReviewers(fromFooters),
+ magicBranch.getCombinedCcs(fromFooters))
+ .setApprovals(approvals)
+ .setMessage(msg.toString())
+ .setRequestScopePropagator(requestScopePropagator)
+ .setSendMail(true)
+ .setPatchSetDescription(magicBranch.message));
+ if (!magicBranch.hashtags.isEmpty()) {
+ // Any change owner is allowed to add hashtags when creating a change.
+ bu.addOp(
+ changeId,
+ hashtagsFactory
+ .create(new HashtagsInput(magicBranch.hashtags))
+ .setFireEvent(false));
+ }
+ if (!Strings.isNullOrEmpty(magicBranch.topic)) {
+ bu.addOp(
+ changeId,
+ new BatchUpdateOp() {
+ @Override
+ public boolean updateChange(ChangeContext ctx) {
+ ctx.getUpdate(psId).setTopic(magicBranch.topic);
+ return true;
+ }
+ });
+ }
bu.addOp(
changeId,
new BatchUpdateOp() {
@Override
public boolean updateChange(ChangeContext ctx) {
- ctx.getUpdate(psId).setTopic(magicBranch.topic);
- return true;
+ CreateRequest.this.change = ctx.getChange();
+ return false;
}
});
+ bu.addOp(changeId, new ChangeProgressOp(progress));
+ } catch (Exception e) {
+ throw asRestApiException(e);
}
- bu.addOp(
- changeId,
- new BatchUpdateOp() {
- @Override
- public boolean updateChange(ChangeContext ctx) {
- CreateRequest.this.change = ctx.getChange();
- return false;
- }
- });
- bu.addOp(changeId, new ChangeProgressOp(progress));
- } catch (Exception e) {
- throw INSERT_EXCEPTION.apply(e);
}
}
}
@@ -2522,67 +2601,88 @@ class ReceiveCommits {
private void submit(Collection<CreateRequest> create, Collection<ReplaceRequest> replace)
throws RestApiException, UpdateException, IOException, ConfigInvalidException,
PermissionBackendException {
- Map<ObjectId, Change> bySha = Maps.newHashMapWithExpectedSize(create.size() + replace.size());
- for (CreateRequest r : create) {
+ try (TraceTimer traceTimer = newTimer("submit")) {
+ Map<ObjectId, Change> bySha = Maps.newHashMapWithExpectedSize(create.size() + replace.size());
+ for (CreateRequest r : create) {
+ requireNonNull(
+ r.change,
+ () -> String.format("cannot submit new change %s; op may not have run", r.changeId));
+ bySha.put(r.commit, r.change);
+ }
+ for (ReplaceRequest r : replace) {
+ bySha.put(r.newCommitId, r.notes.getChange());
+ }
+ Change tipChange = bySha.get(magicBranch.cmd.getNewId());
requireNonNull(
- r.change,
- () -> String.format("cannot submit new change %s; op may not have run", r.changeId));
- bySha.put(r.commit, r.change);
- }
- for (ReplaceRequest r : replace) {
- bySha.put(r.newCommitId, r.notes.getChange());
- }
- Change tipChange = bySha.get(magicBranch.cmd.getNewId());
- requireNonNull(
- tipChange,
- () ->
- String.format(
- "tip of push does not correspond to a change; found these changes: %s", bySha));
- logger.atFine().log(
- "Processing submit with tip change %s (%s)", tipChange.getId(), magicBranch.cmd.getNewId());
- try (MergeOp op = mergeOpProvider.get()) {
- op.merge(tipChange, user, false, new SubmitInput(), false);
+ tipChange,
+ () ->
+ String.format(
+ "tip of push does not correspond to a change; found these changes: %s", bySha));
+ logger.atFine().log(
+ "Processing submit with tip change %s (%s)",
+ tipChange.getId(), magicBranch.cmd.getNewId());
+ try (MergeOp op = mergeOpProvider.get()) {
+ SubmitInput submitInput = new SubmitInput();
+ submitInput.notify = magicBranch.notifyHandling;
+ submitInput.notifyDetails = new HashMap<>();
+ submitInput.notifyDetails.put(
+ RecipientType.TO,
+ new NotifyInfo(magicBranch.notifyTo.stream().map(Object::toString).collect(toList())));
+ submitInput.notifyDetails.put(
+ RecipientType.CC,
+ new NotifyInfo(magicBranch.notifyCc.stream().map(Object::toString).collect(toList())));
+ submitInput.notifyDetails.put(
+ RecipientType.BCC,
+ new NotifyInfo(magicBranch.notifyBcc.stream().map(Object::toString).collect(toList())));
+ op.merge(tipChange, user, false, submitInput, false);
+ }
}
}
private void preparePatchSetsForReplace(List<CreateRequest> newChanges) {
- try {
- readChangesForReplace();
- for (ReplaceRequest req : replaceByChange.values()) {
- if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
- req.validateNewPatchSet();
+ try (TraceTimer traceTimer =
+ newTimer(
+ "preparePatchSetsForReplace", Metadata.builder().resourceCount(newChanges.size()))) {
+ try {
+ readChangesForReplace();
+ for (ReplaceRequest req : replaceByChange.values()) {
+ if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
+ req.validateNewPatchSet();
+ }
}
- }
- } catch (StorageException err) {
- logger.atSevere().withCause(err).log(
- "Cannot read database before replacement for project %s", project.getName());
- rejectRemainingRequests(replaceByChange.values(), INTERNAL_SERVER_ERROR);
- } catch (IOException | PermissionBackendException err) {
- logger.atSevere().withCause(err).log(
- "Cannot read repository before replacement for project %s", project.getName());
- rejectRemainingRequests(replaceByChange.values(), INTERNAL_SERVER_ERROR);
- }
- logger.atFine().log("Read %d changes to replace", replaceByChange.size());
-
- if (magicBranch != null && magicBranch.cmd.getResult() != NOT_ATTEMPTED) {
- // Cancel creations tied to refs/for/ or refs/drafts/ command.
- for (ReplaceRequest req : replaceByChange.values()) {
- if (req.inputCommand == magicBranch.cmd && req.cmd != null) {
+ } catch (StorageException err) {
+ logger.atSevere().withCause(err).log(
+ "Cannot read database before replacement for project %s", project.getName());
+ rejectRemainingRequests(replaceByChange.values(), INTERNAL_SERVER_ERROR);
+ } catch (IOException | PermissionBackendException err) {
+ logger.atSevere().withCause(err).log(
+ "Cannot read repository before replacement for project %s", project.getName());
+ rejectRemainingRequests(replaceByChange.values(), INTERNAL_SERVER_ERROR);
+ }
+ logger.atFine().log("Read %d changes to replace", replaceByChange.size());
+
+ if (magicBranch != null && magicBranch.cmd.getResult() != NOT_ATTEMPTED) {
+ // Cancel creations tied to refs/for/ command.
+ for (ReplaceRequest req : replaceByChange.values()) {
+ if (req.inputCommand == magicBranch.cmd && req.cmd != null) {
+ req.cmd.setResult(Result.REJECTED_OTHER_REASON, "aborted");
+ }
+ }
+ for (CreateRequest req : newChanges) {
req.cmd.setResult(Result.REJECTED_OTHER_REASON, "aborted");
}
}
- for (CreateRequest req : newChanges) {
- req.cmd.setResult(Result.REJECTED_OTHER_REASON, "aborted");
- }
}
}
private void readChangesForReplace() {
- Collection<ChangeNotes> allNotes =
- notesFactory.create(
- replaceByChange.values().stream().map(r -> r.ontoChange).collect(toList()));
- for (ChangeNotes notes : allNotes) {
- replaceByChange.get(notes.getChangeId()).notes = notes;
+ try (TraceTimer traceTimer = newTimer("readChangesForReplace")) {
+ Collection<ChangeNotes> allNotes =
+ notesFactory.create(
+ replaceByChange.values().stream().map(r -> r.ontoChange).collect(toList()));
+ for (ChangeNotes notes : allNotes) {
+ replaceByChange.get(notes.getChangeId()).notes = notes;
+ }
}
}
@@ -2644,24 +2744,26 @@ class ReceiveCommits {
* @throws PermissionBackendException
*/
boolean validateNewPatchSet() throws IOException, PermissionBackendException {
- if (!validateNewPatchSetNoteDb()) {
- return false;
- }
- sameTreeWarning();
-
- if (magicBranch != null) {
- validateMagicBranchWipStatusChange();
- if (inputCommand.getResult() != NOT_ATTEMPTED) {
+ try (TraceTimer traceTimer = newTimer("validateNewPatchSet")) {
+ if (!validateNewPatchSetNoteDb()) {
return false;
}
+ sameTreeWarning();
- if (magicBranch.edit || magicBranch.draft) {
- return newEdit();
+ if (magicBranch != null) {
+ validateMagicBranchWipStatusChange();
+ if (inputCommand.getResult() != NOT_ATTEMPTED) {
+ return false;
+ }
+
+ if (magicBranch.edit) {
+ return newEdit();
+ }
}
- }
- newPatchSet();
- return true;
+ newPatchSet();
+ return true;
+ }
}
boolean validateNewPatchSetForAutoClose() throws IOException, PermissionBackendException {
@@ -2675,59 +2777,63 @@ class ReceiveCommits {
/** Validates the new PS against permissions and notedb status. */
private boolean validateNewPatchSetNoteDb() throws IOException, PermissionBackendException {
- if (notes == null) {
- reject(inputCommand, "change " + ontoChange + " not found");
- return false;
- }
-
- Change change = notes.getChange();
- priorPatchSet = change.currentPatchSetId();
- if (!revisions.containsValue(priorPatchSet)) {
- reject(inputCommand, "change " + ontoChange + " missing revisions");
- return false;
- }
-
- RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
+ try (TraceTimer traceTimer = newTimer("validateNewPatchSetNoteDb")) {
+ if (notes == null) {
+ reject(inputCommand, "change " + ontoChange + " not found");
+ return false;
+ }
- // Not allowed to create a new patch set if the current patch set is locked.
- if (psUtil.isPatchSetLocked(notes)) {
- reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
- return false;
- }
+ Change change = notes.getChange();
+ priorPatchSet = change.currentPatchSetId();
+ if (!revisions.containsValue(priorPatchSet)) {
+ reject(inputCommand, "change " + ontoChange + " missing revisions");
+ return false;
+ }
- try {
- permissions.change(notes).check(ChangePermission.ADD_PATCH_SET);
- } catch (AuthException no) {
- reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
- return false;
- }
+ RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
- if (change.isClosed()) {
- reject(inputCommand, "change " + ontoChange + " closed");
- return false;
- } else if (revisions.containsKey(newCommit)) {
- reject(inputCommand, "commit already exists (in the change)");
- return false;
- }
+ // Not allowed to create a new patch set if the current patch set is locked.
+ if (psUtil.isPatchSetLocked(notes)) {
+ reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
+ return false;
+ }
- for (Ref r : receivePack.getRepository().getRefDatabase().getRefsByPrefix("refs/changes")) {
- if (r.getObjectId().equals(newCommit)) {
- reject(inputCommand, "commit already exists (in the project)");
+ try {
+ permissions.change(notes).check(ChangePermission.ADD_PATCH_SET);
+ } catch (AuthException no) {
+ reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
return false;
}
- }
- for (RevCommit prior : revisions.keySet()) {
- // Don't allow a change to directly depend upon itself. This is a
- // very common error due to users making a new commit rather than
- // amending when trying to address review comments.
- if (receivePack.getRevWalk().isMergedInto(prior, newCommit)) {
- reject(inputCommand, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
+ if (change.isClosed()) {
+ reject(inputCommand, "change " + ontoChange + " closed");
+ return false;
+ } else if (revisions.containsKey(newCommit)) {
+ reject(inputCommand, "commit already exists (in the change)");
return false;
}
- }
- return true;
+ for (Ref r : receivePack.getRepository().getRefDatabase().getRefsByPrefix("refs/changes")) {
+ if (r.getObjectId().equals(newCommit)) {
+ reject(inputCommand, "commit already exists (in the project)");
+ return false;
+ }
+ }
+
+ try (TraceTimer traceTimer2 = newTimer("validateNewPatchSetNoteDb#isMergedInto")) {
+ for (RevCommit prior : revisions.keySet()) {
+ // Don't allow a change to directly depend upon itself. This is a
+ // very common error due to users making a new commit rather than
+ // amending when trying to address review comments.
+ if (receivePack.getRevWalk().isMergedInto(prior, newCommit)) {
+ reject(inputCommand, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
}
/** Validates whether the WIP change is allowed. Rejects inputCommand if not. */
@@ -2746,9 +2852,9 @@ class ReceiveCommits {
if (!hasWriteConfigPermission) {
try {
- permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ permissions.change(notes).check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);
} catch (AuthException e1) {
- reject(inputCommand, ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP);
+ reject(inputCommand, ONLY_USERS_WITH_TOGGLE_WIP_STATE_PERM_CAN_MODIFY_WIP);
}
}
}
@@ -2756,39 +2862,41 @@ class ReceiveCommits {
/** prints a warning if the new PS has the same tree as the previous commit. */
private void sameTreeWarning() throws IOException {
- RevWalk rw = receivePack.getRevWalk();
- RevCommit newCommit = rw.parseCommit(newCommitId);
- RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
-
- if (newCommit.getTree().equals(priorCommit.getTree())) {
- rw.parseBody(newCommit);
- rw.parseBody(priorCommit);
- boolean messageEq =
- Objects.equals(newCommit.getFullMessage(), priorCommit.getFullMessage());
- boolean parentsEq = parentsEqual(newCommit, priorCommit);
- boolean authorEq = authorEqual(newCommit, priorCommit);
- ObjectReader reader = receivePack.getRevWalk().getObjectReader();
-
- if (messageEq && parentsEq && authorEq) {
- addMessage(
- String.format(
- "warning: no changes between prior commit %s and new commit %s",
- reader.abbreviate(priorCommit).name(), reader.abbreviate(newCommit).name()));
- } else {
- StringBuilder msg = new StringBuilder();
- msg.append("warning: ").append(reader.abbreviate(newCommit).name());
- msg.append(":");
- msg.append(" no files changed");
- if (!authorEq) {
- msg.append(", author changed");
- }
- if (!messageEq) {
- msg.append(", message updated");
- }
- if (!parentsEq) {
- msg.append(", was rebased");
+ try (TraceTimer traceTimer = newTimer("sameTreeWarning")) {
+ RevWalk rw = receivePack.getRevWalk();
+ RevCommit newCommit = rw.parseCommit(newCommitId);
+ RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
+
+ if (newCommit.getTree().equals(priorCommit.getTree())) {
+ rw.parseBody(newCommit);
+ rw.parseBody(priorCommit);
+ boolean messageEq =
+ Objects.equals(newCommit.getFullMessage(), priorCommit.getFullMessage());
+ boolean parentsEq = parentsEqual(newCommit, priorCommit);
+ boolean authorEq = authorEqual(newCommit, priorCommit);
+ ObjectReader reader = receivePack.getRevWalk().getObjectReader();
+
+ if (messageEq && parentsEq && authorEq) {
+ addMessage(
+ String.format(
+ "warning: no changes between prior commit %s and new commit %s",
+ abbreviateName(priorCommit, reader), abbreviateName(newCommit, reader)));
+ } else {
+ StringBuilder msg = new StringBuilder();
+ msg.append("warning: ").append(abbreviateName(newCommit, reader));
+ msg.append(":");
+ msg.append(" no files changed");
+ if (!authorEq) {
+ msg.append(", author changed");
+ }
+ if (!messageEq) {
+ msg.append(", message updated");
+ }
+ if (!parentsEq) {
+ msg.append(", was rebased");
+ }
+ addMessage(msg.toString());
}
- addMessage(msg.toString());
}
}
}
@@ -2798,33 +2906,36 @@ class ReceiveCommits {
* failure.
*/
private boolean newEdit() {
- psId = notes.getChange().currentPatchSetId();
- Optional<ChangeEdit> edit;
+ try (TraceTimer traceTimer = newTimer("newEdit")) {
+ psId = notes.getChange().currentPatchSetId();
+ Optional<ChangeEdit> edit;
- try {
- edit = editUtil.byChange(notes, user);
- } catch (AuthException | IOException e) {
- logger.atSevere().withCause(e).log("Cannot retrieve edit");
- return false;
- }
+ try {
+ edit = editUtil.byChange(notes, user);
+ } catch (AuthException | IOException e) {
+ logger.atSevere().withCause(e).log("Cannot retrieve edit");
+ return false;
+ }
- if (edit.isPresent()) {
- if (edit.get().getBasePatchSet().getId().equals(psId)) {
- // replace edit
- cmd =
- new ReceiveCommand(edit.get().getEditCommit(), newCommitId, edit.get().getRefName());
+ if (edit.isPresent()) {
+ if (edit.get().getBasePatchSet().id().equals(psId)) {
+ // replace edit
+ cmd =
+ new ReceiveCommand(
+ edit.get().getEditCommit(), newCommitId, edit.get().getRefName());
+ } else {
+ // delete old edit ref on rebase
+ prev =
+ new ReceiveCommand(
+ edit.get().getEditCommit(), ObjectId.zeroId(), edit.get().getRefName());
+ createEditCommand();
+ }
} else {
- // delete old edit ref on rebase
- prev =
- new ReceiveCommand(
- edit.get().getEditCommit(), ObjectId.zeroId(), edit.get().getRefName());
createEditCommand();
}
- } else {
- createEditCommand();
- }
- return true;
+ return true;
+ }
}
/** Creates a ReceiveCommand for a new edit. */
@@ -2838,47 +2949,52 @@ class ReceiveCommits {
/** Updates 'this' to add a new patchset. */
private void newPatchSet() throws IOException {
- RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
- psId =
- ChangeUtil.nextPatchSetIdFromAllRefsMap(allRefs(), notes.getChange().currentPatchSetId());
- info = patchSetInfoFactory.get(receivePack.getRevWalk(), newCommit, psId);
- cmd = new ReceiveCommand(ObjectId.zeroId(), newCommitId, psId.toRefName());
+ try (TraceTimer traceTimer = newTimer("newPatchSet")) {
+ RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
+ psId =
+ ChangeUtil.nextPatchSetIdFromAllRefsMap(
+ allRefs(), notes.getChange().currentPatchSetId());
+ info = patchSetInfoFactory.get(receivePack.getRevWalk(), newCommit, psId);
+ cmd = new ReceiveCommand(ObjectId.zeroId(), newCommitId, psId.toRefName());
+ }
}
void addOps(BatchUpdate bu, @Nullable Task progress) throws IOException {
- if (magicBranch != null && (magicBranch.edit || magicBranch.draft)) {
- bu.addOp(notes.getChangeId(), new ReindexOnlyOp());
- if (prev != null) {
- bu.addRepoOnlyOp(new UpdateOneRefOp(prev));
+ try (TraceTimer traceTimer = newTimer("addOps")) {
+ if (magicBranch != null && magicBranch.edit) {
+ bu.addOp(notes.getChangeId(), new ReindexOnlyOp());
+ if (prev != null) {
+ bu.addRepoOnlyOp(new UpdateOneRefOp(prev));
+ }
+ bu.addRepoOnlyOp(new UpdateOneRefOp(cmd));
+ return;
+ }
+ RevWalk rw = receivePack.getRevWalk();
+ // TODO(dborowitz): Move to ReplaceOp#updateRepo.
+ RevCommit newCommit = rw.parseCommit(newCommitId);
+ rw.parseBody(newCommit);
+
+ RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
+ replaceOp =
+ replaceOpFactory
+ .create(
+ projectState,
+ notes.getChange().getDest(),
+ checkMergedInto,
+ checkMergedInto ? inputCommand.getNewId().name() : null,
+ priorPatchSet,
+ priorCommit,
+ psId,
+ newCommit,
+ info,
+ groups,
+ magicBranch,
+ receivePack.getPushCertificate())
+ .setRequestScopePropagator(requestScopePropagator);
+ bu.addOp(notes.getChangeId(), replaceOp);
+ if (progress != null) {
+ bu.addOp(notes.getChangeId(), new ChangeProgressOp(progress));
}
- bu.addRepoOnlyOp(new UpdateOneRefOp(cmd));
- return;
- }
- RevWalk rw = receivePack.getRevWalk();
- // TODO(dborowitz): Move to ReplaceOp#updateRepo.
- RevCommit newCommit = rw.parseCommit(newCommitId);
- rw.parseBody(newCommit);
-
- RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
- replaceOp =
- replaceOpFactory
- .create(
- projectState,
- notes.getChange().getDest(),
- checkMergedInto,
- checkMergedInto ? inputCommand.getNewId().name() : null,
- priorPatchSet,
- priorCommit,
- psId,
- newCommit,
- info,
- groups,
- magicBranch,
- receivePack.getPushCertificate())
- .setRequestScopePropagator(requestScopePropagator);
- bu.addOp(notes.getChangeId(), replaceOp);
- if (progress != null) {
- bu.addOp(notes.getChangeId(), new ChangeProgressOp(progress));
}
}
@@ -2899,12 +3015,12 @@ class ReceiveCommits {
private void addOps(BatchUpdate bu) {
bu.addOp(
- psId.getParentKey(),
+ psId.changeId(),
new BatchUpdateOp() {
@Override
public boolean updateChange(ChangeContext ctx) {
PatchSet ps = psUtil.get(ctx.getNotes(), psId);
- List<String> oldGroups = ps.getGroups();
+ List<String> oldGroups = ps.groups();
if (oldGroups == null) {
if (groups == null) {
return false;
@@ -2912,7 +3028,7 @@ class ReceiveCommits {
} else if (sameGroups(oldGroups, groups)) {
return false;
}
- psUtil.setGroups(ctx.getUpdate(psId), ps, groups);
+ ctx.getUpdate(psId).setGroups(groups);
return true;
}
});
@@ -2976,7 +3092,11 @@ class ReceiveCommits {
}
private void initChangeRefMaps() {
- if (refsByChange == null) {
+ if (refsByChange != null) {
+ return;
+ }
+
+ try (TraceTimer traceTimer = newTimer("initChangeRefMaps")) {
int estRefsPerChange = 4;
refsById = MultimapBuilder.hashKeys().arrayListValues().build();
refsByChange =
@@ -2989,7 +3109,7 @@ class ReceiveCommits {
PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
if (psId != null) {
refsById.put(obj, ref);
- refsByChange.put(psId.getParentKey(), ref);
+ refsByChange.put(psId.changeId(), ref);
}
}
}
@@ -3035,17 +3155,19 @@ class ReceiveCommits {
// Run RefValidators on the command. If any validator fails, the command status is set to
// REJECTED, and the return value is 'false'
private boolean validRefOperation(ReceiveCommand cmd) {
- RefOperationValidators refValidators = refValidatorsFactory.create(getProject(), user, cmd);
+ try (TraceTimer traceTimer = newTimer("validRefOperation")) {
+ RefOperationValidators refValidators = refValidatorsFactory.create(getProject(), user, cmd);
- try {
- messages.addAll(refValidators.validateForRefOperation());
- } catch (RefOperationValidationException e) {
- messages.addAll(Lists.newArrayList(e.getMessages()));
- reject(cmd, e.getMessage());
- return false;
- }
+ try {
+ messages.addAll(refValidators.validateForRefOperation());
+ } catch (RefOperationValidationException e) {
+ messages.addAll(e.getMessages());
+ reject(cmd, e.getMessage());
+ return false;
+ }
- return true;
+ return true;
+ }
}
/**
@@ -3053,185 +3175,196 @@ class ReceiveCommits {
*
* <p>On validation failure, the command is rejected.
*/
- private void validateRegularPushCommits(Branch.NameKey branch, ReceiveCommand cmd)
+ private void validateRegularPushCommits(BranchNameKey branch, ReceiveCommand cmd)
throws PermissionBackendException {
- boolean skipValidation =
- !RefNames.REFS_CONFIG.equals(cmd.getRefName())
- && !(MagicBranch.isMagicBranch(cmd.getRefName())
- || NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches())
- && pushOptions.containsKey(PUSH_OPTION_SKIP_VALIDATION);
- if (skipValidation) {
- if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) {
- reject(cmd, "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION);
- return;
- }
+ try (TraceTimer traceTimer =
+ newTimer("validateRegularPushCommits", Metadata.builder().branchName(branch.branch()))) {
+ boolean skipValidation =
+ !RefNames.REFS_CONFIG.equals(cmd.getRefName())
+ && !(MagicBranch.isMagicBranch(cmd.getRefName())
+ || NEW_PATCHSET_PATTERN.matcher(cmd.getRefName()).matches())
+ && pushOptions.containsKey(PUSH_OPTION_SKIP_VALIDATION);
+ if (skipValidation) {
+ if (projectState.is(BooleanProjectConfig.USE_SIGNED_OFF_BY)) {
+ reject(cmd, "requireSignedOffBy prevents option " + PUSH_OPTION_SKIP_VALIDATION);
+ return;
+ }
- Optional<AuthException> err =
- checkRefPermission(permissions.ref(branch.get()), RefPermission.SKIP_VALIDATION);
- if (err.isPresent()) {
- rejectProhibited(cmd, err.get());
- return;
- }
- if (!Iterables.isEmpty(rejectCommits)) {
- reject(cmd, "reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION);
+ Optional<AuthException> err =
+ checkRefPermission(permissions.ref(branch.branch()), RefPermission.SKIP_VALIDATION);
+ if (err.isPresent()) {
+ rejectProhibited(cmd, err.get());
+ return;
+ }
+ if (!Iterables.isEmpty(rejectCommits)) {
+ reject(cmd, "reject-commits prevents " + PUSH_OPTION_SKIP_VALIDATION);
+ }
}
- }
- BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
- RevWalk walk = receivePack.getRevWalk();
- walk.reset();
- walk.sort(RevSort.NONE);
- try {
- RevObject parsedObject = walk.parseAny(cmd.getNewId());
- if (!(parsedObject instanceof RevCommit)) {
- return;
- }
- ListMultimap<ObjectId, Ref> existing = changeRefsById();
- walk.markStart((RevCommit) parsedObject);
- markHeadsAsUninteresting(walk, cmd.getRefName());
- int limit = receiveConfig.maxBatchCommits;
- int n = 0;
- for (RevCommit c; (c = walk.next()) != null; ) {
- // Even if skipValidation is set, we still get here when at least one plugin
- // commit validator requires to validate all commits. In this case, however,
- // we don't need to check the commit limit.
- if (++n > limit && !skipValidation) {
- logger.atFine().log("Number of new commits exceeds limit of %d", limit);
- reject(
- cmd,
- String.format(
- "more than %d commits, and %s not set", limit, PUSH_OPTION_SKIP_VALIDATION));
+ BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
+ RevWalk walk = receivePack.getRevWalk();
+ walk.reset();
+ walk.sort(RevSort.NONE);
+ try {
+ RevObject parsedObject = walk.parseAny(cmd.getNewId());
+ if (!(parsedObject instanceof RevCommit)) {
return;
}
- if (existing.keySet().contains(c)) {
- continue;
- }
+ ListMultimap<ObjectId, Ref> existing = changeRefsById();
+ walk.markStart((RevCommit) parsedObject);
+ markHeadsAsUninteresting(walk, cmd.getRefName());
+ int limit = receiveConfig.maxBatchCommits;
+ int n = 0;
+ for (RevCommit c; (c = walk.next()) != null; ) {
+ // Even if skipValidation is set, we still get here when at least one plugin
+ // commit validator requires to validate all commits. In this case, however,
+ // we don't need to check the commit limit.
+ if (++n > limit && !skipValidation) {
+ logger.atFine().log("Number of new commits exceeds limit of %d", limit);
+ reject(
+ cmd,
+ String.format(
+ "more than %d commits, and %s not set", limit, PUSH_OPTION_SKIP_VALIDATION));
+ return;
+ }
+ if (existing.keySet().contains(c)) {
+ continue;
+ }
- if (!validator.validCommit(
- walk.getObjectReader(), cmd, c, false, messages, rejectCommits, null, skipValidation)) {
- break;
+ BranchCommitValidator.Result validationResult =
+ validator.validateCommit(
+ walk.getObjectReader(), cmd, c, false, rejectCommits, null, skipValidation);
+ messages.addAll(validationResult.messages());
+ if (!validationResult.isValid()) {
+ break;
+ }
}
+ logger.atFine().log("Validated %d new commits", n);
+ } catch (IOException err) {
+ cmd.setResult(REJECTED_MISSING_OBJECT);
+ logger.atSevere().withCause(err).log(
+ "Invalid pack upload; one or more objects weren't sent");
}
- logger.atFine().log("Validated %d new commits", n);
- } catch (IOException err) {
- cmd.setResult(REJECTED_MISSING_OBJECT);
- logger.atSevere().withCause(err).log("Invalid pack upload; one or more objects weren't sent");
}
}
private void autoCloseChanges(ReceiveCommand cmd, Task progress) {
- logger.atFine().log("Starting auto-closing of changes");
- String refName = cmd.getRefName();
- Set<Change.Id> ids = new HashSet<>();
-
- // TODO(dborowitz): Combine this BatchUpdate with the main one in
- // handleRegularCommands
- try {
- retryHelper.execute(
- updateFactory -> {
- try (BatchUpdate bu =
- updateFactory.create(projectState.getNameKey(), user, TimeUtil.nowTs());
- ObjectInserter ins = repo.newObjectInserter();
- ObjectReader reader = ins.newReader();
- RevWalk rw = new RevWalk(reader)) {
- bu.setRepository(repo, rw, ins);
- // TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
-
- RevCommit newTip = rw.parseCommit(cmd.getNewId());
- Branch.NameKey branch = new Branch.NameKey(project.getNameKey(), refName);
-
- rw.reset();
- rw.sort(RevSort.REVERSE);
- rw.markStart(newTip);
- if (!ObjectId.zeroId().equals(cmd.getOldId())) {
- rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
- }
+ try (TraceTimer traceTimer = newTimer("autoCloseChanges")) {
+ logger.atFine().log("Starting auto-closing of changes");
+ String refName = cmd.getRefName();
+ Set<Change.Id> ids = new HashSet<>();
- ListMultimap<ObjectId, Ref> byCommit = changeRefsById();
- Map<Change.Key, ChangeData> changeDataByKey = null;
- List<ReplaceRequest> replaceAndClose = new ArrayList<>();
-
- int existingPatchSets = 0;
- int newPatchSets = 0;
- COMMIT:
- for (RevCommit c; (c = rw.next()) != null; ) {
- rw.parseBody(c);
-
- for (Ref ref : byCommit.get(c.copy())) {
- PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
- Optional<ChangeNotes> notes = getChangeNotes(psId.getParentKey());
- if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
- existingPatchSets++;
- bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
- bu.addOp(
- psId.getParentKey(),
- mergedByPushOpFactory.create(
- requestScopePropagator, psId, refName, newTip.getId().getName()));
- continue COMMIT;
- }
+ // TODO(dborowitz): Combine this BatchUpdate with the main one in
+ // handleRegularCommands
+ try {
+ retryHelper.execute(
+ updateFactory -> {
+ try (BatchUpdate bu =
+ updateFactory.create(projectState.getNameKey(), user, TimeUtil.nowTs());
+ ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo, rw, ins);
+ // TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
+
+ RevCommit newTip = rw.parseCommit(cmd.getNewId());
+ BranchNameKey branch = BranchNameKey.create(project.getNameKey(), refName);
+
+ rw.reset();
+ rw.sort(RevSort.REVERSE);
+ rw.markStart(newTip);
+ if (!ObjectId.zeroId().equals(cmd.getOldId())) {
+ rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
}
- for (String changeId : c.getFooterLines(CHANGE_ID)) {
- if (changeDataByKey == null) {
- changeDataByKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
+ ListMultimap<ObjectId, Ref> byCommit = changeRefsById();
+ Map<Change.Key, ChangeData> changeDataByKey = null;
+ List<ReplaceRequest> replaceAndClose = new ArrayList<>();
+
+ int existingPatchSets = 0;
+ int newPatchSets = 0;
+ COMMIT:
+ for (RevCommit c; (c = rw.next()) != null; ) {
+ rw.parseBody(c);
+
+ for (Ref ref : byCommit.get(c.copy())) {
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
+ if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
+ existingPatchSets++;
+ bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
+ bu.addOp(
+ psId.changeId(),
+ mergedByPushOpFactory.create(
+ requestScopePropagator, psId, refName, newTip.getId().getName()));
+ continue COMMIT;
+ }
}
- ChangeData onto = changeDataByKey.get(new Change.Key(changeId.trim()));
- if (onto != null) {
- newPatchSets++;
- // Hold onto this until we're done with the walk, as the call to
- // req.validate below calls isMergedInto which resets the walk.
- ChangeNotes ontoNotes = onto.notes();
- ReplaceRequest req = new ReplaceRequest(ontoNotes.getChangeId(), c, cmd, false);
- req.notes = ontoNotes;
- replaceAndClose.add(req);
- continue COMMIT;
+ for (String changeId : c.getFooterLines(FooterConstants.CHANGE_ID)) {
+ if (changeDataByKey == null) {
+ changeDataByKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
+ }
+
+ ChangeData onto = changeDataByKey.get(Change.key(changeId.trim()));
+ if (onto != null) {
+ newPatchSets++;
+ // Hold onto this until we're done with the walk, as the call to
+ // req.validate below calls isMergedInto which resets the walk.
+ ChangeNotes ontoNotes = onto.notes();
+ ReplaceRequest req =
+ new ReplaceRequest(ontoNotes.getChangeId(), c, cmd, false);
+ req.notes = ontoNotes;
+ replaceAndClose.add(req);
+ continue COMMIT;
+ }
}
}
- }
- for (ReplaceRequest req : replaceAndClose) {
- Change.Id id = req.notes.getChangeId();
- if (!req.validateNewPatchSetForAutoClose()) {
- logger.atFine().log("Not closing %s because validation failed", id);
- continue;
+ for (ReplaceRequest req : replaceAndClose) {
+ Change.Id id = req.notes.getChangeId();
+ if (!req.validateNewPatchSetForAutoClose()) {
+ logger.atFine().log("Not closing %s because validation failed", id);
+ continue;
+ }
+ req.addOps(bu, null);
+ bu.addOp(id, setPrivateOpFactory.create(false, null));
+ bu.addOp(
+ id,
+ mergedByPushOpFactory
+ .create(
+ requestScopePropagator, req.psId, refName, newTip.getId().getName())
+ .setPatchSetProvider(req.replaceOp::getPatchSet));
+ bu.addOp(id, new ChangeProgressOp(progress));
+ ids.add(id);
}
- req.addOps(bu, null);
- bu.addOp(id, setPrivateOpFactory.create(false, null));
- bu.addOp(
- id,
- mergedByPushOpFactory
- .create(requestScopePropagator, req.psId, refName, newTip.getId().getName())
- .setPatchSetProvider(req.replaceOp::getPatchSet));
- bu.addOp(id, new ChangeProgressOp(progress));
- ids.add(id);
+
+ logger.atFine().log(
+ "Auto-closing %d changes with existing patch sets and %d with new patch sets",
+ existingPatchSets, newPatchSets);
+ bu.execute();
+ } catch (IOException | StorageException | PermissionBackendException e) {
+ logger.atSevere().withCause(e).log("Failed to auto-close changes");
+ return null;
}
- logger.atFine().log(
- "Auto-closing %s changes with existing patch sets and %s with new patch sets",
- existingPatchSets, newPatchSets);
- bu.execute();
- } catch (IOException | StorageException | PermissionBackendException e) {
- logger.atSevere().withCause(e).log("Failed to auto-close changes");
- return null;
- }
+ // If we are here, we didn't throw UpdateException. Record the result.
+ // The ordering is indeterminate due to the HashSet; unfortunately, Change.Id doesn't
+ // fit into TreeSet.
+ ids.stream().forEach(id -> resultChangeIds.add(ResultChangeIds.Key.AUTOCLOSED, id));
- // If we are here, we didn't throw UpdateException. Record the result.
- // The ordering is indeterminate due to the HashSet; unfortunately, Change.Id doesn't
- // fit into TreeSet.
- ids.stream().forEach(id -> resultChangeIds.add(ResultChangeIds.Key.AUTOCLOSED, id));
-
- return null;
- },
- // Use a multiple of the default timeout to account for inner retries that may otherwise
- // eat up the whole timeout so that no time is left to retry this outer action.
- RetryHelper.options()
- .timeout(retryHelper.getDefaultTimeout(ActionType.CHANGE_UPDATE).multipliedBy(5))
- .build());
- } catch (RestApiException e) {
- logger.atSevere().withCause(e).log("Can't insert patchset");
- } catch (UpdateException e) {
- logger.atSevere().withCause(e).log("Failed to auto-close changes");
+ return null;
+ },
+ // Use a multiple of the default timeout to account for inner retries that may otherwise
+ // eat up the whole timeout so that no time is left to retry this outer action.
+ RetryHelper.options()
+ .timeout(retryHelper.getDefaultTimeout(ActionType.CHANGE_UPDATE).multipliedBy(5))
+ .build());
+ } catch (RestApiException e) {
+ logger.atSevere().withCause(e).log("Can't insert patchset");
+ } catch (UpdateException e) {
+ logger.atSevere().withCause(e).log("Failed to auto-close changes");
+ }
}
}
@@ -3244,7 +3377,7 @@ class ReceiveCommits {
}
private <T> T executeIndexQuery(Action<T> action) {
- try {
+ try (TraceTimer traceTimer = newTimer("executeIndexQuery")) {
return retryHelper.execute(
ActionType.INDEX_QUERY, action, StorageException.class::isInstance);
} catch (Exception e) {
@@ -3253,19 +3386,22 @@ class ReceiveCommits {
}
}
- private Map<Change.Key, ChangeData> openChangesByKeyByBranch(Branch.NameKey branch) {
- Map<Change.Key, ChangeData> r = new HashMap<>();
- for (ChangeData cd : queryProvider.get().byBranchOpen(branch)) {
- try {
- // ChangeData is not materialised into a ChangeNotes for avoiding
- // to load a potentially large number of changes meta-data into memory
- // which would cause unnecessary disk I/O, CPU and heap utilisation.
- r.put(cd.change().getKey(), cd);
- } catch (NoSuchChangeException e) {
- // Ignore deleted change
+ private Map<Change.Key, ChangeData> openChangesByKeyByBranch(BranchNameKey branch) {
+ try (TraceTimer traceTimer =
+ newTimer("openChangesByKeyByBranch", Metadata.builder().branchName(branch.branch()))) {
+ Map<Change.Key, ChangeData> r = new HashMap<>();
+ for (ChangeData cd : queryProvider.get().byBranchOpen(branch)) {
+ try {
+ // ChangeData is not materialised into a ChangeNotes for avoiding
+ // to load a potentially large number of changes meta-data into memory
+ // which would cause unnecessary disk I/O, CPU and heap utilisation.
+ r.put(cd.change().getKey(), cd);
+ } catch (NoSuchChangeException e) {
+ // Ignore deleted change
+ }
}
+ return r;
}
- return r;
}
// allRefsWatcher hooks into the protocol negotation to get a list of all known refs.
@@ -3275,7 +3411,25 @@ class ReceiveCommits {
return allRefsWatcher.getAllRefs();
}
+ private TraceTimer newTimer(String name) {
+ return newTimer(getClass(), name);
+ }
+
+ private TraceTimer newTimer(Class<?> clazz, String name) {
+ return newTimer(clazz, name, Metadata.builder());
+ }
+
+ private TraceTimer newTimer(String name, Metadata.Builder metadataBuilder) {
+ return newTimer(getClass(), name, metadataBuilder);
+ }
+
+ private TraceTimer newTimer(Class<?> clazz, String name, Metadata.Builder metadataBuilder) {
+ metadataBuilder.projectName(project.getName());
+ return TraceContext.newTimer(clazz.getSimpleName() + "#" + name, metadataBuilder.build());
+ }
+
private static void reject(ReceiveCommand cmd, String why) {
+ logger.atFine().log("Rejecting command '%s': %s", cmd, why);
cmd.setResult(REJECTED_OTHER_REASON, why);
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
index cfe06ea7bd..83bf55441d 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
@@ -14,43 +14,55 @@
package com.google.gerrit.server.git.receive;
-import com.google.auto.value.AutoValue;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.HookUtil;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Provider;
+import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
-import org.eclipse.jgit.transport.BaseReceivePack;
+import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
-/** Exposes only the non refs/changes/ reference names. */
+/**
+ * Exposes only the non refs/changes/ reference names and provide additional haves.
+ *
+ * <p>Negotiation on Git push is suboptimal in that it tends to send more objects to the server than
+ * it should. This results in increased latencies for {@code git push}.
+ *
+ * <p>Ref advertisement for Git pushes still works in a "the server speaks first fashion" as Git
+ * Protocol V2 only addressed fetches Therefore the server needs to send all available references.
+ * For large repositories, this can be in the tens of megabytes to send to the client. We therefore
+ * remove all refs in refs/changes/* to scale down that footprint. Trivially, this would increase
+ * the unnecessary objects that the client has to send to the server because the common ancestor it
+ * finds in negotiation might be further back in history.
+ *
+ * <p>To work around this, we advertise the last 32 changes in that repository as additional {@code
+ * .haves}. This is a heuristical approach that aims at scaling down the number of unnecessary
+ * objects that client sends to the server. Unnecessary here refers to objects that the server
+ * already has.
+ *
+ * <p>TODO(hiesel): Instrument this heuristic and proof its value.
+ */
public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- @VisibleForTesting
- @AutoValue
- public abstract static class Result {
- public abstract Map<String, Ref> allRefs();
-
- public abstract Set<ObjectId> additionalHaves();
- }
-
private final Provider<InternalChangeQuery> queryProvider;
private final Project.NameKey projectName;
@@ -66,31 +78,18 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
"ReceiveCommitsAdvertiseRefsHook cannot be used for UploadPack");
}
- @SuppressWarnings("deprecation")
@Override
- public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException {
- Result r = advertiseRefs(HookUtil.ensureAllRefsAdvertised(rp));
- rp.setAdvertisedRefs(r.allRefs(), r.additionalHaves());
- }
-
- @VisibleForTesting
- public Result advertiseRefs(Map<String, Ref> oldRefs) {
- Map<String, Ref> r = Maps.newHashMapWithExpectedSize(oldRefs.size());
- Set<ObjectId> allPatchSets = Sets.newHashSetWithExpectedSize(oldRefs.size());
- for (Map.Entry<String, Ref> e : oldRefs.entrySet()) {
- String name = e.getKey();
- if (!skip(name)) {
- r.put(name, e.getValue());
- }
- if (name.startsWith(RefNames.REFS_CHANGES)) {
- allPatchSets.add(e.getValue().getObjectId());
- }
- }
- return new AutoValue_ReceiveCommitsAdvertiseRefsHook_Result(
- r, advertiseOpenChanges(allPatchSets));
+ public void advertiseRefs(ReceivePack rp) throws ServiceMayNotContinueException {
+ Map<String, Ref> advertisedRefs = HookUtil.ensureAllRefsAdvertised(rp);
+ advertisedRefs.keySet().stream()
+ .filter(ReceiveCommitsAdvertiseRefsHook::skip)
+ .collect(toImmutableList())
+ .forEach(r -> advertisedRefs.remove(r));
+ rp.setAdvertisedRefs(advertisedRefs, advertiseOpenChanges(rp.getRepository()));
}
- private Set<ObjectId> advertiseOpenChanges(Set<ObjectId> allPatchSets) {
+ private Set<ObjectId> advertiseOpenChanges(Repository repo)
+ throws ServiceMayNotContinueException {
// Advertise some recent open changes, in case a commit is based on one.
int limit = 32;
try {
@@ -109,15 +108,20 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
.byProjectOpen(projectName)) {
PatchSet ps = cd.currentPatchSet();
if (ps != null) {
- ObjectId id = ObjectId.fromString(ps.getRevision().get());
// Ensure we actually observed a patch set ref pointing to this
// object, in case the database is out of sync with the repo and the
// object doesn't actually exist.
- if (allPatchSets.contains(id)) {
- r.add(id);
+ try {
+ Ref psRef = repo.getRefDatabase().exactRef(RefNames.patchSetRef(ps.id()));
+ if (psRef != null) {
+ r.add(ps.commitId());
+ }
+ } catch (IOException e) {
+ throw new ServiceMayNotContinueException(e);
}
}
}
+
return r;
} catch (StorageException err) {
logger.atSevere().withCause(err).log("Cannot list open changes of %s", projectName);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
new file mode 100644
index 0000000000..d5744663eb
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.receive;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
+
+/**
+ * Helper to ensure that the chain for advertising refs is the same in tests and production code.
+ */
+public class ReceiveCommitsAdvertiseRefsHookChain {
+
+ /**
+ * Returns a single {@link AdvertiseRefsHook} that encompasses a chain of {@link
+ * AdvertiseRefsHook} to be used for advertising when processing a Git push.
+ */
+ public static AdvertiseRefsHook create(
+ AllRefsWatcher allRefsWatcher,
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
+ AllUsersName allUsersName,
+ Provider<InternalChangeQuery> queryProvider,
+ Project.NameKey projectName) {
+ return create(
+ allRefsWatcher,
+ usersSelfAdvertiseRefsHook,
+ allUsersName,
+ queryProvider,
+ projectName,
+ false);
+ }
+
+ /**
+ * Returns a single {@link AdvertiseRefsHook} that encompasses a chain of {@link
+ * AdvertiseRefsHook} to be used for advertising when processing a Git push. Omits {@link
+ * HackPushNegotiateHook} as that does not advertise refs on it's own but adds {@code .have} based
+ * on history which is not relevant for the tests we have.
+ */
+ @VisibleForTesting
+ public static AdvertiseRefsHook createForTest(
+ Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName, CurrentUser user) {
+ return create(
+ new AllRefsWatcher(),
+ new UsersSelfAdvertiseRefsHook(Providers.of(user)),
+ new AllUsersName(AllUsersNameProvider.DEFAULT),
+ queryProvider,
+ projectName,
+ true);
+ }
+
+ private static AdvertiseRefsHook create(
+ AllRefsWatcher allRefsWatcher,
+ UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
+ AllUsersName allUsersName,
+ Provider<InternalChangeQuery> queryProvider,
+ Project.NameKey projectName,
+ boolean skipHackPushNegotiateHook) {
+ List<AdvertiseRefsHook> advHooks = new ArrayList<>();
+ advHooks.add(allRefsWatcher);
+ advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
+ if (!skipHackPushNegotiateHook) {
+ advHooks.add(new HackPushNegotiateHook());
+ }
+ if (projectName.equals(allUsersName)) {
+ advHooks.add(usersSelfAdvertiseRefsHook);
+ }
+ return AdvertiseRefsHookChain.newChain(advHooks);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveConfig.java b/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
index b1c9f133a3..cdbf3108c8 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
@@ -27,7 +27,6 @@ import org.eclipse.jgit.lib.Config;
class ReceiveConfig {
final boolean checkMagicRefs;
final boolean checkReferencedObjectsAreReachable;
- final boolean allowDrafts;
final int maxBatchCommits;
final boolean disablePrivateChanges;
private final int systemMaxBatchChanges;
@@ -38,7 +37,6 @@ class ReceiveConfig {
checkMagicRefs = config.getBoolean("receive", null, "checkMagicRefs", true);
checkReferencedObjectsAreReachable =
config.getBoolean("receive", null, "checkReferencedObjectsAreReachable", true);
- allowDrafts = config.getBoolean("change", null, "allowDrafts", false);
maxBatchCommits = config.getInt("receive", null, "maxBatchCommits", 10000);
systemMaxBatchChanges = config.getInt("receive", "maxBatchChanges", 0);
disablePrivateChanges = config.getBoolean("change", null, "disablePrivateChanges", false);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveConstants.java b/java/com/google/gerrit/server/git/receive/ReceiveConstants.java
index 03a1b33bbe..df1888be3f 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveConstants.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveConstants.java
@@ -20,8 +20,8 @@ public final class ReceiveConstants {
public static final String PUSH_OPTION_SKIP_VALIDATION = "skip-validation";
@VisibleForTesting
- public static final String ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP =
- "only change owner or project owner can modify Work-in-Progress";
+ public static final String ONLY_USERS_WITH_TOGGLE_WIP_STATE_PERM_CAN_MODIFY_WIP =
+ "only users with Toogle-Wip-State permission can modify Work-in-Progress";
static final String COMMAND_REJECTION_MESSAGE_FOOTER =
"Contact an administrator to fix the permissions";
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveRefFilter.java b/java/com/google/gerrit/server/git/receive/ReceiveRefFilter.java
index 16cba53c82..24a89f7d87 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveRefFilter.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveRefFilter.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.git.receive;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CACHE_AUTOMERGE;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+import static com.google.gerrit.entities.RefNames.REFS_CACHE_AUTOMERGE;
+import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
import com.google.common.collect.Maps;
import java.util.Map;
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 4d0d2c3559..e95cf3b198 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -29,19 +29,19 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
@@ -99,7 +99,7 @@ public class ReplaceOp implements BatchUpdateOp {
public interface Factory {
ReplaceOp create(
ProjectState projectState,
- Branch.NameKey dest,
+ BranchNameKey dest,
boolean checkMergedInto,
@Nullable String mergeResultRevId,
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
@@ -132,7 +132,7 @@ public class ReplaceOp implements BatchUpdateOp {
private final ReviewerAdder reviewerAdder;
private final ProjectState projectState;
- private final Branch.NameKey dest;
+ private final BranchNameKey dest;
private final boolean checkMergedInto;
private final String mergeResultRevId;
private final PatchSet.Id priorPatchSetId;
@@ -177,7 +177,7 @@ public class ReplaceOp implements BatchUpdateOp {
@SendEmailExecutor ExecutorService sendEmailExecutor,
ReviewerAdder reviewerAdder,
@Assisted ProjectState projectState,
- @Assisted Branch.NameKey dest,
+ @Assisted BranchNameKey dest,
@Assisted boolean checkMergedInto,
@Assisted @Nullable String mergeResultRevId,
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
@@ -232,7 +232,7 @@ public class ReplaceOp implements BatchUpdateOp {
commitId);
if (checkMergedInto) {
- String mergedInto = findMergedInto(ctx, dest.get(), commit);
+ String mergedInto = findMergedInto(ctx, dest.branch(), commit);
if (mergedInto != null) {
mergedByPushOp =
mergedByPushOpFactory.create(
@@ -255,7 +255,7 @@ public class ReplaceOp implements BatchUpdateOp {
}
if (groups.isEmpty()) {
PatchSet prevPs = psUtil.current(notes);
- groups = prevPs != null ? prevPs.getGroups() : ImmutableList.of();
+ groups = prevPs != null ? prevPs.groups() : ImmutableList.of();
}
ChangeData cd = changeDataFactory.create(ctx.getNotes());
@@ -295,7 +295,7 @@ public class ReplaceOp implements BatchUpdateOp {
}
if (shouldPublishComments()) {
boolean workInProgress = change.isWorkInProgress();
- if (magicBranch != null && magicBranch.workInProgress) {
+ if (magicBranch.workInProgress) {
workInProgress = true;
}
comments = publishComments(ctx, workInProgress);
@@ -459,7 +459,7 @@ public class ReplaceOp implements BatchUpdateOp {
continue;
}
- LabelType lt = projectState.getLabelTypes().byLabel(a.getLabelId());
+ LabelType lt = projectState.getLabelTypes().byLabel(a.labelId());
if (lt != null) {
current.put(lt.getName(), a);
}
@@ -481,11 +481,7 @@ public class ReplaceOp implements BatchUpdateOp {
change.setCurrentPatchSet(info);
List<String> idList = commit.getFooterLines(CHANGE_ID);
- if (idList.isEmpty()) {
- change.setKey(new Change.Key("I" + commitId.name()));
- } else {
- change.setKey(new Change.Key(idList.get(idList.size() - 1).trim()));
- }
+ change.setKey(Change.key(idList.get(idList.size() - 1).trim()));
}
private List<Comment> publishComments(ChangeContext ctx, boolean workInProgress) {
@@ -548,7 +544,7 @@ public class ReplaceOp implements BatchUpdateOp {
try {
ReplacePatchSetSender cm =
replacePatchSetFactory.create(projectState.getNameKey(), notes.getChangeId());
- cm.setFrom(ctx.getAccount().getAccount().getId());
+ cm.setFrom(ctx.getAccount().account().id());
cm.setPatchSet(newPatchSet, info);
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
cm.setNotify(ctx.getNotify(notes.getChangeId()));
@@ -556,7 +552,7 @@ public class ReplaceOp implements BatchUpdateOp {
Streams.concat(
oldRecipients.getReviewers().stream(),
reviewerAdditions.flattenResults(AddReviewersOp.Result::addedReviewers).stream()
- .map(PatchSetApproval::getAccountId))
+ .map(PatchSetApproval::accountId))
.collect(toImmutableSet()));
cm.addExtraCC(
Streams.concat(
@@ -567,7 +563,7 @@ public class ReplaceOp implements BatchUpdateOp {
cm.send();
} catch (Exception e) {
logger.atSevere().withCause(e).log(
- "Cannot send email for new patch set %s", newPatchSet.getId());
+ "Cannot send email for new patch set %s", newPatchSet.id());
}
}
diff --git a/java/com/google/gerrit/server/git/receive/ResultChangeIds.java b/java/com/google/gerrit/server/git/receive/ResultChangeIds.java
index e326141a1f..805822c5e3 100644
--- a/java/com/google/gerrit/server/git/receive/ResultChangeIds.java
+++ b/java/com/google/gerrit/server/git/receive/ResultChangeIds.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.git.receive;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
diff --git a/java/com/google/gerrit/server/git/receive/testing/BUILD b/java/com/google/gerrit/server/git/receive/testing/BUILD
new file mode 100644
index 0000000000..06407aefea
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/testing/BUILD
@@ -0,0 +1,14 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+java_library(
+ name = "testing",
+ testonly = 1,
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//lib:guava",
+ "//lib:jgit",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ ],
+)
diff --git a/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java b/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java
new file mode 100644
index 0000000000..c54ab25f94
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.receive.testing;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.StreamSupport;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RefAdvertiser;
+
+/** Helper to collect advertised refs and additonal haves and verify them in tests. */
+public class TestRefAdvertiser extends RefAdvertiser {
+
+ @VisibleForTesting
+ @AutoValue
+ public abstract static class Result {
+ public abstract Map<String, Ref> allRefs();
+
+ public abstract Set<ObjectId> additionalHaves();
+
+ public static Result create(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) {
+ return new AutoValue_TestRefAdvertiser_Result(allRefs, additionalHaves);
+ }
+ }
+
+ private final Map<String, Ref> advertisedRefs;
+ private final Set<ObjectId> additionalHaves;
+ private final Repository repo;
+
+ public TestRefAdvertiser(Repository repo) {
+ advertisedRefs = new HashMap<>();
+ additionalHaves = new HashSet<>();
+ this.repo = repo;
+ }
+
+ @Override
+ protected void writeOne(CharSequence line) throws IOException {
+ List<String> lineParts =
+ StreamSupport.stream(Splitter.on(' ').split(line).spliterator(), false)
+ .map(String::trim)
+ .collect(toImmutableList());
+ if (".have".equals(lineParts.get(1))) {
+ additionalHaves.add(ObjectId.fromString(lineParts.get(0)));
+ } else {
+ ObjectId id = ObjectId.fromString(lineParts.get(0));
+ Ref ref =
+ repo.getRefDatabase().getRefs().stream()
+ .filter(r -> r.getObjectId().equals(id))
+ .findAny()
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ line.toString() + " does not conform to expected pattern"));
+ advertisedRefs.put(lineParts.get(1), ref);
+ }
+ }
+
+ @Override
+ protected void end() {}
+
+ public Result result() {
+ return Result.create(advertisedRefs, additionalHaves);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/validators/AccountValidator.java b/java/com/google/gerrit/server/git/validators/AccountValidator.java
index 08870d36dc..c6af49cab6 100644
--- a/java/com/google/gerrit/server/git/validators/AccountValidator.java
+++ b/java/com/google/gerrit/server/git/validators/AccountValidator.java
@@ -18,7 +18,7 @@ import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountConfig;
import com.google.gerrit.server.account.AccountProperties;
@@ -87,10 +87,10 @@ public class AccountValidator {
messages.add("cannot deactivate own account");
}
- String newPreferredEmail = newAccount.get().getPreferredEmail();
+ String newPreferredEmail = newAccount.get().preferredEmail();
if (newPreferredEmail != null
&& (!oldAccount.isPresent()
- || !newPreferredEmail.equals(oldAccount.get().getPreferredEmail()))) {
+ || !newPreferredEmail.equals(oldAccount.get().preferredEmail()))) {
if (!emailValidator.isValid(newPreferredEmail)) {
messages.add(
String.format(
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index ff21b2dddc..6773d29fc7 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.git.validators;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
+import static com.google.gerrit.entities.Change.CHANGE_ID_PATTERN;
+import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
+import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
import static java.util.stream.Collectors.toList;
import com.google.common.annotations.VisibleForTesting;
@@ -25,14 +25,14 @@ import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
@@ -127,7 +127,7 @@ public class CommitValidators {
public CommitValidators forReceiveCommits(
PermissionBackend.ForProject forProject,
- Branch.NameKey branch,
+ BranchNameKey branch,
IdentifiedUser user,
SshInfo sshInfo,
NoteMap rejectCommits,
@@ -135,8 +135,8 @@ public class CommitValidators {
@Nullable Change change,
boolean skipValidation)
throws IOException {
- PermissionBackend.ForRef perm = forProject.ref(branch.get());
- ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
+ PermissionBackend.ForRef perm = forProject.ref(branch.branch());
+ ProjectState projectState = projectCache.checkedGet(branch.project());
ImmutableList.Builder<CommitValidationListener> validators = ImmutableList.builder();
validators
.add(new UploadMergesPermissionValidator(perm))
@@ -164,21 +164,21 @@ public class CommitValidators {
public CommitValidators forGerritCommits(
PermissionBackend.ForProject forProject,
- Branch.NameKey branch,
+ BranchNameKey branch,
IdentifiedUser user,
SshInfo sshInfo,
RevWalk rw,
@Nullable Change change)
throws IOException {
- PermissionBackend.ForRef perm = forProject.ref(branch.get());
- ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
+ PermissionBackend.ForRef perm = forProject.ref(branch.branch());
+ ProjectState projectState = projectCache.checkedGet(branch.project());
ImmutableList.Builder<CommitValidationListener> validators = ImmutableList.builder();
validators
.add(new UploadMergesPermissionValidator(perm))
.add(new ProjectStateValidationListener(projectState))
.add(new AmendedGerritMergeCommitValidationListener(perm, gerritIdent))
.add(new AuthorUploaderValidator(user, perm, urlFormatter.get()))
- .add(new SignedOffByValidator(user, perm, projectCache.checkedGet(branch.getParentKey())))
+ .add(new SignedOffByValidator(user, perm, projectCache.checkedGet(branch.project())))
.add(
new ChangeIdValidator(
projectState,
@@ -196,7 +196,7 @@ public class CommitValidators {
}
public CommitValidators forMergedCommits(
- PermissionBackend.ForProject forProject, Branch.NameKey branch, IdentifiedUser user)
+ PermissionBackend.ForProject forProject, BranchNameKey branch, IdentifiedUser user)
throws IOException {
// Generally only include validators that are based on permissions of the
// user creating a change for a merged commit; generally exclude
@@ -211,11 +211,11 @@ public class CommitValidators {
// discuss what to do about it.
// - Plugin validators may do things like require certain commit message
// formats, so we play it safe and exclude them.
- PermissionBackend.ForRef perm = forProject.ref(branch.get());
+ PermissionBackend.ForRef perm = forProject.ref(branch.branch());
ImmutableList.Builder<CommitValidationListener> validators = ImmutableList.builder();
validators
.add(new UploadMergesPermissionValidator(perm))
- .add(new ProjectStateValidationListener(projectCache.checkedGet(branch.getParentKey())))
+ .add(new ProjectStateValidationListener(projectCache.checkedGet(branch.project())))
.add(new AuthorUploaderValidator(user, perm, urlFormatter.get()))
.add(new CommitterUploaderValidator(user, perm, urlFormatter.get()));
return new CommitValidators(validators.build());
@@ -395,7 +395,7 @@ public class CommitValidators {
/** If this is the special project configuration branch, validate the config. */
public static class ConfigValidator implements CommitValidationListener {
private final ProjectConfig.Factory projectConfigFactory;
- private final Branch.NameKey branch;
+ private final BranchNameKey branch;
private final IdentifiedUser user;
private final RevWalk rw;
private final AllUsersName allUsers;
@@ -403,7 +403,7 @@ public class CommitValidators {
public ConfigValidator(
ProjectConfig.Factory projectConfigFactory,
- Branch.NameKey branch,
+ BranchNameKey branch,
IdentifiedUser user,
RevWalk rw,
AllUsersName allUsers,
@@ -419,7 +419,7 @@ public class CommitValidators {
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
- if (REFS_CONFIG.equals(branch.get())) {
+ if (REFS_CONFIG.equals(branch.branch())) {
List<CommitValidationMessage> messages = new ArrayList<>();
try {
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidationListener.java b/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
index 6edd04eeff..b47d7d656b 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.git.validators;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.project.ProjectState;
@@ -44,7 +44,7 @@ public interface MergeValidationListener {
Repository repo,
CodeReviewCommit commit,
ProjectState destProject,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException;
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 75be8f35bf..e17e1290a9 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -17,16 +17,16 @@ package com.google.gerrit.server.git.validators;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountProperties;
import com.google.gerrit.server.config.AllProjectsName;
@@ -80,7 +80,7 @@ public class MergeValidators {
Repository repo,
CodeReviewCommit commit,
ProjectState destProject,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException {
@@ -156,11 +156,11 @@ public class MergeValidators {
final Repository repo,
final CodeReviewCommit commit,
final ProjectState destProject,
- final Branch.NameKey destBranch,
+ final BranchNameKey destBranch,
final PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException {
- if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
+ if (RefNames.REFS_CONFIG.equals(destBranch.branch())) {
final Project.NameKey newParent;
try {
ProjectConfig cfg = projectConfigFactory.create(destProject.getNameKey());
@@ -251,7 +251,7 @@ public class MergeValidators {
Repository repo,
CodeReviewCommit commit,
ProjectState destProject,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException {
@@ -285,18 +285,17 @@ public class MergeValidators {
Repository repo,
CodeReviewCommit commit,
ProjectState destProject,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException {
- Account.Id accountId = Account.Id.fromRef(destBranch.get());
+ Account.Id accountId = Account.Id.fromRef(destBranch.branch());
if (!allUsersName.equals(destProject.getNameKey()) || accountId == null) {
return;
}
ChangeData cd =
- changeDataFactory.create(
- destProject.getProject().getNameKey(), patchSetId.getParentKey());
+ changeDataFactory.create(destProject.getProject().getNameKey(), patchSetId.changeId());
try {
if (!cd.currentFilePaths().contains(AccountProperties.ACCOUNT_CONFIG)) {
return;
@@ -336,13 +335,13 @@ public class MergeValidators {
Repository repo,
CodeReviewCommit commit,
ProjectState destProject,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
PatchSet.Id patchSetId,
IdentifiedUser caller)
throws MergeValidationException {
// Groups are stored inside the 'All-Users' repository.
if (!allUsersName.equals(destProject.getNameKey())
- || !RefNames.isGroupRef(destBranch.get())) {
+ || !RefNames.isGroupRef(destBranch.branch())) {
return;
}
diff --git a/java/com/google/gerrit/server/git/validators/OnSubmitValidationListener.java b/java/com/google/gerrit/server/git/validators/OnSubmitValidationListener.java
index 308fdc01af..432dda32ca 100644
--- a/java/com/google/gerrit/server/git/validators/OnSubmitValidationListener.java
+++ b/java/com/google/gerrit/server/git/validators/OnSubmitValidationListener.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.git.validators;
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.RefCache;
import com.google.gerrit.server.update.ChainedReceiveCommands;
import com.google.gerrit.server.validators.ValidationException;
diff --git a/java/com/google/gerrit/server/git/validators/OnSubmitValidators.java b/java/com/google/gerrit/server/git/validators/OnSubmitValidators.java
index 409240e257..6faa7afb36 100644
--- a/java/com/google/gerrit/server/git/validators/OnSubmitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/OnSubmitValidators.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git.validators;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.validators.OnSubmitValidationListener.Arguments;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.submit.IntegrationException;
diff --git a/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java b/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java
index 9eaf2d2629..d27cc38f15 100644
--- a/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java
+++ b/java/com/google/gerrit/server/git/validators/RefOperationValidationException.java
@@ -14,27 +14,28 @@
package com.google.gerrit.server.git.validators;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.server.validators.ValidationException;
public class RefOperationValidationException extends ValidationException {
private static final long serialVersionUID = 1L;
- private final Iterable<ValidationMessage> messages;
+ private final ImmutableList<ValidationMessage> messages;
- public RefOperationValidationException(String reason, Iterable<ValidationMessage> messages) {
+ public RefOperationValidationException(String reason, ImmutableList<ValidationMessage> messages) {
super(reason);
this.messages = messages;
}
- public Iterable<ValidationMessage> getMessages() {
+ public ImmutableList<ValidationMessage> getMessages() {
return messages;
}
@Override
public String getMessage() {
- StringBuilder msg = new StringBuilder(super.getMessage());
- for (ValidationMessage error : messages) {
- msg.append("\n").append(error.getMessage());
- }
- return msg.toString();
+ return messages.stream()
+ .map(ValidationMessage::getMessage)
+ .collect(joining("\n", super.getMessage() + "\n", ""));
}
}
diff --git a/java/com/google/gerrit/server/git/validators/RefOperationValidators.java b/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
index 3c0208c787..e9734a3444 100644
--- a/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
+++ b/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
@@ -13,14 +13,14 @@
// limitations under the License.
package com.google.gerrit.server.git.validators;
-import com.google.common.base.Predicate;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.events.RefReceivedEvent;
@@ -39,8 +39,6 @@ import org.eclipse.jgit.transport.ReceiveCommand;
public class RefOperationValidators {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private static final GetErrorMessages GET_ERRORS = new GetErrorMessages();
-
public interface Factory {
RefOperationValidators create(Project project, IdentifiedUser user, ReceiveCommand cmd);
}
@@ -93,22 +91,15 @@ public class RefOperationValidators {
return messages;
}
- private void throwException(Iterable<ValidationMessage> messages, RefReceivedEvent event)
+ private void throwException(List<ValidationMessage> messages, RefReceivedEvent event)
throws RefOperationValidationException {
- Iterable<ValidationMessage> errors = Iterables.filter(messages, GET_ERRORS);
String header =
String.format(
"Ref \"%s\" %S in project %s validation failed",
event.command.getRefName(), event.command.getType(), event.project.getName());
logger.atSevere().log(header);
- throw new RefOperationValidationException(header, errors);
- }
-
- private static class GetErrorMessages implements Predicate<ValidationMessage> {
- @Override
- public boolean apply(ValidationMessage input) {
- return input.isError();
- }
+ throw new RefOperationValidationException(
+ header, messages.stream().filter(ValidationMessage::isError).collect(toImmutableList()));
}
private static class DisallowCreationAndDeletionOfGerritMaintainedBranches
diff --git a/java/com/google/gerrit/server/git/validators/UploadValidationListener.java b/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
index 13065e4be6..8f1d5e4a25 100644
--- a/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
+++ b/java/com/google/gerrit/server/git/validators/UploadValidationListener.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.git.validators;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.validators.ValidationException;
import java.util.Collection;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/git/validators/UploadValidators.java b/java/com/google/gerrit/server/git/validators/UploadValidators.java
index 25952837d7..6847a28f26 100644
--- a/java/com/google/gerrit/server/git/validators/UploadValidators.java
+++ b/java/com/google/gerrit/server/git/validators/UploadValidators.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.git.validators;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/group/GroupAuditService.java b/java/com/google/gerrit/server/group/GroupAuditService.java
index 4c02adaf5f..30e5d3c6b0 100644
--- a/java/com/google/gerrit/server/group/GroupAuditService.java
+++ b/java/com/google/gerrit/server/group/GroupAuditService.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.group;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.AuditEvent;
import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/group/GroupResolver.java b/java/com/google/gerrit/server/group/GroupResolver.java
index 5fe3e8ea36..a54f465e87 100644
--- a/java/com/google/gerrit/server/group/GroupResolver.java
+++ b/java/com/google/gerrit/server/group/GroupResolver.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.group;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.GroupCache;
@@ -81,7 +81,7 @@ public class GroupResolver {
* @return the group, null if no group is found for the given group ID
*/
public GroupDescription.Basic parseId(String id) {
- AccountGroup.UUID uuid = new AccountGroup.UUID(id);
+ AccountGroup.UUID uuid = AccountGroup.uuid(id);
if (groupBackend.handles(uuid)) {
GroupDescription.Basic d = groupBackend.get(uuid);
if (d != null) {
diff --git a/java/com/google/gerrit/server/group/InternalGroup.java b/java/com/google/gerrit/server/group/InternalGroup.java
index 7828586271..639cd7ab6f 100644
--- a/java/com/google/gerrit/server/group/InternalGroup.java
+++ b/java/com/google/gerrit/server/group/InternalGroup.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.group;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.io.Serializable;
import java.sql.Timestamp;
import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/group/InternalGroupDescription.java b/java/com/google/gerrit/server/group/InternalGroupDescription.java
index 1d2252d05d..c70c8bf9a4 100644
--- a/java/com/google/gerrit/server/group/InternalGroupDescription.java
+++ b/java/com/google/gerrit/server/group/InternalGroupDescription.java
@@ -20,8 +20,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
public class InternalGroupDescription implements GroupDescription.Internal {
diff --git a/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java b/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
index 2a9538d07a..b2d9632122 100644
--- a/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
+++ b/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
@@ -20,9 +20,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ScheduleConfig;
diff --git a/java/com/google/gerrit/server/group/SubgroupResource.java b/java/com/google/gerrit/server/group/SubgroupResource.java
index a33e96b2f7..ceea2dc81d 100644
--- a/java/com/google/gerrit/server/group/SubgroupResource.java
+++ b/java/com/google/gerrit/server/group/SubgroupResource.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.group;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.inject.TypeLiteral;
public class SubgroupResource extends GroupResource {
diff --git a/java/com/google/gerrit/server/group/SystemGroupBackend.java b/java/com/google/gerrit/server/group/SystemGroupBackend.java
index 85c1e7316b..7821a01d67 100644
--- a/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StartupCheck;
import com.google.gerrit.server.StartupException;
@@ -56,19 +56,19 @@ public class SystemGroupBackend extends AbstractGroupBackend {
/** Common UUID assigned to the "Anonymous Users" group. */
public static final AccountGroup.UUID ANONYMOUS_USERS =
- new AccountGroup.UUID(SYSTEM_GROUP_SCHEME + "Anonymous-Users");
+ AccountGroup.uuid(SYSTEM_GROUP_SCHEME + "Anonymous-Users");
/** Common UUID assigned to the "Registered Users" group. */
public static final AccountGroup.UUID REGISTERED_USERS =
- new AccountGroup.UUID(SYSTEM_GROUP_SCHEME + "Registered-Users");
+ AccountGroup.uuid(SYSTEM_GROUP_SCHEME + "Registered-Users");
/** Common UUID assigned to the "Project Owners" placeholder group. */
public static final AccountGroup.UUID PROJECT_OWNERS =
- new AccountGroup.UUID(SYSTEM_GROUP_SCHEME + "Project-Owners");
+ AccountGroup.uuid(SYSTEM_GROUP_SCHEME + "Project-Owners");
/** Common UUID assigned to the "Change Owner" placeholder group. */
public static final AccountGroup.UUID CHANGE_OWNER =
- new AccountGroup.UUID(SYSTEM_GROUP_SCHEME + "Change-Owner");
+ AccountGroup.uuid(SYSTEM_GROUP_SCHEME + "Change-Owner");
private static final AccountGroup.UUID[] all = {
ANONYMOUS_USERS, REGISTERED_USERS, PROJECT_OWNERS, CHANGE_OWNER,
diff --git a/java/com/google/gerrit/server/group/db/AuditLogFormatter.java b/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
index 508f5ef333..ec4c0fcbe3 100644
--- a/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
+++ b/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
@@ -20,8 +20,8 @@ import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.GroupBackend;
@@ -51,7 +51,7 @@ public class AuditLogFormatter {
}
private static Optional<Account> getAccount(AccountCache accountCache, Account.Id accountId) {
- return accountCache.get(accountId).map(AccountState::getAccount);
+ return accountCache.get(accountId).map(AccountState::account);
}
private static Optional<GroupDescription.Basic> getGroup(
@@ -72,7 +72,7 @@ public class AuditLogFormatter {
}
private static Optional<Account> getAccount(ImmutableSet<Account> accounts, Account.Id id) {
- return accounts.stream().filter(account -> account.getId().equals(id)).findAny();
+ return accounts.stream().filter(account -> account.id().equals(id)).findAny();
}
public static AuditLogFormatter createPartiallyWorkingFallBack() {
@@ -119,7 +119,7 @@ public class AuditLogFormatter {
* @return a {@code PersonIdent} which can be used for the author of a commit
*/
public PersonIdent getParsableAuthorIdent(Account account, PersonIdent personIdent) {
- return getParsableAuthorIdent(account.getName(), account.getId(), personIdent);
+ return getParsableAuthorIdent(account.getName(), account.id(), personIdent);
}
/**
diff --git a/java/com/google/gerrit/server/group/db/AuditLogReader.java b/java/com/google/gerrit/server/group/db/AuditLogReader.java
index 106ee6b472..d8f0a0fcc0 100644
--- a/java/com/google/gerrit/server/group/db/AuditLogReader.java
+++ b/java/com/google/gerrit/server/group/db/AuditLogReader.java
@@ -14,18 +14,19 @@
package com.google.gerrit.server.group.db;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.notedb.NoteDbUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -49,12 +50,10 @@ import org.eclipse.jgit.util.RawParseUtils;
public class AuditLogReader {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final String serverId;
private final AllUsersName allUsersName;
@Inject
- public AuditLogReader(@GerritServerId String serverId, AllUsersName allUsersName) {
- this.serverId = serverId;
+ public AuditLogReader(AllUsersName allUsersName) {
this.allUsersName = allUsersName;
}
@@ -69,70 +68,79 @@ public class AuditLogReader {
private ImmutableList<AccountGroupMemberAudit> getMembersAudit(
AccountGroup.Id groupId, List<ParsedCommit> commits) {
- ListMultimap<MemberKey, AccountGroupMemberAudit> audits =
+ ListMultimap<MemberKey, AccountGroupMemberAudit.Builder> audits =
MultimapBuilder.hashKeys().linkedListValues().build();
- ImmutableList.Builder<AccountGroupMemberAudit> result = ImmutableList.builder();
+ List<AccountGroupMemberAudit.Builder> result = new ArrayList<>();
for (ParsedCommit pc : commits) {
for (Account.Id id : pc.addedMembers()) {
MemberKey key = MemberKey.create(groupId, id);
- AccountGroupMemberAudit audit =
- new AccountGroupMemberAudit(
- new AccountGroupMemberAudit.Key(id, groupId, pc.when()), pc.authorId());
+ AccountGroupMemberAudit.Builder audit =
+ AccountGroupMemberAudit.builder()
+ .memberId(id)
+ .groupId(groupId)
+ .addedOn(pc.when())
+ .addedBy(pc.authorId());
audits.put(key, audit);
result.add(audit);
}
for (Account.Id id : pc.removedMembers()) {
- List<AccountGroupMemberAudit> adds = audits.get(MemberKey.create(groupId, id));
+ List<AccountGroupMemberAudit.Builder> adds = audits.get(MemberKey.create(groupId, id));
if (!adds.isEmpty()) {
- AccountGroupMemberAudit audit = adds.remove(0);
+ AccountGroupMemberAudit.Builder audit = adds.remove(0);
audit.removed(pc.authorId(), pc.when());
} else {
// Match old behavior of DbGroupAuditListener and add a "legacy" add/remove pair.
- AccountGroupMemberAudit audit =
- new AccountGroupMemberAudit(
- new AccountGroupMemberAudit.Key(id, groupId, pc.when()), pc.authorId());
- audit.removedLegacy();
+ AccountGroupMemberAudit.Builder audit =
+ AccountGroupMemberAudit.builder()
+ .groupId(groupId)
+ .memberId(id)
+ .addedOn(pc.when())
+ .addedBy(pc.authorId())
+ .removedLegacy();
result.add(audit);
}
}
}
- return result.build();
+ return result.stream().map(AccountGroupMemberAudit.Builder::build).collect(toImmutableList());
}
- public ImmutableList<AccountGroupByIdAud> getSubgroupsAudit(
+ public ImmutableList<AccountGroupByIdAudit> getSubgroupsAudit(
Repository repo, AccountGroup.UUID uuid) throws IOException, ConfigInvalidException {
return getSubgroupsAudit(getGroupId(repo, uuid), parseCommits(repo, uuid));
}
- private ImmutableList<AccountGroupByIdAud> getSubgroupsAudit(
+ private ImmutableList<AccountGroupByIdAudit> getSubgroupsAudit(
AccountGroup.Id groupId, List<ParsedCommit> commits) {
- ListMultimap<SubgroupKey, AccountGroupByIdAud> audits =
+ ListMultimap<SubgroupKey, AccountGroupByIdAudit.Builder> audits =
MultimapBuilder.hashKeys().linkedListValues().build();
- ImmutableList.Builder<AccountGroupByIdAud> result = ImmutableList.builder();
+ List<AccountGroupByIdAudit.Builder> result = new ArrayList<>();
for (ParsedCommit pc : commits) {
for (AccountGroup.UUID uuid : pc.addedSubgroups()) {
SubgroupKey key = SubgroupKey.create(groupId, uuid);
- AccountGroupByIdAud audit =
- new AccountGroupByIdAud(
- new AccountGroupByIdAud.Key(groupId, uuid, pc.when()), pc.authorId());
+ AccountGroupByIdAudit.Builder audit =
+ AccountGroupByIdAudit.builder()
+ .groupId(groupId)
+ .includeUuid(uuid)
+ .addedOn(pc.when())
+ .addedBy(pc.authorId());
audits.put(key, audit);
result.add(audit);
}
for (AccountGroup.UUID uuid : pc.removedSubgroups()) {
- List<AccountGroupByIdAud> adds = audits.get(SubgroupKey.create(groupId, uuid));
+ List<AccountGroupByIdAudit.Builder> adds = audits.get(SubgroupKey.create(groupId, uuid));
if (!adds.isEmpty()) {
- AccountGroupByIdAud audit = adds.remove(0);
+ AccountGroupByIdAudit.Builder audit = adds.remove(0);
audit.removed(pc.authorId(), pc.when());
} else {
// Unlike members, DbGroupAuditListener didn't insert an add/remove pair here.
}
}
}
- return result.build();
+ return result.stream().map(AccountGroupByIdAudit.Builder::build).collect(toImmutableList());
}
private Optional<ParsedCommit> parse(AccountGroup.UUID uuid, RevCommit c) {
- Optional<Account.Id> authorId = NoteDbUtil.parseIdent(c.getAuthorIdent(), serverId);
+ Optional<Account.Id> authorId = NoteDbUtil.parseIdent(c.getAuthorIdent());
if (!authorId.isPresent()) {
// Only report audit events from identified users, since this was a non-nullable field in
// ReviewDb. May be revisited.
@@ -168,7 +176,7 @@ public class AuditLogReader {
private Optional<Account.Id> parseAccount(AccountGroup.UUID uuid, RevCommit c, FooterLine line) {
Optional<Account.Id> result =
Optional.ofNullable(RawParseUtils.parsePersonIdent(line.getValue()))
- .flatMap(ident -> NoteDbUtil.parseIdent(ident, serverId));
+ .flatMap(ident -> NoteDbUtil.parseIdent(ident));
if (!result.isPresent()) {
logInvalid(uuid, c, line);
}
@@ -182,7 +190,7 @@ public class AuditLogReader {
logInvalid(uuid, c, line);
return Optional.empty();
}
- return Optional.of(new AccountGroup.UUID(ident.getEmailAddress()));
+ return Optional.of(AccountGroup.uuid(ident.getEmailAddress()));
}
private static void logInvalid(AccountGroup.UUID uuid, RevCommit c, FooterLine line) {
diff --git a/java/com/google/gerrit/server/group/db/GroupConfig.java b/java/com/google/gerrit/server/group/db/GroupConfig.java
index 903b9c08b0..ab5c9b8563 100644
--- a/java/com/google/gerrit/server/group/db/GroupConfig.java
+++ b/java/com/google/gerrit/server/group/db/GroupConfig.java
@@ -24,11 +24,11 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.DuplicateKeyException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.group.InternalGroup;
@@ -411,12 +411,12 @@ public class GroupConfig extends VersionedMetaData {
}
private ImmutableSet<Account.Id> readMembers() throws IOException, ConfigInvalidException {
- return readFromFile(MEMBERS_FILE, entry -> new Account.Id(Integer.parseInt(entry)));
+ return readFromFile(MEMBERS_FILE, entry -> Account.id(Integer.parseInt(entry)));
}
private ImmutableSet<AccountGroup.UUID> readSubgroups()
throws IOException, ConfigInvalidException {
- return readFromFile(SUBGROUPS_FILE, AccountGroup.UUID::new);
+ return readFromFile(SUBGROUPS_FILE, AccountGroup::uuid);
}
private <E> ImmutableSet<E> readFromFile(String filePath, Function<String, E> fromStringFunction)
diff --git a/java/com/google/gerrit/server/group/db/GroupConfigEntry.java b/java/com/google/gerrit/server/group/db/GroupConfigEntry.java
index f7a104d7d9..81f5c7e4d0 100644
--- a/java/com/google/gerrit/server/group/db/GroupConfigEntry.java
+++ b/java/com/google/gerrit/server/group/db/GroupConfigEntry.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.group.db;
import com.google.common.base.Strings;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
@@ -45,7 +45,7 @@ enum GroupConfigEntry {
String.format(
"ID of the group %s must not be negative, found %d", groupUuid.get(), id));
}
- group.setId(new AccountGroup.Id(id));
+ group.setId(AccountGroup.id(id));
}
@Override
@@ -77,7 +77,7 @@ enum GroupConfigEntry {
// the NoteDb migration converted such groups faithfully, so we need to be able to read them
// back here.
name = Strings.nullToEmpty(name);
- group.setNameKey(new AccountGroup.NameKey(name));
+ group.setNameKey(AccountGroup.nameKey(name));
}
@Override
@@ -135,7 +135,7 @@ enum GroupConfigEntry {
throw new ConfigInvalidException(
String.format("Owner UUID of the group %s must be defined", groupUuid.get()));
}
- group.setOwnerGroupUUID(new AccountGroup.UUID(ownerGroupUuid));
+ group.setOwnerGroupUUID(AccountGroup.uuid(ownerGroupUuid));
}
@Override
diff --git a/java/com/google/gerrit/server/group/db/GroupNameNotes.java b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
index eda7153cf2..70d7a1aad3 100644
--- a/java/com/google/gerrit/server/group/db/GroupNameNotes.java
+++ b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
@@ -29,10 +29,11 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.DuplicateKeyException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import java.util.Collection;
@@ -266,7 +267,7 @@ public class GroupNameNotes extends VersionedMetaData {
RevCommit oldCommit = ref != null ? rw.parseCommit(ref.getObjectId()) : null;
for (Map.Entry<AccountGroup.UUID, String> e : biMap.entrySet()) {
- AccountGroup.NameKey nameKey = new AccountGroup.NameKey(e.getValue());
+ AccountGroup.NameKey nameKey = AccountGroup.nameKey(e.getValue());
ObjectId noteKey = getNoteKey(nameKey);
noteMap.set(noteKey, getAsNoteData(e.getKey(), nameKey), inserter);
}
@@ -286,7 +287,7 @@ public class GroupNameNotes extends VersionedMetaData {
cb.setMessage("Store " + n + " group name" + (n != 1 ? "s" : ""));
ObjectId newId = inserter.insert(cb).copy();
- ObjectId oldId = oldCommit != null ? oldCommit.copy() : ObjectId.zeroId();
+ ObjectId oldId = ObjectIds.copyOrZero(oldCommit);
bru.addCommand(new ReceiveCommand(oldId, newId, RefNames.REFS_GROUPNAMES));
}
}
@@ -442,7 +443,7 @@ public class GroupNameNotes extends VersionedMetaData {
throw new ConfigInvalidException(String.format("UUID for group '%s' must be defined", name));
}
- return new GroupReference(new AccountGroup.UUID(uuid), name);
+ return new GroupReference(AccountGroup.uuid(uuid), name);
}
private String getCommitMessage() {
diff --git a/java/com/google/gerrit/server/group/db/Groups.java b/java/com/google/gerrit/server/group/db/Groups.java
index 37de011fb7..2925cb3ee2 100644
--- a/java/com/google/gerrit/server/group/db/Groups.java
+++ b/java/com/google/gerrit/server/group/db/Groups.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.group.db;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.InternalGroup;
@@ -152,7 +152,7 @@ public class Groups {
* @throws IOException if an error occurs while reading from NoteDb
* @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
*/
- public List<AccountGroupByIdAud> getSubgroupsAudit(Repository repo, AccountGroup.UUID groupUuid)
+ public List<AccountGroupByIdAudit> getSubgroupsAudit(Repository repo, AccountGroup.UUID groupUuid)
throws IOException, ConfigInvalidException {
return auditLogReader.getSubgroupsAudit(repo, groupUuid);
}
diff --git a/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
index 3afb793566..8a6cd9465e 100644
--- a/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.group.db;
import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.error;
import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.warning;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.GroupBackend;
diff --git a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
index c3ca60bf11..04143044cf 100644
--- a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
+++ b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
@@ -24,10 +24,10 @@ import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Inject;
@@ -163,7 +163,7 @@ public class GroupsNoteDbConsistencyChecker {
continue;
}
- ObjectId nameKey = GroupNameNotes.getNoteKey(new AccountGroup.NameKey(gRef.getName()));
+ ObjectId nameKey = GroupNameNotes.getNoteKey(AccountGroup.nameKey(gRef.getName()));
if (!Objects.equals(nameKey, note)) {
result.problems.add(
error("notename entry %s does not match name %s", note, gRef.getName()));
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index b450ab856e..7f1ba6a178 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -21,13 +21,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
@@ -42,6 +42,7 @@ import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.GroupAuditService;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.group.GroupIndexer;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.update.RetryHelper;
@@ -265,7 +266,10 @@ public class GroupsUpdate {
throws DuplicateKeyException, IOException, ConfigInvalidException {
try (TraceTimer timer =
TraceContext.newTimer(
- "Creating group '%s'", groupUpdate.getName().orElseGet(groupCreation::getNameKey))) {
+ "Creating group",
+ Metadata.builder()
+ .groupName(groupUpdate.getName().orElseGet(groupCreation::getNameKey).get())
+ .build())) {
InternalGroup createdGroup = createGroupInNoteDbWithRetry(groupCreation, groupUpdate);
evictCachesOnGroupCreation(createdGroup);
dispatchAuditEventsOnGroupCreation(createdGroup);
@@ -285,7 +289,9 @@ public class GroupsUpdate {
*/
public void updateGroup(AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
throws DuplicateKeyException, IOException, NoSuchGroupException, ConfigInvalidException {
- try (TraceTimer timer = TraceContext.newTimer("Updating group %s", groupUuid)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Updating group", Metadata.builder().groupUuid(groupUuid.get()).build())) {
Optional<Timestamp> updatedOn = groupUpdate.getUpdatedOn();
if (!updatedOn.isPresent()) {
updatedOn = Optional.of(TimeUtil.nowTs());
diff --git a/java/com/google/gerrit/server/group/db/InternalGroupCreation.java b/java/com/google/gerrit/server/group/db/InternalGroupCreation.java
index bb21d62ece..89885471ea 100644
--- a/java/com/google/gerrit/server/group/db/InternalGroupCreation.java
+++ b/java/com/google/gerrit/server/group/db/InternalGroupCreation.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.group.db;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
/**
* Definition of all properties necessary for a group creation.
diff --git a/java/com/google/gerrit/server/group/db/InternalGroupUpdate.java b/java/com/google/gerrit/server/group/db/InternalGroupUpdate.java
index bff295217a..9e6539ae37 100644
--- a/java/com/google/gerrit/server/group/db/InternalGroupUpdate.java
+++ b/java/com/google/gerrit/server/group/db/InternalGroupUpdate.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.group.db;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import java.sql.Timestamp;
import java.util.Optional;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/group/db/RenameGroupOp.java b/java/com/google/gerrit/server/group/db/RenameGroupOp.java
index e002192b50..35ff5134de 100644
--- a/java/com/google/gerrit/server/group/db/RenameGroupOp.java
+++ b/java/com/google/gerrit/server/group/db/RenameGroupOp.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.group.db;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.DefaultQueueOp;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
diff --git a/java/com/google/gerrit/server/group/db/testing/BUILD b/java/com/google/gerrit/server/group/db/testing/BUILD
index 0cc45fd020..8f33f98a48 100644
--- a/java/com/google/gerrit/server/group/db/testing/BUILD
+++ b/java/com/google/gerrit/server/group/db/testing/BUILD
@@ -9,7 +9,7 @@ java_library(
deps = [
"//java/com/google/gerrit/server",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib:jgit",
+ "//lib:jgit-junit",
],
)
diff --git a/java/com/google/gerrit/server/group/testing/BUILD b/java/com/google/gerrit/server/group/testing/BUILD
index 9b6d8de925..fd61dffab0 100644
--- a/java/com/google/gerrit/server/group/testing/BUILD
+++ b/java/com/google/gerrit/server/group/testing/BUILD
@@ -8,10 +8,10 @@ java_library(
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
index 2f91394dee..8f20e927b8 100644
--- a/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
+++ b/java/com/google/gerrit/server/group/testing/InternalGroupSubject.java
@@ -18,17 +18,16 @@ import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.BooleanSubject;
import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.DefaultSubject;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import java.sql.Timestamp;
import org.eclipse.jgit.lib.ObjectId;
-public class InternalGroupSubject extends Subject<InternalGroupSubject, InternalGroup> {
+public class InternalGroupSubject extends Subject {
public static InternalGroupSubject assertThat(InternalGroup group) {
return assertAbout(internalGroups()).that(group);
@@ -38,73 +37,65 @@ public class InternalGroupSubject extends Subject<InternalGroupSubject, Internal
return InternalGroupSubject::new;
}
- private InternalGroupSubject(FailureMetadata metadata, InternalGroup actual) {
- super(metadata, actual);
+ private final InternalGroup group;
+
+ private InternalGroupSubject(FailureMetadata metadata, InternalGroup group) {
+ super(metadata, group);
+ this.group = group;
}
- public ComparableSubject<?, AccountGroup.UUID> groupUuid() {
+ public ComparableSubject<AccountGroup.UUID> groupUuid() {
isNotNull();
- InternalGroup group = actual();
- return check("groupUuid()").that(group.getGroupUUID());
+ return check("getGroupUUID()").that(group.getGroupUUID());
}
- public ComparableSubject<?, AccountGroup.NameKey> nameKey() {
+ public ComparableSubject<AccountGroup.NameKey> nameKey() {
isNotNull();
- InternalGroup group = actual();
- return check("nameKey()").that(group.getNameKey());
+ return check("getNameKey()").that(group.getNameKey());
}
public StringSubject name() {
isNotNull();
- InternalGroup group = actual();
- return check("name()").that(group.getName());
+ return check("getName()").that(group.getName());
}
- public Subject<DefaultSubject, Object> id() {
+ public Subject id() {
isNotNull();
- InternalGroup group = actual();
- return check("id()").that(group.getId());
+ return check("getId()").that(group.getId());
}
public StringSubject description() {
isNotNull();
- InternalGroup group = actual();
- return check("description()").that(group.getDescription());
+ return check("getDescription()").that(group.getDescription());
}
- public ComparableSubject<?, AccountGroup.UUID> ownerGroupUuid() {
+ public ComparableSubject<AccountGroup.UUID> ownerGroupUuid() {
isNotNull();
- InternalGroup group = actual();
- return check("ownerGroupUuid()").that(group.getOwnerGroupUUID());
+ return check("getOwnerGroupUUID()").that(group.getOwnerGroupUUID());
}
public BooleanSubject visibleToAll() {
isNotNull();
- InternalGroup group = actual();
- return check("visibleToAll()").that(group.isVisibleToAll());
+ return check("isVisibleToAll()").that(group.isVisibleToAll());
}
- public ComparableSubject<?, Timestamp> createdOn() {
+ public ComparableSubject<Timestamp> createdOn() {
isNotNull();
- InternalGroup group = actual();
- return check("createdOn()").that(group.getCreatedOn());
+ return check("getCreatedOn()").that(group.getCreatedOn());
}
public IterableSubject members() {
isNotNull();
- InternalGroup group = actual();
- return check("members()").that(group.getMembers());
+ return check("getMembers()").that(group.getMembers());
}
public IterableSubject subgroups() {
isNotNull();
- InternalGroup group = actual();
- return check("subgroups()").that(group.getSubgroups());
+ return check("getSubgroups()").that(group.getSubgroups());
}
- public ComparableSubject<?, ObjectId> refState() {
+ public ComparableSubject<ObjectId> refState() {
isNotNull();
- InternalGroup group = actual();
- return check("refState()").that(group.getRefState());
+ return check("getRefState()").that(group.getRefState());
}
}
diff --git a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
index 550b15c421..51c7ca3a0a 100644
--- a/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
+++ b/java/com/google/gerrit/server/group/testing/TestGroupBackend.java
@@ -20,7 +20,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
@@ -43,7 +43,7 @@ public class TestGroupBackend implements GroupBackend {
*/
public GroupDescription.Basic create(String name) {
requireNonNull(name);
- return create(new AccountGroup.UUID(name.startsWith(PREFIX) ? name : PREFIX + name));
+ return create(AccountGroup.uuid(name.startsWith(PREFIX) ? name : PREFIX + name));
}
/**
diff --git a/java/com/google/gerrit/server/index/AbstractIndexModule.java b/java/com/google/gerrit/server/index/AbstractIndexModule.java
index 352ea4b3f2..995b4b6bd4 100644
--- a/java/com/google/gerrit/server/index/AbstractIndexModule.java
+++ b/java/com/google/gerrit/server/index/AbstractIndexModule.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.index;
import com.google.gerrit.index.IndexConfig;
-import com.google.gerrit.index.Schema;
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -44,9 +43,21 @@ public abstract class AbstractIndexModule extends AbstractModule {
@Override
protected void configure() {
if (slave) {
- bind(AccountIndex.Factory.class).toInstance(AbstractIndexModule::createDummyIndexFactory);
- bind(ChangeIndex.Factory.class).toInstance(AbstractIndexModule::createDummyIndexFactory);
- bind(ProjectIndex.Factory.class).toInstance(AbstractIndexModule::createDummyIndexFactory);
+ bind(AccountIndex.Factory.class)
+ .toInstance(
+ s -> {
+ throw new UnsupportedOperationException();
+ });
+ bind(ChangeIndex.Factory.class)
+ .toInstance(
+ s -> {
+ throw new UnsupportedOperationException();
+ });
+ bind(ProjectIndex.Factory.class)
+ .toInstance(
+ s -> {
+ throw new UnsupportedOperationException();
+ });
} else {
install(
new FactoryModuleBuilder()
@@ -74,11 +85,6 @@ public abstract class AbstractIndexModule extends AbstractModule {
}
}
- @SuppressWarnings("unused")
- private static <T> T createDummyIndexFactory(Schema<?> schema) {
- throw new UnsupportedOperationException();
- }
-
protected abstract Class<? extends AccountIndex> getAccountIndex();
protected abstract Class<? extends ChangeIndex> getChangeIndex();
diff --git a/java/com/google/gerrit/server/index/IndexModule.java b/java/com/google/gerrit/server/index/IndexModule.java
index 612c637dfa..17665c0903 100644
--- a/java/com/google/gerrit/server/index/IndexModule.java
+++ b/java/com/google/gerrit/server/index/IndexModule.java
@@ -22,10 +22,10 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.SchemaDefinitions;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.project.ProjectIndexRewriter;
@@ -71,11 +71,6 @@ import org.eclipse.jgit.lib.Config;
* (e.g. Lucene).
*/
public class IndexModule extends LifecycleModule {
- public enum IndexType {
- LUCENE,
- ELASTICSEARCH
- }
-
public static final ImmutableCollection<SchemaDefinitions<?>> ALL_SCHEMA_DEFS =
ImmutableList.of(
AccountSchemaDefinitions.INSTANCE,
@@ -86,19 +81,8 @@ public class IndexModule extends LifecycleModule {
/** Type of secondary index. */
public static IndexType getIndexType(Injector injector) {
Config cfg = injector.getInstance(Key.get(Config.class, GerritServerConfig.class));
- if (cfg != null) {
- return cfg.getEnum("index", null, "type", IndexType.LUCENE);
- }
- return IndexType.LUCENE;
- }
-
- /** Type of secondary index. */
- // TODO: stop relying on this method fostering error-prone string comparisons.
- public static String getIndexType(@Nullable Config cfg) {
- if (cfg != null) {
- return cfg.getString("index", null, "type");
- }
- return IndexType.LUCENE.name();
+ String configValue = cfg != null ? cfg.getString("index", null, "type") : null;
+ return new IndexType(configValue);
}
private final int threads;
diff --git a/java/com/google/gerrit/server/index/IndexUtils.java b/java/com/google/gerrit/server/index/IndexUtils.java
index 4b5cd4946c..a45777edeb 100644
--- a/java/com/google/gerrit/server/index/IndexUtils.java
+++ b/java/com/google/gerrit/server/index/IndexUtils.java
@@ -16,18 +16,21 @@ package com.google.gerrit.server.index;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
+import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID_STR;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.project.ProjectField;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.group.GroupField;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.SingleGroupUser;
import java.io.IOException;
import java.util.Set;
@@ -56,17 +59,18 @@ public final class IndexUtils {
}
}
- public static Set<String> accountFields(QueryOptions opts) {
- return accountFields(opts.fields());
+ public static Set<String> accountFields(QueryOptions opts, boolean useLegacyNumericFields) {
+ return accountFields(opts.fields(), useLegacyNumericFields);
}
- public static Set<String> accountFields(Set<String> fields) {
- return fields.contains(AccountField.ID.getName())
- ? fields
- : Sets.union(fields, ImmutableSet.of(AccountField.ID.getName()));
+ public static Set<String> accountFields(Set<String> fields, boolean useLegacyNumericFields) {
+ String idFieldName =
+ useLegacyNumericFields ? AccountField.ID.getName() : AccountField.ID_STR.getName();
+ return fields.contains(idFieldName) ? fields : Sets.union(fields, ImmutableSet.of(idFieldName));
}
- public static Set<String> changeFields(QueryOptions opts) {
+ public static Set<String> changeFields(QueryOptions opts, boolean useLegacyNumericFields) {
+ FieldDef<ChangeData, ?> idField = useLegacyNumericFields ? LEGACY_ID : LEGACY_ID_STR;
// Ensure we request enough fields to construct a ChangeData. We need both
// change ID and project, which can either come via the Change field or
// separate fields.
@@ -75,10 +79,10 @@ public final class IndexUtils {
// A Change is always sufficient.
return fs;
}
- if (fs.contains(PROJECT.getName()) && fs.contains(LEGACY_ID.getName())) {
+ if (fs.contains(PROJECT.getName()) && fs.contains(idField.getName())) {
return fs;
}
- return Sets.union(fs, ImmutableSet.of(LEGACY_ID.getName(), PROJECT.getName()));
+ return Sets.union(fs, ImmutableSet.of(idField.getName(), PROJECT.getName()));
}
public static Set<String> groupFields(QueryOptions opts) {
diff --git a/java/com/google/gerrit/server/index/account/AccountField.java b/java/com/google/gerrit/server/index/account/AccountField.java
index f67a41d045..0dd22cea91 100644
--- a/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/java/com/google/gerrit/server/index/account/AccountField.java
@@ -21,28 +21,33 @@ import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
import static java.util.stream.Collectors.toSet;
-import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.SchemaUtil;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
/** Secondary index schemas for accounts. */
public class AccountField {
public static final FieldDef<AccountState, Integer> ID =
- integer("id").stored().build(a -> a.getAccount().getId().get());
+ integer("id").stored().build(a -> a.account().id().get());
+
+ public static final FieldDef<AccountState, String> ID_STR =
+ exact("id_str").stored().build(a -> String.valueOf(a.account().id().get()));
/**
* External IDs.
@@ -52,7 +57,7 @@ public class AccountField {
*/
public static final FieldDef<AccountState, Iterable<String>> EXTERNAL_ID =
exact("external_id")
- .buildRepeatable(a -> Iterables.transform(a.getExternalIds(), id -> id.key().get()));
+ .buildRepeatable(a -> Iterables.transform(a.externalIds(), id -> id.key().get()));
/**
* Fuzzy prefix match on name and email parts.
@@ -67,7 +72,7 @@ public class AccountField {
public static final FieldDef<AccountState, Iterable<String>> NAME_PART =
prefix("name")
.buildRepeatable(
- a -> getNameParts(a, Iterables.transform(a.getExternalIds(), ExternalId::email)));
+ a -> getNameParts(a, Iterables.transform(a.externalIds(), ExternalId::email)));
/**
* Fuzzy prefix match on name and preferred email parts. Parts of secondary emails are not
@@ -75,13 +80,13 @@ public class AccountField {
*/
public static final FieldDef<AccountState, Iterable<String>> NAME_PART_NO_SECONDARY_EMAIL =
prefix("name2")
- .buildRepeatable(a -> getNameParts(a, Arrays.asList(a.getAccount().getPreferredEmail())));
+ .buildRepeatable(a -> getNameParts(a, Arrays.asList(a.account().preferredEmail())));
public static final FieldDef<AccountState, String> FULL_NAME =
- exact("full_name").build(a -> a.getAccount().getFullName());
+ exact("full_name").build(a -> a.account().fullName());
public static final FieldDef<AccountState, String> ACTIVE =
- exact("inactive").build(a -> a.getAccount().isActive() ? "1" : "0");
+ exact("inactive").build(a -> a.account().isActive() ? "1" : "0");
/**
* All emails (preferred email + secondary emails). Use this field only if the current user is
@@ -93,10 +98,10 @@ public class AccountField {
prefix("email")
.buildRepeatable(
a ->
- FluentIterable.from(a.getExternalIds())
+ FluentIterable.from(a.externalIds())
.transform(ExternalId::email)
- .append(Collections.singleton(a.getAccount().getPreferredEmail()))
- .filter(Predicates.notNull())
+ .append(Collections.singleton(a.account().preferredEmail()))
+ .filter(Objects::nonNull)
.transform(String::toLowerCase)
.toSet());
@@ -104,24 +109,24 @@ public class AccountField {
prefix("preferredemail")
.build(
a -> {
- String preferredEmail = a.getAccount().getPreferredEmail();
+ String preferredEmail = a.account().preferredEmail();
return preferredEmail != null ? preferredEmail.toLowerCase() : null;
});
public static final FieldDef<AccountState, String> PREFERRED_EMAIL_EXACT =
- exact("preferredemail_exact").build(a -> a.getAccount().getPreferredEmail());
+ exact("preferredemail_exact").build(a -> a.account().preferredEmail());
public static final FieldDef<AccountState, Timestamp> REGISTERED =
- timestamp("registered").build(a -> a.getAccount().getRegisteredOn());
+ timestamp("registered").build(a -> a.account().registeredOn());
public static final FieldDef<AccountState, String> USERNAME =
- exact("username").build(a -> a.getUserName().map(String::toLowerCase).orElse(""));
+ exact("username").build(a -> a.userName().map(String::toLowerCase).orElse(""));
public static final FieldDef<AccountState, Iterable<String>> WATCHED_PROJECT =
exact("watchedproject")
.buildRepeatable(
a ->
- FluentIterable.from(a.getProjectWatches().keySet())
+ FluentIterable.from(a.projectWatches().keySet())
.transform(k -> k.project().get())
.toSet());
@@ -136,15 +141,19 @@ public class AccountField {
storedOnly("ref_state")
.buildRepeatable(
a -> {
- if (a.getAccount().getMetaId() == null) {
+ if (a.account().metaId() == null) {
return ImmutableList.of();
}
return ImmutableList.of(
RefState.create(
- RefNames.refsUsers(a.getAccount().getId()),
- ObjectId.fromString(a.getAccount().getMetaId()))
- .toByteArray(a.getAllUsersNameForIndexing()));
+ RefNames.refsUsers(a.account().id()),
+ ObjectId.fromString(a.account().metaId()))
+ // We use the default AllUsers name to avoid having to pass around that
+ // variable just for indexing.
+ // This field is only used for staleness detection which will discover the
+ // default name and replace it with the actually configured name.
+ .toByteArray(new AllUsersName(AllUsersNameProvider.DEFAULT)));
});
/**
@@ -157,13 +166,13 @@ public class AccountField {
storedOnly("external_id_state")
.buildRepeatable(
a ->
- a.getExternalIds().stream()
+ a.externalIds().stream()
.filter(e -> e.blobId() != null)
.map(ExternalId::toByteArray)
.collect(toSet()));
private static final Set<String> getNameParts(AccountState a, Iterable<String> emails) {
- String fullName = a.getAccount().getFullName();
+ String fullName = a.account().fullName();
Set<String> parts = SchemaUtil.getNameParts(fullName, emails);
// Additional values not currently added by getPersonParts.
diff --git a/java/com/google/gerrit/server/index/account/AccountIndex.java b/java/com/google/gerrit/server/index/account/AccountIndex.java
index 5c1b3dcafc..1838edfb25 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndex.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndex.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.index.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.query.account.AccountPredicates;
@@ -27,6 +27,6 @@ public interface AccountIndex extends Index<Account.Id, AccountState> {
@Override
default Predicate<AccountState> keyPredicate(Account.Id id) {
- return AccountPredicates.id(id);
+ return AccountPredicates.id(getSchema(), id);
}
}
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexCollection.java b/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
index 2a14f9bfe9..eb1f7846da 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.account;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.IndexCollection;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java b/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
index af23b52406..3a34d47eb8 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.account;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.IndexDefinition;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java b/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
index 35b967ceea..643c2491ed 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
@@ -16,22 +16,27 @@ package com.google.gerrit.server.index.account;
import static java.util.Objects.requireNonNull;
+import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.IndexRewriter;
import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.IndexPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.TooManyTermsInQueryException;
import com.google.gerrit.server.account.AccountState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.eclipse.jgit.util.MutableInteger;
@Singleton
public class AccountIndexRewriter implements IndexRewriter<AccountState> {
-
private final AccountIndexCollection indexes;
+ private final IndexConfig config;
@Inject
- AccountIndexRewriter(AccountIndexCollection indexes) {
+ AccountIndexRewriter(AccountIndexCollection indexes, IndexConfig config) {
this.indexes = indexes;
+ this.config = config;
}
@Override
@@ -39,6 +44,32 @@ public class AccountIndexRewriter implements IndexRewriter<AccountState> {
throws QueryParseException {
AccountIndex index = indexes.getSearchIndex();
requireNonNull(index, "no active search index configured for accounts");
+ validateMaxTermsInQuery(in);
return new IndexedAccountQuery(index, in, opts);
}
+
+ /**
+ * Validates whether a query has too many terms.
+ *
+ * @param predicate the predicate for which the leaf predicates should be counted
+ * @throws QueryParseException thrown if the query has too many terms
+ */
+ public void validateMaxTermsInQuery(Predicate<AccountState> predicate)
+ throws QueryParseException {
+ MutableInteger leafTerms = new MutableInteger();
+ validateMaxTermsInQuery(predicate, leafTerms);
+ }
+
+ private void validateMaxTermsInQuery(Predicate<AccountState> predicate, MutableInteger leafTerms)
+ throws TooManyTermsInQueryException {
+ if (!(predicate instanceof IndexPredicate)) {
+ if (++leafTerms.value > config.maxTerms()) {
+ throw new TooManyTermsInQueryException();
+ }
+ }
+
+ for (Predicate<AccountState> childPredicate : predicate.getChildren()) {
+ validateMaxTermsInQuery(childPredicate, leafTerms);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexer.java b/java/com/google/gerrit/server/index/account/AccountIndexer.java
index 7f4f295a1e..8ced00598f 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexer.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.index.account;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
public interface AccountIndexer {
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
index 932e2c30aa..b908846336 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
@@ -17,12 +17,13 @@ package com.google.gerrit.server.index.account;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.events.AccountIndexedListener;
import com.google.gerrit.index.Index;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.plugincontext.PluginSetContext;
@@ -90,13 +91,21 @@ public class AccountIndexerImpl implements AccountIndexer {
if (accountState.isPresent()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Replacing account %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ "Replacing account in index",
+ Metadata.builder()
+ .accountId(id.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.replace(accountState.get());
}
} else {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Deleting account %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ "Deleting account in index",
+ Metadata.builder()
+ .accountId(id.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.delete(id);
}
}
diff --git a/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java b/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
index c41814f183..157e290758 100644
--- a/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
@@ -49,7 +49,18 @@ public class AccountSchemaDefinitions extends SchemaDefinitions<AccountState> {
@Deprecated static final Schema<AccountState> V9 = schema(V8);
// Lucene index was changed to add additional fields for sorting.
- static final Schema<AccountState> V10 = schema(V9);
+ @Deprecated static final Schema<AccountState> V10 = schema(V9);
+
+ // New numeric types: use dimensional points using the k-d tree geo-spatial data structure
+ // to offer fast single- and multi-dimensional numeric range. As the consequense, integer
+ // document id type is replaced with string document id type.
+ static final Schema<AccountState> V11 =
+ new Schema.Builder<AccountState>()
+ .add(V10)
+ .remove(AccountField.ID)
+ .add(AccountField.ID_STR)
+ .legacyNumericFields(false)
+ .build();
public static final String NAME = "accounts";
public static final AccountSchemaDefinitions INSTANCE = new AccountSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java b/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
index acb72366ea..c1077b1c6a 100644
--- a/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
+++ b/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
@@ -21,8 +21,8 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.SiteIndexer;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
diff --git a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
index 8b9fa27754..b3d961a016 100644
--- a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
+++ b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.index.account;
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.DataSource;
@@ -23,7 +24,6 @@ import com.google.gerrit.index.query.IndexedQuery;
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
public class IndexedAccountQuery extends IndexedQuery<Account.Id, AccountState>
diff --git a/java/com/google/gerrit/server/index/account/StalenessChecker.java b/java/com/google/gerrit/server/index/account/StalenessChecker.java
index 46647002f3..aad9527092 100644
--- a/java/com/google/gerrit/server/index/account/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/account/StalenessChecker.java
@@ -22,16 +22,17 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.query.FieldBundle;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.IndexUtils;
import com.google.inject.Inject;
@@ -60,6 +61,12 @@ public class StalenessChecker {
AccountField.REF_STATE.getName(),
AccountField.EXTERNAL_ID_STATE.getName());
+ public static final ImmutableSet<String> FIELDS2 =
+ ImmutableSet.of(
+ AccountField.ID_STR.getName(),
+ AccountField.REF_STATE.getName(),
+ AccountField.EXTERNAL_ID_STATE.getName());
+
private final AccountIndexCollection indexes;
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
@@ -92,8 +99,13 @@ public class StalenessChecker {
return false;
}
+ boolean useLegacyNumericFields = i.getSchema().useLegacyNumericFields();
+ ImmutableSet<String> fields = useLegacyNumericFields ? FIELDS : FIELDS2;
Optional<FieldBundle> result =
- i.getRaw(id, QueryOptions.create(indexConfig, 0, 1, IndexUtils.accountFields(FIELDS)));
+ i.getRaw(
+ id,
+ QueryOptions.create(
+ indexConfig, 0, 1, IndexUtils.accountFields(fields, useLegacyNumericFields)));
if (!result.isPresent()) {
// The document is missing in the index.
try (Repository repo = repoManager.openRepository(allUsersName)) {
@@ -106,7 +118,11 @@ public class StalenessChecker {
for (Map.Entry<Project.NameKey, RefState> e :
RefState.parseStates(result.get().getValue(AccountField.REF_STATE)).entries()) {
- try (Repository repo = repoManager.openRepository(e.getKey())) {
+ // Custom All-Users repository names are not indexed. Instead, the default name is used.
+ // Therefore, defer to the currently configured All-Users name.
+ Project.NameKey repoName =
+ e.getKey().get().equals(AllUsersNameProvider.DEFAULT) ? allUsersName : e.getKey();
+ try (Repository repo = repoManager.openRepository(repoName)) {
if (!e.getValue().match(repo)) {
// Ref was modified since the account was indexed.
return true;
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index a9980ef583..005f4c5691 100644
--- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -25,10 +25,10 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.SiteIndexer;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 394761bb27..07e9b9e4fc 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.index.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.index.FieldDef.exact;
import static com.google.gerrit.index.FieldDef.fullText;
import static com.google.gerrit.index.FieldDef.intRange;
@@ -42,23 +43,22 @@ import com.google.common.io.Files;
import com.google.common.primitives.Longs;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.converter.ChangeProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetProtoConverter;
+import com.google.gerrit.entities.converter.ProtoConverter;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.SchemaUtil;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.mail.Address;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
-import com.google.gerrit.reviewdb.converter.ProtoConverter;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
@@ -107,6 +107,9 @@ public class ChangeField {
public static final FieldDef<ChangeData, Integer> LEGACY_ID =
integer("legacy_id").stored().build(cd -> cd.getId().get());
+ public static final FieldDef<ChangeData, String> LEGACY_ID_STR =
+ exact("legacy_id_str").stored().build(cd -> String.valueOf(cd.getId().get()));
+
/** Newer style Change-Id key. */
public static final FieldDef<ChangeData, String> ID =
prefix(ChangeQueryBuilder.FIELD_CHANGE_ID).build(changeGetter(c -> c.getKey().get()));
@@ -128,7 +131,7 @@ public class ChangeField {
/** Reference (aka branch) the change will submit onto. */
public static final FieldDef<ChangeData, String> REF =
- exact(ChangeQueryBuilder.FIELD_REF).build(changeGetter(c -> c.getDest().get()));
+ exact(ChangeQueryBuilder.FIELD_REF).build(changeGetter(c -> c.getDest().branch()));
/** Topic, a short annotation on the branch. */
public static final FieldDef<ChangeData, String> EXACT_TOPIC =
@@ -237,7 +240,6 @@ public class ChangeField {
Set<String> r = new HashSet<>();
for (String path : paths) {
StringBuilder directory = new StringBuilder();
- directory.append("");
r.add(directory.toString());
String nextPart = null;
for (String part : s.split(path)) {
@@ -450,14 +452,8 @@ public class ChangeField {
public static final FieldDef<ChangeData, Iterable<String>> EXACT_COMMIT =
exact(ChangeQueryBuilder.FIELD_EXACTCOMMIT).buildRepeatable(ChangeField::getRevisions);
- private static Set<String> getRevisions(ChangeData cd) {
- Set<String> revisions = new HashSet<>();
- for (PatchSet ps : cd.patchSets()) {
- if (ps.getRevision() != null) {
- revisions.add(ps.getRevision().get());
- }
- }
- return revisions;
+ private static ImmutableSet<String> getRevisions(ChangeData cd) {
+ return cd.patchSets().stream().map(ps -> ps.commitId().name()).collect(toImmutableSet());
}
/** Tracking id extracted from a footer. */
@@ -473,13 +469,12 @@ public class ChangeField {
Set<String> allApprovals = new HashSet<>();
Set<String> distinctApprovals = new HashSet<>();
for (PatchSetApproval a : cd.currentApprovals()) {
- if (a.getValue() != 0 && !a.isLegacySubmit()) {
- allApprovals.add(formatLabel(a.getLabel(), a.getValue(), a.getAccountId()));
- if (owners && cd.change().getOwner().equals(a.getAccountId())) {
- allApprovals.add(
- formatLabel(a.getLabel(), a.getValue(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
+ if (a.value() != 0 && !a.isLegacySubmit()) {
+ allApprovals.add(formatLabel(a.label(), a.value(), a.accountId()));
+ if (owners && cd.change().getOwner().equals(a.accountId())) {
+ allApprovals.add(formatLabel(a.label(), a.value(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
}
- distinctApprovals.add(formatLabel(a.getLabel(), a.getValue()));
+ distinctApprovals.add(formatLabel(a.label(), a.value()));
}
}
allApprovals.addAll(distinctApprovals);
@@ -669,8 +664,7 @@ public class ChangeField {
public static final FieldDef<ChangeData, Iterable<String>> GROUP =
exact(ChangeQueryBuilder.FIELD_GROUP)
.buildRepeatable(
- cd ->
- cd.patchSets().stream().flatMap(ps -> ps.getGroups().stream()).collect(toSet()));
+ cd -> cd.patchSets().stream().flatMap(ps -> ps.groups().stream()).collect(toSet()));
/** Serialized patch set object, used for pre-populating results. */
public static final FieldDef<ChangeData, Iterable<byte[]>> PATCH_SET =
@@ -776,7 +770,7 @@ public class ChangeField {
SubmitRecord.Label srl = new SubmitRecord.Label();
srl.label = label.label;
srl.status = label.status;
- srl.appliedBy = label.appliedBy != null ? new Account.Id(label.appliedBy) : null;
+ srl.appliedBy = label.appliedBy != null ? Account.id(label.appliedBy) : null;
rec.labels.add(srl);
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndex.java b/java/com/google/gerrit/server/index/change/ChangeIndex.java
index 855bfe9f2c..29bff0a322 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndex.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndex.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.index.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.LegacyChangeIdPredicate;
+import com.google.gerrit.server.query.change.LegacyChangeIdStrPredicate;
public interface ChangeIndex extends Index<Change.Id, ChangeData> {
public interface Factory
@@ -27,6 +28,8 @@ public interface ChangeIndex extends Index<Change.Id, ChangeData> {
@Override
default Predicate<ChangeData> keyPredicate(Change.Id id) {
- return new LegacyChangeIdPredicate(id);
+ return getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(id)
+ : new LegacyChangeIdStrPredicate(id);
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java b/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
index a353a2acf5..b8e2f3e3da 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.change;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexCollection;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java b/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
index 79454298f5..7de9e743ca 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.change;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexDefinition;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index 9e81e75dc6..63c52977a6 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -19,6 +19,8 @@ import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Change.Status;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.IndexRewriter;
@@ -31,8 +33,7 @@ import com.google.gerrit.index.query.NotPredicate;
import com.google.gerrit.index.query.OrPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.index.query.TooManyTermsInQueryException;
import com.google.gerrit.server.query.change.AndChangeSource;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
@@ -155,7 +156,7 @@ public class ChangeIndexRewriter implements IndexRewriter<ChangeData> {
MutableInteger leafTerms = new MutableInteger();
Predicate<ChangeData> out = rewriteImpl(in, index, opts, leafTerms);
- if (in == out || out instanceof IndexPredicate) {
+ if (isSameInstance(in, out) || out instanceof IndexPredicate) {
return new IndexedChangeQuery(index, out, opts);
} else if (out == null /* cannot rewrite */) {
return in;
@@ -183,7 +184,7 @@ public class ChangeIndexRewriter implements IndexRewriter<ChangeData> {
throws QueryParseException {
if (isIndexPredicate(in, index)) {
if (++leafTerms.value > config.maxTerms()) {
- throw new QueryParseException("too many terms in query");
+ throw new TooManyTermsInQueryException();
}
return in;
} else if (in instanceof LimitPredicate) {
@@ -207,7 +208,7 @@ public class ChangeIndexRewriter implements IndexRewriter<ChangeData> {
for (int i = 0; i < n; i++) {
Predicate<ChangeData> c = in.getChild(i);
Predicate<ChangeData> nc = rewriteImpl(c, index, opts, leafTerms);
- if (nc == c) {
+ if (isSameInstance(nc, c)) {
isIndexed.set(i);
newChildren.add(c);
} else if (nc == null /* cannot rewrite c */) {
@@ -291,4 +292,9 @@ public class ChangeIndexRewriter implements IndexRewriter<ChangeData> {
return p.getChildCount() > 0
&& (p instanceof AndPredicate || p instanceof OrPredicate || p instanceof NotPredicate);
}
+
+ @SuppressWarnings("ReferenceEquality")
+ private static <T> boolean isSameInstance(T a, T b) {
+ return a == b;
+ }
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 4597cbdd8d..f6d86bf171 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -22,15 +22,18 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.index.Index;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.IndexExecutor;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.plugincontext.PluginSetContext;
+import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -66,6 +69,7 @@ public class ChangeIndexer {
@Nullable private final ChangeIndexCollection indexes;
@Nullable private final ChangeIndex index;
private final ChangeData.Factory changeDataFactory;
+ private final ChangeNotes.Factory notesFactory;
private final ThreadLocalRequestContext context;
private final ListeningExecutorService batchExecutor;
private final ListeningExecutorService executor;
@@ -82,6 +86,7 @@ public class ChangeIndexer {
ChangeIndexer(
@GerritServerConfig Config cfg,
ChangeData.Factory changeDataFactory,
+ ChangeNotes.Factory notesFactory,
ThreadLocalRequestContext context,
PluginSetContext<ChangeIndexedListener> indexedListeners,
StalenessChecker stalenessChecker,
@@ -90,6 +95,7 @@ public class ChangeIndexer {
@Assisted ChangeIndex index) {
this.executor = executor;
this.changeDataFactory = changeDataFactory;
+ this.notesFactory = notesFactory;
this.context = context;
this.indexedListeners = indexedListeners;
this.stalenessChecker = stalenessChecker;
@@ -103,6 +109,7 @@ public class ChangeIndexer {
ChangeIndexer(
@GerritServerConfig Config cfg,
ChangeData.Factory changeDataFactory,
+ ChangeNotes.Factory notesFactory,
ThreadLocalRequestContext context,
PluginSetContext<ChangeIndexedListener> indexedListeners,
StalenessChecker stalenessChecker,
@@ -111,6 +118,7 @@ public class ChangeIndexer {
@Assisted ChangeIndexCollection indexes) {
this.executor = executor;
this.changeDataFactory = changeDataFactory;
+ this.notesFactory = notesFactory;
this.context = context;
this.indexedListeners = indexedListeners;
this.stalenessChecker = stalenessChecker;
@@ -133,6 +141,7 @@ public class ChangeIndexer {
public ListenableFuture<?> indexAsync(Project.NameKey project, Change.Id id) {
IndexTask task = new IndexTask(project, id);
if (queuedIndexTasks.add(task)) {
+ fireChangeScheduledForIndexingEvent(project.get(), id.get());
return submit(task);
}
return Futures.immediateFuture(null);
@@ -158,6 +167,11 @@ public class ChangeIndexer {
* @param cd change to index.
*/
public void index(ChangeData cd) {
+ fireChangeScheduledForIndexingEvent(cd.project().get(), cd.getId().get());
+ doIndex(cd);
+ }
+
+ private void doIndex(ChangeData cd) {
indexImpl(cd);
// Always double-check whether the change might be stale immediately after
@@ -186,18 +200,30 @@ public class ChangeIndexer {
for (Index<?, ChangeData> i : getWriteIndexes()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Replacing change %d in index version %d",
- cd.getId().get(), i.getSchema().getVersion())) {
+ "Replacing change in index",
+ Metadata.builder()
+ .changeId(cd.getId().get())
+ .patchSetId(cd.currentPatchSet().number())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.replace(cd);
}
}
fireChangeIndexedEvent(cd.project().get(), cd.getId().get());
}
+ private void fireChangeScheduledForIndexingEvent(String projectName, int id) {
+ indexedListeners.runEach(l -> l.onChangeScheduledForIndexing(projectName, id));
+ }
+
private void fireChangeIndexedEvent(String projectName, int id) {
indexedListeners.runEach(l -> l.onChangeIndexed(projectName, id));
}
+ private void fireChangeScheduledForDeletionFromIndexEvent(int id) {
+ indexedListeners.runEach(l -> l.onChangeScheduledForDeletionFromIndex(id));
+ }
+
private void fireChangeDeletedFromIndexEvent(int id) {
indexedListeners.runEach(l -> l.onChangeDeleted(id));
}
@@ -228,6 +254,7 @@ public class ChangeIndexer {
* @return future for the deleting task.
*/
public ListenableFuture<?> deleteAsync(Change.Id id) {
+ fireChangeScheduledForDeletionFromIndexEvent(id.get());
return submit(new DeleteTask(id));
}
@@ -237,6 +264,11 @@ public class ChangeIndexer {
* @param id change ID to delete.
*/
public void delete(Change.Id id) {
+ fireChangeScheduledForDeletionFromIndexEvent(id.get());
+ doDelete(id);
+ }
+
+ private void doDelete(Change.Id id) {
new DeleteTask(id).call();
}
@@ -327,8 +359,12 @@ public class ChangeIndexer {
@Override
public Void callImpl() throws Exception {
remove();
- ChangeData cd = changeDataFactory.create(project, id);
- index(cd);
+ try {
+ ChangeNotes changeNotes = notesFactory.createChecked(project, id);
+ doIndex(changeDataFactory.create(changeNotes));
+ } catch (NoSuchChangeException e) {
+ doDelete(id);
+ }
return null;
}
@@ -374,7 +410,11 @@ public class ChangeIndexer {
for (ChangeIndex i : getWriteIndexes()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Deleting change %d in index version %d", id.get(), i.getSchema().getVersion())) {
+ "Deleting change in index",
+ Metadata.builder()
+ .changeId(id.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.delete(id);
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index cde6a6433d..72153c43b2 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -22,7 +22,7 @@ import com.google.gerrit.server.query.change.ChangeData;
public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
@Deprecated
- static final Schema<ChangeData> V39 =
+ static final Schema<ChangeData> V55 =
schema(
ChangeField.ADDED,
ChangeField.APPROVAL,
@@ -36,11 +36,16 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
ChangeField.COMMIT_MESSAGE,
ChangeField.DELETED,
ChangeField.DELTA,
+ ChangeField.DIRECTORY,
ChangeField.DRAFTBY,
ChangeField.EDITBY,
+ ChangeField.EXACT_AUTHOR,
ChangeField.EXACT_COMMIT,
+ ChangeField.EXACT_COMMITTER,
ChangeField.EXACT_TOPIC,
+ ChangeField.EXTENSION,
ChangeField.FILE_PART,
+ ChangeField.FOOTER,
ChangeField.FUZZY_TOPIC,
ChangeField.GROUP,
ChangeField.HASHTAG,
@@ -49,70 +54,49 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
ChangeField.LABEL,
ChangeField.LEGACY_ID,
ChangeField.MERGEABLE,
+ ChangeField.ONLY_EXTENSIONS,
ChangeField.OWNER,
ChangeField.PATCH_SET,
ChangeField.PATH,
+ ChangeField.PENDING_REVIEWER,
+ ChangeField.PENDING_REVIEWER_BY_EMAIL,
+ ChangeField.PRIVATE,
ChangeField.PROJECT,
ChangeField.PROJECTS,
ChangeField.REF,
ChangeField.REF_STATE,
ChangeField.REF_STATE_PATTERN,
+ ChangeField.REVERT_OF,
ChangeField.REVIEWEDBY,
ChangeField.REVIEWER,
+ ChangeField.REVIEWER_BY_EMAIL,
ChangeField.STAR,
ChangeField.STARBY,
+ ChangeField.STARTED,
ChangeField.STATUS,
ChangeField.STORED_SUBMIT_RECORD_LENIENT,
ChangeField.STORED_SUBMIT_RECORD_STRICT,
ChangeField.SUBMISSIONID,
ChangeField.SUBMIT_RECORD,
+ ChangeField.TOTAL_COMMENT_COUNT,
ChangeField.TR,
ChangeField.UNRESOLVED_COMMENT_COUNT,
- ChangeField.UPDATED);
-
- @Deprecated static final Schema<ChangeData> V40 = schema(V39, ChangeField.PRIVATE);
- @Deprecated static final Schema<ChangeData> V41 = schema(V40, ChangeField.REVIEWER_BY_EMAIL);
- @Deprecated static final Schema<ChangeData> V42 = schema(V41, ChangeField.WIP);
-
- @Deprecated
- static final Schema<ChangeData> V43 =
- schema(V42, ChangeField.EXACT_AUTHOR, ChangeField.EXACT_COMMITTER);
-
- @Deprecated
- static final Schema<ChangeData> V44 =
- schema(
- V43,
- ChangeField.STARTED,
- ChangeField.PENDING_REVIEWER,
- ChangeField.PENDING_REVIEWER_BY_EMAIL);
-
- @Deprecated static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF);
-
- @Deprecated static final Schema<ChangeData> V46 = schema(V45);
-
- // Removal of draft change workflow requires reindexing
- @Deprecated static final Schema<ChangeData> V47 = schema(V46);
-
- // Rename of star label 'mute' to 'reviewed' requires reindexing
- @Deprecated static final Schema<ChangeData> V48 = schema(V47);
-
- @Deprecated static final Schema<ChangeData> V49 = schema(V48);
-
- // Bump Lucene version requires reindexing
- @Deprecated static final Schema<ChangeData> V50 = schema(V49);
-
- @Deprecated static final Schema<ChangeData> V51 = schema(V50, ChangeField.TOTAL_COMMENT_COUNT);
-
- @Deprecated static final Schema<ChangeData> V52 = schema(V51, ChangeField.EXTENSION);
-
- @Deprecated static final Schema<ChangeData> V53 = schema(V52, ChangeField.ONLY_EXTENSIONS);
-
- @Deprecated static final Schema<ChangeData> V54 = schema(V53, ChangeField.FOOTER);
-
- @Deprecated static final Schema<ChangeData> V55 = schema(V54, ChangeField.DIRECTORY);
+ ChangeField.UPDATED,
+ ChangeField.WIP);
// The computation of the 'extension' field is changed, hence reindexing is required.
- static final Schema<ChangeData> V56 = schema(V55);
+ @Deprecated static final Schema<ChangeData> V56 = schema(V55);
+
+ // New numeric types: use dimensional points using the k-d tree geo-spatial data structure
+ // to offer fast single- and multi-dimensional numeric range. As the consequense, integer
+ // document id type is replaced with string document id type.
+ static final Schema<ChangeData> V57 =
+ new Schema.Builder<ChangeData>()
+ .add(V56)
+ .remove(ChangeField.LEGACY_ID)
+ .add(ChangeField.LEGACY_ID_STR)
+ .legacyNumericFields(false)
+ .build();
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/index/change/DummyChangeIndex.java b/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
index 9be93f77bb..ae3b729d85 100644
--- a/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
+++ b/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.index.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
diff --git a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index ed09eed2e9..57a2091390 100644
--- a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.DataSource;
@@ -31,7 +32,6 @@ import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import java.util.HashMap;
diff --git a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
index fd12345e16..f6d3b6f5bf 100644
--- a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
+++ b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
@@ -17,37 +17,30 @@ package com.google.gerrit.server.index.change;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static com.google.gerrit.server.query.change.ChangeData.asChanges;
-import com.google.common.base.Objects;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.QueueProvider.QueueType;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.account.AccountIndexer;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import org.eclipse.jgit.lib.Config;
@@ -58,15 +51,12 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
private final Provider<InternalChangeQuery> queryProvider;
private final ChangeIndexer.Factory indexerFactory;
private final ChangeIndexCollection indexes;
- private final ChangeNotes.Factory notesFactory;
private final AllUsersName allUsersName;
private final AccountCache accountCache;
private final Provider<AccountIndexer> indexer;
private final ListeningExecutorService executor;
private final boolean enabled;
- private final Set<Index> queuedIndexTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
-
@Inject
ReindexAfterRefUpdate(
@GerritServerConfig Config cfg,
@@ -74,7 +64,6 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
Provider<InternalChangeQuery> queryProvider,
ChangeIndexer.Factory indexerFactory,
ChangeIndexCollection indexes,
- ChangeNotes.Factory notesFactory,
AllUsersName allUsersName,
AccountCache accountCache,
Provider<AccountIndexer> indexer,
@@ -83,7 +72,6 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
this.queryProvider = queryProvider;
this.indexerFactory = indexerFactory;
this.indexes = indexes;
- this.notesFactory = notesFactory;
this.allUsersName = allUsersName;
this.accountCache = accountCache;
this.indexer = indexer;
@@ -113,12 +101,9 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
@Override
public void onSuccess(List<Change> changes) {
for (Change c : changes) {
- Index task = new Index(event, c.getId());
- if (queuedIndexTasks.add(task)) {
- // Don't retry indefinitely; if this fails changes may be stale.
- @SuppressWarnings("unused")
- Future<?> possiblyIgnoredError = executor.submit(task);
- }
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ indexerFactory.create(executor, indexes).indexAsync(c.getProject(), c.getId());
}
}
@@ -160,11 +145,11 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
@Override
protected List<Change> impl(RequestContext ctx) {
String ref = event.getRefName();
- Project.NameKey project = new Project.NameKey(event.getProjectName());
+ Project.NameKey project = Project.nameKey(event.getProjectName());
if (ref.equals(RefNames.REFS_CONFIG)) {
return asChanges(queryProvider.get().byProjectOpen(project));
}
- return asChanges(queryProvider.get().byBranchNew(new Branch.NameKey(project, ref)));
+ return asChanges(queryProvider.get().byBranchNew(BranchNameKey.create(project, ref)));
}
@Override
@@ -178,51 +163,4 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
@Override
protected void remove() {}
}
-
- private class Index extends Task<Void> {
- private final Change.Id id;
-
- Index(Event event, Change.Id id) {
- super(event);
- this.id = id;
- }
-
- @Override
- protected Void impl(RequestContext ctx) throws IOException {
- // Reload change, as some time may have passed since GetChanges.
- remove();
- try {
- Change c =
- notesFactory.createChecked(new Project.NameKey(event.getProjectName()), id).getChange();
- indexerFactory.create(executor, indexes).index(c);
- } catch (NoSuchChangeException e) {
- indexerFactory.create(executor, indexes).delete(id);
- }
- return null;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(Index.class, id.get());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Index)) {
- return false;
- }
- Index other = (Index) obj;
- return id.get() == other.id.get();
- }
-
- @Override
- public String toString() {
- return "Index change " + id.get() + " of project " + event.getProjectName();
- }
-
- @Override
- protected void remove() {
- queuedIndexTasks.remove(this);
- }
- }
}
diff --git a/java/com/google/gerrit/server/index/change/StalenessChecker.java b/java/com/google/gerrit/server/index/change/StalenessChecker.java
index 338cf3dd5f..47fd7ba58d 100644
--- a/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -29,11 +29,11 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.RefState;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
@@ -131,8 +131,7 @@ public class StalenessChecker {
String s = new String(b, UTF_8);
List<String> parts = Splitter.on(':').splitToList(s);
RefStatePattern.check(parts.size() == 2, s);
- result.put(
- new Project.NameKey(Url.decode(parts.get(0))), RefStatePattern.create(parts.get(1)));
+ result.put(Project.nameKey(Url.decode(parts.get(0))), RefStatePattern.create(parts.get(1)));
}
return result;
}
diff --git a/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java b/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
index 3474934d87..0dbbbc5e92 100644
--- a/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
+++ b/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
@@ -23,8 +23,8 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.SiteIndexer;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.db.Groups;
diff --git a/java/com/google/gerrit/server/index/group/GroupField.java b/java/com/google/gerrit/server/index/group/GroupField.java
index 29e386731e..a3d913d9b8 100644
--- a/java/com/google/gerrit/server/index/group/GroupField.java
+++ b/java/com/google/gerrit/server/index/group/GroupField.java
@@ -23,13 +23,13 @@ import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
import com.google.common.base.MoreObjects;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.SchemaUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import java.sql.Timestamp;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
/** Secondary index schemas for groups. */
@@ -82,7 +82,7 @@ public class GroupField {
storedOnly("ref_state")
.build(
g -> {
- byte[] a = new byte[Constants.OBJECT_ID_STRING_LENGTH];
+ byte[] a = new byte[ObjectIds.STR_LEN];
MoreObjects.firstNonNull(g.getRefState(), ObjectId.zeroId()).copyTo(a, 0);
return a;
});
diff --git a/java/com/google/gerrit/server/index/group/GroupIndex.java b/java/com/google/gerrit/server/index/group/GroupIndex.java
index 6a430f8e77..daa213186c 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndex.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndex.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.index.group;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.query.group.GroupPredicates;
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexCollection.java b/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
index 531c4463d1..9d74b7dddd 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.group;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.IndexCollection;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java b/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
index d117dfd5d2..e403752796 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.index.group;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.IndexDefinition;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexer.java b/java/com/google/gerrit/server/index/group/GroupIndexer.java
index 5d9232efa7..25d584054b 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexer.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexer.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.index.group;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
public interface GroupIndexer {
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index 5982de781c..790066d910 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -17,12 +17,13 @@ package com.google.gerrit.server.index.group;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.events.GroupIndexedListener;
import com.google.gerrit.index.Index;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.plugincontext.PluginSetContext;
@@ -90,13 +91,21 @@ public class GroupIndexerImpl implements GroupIndexer {
if (internalGroup.isPresent()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Replacing group %s in index version %d", uuid.get(), i.getSchema().getVersion())) {
+ "Replacing group",
+ Metadata.builder()
+ .groupUuid(uuid.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.replace(internalGroup.get());
}
} else {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Deleting group %s in index version %d", uuid.get(), i.getSchema().getVersion())) {
+ "Deleting group",
+ Metadata.builder()
+ .groupUuid(uuid.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.delete(uuid);
}
}
diff --git a/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java b/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
index 6d0f3b6b5c..e64e2eb473 100644
--- a/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
@@ -43,7 +43,11 @@ public class GroupSchemaDefinitions extends SchemaDefinitions<InternalGroup> {
@Deprecated static final Schema<InternalGroup> V6 = schema(V5);
// Lucene index was changed to add an additional field for sorting.
- static final Schema<InternalGroup> V7 = schema(V6);
+ @Deprecated static final Schema<InternalGroup> V7 = schema(V6);
+
+ // New numeric types: use dimensional points using the k-d tree geo-spatial data structure
+ // to offer fast single- and multi-dimensional numeric range.
+ static final Schema<InternalGroup> V8 = schema(V7, false);
public static final GroupSchemaDefinitions INSTANCE = new GroupSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
index 32393b0687..dfdf3ca171 100644
--- a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
+++ b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.index.group;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.Index;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
@@ -21,7 +22,6 @@ import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.IndexedQuery;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import java.util.HashSet;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/index/group/StalenessChecker.java b/java/com/google/gerrit/server/index/group/StalenessChecker.java
index 7900287f4b..3a721c3f60 100644
--- a/java/com/google/gerrit/server/index/group/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/group/StalenessChecker.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.index.group;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.FieldBundle;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java b/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
index 305cd25285..b760fd7d7c 100644
--- a/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
+++ b/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
@@ -21,10 +21,10 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.SiteIndexer;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndex;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java b/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
index ce2b634b79..6a844b5098 100644
--- a/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
+++ b/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.index.project;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.IndexDefinition;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.project.ProjectSchemaDefinitions;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
public class ProjectIndexDefinition
diff --git a/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java b/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
index 6d6f78d91d..4de83be716 100644
--- a/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
@@ -17,12 +17,13 @@ package com.google.gerrit.server.index.project;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.project.ProjectIndexer;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.plugincontext.PluginSetContext;
@@ -78,8 +79,11 @@ public class ProjectIndexerImpl implements ProjectIndexer {
for (ProjectIndex i : getWriteIndexes()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Replacing project %s in index version %d",
- nameKey.get(), i.getSchema().getVersion())) {
+ "Replacing project",
+ Metadata.builder()
+ .projectName(nameKey.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.replace(projectData);
}
}
@@ -89,8 +93,11 @@ public class ProjectIndexerImpl implements ProjectIndexer {
for (ProjectIndex i : getWriteIndexes()) {
try (TraceTimer traceTimer =
TraceContext.newTimer(
- "Deleting project %s in index version %d",
- nameKey.get(), i.getSchema().getVersion())) {
+ "Deleting project",
+ Metadata.builder()
+ .projectName(nameKey.get())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
i.delete(nameKey);
}
}
diff --git a/java/com/google/gerrit/server/index/project/StalenessChecker.java b/java/com/google/gerrit/server/index/project/StalenessChecker.java
index dc5ebc6ea9..e4c1a7ddb2 100644
--- a/java/com/google/gerrit/server/index/project/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/project/StalenessChecker.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.index.project;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.RefState;
@@ -25,8 +27,6 @@ import com.google.gerrit.index.project.ProjectField;
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.query.FieldBundle;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/ioutil/BUILD b/java/com/google/gerrit/server/ioutil/BUILD
index ed58d5bd40..fd0c4f10eb 100644
--- a/java/com/google/gerrit/server/ioutil/BUILD
+++ b/java/com/google/gerrit/server/ioutil/BUILD
@@ -5,10 +5,10 @@ java_library(
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//lib:automaton",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
+ "//lib:jgit-archive",
],
)
diff --git a/java/com/google/gerrit/server/ioutil/BasicSerialization.java b/java/com/google/gerrit/server/ioutil/BasicSerialization.java
index c7f2ecd54e..296cf22c5f 100644
--- a/java/com/google/gerrit/server/ioutil/BasicSerialization.java
+++ b/java/com/google/gerrit/server/ioutil/BasicSerialization.java
@@ -31,7 +31,7 @@ package com.google.gerrit.server.ioutil;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.gerrit.reviewdb.client.CodedEnum;
+import com.google.gerrit.entities.CodedEnum;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
diff --git a/java/com/google/gerrit/server/logging/BUILD b/java/com/google/gerrit/server/logging/BUILD
index 672cb75ac3..7af34f7cd7 100644
--- a/java/com/google/gerrit/server/logging/BUILD
+++ b/java/com/google/gerrit/server/logging/BUILD
@@ -8,12 +8,15 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/server/util/time",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/flogger:api",
+ "//lib/guice",
"//lib/log:log4j",
],
)
diff --git a/java/com/google/gerrit/server/logging/CallerFinder.java b/java/com/google/gerrit/server/logging/CallerFinder.java
index c27dbbb05c..bd7e608f31 100644
--- a/java/com/google/gerrit/server/logging/CallerFinder.java
+++ b/java/com/google/gerrit/server/logging/CallerFinder.java
@@ -142,13 +142,27 @@ public abstract class CallerFinder {
/**
* The minimum number of calls known to have occurred between the first call to the target class
- * and the call of {@link #findCaller()}. If in doubt, specify zero here to avoid accidentally
+ * and the call of {@link #findCallerLazy()}. If in doubt, specify zero here to avoid accidentally
* skipping past the caller.
*
* @return the number of stack elements to skip when computing the caller
*/
public abstract int skip();
+ /**
+ * Packages that should be ignored and not be considered as caller once a target has been found.
+ *
+ * @return the ignored packages
+ */
+ public abstract ImmutableList<String> ignoredPackages();
+
+ /**
+ * Classes that should be ignored and not be considered as caller once a target has been found.
+ *
+ * @return the qualified names of the ignored classes
+ */
+ public abstract ImmutableList<String> ignoredClasses();
+
@AutoValue.Builder
public abstract static class Builder {
abstract ImmutableList.Builder<Class<?>> targetsBuilder();
@@ -164,10 +178,24 @@ public abstract class CallerFinder {
public abstract Builder skip(int skip);
+ abstract ImmutableList.Builder<String> ignoredPackagesBuilder();
+
+ public Builder addIgnoredPackage(String ignoredPackage) {
+ ignoredPackagesBuilder().add(ignoredPackage);
+ return this;
+ }
+
+ abstract ImmutableList.Builder<String> ignoredClassesBuilder();
+
+ public Builder addIgnoredClass(Class<?> ignoredClass) {
+ ignoredClassesBuilder().add(ignoredClass.getName());
+ return this;
+ }
+
public abstract CallerFinder build();
}
- public LazyArg<String> findCaller() {
+ public LazyArg<String> findCallerLazy() {
return lazy(
() ->
targets().stream()
@@ -194,7 +222,9 @@ public abstract class CallerFinder {
StackTraceElement element = stack[index];
if (isCaller(target, element.getClassName(), matchSubClasses())) {
foundCaller = true;
- } else if (foundCaller) {
+ } else if (foundCaller
+ && !ignoredPackages().contains(getPackageName(element))
+ && !ignoredClasses().contains(element.getClassName())) {
return Optional.of(element.toString());
}
}
@@ -206,6 +236,11 @@ public abstract class CallerFinder {
}
}
+ private static String getPackageName(StackTraceElement element) {
+ String className = element.getClassName();
+ return className.substring(0, className.lastIndexOf("."));
+ }
+
private boolean isCaller(Class<?> target, String className, boolean matchSubClasses)
throws ClassNotFoundException {
if (matchSubClasses) {
diff --git a/java/com/google/gerrit/server/logging/LoggingContext.java b/java/com/google/gerrit/server/logging/LoggingContext.java
index cb7d01e588..8e786fc09f 100644
--- a/java/com/google/gerrit/server/logging/LoggingContext.java
+++ b/java/com/google/gerrit/server/logging/LoggingContext.java
@@ -14,8 +14,14 @@
package com.google.gerrit.server.logging;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.flogger.backend.Tags;
+import com.google.inject.Provider;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
@@ -35,6 +41,9 @@ public class LoggingContext extends com.google.common.flogger.backend.system.Log
private static final ThreadLocal<MutableTags> tags = new ThreadLocal<>();
private static final ThreadLocal<Boolean> forceLogging = new ThreadLocal<>();
+ private static final ThreadLocal<Boolean> performanceLogging = new ThreadLocal<>();
+ private static final ThreadLocal<MutablePerformanceLogRecords> performanceLogRecords =
+ new ThreadLocal<>();
private LoggingContext() {}
@@ -47,14 +56,39 @@ public class LoggingContext extends com.google.common.flogger.backend.system.Log
if (runnable instanceof LoggingContextAwareRunnable) {
return runnable;
}
- return new LoggingContextAwareRunnable(runnable);
+
+ // Pass the MutablePerformanceLogRecords instance into the LoggingContextAwareRunnable
+ // constructor so that performance log records that are created in the wrapped runnable are
+ // added to this MutablePerformanceLogRecords instance. This is important since performance
+ // log records are processed only at the end of the request and performance log records that
+ // are created in another thread should not get lost.
+ return new LoggingContextAwareRunnable(
+ runnable, getInstance().getMutablePerformanceLogRecords());
}
public static <T> Callable<T> copy(Callable<T> callable) {
if (callable instanceof LoggingContextAwareCallable) {
return callable;
}
- return new LoggingContextAwareCallable<>(callable);
+
+ // Pass the MutablePerformanceLogRecords instance into the LoggingContextAwareCallable
+ // constructor so that performance log records that are created in the wrapped runnable are
+ // added to this MutablePerformanceLogRecords instance. This is important since performance
+ // log records are processed only at the end of the request and performance log records that
+ // are created in another thread should not get lost.
+ return new LoggingContextAwareCallable<>(
+ callable, getInstance().getMutablePerformanceLogRecords());
+ }
+
+ public boolean isEmpty() {
+ return tags.get() == null && forceLogging.get() == null && performanceLogging.get() == null;
+ }
+
+ public void clear() {
+ tags.remove();
+ forceLogging.remove();
+ performanceLogging.remove();
+ performanceLogRecords.remove();
}
@Override
@@ -119,4 +153,113 @@ public class LoggingContext extends com.google.common.flogger.backend.system.Log
}
return Boolean.TRUE.equals(oldValue);
}
+
+ boolean isPerformanceLogging() {
+ Boolean isPerformanceLogging = performanceLogging.get();
+ return isPerformanceLogging != null ? isPerformanceLogging : false;
+ }
+
+ /**
+ * Enables performance logging.
+ *
+ * <p>It's important to enable performance logging only in a context that ensures to consume the
+ * captured performance log records. Otherwise captured performance log records might leak into
+ * other requests that are executed by the same thread (if a thread pool is used to process
+ * requests).
+ *
+ * @param enable whether performance logging should be enabled.
+ * @return whether performance logging was be enabled before invoking this method (old value).
+ */
+ boolean performanceLogging(boolean enable) {
+ Boolean oldValue = performanceLogging.get();
+ if (enable) {
+ performanceLogging.set(true);
+ } else {
+ performanceLogging.remove();
+ }
+ return oldValue != null ? oldValue : false;
+ }
+
+ /**
+ * Adds a performance log record, if performance logging is enabled.
+ *
+ * @param recordProvider Provider for the performance log record. This provider is only invoked if
+ * performance logging is enabled. This means if performance logging is disabled, we avoid the
+ * creation of a {@link PerformanceLogRecord}.
+ */
+ public void addPerformanceLogRecord(Provider<PerformanceLogRecord> recordProvider) {
+ if (!isPerformanceLogging()) {
+ // return early and avoid the creation of a PerformanceLogRecord
+ return;
+ }
+
+ getMutablePerformanceLogRecords().add(recordProvider.get());
+ }
+
+ ImmutableList<PerformanceLogRecord> getPerformanceLogRecords() {
+ MutablePerformanceLogRecords records = performanceLogRecords.get();
+ if (records != null) {
+ return records.list();
+ }
+ return ImmutableList.of();
+ }
+
+ void clearPerformanceLogEntries() {
+ performanceLogRecords.remove();
+ }
+
+ /**
+ * Set the performance log records in this logging context. Existing log records are overwritten.
+ *
+ * <p>This method makes a defensive copy of the passed in list.
+ *
+ * @param newPerformanceLogRecords performance log records that should be set
+ */
+ void setPerformanceLogRecords(List<PerformanceLogRecord> newPerformanceLogRecords) {
+ if (newPerformanceLogRecords.isEmpty()) {
+ performanceLogRecords.remove();
+ return;
+ }
+
+ getMutablePerformanceLogRecords().set(newPerformanceLogRecords);
+ }
+
+ /**
+ * Sets a {@link MutablePerformanceLogRecords} instance for storing performance log records.
+ *
+ * <p><strong>Attention:</strong> The passed in {@link MutablePerformanceLogRecords} instance is
+ * directly stored in the logging context.
+ *
+ * <p>This method is intended to be only used when the logging context is copied to a new thread
+ * to ensure that the performance log records that are added in the new thread are added to the
+ * same {@link MutablePerformanceLogRecords} instance (see {@link LoggingContextAwareRunnable} and
+ * {@link LoggingContextAwareCallable}). This is important since performance log records are
+ * processed only at the end of the request and performance log records that are created in
+ * another thread should not get lost.
+ *
+ * @param mutablePerformanceLogRecords the {@link MutablePerformanceLogRecords} instance in which
+ * performance log records should be stored
+ */
+ void setMutablePerformanceLogRecords(MutablePerformanceLogRecords mutablePerformanceLogRecords) {
+ performanceLogRecords.set(requireNonNull(mutablePerformanceLogRecords));
+ }
+
+ private MutablePerformanceLogRecords getMutablePerformanceLogRecords() {
+ MutablePerformanceLogRecords records = performanceLogRecords.get();
+ if (records == null) {
+ records = new MutablePerformanceLogRecords();
+ performanceLogRecords.set(records);
+ }
+ return records;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("tags", tags.get())
+ .add("forceLogging", forceLogging.get())
+ .add("performanceLogging", performanceLogging.get())
+ .add("performanceLogRecords", performanceLogRecords.get())
+ .toString();
+ }
}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
index 6aff5c4b32..d2701d7416 100644
--- a/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.logging;
import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.flogger.FluentLogger;
import java.util.concurrent.Callable;
/**
@@ -31,16 +32,30 @@ import java.util.concurrent.Callable;
* @see LoggingContextAwareRunnable
*/
class LoggingContextAwareCallable<T> implements Callable<T> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Callable<T> callable;
private final Thread callingThread;
private final ImmutableSetMultimap<String, String> tags;
private final boolean forceLogging;
+ private final boolean performanceLogging;
+ private final MutablePerformanceLogRecords mutablePerformanceLogRecords;
- LoggingContextAwareCallable(Callable<T> callable) {
+ /**
+ * Creates a LoggingContextAwareCallable that wraps the given {@link Callable}.
+ *
+ * @param callable Callable that should be wrapped.
+ * @param mutablePerformanceLogRecords instance of {@link MutablePerformanceLogRecords} to which
+ * performance log records that are created from the runnable are added
+ */
+ LoggingContextAwareCallable(
+ Callable<T> callable, MutablePerformanceLogRecords mutablePerformanceLogRecords) {
this.callable = callable;
this.callingThread = Thread.currentThread();
this.tags = LoggingContext.getInstance().getTagsAsMap();
this.forceLogging = LoggingContext.getInstance().isLoggingForced();
+ this.performanceLogging = LoggingContext.getInstance().isPerformanceLogging();
+ this.mutablePerformanceLogRecords = mutablePerformanceLogRecords;
}
@Override
@@ -50,17 +65,29 @@ class LoggingContextAwareCallable<T> implements Callable<T> {
return callable.call();
}
- // propagate logging context
LoggingContext loggingCtx = LoggingContext.getInstance();
- ImmutableSetMultimap<String, String> oldTags = loggingCtx.getTagsAsMap();
- boolean oldForceLogging = loggingCtx.isLoggingForced();
+
+ if (!loggingCtx.isEmpty()) {
+ logger.atWarning().log("Logging context is not empty: %s", loggingCtx);
+ }
+
+ // propagate logging context
loggingCtx.setTags(tags);
loggingCtx.forceLogging(forceLogging);
+ loggingCtx.performanceLogging(performanceLogging);
+
+ // For the performance log records use the {@link MutablePerformanceLogRecords} instance from
+ // the logging context of the calling thread in the logging context of the new thread. This way
+ // performance log records that are created from the new thread are available from the logging
+ // context of the calling thread. This is important since performance log records are processed
+ // only at the end of the request and performance log records that are created in another thread
+ // should not get lost.
+ loggingCtx.setMutablePerformanceLogRecords(mutablePerformanceLogRecords);
try {
return callable.call();
} finally {
- loggingCtx.setTags(oldTags);
- loggingCtx.forceLogging(oldForceLogging);
+ // Cleanup logging context. This is important if the thread is pooled and reused.
+ loggingCtx.clear();
}
}
}
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
index 0bd7d007ad..23162b10e4 100644
--- a/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.logging;
import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.flogger.FluentLogger;
/**
* Wrapper for a {@link Runnable} that copies the {@link LoggingContext} from the current thread to
@@ -49,16 +50,30 @@ import com.google.common.collect.ImmutableSetMultimap;
* @see LoggingContextAwareCallable
*/
public class LoggingContextAwareRunnable implements Runnable {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final Runnable runnable;
private final Thread callingThread;
private final ImmutableSetMultimap<String, String> tags;
private final boolean forceLogging;
+ private final boolean performanceLogging;
+ private final MutablePerformanceLogRecords mutablePerformanceLogRecords;
- LoggingContextAwareRunnable(Runnable runnable) {
+ /**
+ * Creates a LoggingContextAwareRunnable that wraps the given {@link Runnable}.
+ *
+ * @param runnable Runnable that should be wrapped.
+ * @param mutablePerformanceLogRecords instance of {@link MutablePerformanceLogRecords} to which
+ * performance log records that are created from the runnable are added
+ */
+ LoggingContextAwareRunnable(
+ Runnable runnable, MutablePerformanceLogRecords mutablePerformanceLogRecords) {
this.runnable = runnable;
this.callingThread = Thread.currentThread();
this.tags = LoggingContext.getInstance().getTagsAsMap();
this.forceLogging = LoggingContext.getInstance().isLoggingForced();
+ this.performanceLogging = LoggingContext.getInstance().isPerformanceLogging();
+ this.mutablePerformanceLogRecords = mutablePerformanceLogRecords;
}
public Runnable unwrap() {
@@ -73,17 +88,29 @@ public class LoggingContextAwareRunnable implements Runnable {
return;
}
- // propagate logging context
LoggingContext loggingCtx = LoggingContext.getInstance();
- ImmutableSetMultimap<String, String> oldTags = loggingCtx.getTagsAsMap();
- boolean oldForceLogging = loggingCtx.isLoggingForced();
+
+ if (!loggingCtx.isEmpty()) {
+ logger.atWarning().log("Logging context is not empty: %s", loggingCtx);
+ }
+
+ // propagate logging context
loggingCtx.setTags(tags);
loggingCtx.forceLogging(forceLogging);
+ loggingCtx.performanceLogging(performanceLogging);
+
+ // For the performance log records use the {@link MutablePerformanceLogRecords} instance from
+ // the logging context of the calling thread in the logging context of the new thread. This way
+ // performance log records that are created from the new thread are available from the logging
+ // context of the calling thread. This is important since performance log records are processed
+ // only at the end of the request and performance log records that are created in another thread
+ // should not get lost.
+ loggingCtx.setMutablePerformanceLogRecords(mutablePerformanceLogRecords);
try {
runnable.run();
} finally {
- loggingCtx.setTags(oldTags);
- loggingCtx.forceLogging(oldForceLogging);
+ // Cleanup logging context. This is important if the thread is pooled and reused.
+ loggingCtx.clear();
}
}
}
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
new file mode 100644
index 0000000000..7af204e769
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -0,0 +1,339 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.LazyArg;
+import com.google.common.flogger.LazyArgs;
+import com.google.gerrit.common.Nullable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Optional;
+
+/** Metadata that is provided to {@link PerformanceLogger}s as context for performance records. */
+@AutoValue
+public abstract class Metadata {
+ // The numeric ID of an account.
+ public abstract Optional<Integer> accountId();
+
+ // The type of an action (ACCOUNT_UPDATE, CHANGE_UPDATE, GROUP_UPDATE, INDEX_QUERY,
+ // PLUGIN_UPDATE).
+ public abstract Optional<String> actionType();
+
+ // An authentication domain name.
+ public abstract Optional<String> authDomainName();
+
+ // The name of a branch.
+ public abstract Optional<String> branchName();
+
+ // Key of an entity in a cache.
+ public abstract Optional<String> cacheKey();
+
+ // The name of a cache.
+ public abstract Optional<String> cacheName();
+
+ // The name of the implementation class.
+ public abstract Optional<String> className();
+
+ // The numeric ID of a change.
+ public abstract Optional<Integer> changeId();
+
+ // The type of change ID which the user used to identify a change (e.g. numeric ID, triplet etc.).
+ public abstract Optional<String> changeIdType();
+
+ // The type of an event.
+ public abstract Optional<String> eventType();
+
+ // The value of the @Export annotation which was used to register a plugin extension.
+ public abstract Optional<String> exportValue();
+
+ // Path of a file in a repository.
+ public abstract Optional<String> filePath();
+
+ // Garbage collector name.
+ public abstract Optional<String> garbageCollectorName();
+
+ // Git operation (CLONE, FETCH).
+ public abstract Optional<String> gitOperation();
+
+ // The numeric ID of an internal group.
+ public abstract Optional<Integer> groupId();
+
+ // The name of a group.
+ public abstract Optional<String> groupName();
+
+ // The UUID of a group.
+ public abstract Optional<String> groupUuid();
+
+ // HTTP status response code.
+ public abstract Optional<Integer> httpStatus();
+
+ // The name of a secondary index.
+ public abstract Optional<String> indexName();
+
+ // The version of a secondary index.
+ public abstract Optional<Integer> indexVersion();
+
+ // The name of the implementation method.
+ public abstract Optional<String> methodName();
+
+ // One or more resources
+ public abstract Optional<Boolean> multiple();
+
+ // The name of an operation that is performed.
+ public abstract Optional<String> operationName();
+
+ // Partial or full computation
+ public abstract Optional<Boolean> partial();
+
+ // Path of a metadata file in NoteDb.
+ public abstract Optional<String> noteDbFilePath();
+
+ // Name of a metadata ref in NoteDb.
+ public abstract Optional<String> noteDbRefName();
+
+ // Type of a sequence in NoteDb (ACCOUNTS, CHANGES, GROUPS).
+ public abstract Optional<String> noteDbSequenceType();
+
+ // Name of a "table" in NoteDb (if set, always CHANGES).
+ public abstract Optional<String> noteDbTable();
+
+ // The ID of a patch set.
+ public abstract Optional<Integer> patchSetId();
+
+ // Plugin metadata that doesn't fit into any other category.
+ public abstract ImmutableList<PluginMetadata> pluginMetadata();
+
+ // The name of a plugin.
+ public abstract Optional<String> pluginName();
+
+ // The name of a Gerrit project (aka Git repository).
+ public abstract Optional<String> projectName();
+
+ // The type of a Git push to Gerrit (CREATE_REPLACE, NORMAL, AUTOCLOSE).
+ public abstract Optional<String> pushType();
+
+ // The number of resources that is processed.
+ public abstract Optional<Integer> resourceCount();
+
+ // The name of a REST view.
+ public abstract Optional<String> restViewName();
+
+ // The SHA1 of Git commit.
+ public abstract Optional<String> revision();
+
+ // The username of an account.
+ public abstract Optional<String> username();
+
+ /**
+ * Returns a string representation of this instance that is suitable for logging. This is wrapped
+ * in a {@link LazyArg} because it is expensive to evaluate.
+ *
+ * <p>{@link #toString()} formats the {@link Optional} fields as {@code key=Optional[value]} or
+ * {@code key=Optional.empty}. Since this class has many optional fields from which usually only a
+ * few are populated this leads to long string representations such as
+ *
+ * <pre>
+ * Metadata{accountId=Optional.empty, actionType=Optional.empty, authDomainName=Optional.empty,
+ * branchName=Optional.empty, cacheKey=Optional.empty, cacheName=Optional.empty,
+ * className=Optional.empty, changeId=Optional[9212550], changeIdType=Optional.empty,
+ * eventType=Optional.empty, exportValue=Optional.empty, filePath=Optional.empty,
+ * garbageCollectorName=Optional.empty, gitOperation=Optional.empty, groupId=Optional.empty,
+ * groupName=Optional.empty, groupUuid=Optional.empty, httpStatus=Optional.empty,
+ * indexName=Optional.empty, indexVersion=Optional[0], methodName=Optional.empty,
+ * multiple=Optional.empty, operationName=Optional.empty, partial=Optional.empty,
+ * noteDbFilePath=Optional.empty, noteDbRefName=Optional.empty,
+ * noteDbSequenceType=Optional.empty, noteDbTable=Optional.empty, patchSetId=Optional.empty,
+ * pluginMetadata=[], pluginName=Optional.empty, projectName=Optional.empty,
+ * pushType=Optional.empty, resourceCount=Optional.empty, restViewName=Optional.empty,
+ * revision=Optional.empty, username=Optional.empty}
+ * </pre>
+ *
+ * <p>That's hard to read in logs. This is why this method
+ *
+ * <ul>
+ * <li>drops fields which have {@code Optional.empty} as value and
+ * <li>reformats values that are {@code Optional[value]} to {@code value}.
+ * </ul>
+ *
+ * <p>For the example given above the formatted string would look like this:
+ *
+ * <pre>
+ * Metadata{changeId=9212550, indexVersion=0, pluginMetadata=[]}
+ * </pre>
+ *
+ * @return string representation of this instance that is suitable for logging
+ */
+ LazyArg<String> toStringForLoggingLazy() {
+ // Don't use a lambda because different compilers generate different method names for lambdas,
+ // e.g. "lambda$myFunction$0" vs. just "lambda$0" in Eclipse. We need to identify the method
+ // by name to skip it and avoid infinite recursion.
+ return LazyArgs.lazy(this::toStringForLoggingImpl);
+ }
+
+ private String toStringForLoggingImpl() {
+ // Append class name.
+ String className = getClass().getSimpleName();
+ if (className.startsWith("AutoValue_")) {
+ className = className.substring(10);
+ }
+ ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
+
+ // Append key-value pairs for field which are set.
+ Method[] methods = Metadata.class.getDeclaredMethods();
+ Arrays.sort(methods, Comparator.comparing(Method::getName));
+ for (Method method : methods) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ // skip static method
+ continue;
+ }
+
+ if (method.getName().equals("toStringForLoggingLazy")
+ || method.getName().equals("toStringForLoggingImpl")) {
+ // Don't call myself in infinite recursion.
+ continue;
+ }
+
+ if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
+ // skip method since it's not a getter
+ continue;
+ }
+
+ method.setAccessible(true);
+
+ Object returnValue;
+ try {
+ returnValue = method.invoke(this);
+ } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+ // should never happen
+ throw new IllegalStateException(e);
+ }
+
+ if (returnValue instanceof Optional) {
+ Optional<?> fieldValueOptional = (Optional<?>) returnValue;
+ if (!fieldValueOptional.isPresent()) {
+ // drop this key-value pair
+ continue;
+ }
+
+ // format as 'key=value' instead of 'key=Optional[value]'
+ stringHelper.add(method.getName(), fieldValueOptional.get());
+ } else {
+ // not an Optional value, keep as is
+ stringHelper.add(method.getName(), returnValue);
+ }
+ }
+
+ return stringHelper.toString();
+ }
+
+ public static Metadata.Builder builder() {
+ return new AutoValue_Metadata.Builder();
+ }
+
+ public static Metadata empty() {
+ return builder().build();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder accountId(int accountId);
+
+ public abstract Builder actionType(@Nullable String actionType);
+
+ public abstract Builder authDomainName(@Nullable String authDomainName);
+
+ public abstract Builder branchName(@Nullable String branchName);
+
+ public abstract Builder cacheKey(@Nullable String cacheKey);
+
+ public abstract Builder cacheName(@Nullable String cacheName);
+
+ public abstract Builder className(@Nullable String className);
+
+ public abstract Builder changeId(int changeId);
+
+ public abstract Builder changeIdType(@Nullable String changeIdType);
+
+ public abstract Builder eventType(@Nullable String eventType);
+
+ public abstract Builder exportValue(@Nullable String exportValue);
+
+ public abstract Builder filePath(@Nullable String filePath);
+
+ public abstract Builder garbageCollectorName(@Nullable String garbageCollectorName);
+
+ public abstract Builder gitOperation(@Nullable String gitOperation);
+
+ public abstract Builder groupId(int groupId);
+
+ public abstract Builder groupName(@Nullable String groupName);
+
+ public abstract Builder groupUuid(@Nullable String groupUuid);
+
+ public abstract Builder httpStatus(int httpStatus);
+
+ public abstract Builder indexName(@Nullable String indexName);
+
+ public abstract Builder indexVersion(int indexVersion);
+
+ public abstract Builder methodName(@Nullable String methodName);
+
+ public abstract Builder multiple(boolean multiple);
+
+ public abstract Builder operationName(String operationName);
+
+ public abstract Builder partial(boolean partial);
+
+ public abstract Builder noteDbFilePath(@Nullable String noteDbFilePath);
+
+ public abstract Builder noteDbRefName(@Nullable String noteDbRefName);
+
+ public abstract Builder noteDbSequenceType(@Nullable String noteDbSequenceType);
+
+ public abstract Builder noteDbTable(@Nullable String noteDbTable);
+
+ public abstract Builder patchSetId(int patchSetId);
+
+ abstract ImmutableList.Builder<PluginMetadata> pluginMetadataBuilder();
+
+ public Builder addPluginMetadata(PluginMetadata pluginMetadata) {
+ pluginMetadataBuilder().add(pluginMetadata);
+ return this;
+ }
+
+ public abstract Builder pluginName(@Nullable String pluginName);
+
+ public abstract Builder projectName(@Nullable String projectName);
+
+ public abstract Builder pushType(@Nullable String pushType);
+
+ public abstract Builder resourceCount(int resourceCount);
+
+ public abstract Builder restViewName(@Nullable String restViewName);
+
+ public abstract Builder revision(@Nullable String revision);
+
+ public abstract Builder username(@Nullable String username);
+
+ public abstract Metadata build();
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/MutablePerformanceLogRecords.java b/java/com/google/gerrit/server/logging/MutablePerformanceLogRecords.java
new file mode 100644
index 0000000000..4ee70d7c19
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/MutablePerformanceLogRecords.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Thread-safe store for performance log records.
+ *
+ * <p>This class is intended to keep track of performance log records in {@link LoggingContext}. It
+ * needs to be thread-safe because it gets shared between threads when the logging context is copied
+ * to another thread (see {@link LoggingContextAwareRunnable} and {@link
+ * LoggingContextAwareCallable}. In this case the logging contexts of both threads share the same
+ * instance of this class. This is important since performance log records are processed only at the
+ * end of a request and performance log records that are created in another thread should not get
+ * lost.
+ */
+public class MutablePerformanceLogRecords {
+ private final ArrayList<PerformanceLogRecord> performanceLogRecords = new ArrayList<>();
+
+ public synchronized void add(PerformanceLogRecord record) {
+ performanceLogRecords.add(record);
+ }
+
+ public synchronized void set(List<PerformanceLogRecord> records) {
+ performanceLogRecords.clear();
+ performanceLogRecords.addAll(records);
+ }
+
+ public synchronized ImmutableList<PerformanceLogRecord> list() {
+ return ImmutableList.copyOf(performanceLogRecords);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("performanceLogRecords", performanceLogRecords)
+ .toString();
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/MutableTags.java b/java/com/google/gerrit/server/logging/MutableTags.java
index f70a8dbf1d..83009a6ae4 100644
--- a/java/com/google/gerrit/server/logging/MutableTags.java
+++ b/java/com/google/gerrit/server/logging/MutableTags.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.logging;
import static java.util.Objects.requireNonNull;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
@@ -110,4 +111,10 @@ public class MutableTags {
tagMap.forEach(tagsBuilder::addTag);
tags = tagsBuilder.build();
}
+
+ @Override
+ public String toString() {
+ buildTags();
+ return MoreObjects.toStringHelper(this).add("tags", tags).toString();
+ }
}
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogContext.java b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
new file mode 100644
index 0000000000..b6dafdced2
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
@@ -0,0 +1,117 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.Extension;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Context for capturing performance log records. When the context is closed the performance log
+ * records are handed over to the registered {@link PerformanceLogger}s.
+ *
+ * <p>Capturing performance log records is disabled if there are no {@link PerformanceLogger}
+ * registered (in this case the captured performance log records would never be used).
+ *
+ * <p>It's important to enable capturing of performance log records in a context that ensures to
+ * consume the captured performance log records. Otherwise captured performance log records might
+ * leak into other requests that are executed by the same thread (if a thread pool is used to
+ * process requests).
+ */
+public class PerformanceLogContext implements AutoCloseable {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ // Do not use PluginSetContext. PluginSetContext traces the plugin latency with a timer metric
+ // which would result in a performance log and we don't want to log the performance of writing
+ // a performance log in the performance log (endless loop).
+ private final DynamicSet<PerformanceLogger> performanceLoggers;
+
+ private final boolean oldPerformanceLogging;
+ private final ImmutableList<PerformanceLogRecord> oldPerformanceLogRecords;
+
+ public PerformanceLogContext(
+ Config gerritConfig, DynamicSet<PerformanceLogger> performanceLoggers) {
+ this.performanceLoggers = performanceLoggers;
+
+ // Just in case remember the old state and reset performance log entries.
+ this.oldPerformanceLogging = LoggingContext.getInstance().isPerformanceLogging();
+ this.oldPerformanceLogRecords = LoggingContext.getInstance().getPerformanceLogRecords();
+ LoggingContext.getInstance().clearPerformanceLogEntries();
+
+ // Do not create performance log entries if performance logging is disabled or if no
+ // PerformanceLogger is registered.
+ boolean enablePerformanceLogging =
+ gerritConfig.getBoolean("tracing", "performanceLogging", true);
+ LoggingContext.getInstance()
+ .performanceLogging(
+ enablePerformanceLogging && !Iterables.isEmpty(performanceLoggers.entries()));
+ }
+
+ @Override
+ public void close() {
+ if (LoggingContext.getInstance().isPerformanceLogging()) {
+ runEach(performanceLoggers, LoggingContext.getInstance().getPerformanceLogRecords());
+ }
+
+ // Restore old state. Required to support nesting of PerformanceLogContext's.
+ LoggingContext.getInstance().performanceLogging(oldPerformanceLogging);
+ LoggingContext.getInstance().setPerformanceLogRecords(oldPerformanceLogRecords);
+ }
+
+ /**
+ * Invokes all performance loggers.
+ *
+ * <p>Similar to how {@code com.google.gerrit.server.plugincontext.PluginContext} invokes plugins
+ * but without recording metrics for invoking {@link PerformanceLogger}s.
+ *
+ * @param performanceLoggers the performance loggers that should be invoked
+ * @param performanceLogRecords the performance log records that should be handed over to the
+ * performance loggers
+ */
+ private static void runEach(
+ DynamicSet<PerformanceLogger> performanceLoggers,
+ ImmutableList<PerformanceLogRecord> performanceLogRecords) {
+ performanceLoggers
+ .entries()
+ .forEach(
+ p -> {
+ try (TraceContext traceContext = newPluginTrace(p)) {
+ performanceLogRecords.forEach(r -> r.writeTo(p.get()));
+ } catch (Throwable e) {
+ logger.atWarning().withCause(e).log(
+ "Failure in %s of plugin %s", p.get().getClass(), p.getPluginName());
+ }
+ });
+ }
+
+ /**
+ * Opens a trace context for a plugin that implements {@link PerformanceLogger}.
+ *
+ * <p>Basically the same as {@code
+ * com.google.gerrit.server.plugincontext.PluginContext#newTrace(Extension<T>)}. We have this
+ * method here to avoid a dependency on PluginContext which lives in
+ * "//java/com/google/gerrit/server". This package ("//java/com/google/gerrit/server/logging")
+ * should have as few dependencies as possible.
+ *
+ * @param extension performance logger extension
+ * @return the trace context
+ */
+ private static TraceContext newPluginTrace(Extension<PerformanceLogger> extension) {
+ return TraceContext.open().addPluginTag(extension.getPluginName());
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogRecord.java b/java/com/google/gerrit/server/logging/PerformanceLogRecord.java
new file mode 100644
index 0000000000..046eeb3441
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/PerformanceLogRecord.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.auto.value.AutoValue;
+import java.util.Optional;
+
+/**
+ * The record of an operation for which the execution time was measured.
+ *
+ * <p>Metadata to provide additional context can be included by providing a {@link Metadata}
+ * instance.
+ */
+@AutoValue
+public abstract class PerformanceLogRecord {
+ /**
+ * Creates a performance log record without meta data.
+ *
+ * @param operation the name of operation the is was performed
+ * @param durationMs the execution time in milliseconds
+ * @return the performance log record
+ */
+ public static PerformanceLogRecord create(String operation, long durationMs) {
+ return new AutoValue_PerformanceLogRecord(operation, durationMs, Optional.empty());
+ }
+
+ /**
+ * Creates a performance log record with meta data.
+ *
+ * @param operation the name of operation the is was performed
+ * @param durationMs the execution time in milliseconds
+ * @param metadata metadata
+ * @return the performance log record
+ */
+ public static PerformanceLogRecord create(String operation, long durationMs, Metadata metadata) {
+ return new AutoValue_PerformanceLogRecord(operation, durationMs, Optional.of(metadata));
+ }
+
+ public abstract String operation();
+
+ public abstract long durationMs();
+
+ public abstract Optional<Metadata> metadata();
+
+ void writeTo(PerformanceLogger performanceLogger) {
+ if (metadata().isPresent()) {
+ performanceLogger.log(operation(), durationMs(), metadata().get());
+ } else {
+ performanceLogger.log(operation(), durationMs());
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogger.java b/java/com/google/gerrit/server/logging/PerformanceLogger.java
new file mode 100644
index 0000000000..74a1684549
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/PerformanceLogger.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/**
+ * Extension point for logging performance records.
+ *
+ * <p>This extension point is invoked for all operations for which the execution time is measured.
+ * The invocation of the extension point does not happen immediately, but only at the end of a
+ * request (REST call, SSH call, git push). Implementors can write the execution times into a
+ * performance log for further analysis.
+ *
+ * <p>For optimal performance implementors should overwrite the default <code>log</code> methods to
+ * avoid an unneeded instantiation of Metadata.
+ */
+@ExtensionPoint
+public interface PerformanceLogger {
+ /**
+ * Record the execution time of an operation in a performance log.
+ *
+ * @param operation operation that was performed
+ * @param durationMs time that the execution of the operation took (in milliseconds)
+ */
+ default void log(String operation, long durationMs) {
+ log(operation, durationMs, Metadata.empty());
+ }
+
+ /**
+ * Record the execution time of an operation in a performance log.
+ *
+ * @param operation operation that was performed
+ * @param durationMs time that the execution of the operation took (in milliseconds)
+ * @param metadata metadata
+ */
+ void log(String operation, long durationMs, Metadata metadata);
+}
diff --git a/java/com/google/gerrit/server/logging/PluginMetadata.java b/java/com/google/gerrit/server/logging/PluginMetadata.java
new file mode 100644
index 0000000000..21f735960a
--- /dev/null
+++ b/java/com/google/gerrit/server/logging/PluginMetadata.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.Nullable;
+import java.util.Optional;
+
+/**
+ * Key-value pair for custom metadata that is provided by plugins.
+ *
+ * <p>PluginMetadata allows plugins to include custom metadata into the {@link Metadata} instances
+ * that are provided as context for performance tracing.
+ *
+ * <p>Plugins should use PluginMetadata only for metadata kinds that are not known to Gerrit core
+ * (metadata for which {@link Metadata} doesn't have a dedicated field).
+ */
+@AutoValue
+public abstract class PluginMetadata {
+ public static PluginMetadata create(String key, @Nullable String value) {
+ return new AutoValue_PluginMetadata(key, Optional.ofNullable(value));
+ }
+
+ public abstract String key();
+
+ public abstract Optional<String> value();
+}
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
index 5c0406ddd1..21a4ce6f1b 100644
--- a/java/com/google/gerrit/server/logging/TraceContext.java
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
@@ -155,116 +156,66 @@ public class TraceContext implements AutoCloseable {
/**
* Opens a new timer that logs the time for an operation if request tracing is enabled.
*
- * <p>If request tracing is not enabled this is a no-op.
- *
- * @param message the message
- * @return the trace timer
- */
- public static TraceTimer newTimer(String message) {
- return new TraceTimer(message);
- }
-
- /**
- * Opens a new timer that logs the time for an operation if request tracing is enabled.
- *
- * <p>If request tracing is not enabled this is a no-op.
- *
- * @param format the message format string
- * @param arg argument for the message
- * @return the trace timer
- */
- public static TraceTimer newTimer(String format, Object arg) {
- return new TraceTimer(format, arg);
- }
-
- /**
- * Opens a new timer that logs the time for an operation if request tracing is enabled.
- *
- * <p>If request tracing is not enabled this is a no-op.
- *
- * @param format the message format string
- * @param arg1 first argument for the message
- * @param arg2 second argument for the message
+ * @param operation the name of operation the is being performed
* @return the trace timer
*/
- public static TraceTimer newTimer(String format, Object arg1, Object arg2) {
- return new TraceTimer(format, arg1, arg2);
+ public static TraceTimer newTimer(String operation) {
+ return new TraceTimer(requireNonNull(operation, "operation is required"));
}
/**
* Opens a new timer that logs the time for an operation if request tracing is enabled.
*
- * <p>If request tracing is not enabled this is a no-op.
- *
- * @param format the message format string
- * @param arg1 first argument for the message
- * @param arg2 second argument for the message
- * @param arg3 third argument for the message
+ * @param operation the name of operation the is being performed
+ * @param metadata metadata
* @return the trace timer
*/
- public static TraceTimer newTimer(String format, Object arg1, Object arg2, Object arg3) {
- return new TraceTimer(format, arg1, arg2, arg3);
- }
-
- /**
- * Opens a new timer that logs the time for an operation if request tracing is enabled.
- *
- * <p>If request tracing is not enabled this is a no-op.
- *
- * @param format the message format string
- * @param arg1 first argument for the message
- * @param arg2 second argument for the message
- * @param arg3 third argument for the message
- * @param arg4 fourth argument for the message
- * @return the trace timer
- */
- public static TraceTimer newTimer(
- String format, Object arg1, Object arg2, Object arg3, Object arg4) {
- return new TraceTimer(format, arg1, arg2, arg3, arg4);
+ public static TraceTimer newTimer(String operation, Metadata metadata) {
+ return new TraceTimer(
+ requireNonNull(operation, "operation is required"),
+ requireNonNull(metadata, "metadata is required"));
}
public static class TraceTimer implements AutoCloseable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final Consumer<Long> logFn;
+ private final Consumer<Long> doneLogFn;
private final Stopwatch stopwatch;
- private TraceTimer(String message) {
- this(elapsedMs -> logger.atFine().log(message + " (%d ms)", elapsedMs));
- }
-
- private TraceTimer(String format, @Nullable Object arg) {
- this(elapsedMs -> logger.atFine().log(format + " (%d ms)", arg, elapsedMs));
- }
-
- private TraceTimer(String format, @Nullable Object arg1, @Nullable Object arg2) {
- this(elapsedMs -> logger.atFine().log(format + " (%d ms)", arg1, arg2, elapsedMs));
- }
-
- private TraceTimer(
- String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
- this(elapsedMs -> logger.atFine().log(format + " (%d ms)", arg1, arg2, arg3, elapsedMs));
+ private TraceTimer(String operation) {
+ this(
+ () -> logger.atFine().log("Starting timer for %s", operation),
+ elapsedMs -> {
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(() -> PerformanceLogRecord.create(operation, elapsedMs));
+ logger.atFine().log("%s done (%d ms)", operation, elapsedMs);
+ });
}
- private TraceTimer(
- String format,
- @Nullable Object arg1,
- @Nullable Object arg2,
- @Nullable Object arg3,
- @Nullable Object arg4) {
+ private TraceTimer(String operation, Metadata metadata) {
this(
- elapsedMs -> logger.atFine().log(format + " (%d ms)", arg1, arg2, arg3, arg4, elapsedMs));
+ () ->
+ logger.atFine().log(
+ "Starting timer for %s (%s)", operation, metadata.toStringForLoggingLazy()),
+ elapsedMs -> {
+ LoggingContext.getInstance()
+ .addPerformanceLogRecord(
+ () -> PerformanceLogRecord.create(operation, elapsedMs, metadata));
+ logger.atFine().log(
+ "%s (%s) done (%d ms)", operation, metadata.toStringForLoggingLazy(), elapsedMs);
+ });
}
- private TraceTimer(Consumer<Long> logFn) {
- this.logFn = logFn;
+ private TraceTimer(Runnable startLogFn, Consumer<Long> doneLogFn) {
+ startLogFn.run();
+ this.doneLogFn = doneLogFn;
this.stopwatch = Stopwatch.createStarted();
}
@Override
public void close() {
stopwatch.stop();
- logFn.accept(stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ doneLogFn.accept(stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
}
@@ -286,6 +237,12 @@ public class TraceContext implements AutoCloseable {
return this;
}
+ public ImmutableMap<String, String> getTags() {
+ ImmutableMap.Builder<String, String> tagMap = ImmutableMap.builder();
+ tags.cellSet().forEach(c -> tagMap.put(c.getRowKey(), c.getColumnKey()));
+ return tagMap.build();
+ }
+
public TraceContext addPluginTag(String pluginName) {
return addTag(PLUGIN_TAG, pluginName);
}
@@ -299,6 +256,15 @@ public class TraceContext implements AutoCloseable {
return this;
}
+ public boolean isTracing() {
+ return LoggingContext.getInstance().isLoggingForced();
+ }
+
+ public Optional<String> getTraceId() {
+ return LoggingContext.getInstance().getTagsAsMap().get(RequestId.Type.TRACE_ID.name()).stream()
+ .findFirst();
+ }
+
@Override
public void close() {
for (Table.Cell<String, String, Boolean> cell : tags.cellSet()) {
diff --git a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 5b5f33d522..2ff5fc36e1 100644
--- a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.mail;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
diff --git a/java/com/google/gerrit/server/mail/MailUtil.java b/java/com/google/gerrit/server/mail/MailUtil.java
index 4afcc7b2a2..1040a55264 100644
--- a/java/com/google/gerrit/server/mail/MailUtil.java
+++ b/java/com/google/gerrit/server/mail/MailUtil.java
@@ -18,8 +18,8 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.account.AccountResolver;
import java.io.IOException;
@@ -62,7 +62,7 @@ public class MailUtil {
@SuppressWarnings("deprecation")
private static Account.Id toAccountId(AccountResolver accountResolver, String nameOrEmail)
throws UnprocessableEntityException, IOException, ConfigInvalidException {
- return accountResolver.resolveByNameOrEmail(nameOrEmail).asUnique().getAccount().getId();
+ return accountResolver.resolveByNameOrEmail(nameOrEmail).asUnique().account().id();
}
private static boolean isReviewer(FooterLine candidateFooterLine) {
diff --git a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 897a5f21ac..77be665ebd 100644
--- a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.BaseEncoding;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
import com.google.inject.AbstractModule;
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index b1acab9961..158db1c4a0 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -18,7 +18,14 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -26,23 +33,20 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.mail.HtmlParser;
import com.google.gerrit.mail.MailComment;
import com.google.gerrit.mail.MailHeaderParser;
import com.google.gerrit.mail.MailMessage;
import com.google.gerrit.mail.MailMetadata;
import com.google.gerrit.mail.TextParser;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.PublishCommentUtil;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Emails;
@@ -54,6 +58,7 @@ import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
@@ -83,6 +88,15 @@ import java.util.Set;
public class MailProcessor {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final ImmutableMap<MailComment.CommentType, CommentForValidation.CommentType>
+ MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE =
+ ImmutableMap.of(
+ MailComment.CommentType.CHANGE_MESSAGE,
+ CommentForValidation.CommentType.CHANGE_MESSAGE,
+ MailComment.CommentType.FILE_COMMENT, CommentForValidation.CommentType.FILE_COMMENT,
+ MailComment.CommentType.INLINE_COMMENT,
+ CommentForValidation.CommentType.INLINE_COMMENT);
+
private final Emails emails;
private final InboundEmailRejectionSender.Factory emailRejectionSender;
private final RetryHelper retryHelper;
@@ -98,6 +112,7 @@ public class MailProcessor {
private final ApprovalsUtil approvalsUtil;
private final AccountCache accountCache;
private final DynamicItem<UrlFormatter> urlFormatter;
+ private final PluginSetContext<CommentValidator> commentValidators;
@Inject
public MailProcessor(
@@ -115,7 +130,8 @@ public class MailProcessor {
ApprovalsUtil approvalsUtil,
CommentAdded commentAdded,
AccountCache accountCache,
- DynamicItem<UrlFormatter> urlFormatter) {
+ DynamicItem<UrlFormatter> urlFormatter,
+ PluginSetContext<CommentValidator> commentValidators) {
this.emails = emails;
this.emailRejectionSender = emailRejectionSender;
this.retryHelper = retryHelper;
@@ -131,6 +147,7 @@ public class MailProcessor {
this.approvalsUtil = approvalsUtil;
this.accountCache = accountCache;
this.urlFormatter = urlFormatter;
+ this.commentValidators = commentValidators;
}
/**
@@ -187,7 +204,7 @@ public class MailProcessor {
logger.atWarning().log("Mail: Account %s doesn't exist. Will delete message.", accountId);
return;
}
- if (!accountState.get().getAccount().isActive()) {
+ if (!accountState.get().account().isActive()) {
logger.atWarning().log("Mail: Account %s is inactive. Will delete message.", accountId);
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INACTIVE_ACCOUNT);
return;
@@ -211,7 +228,7 @@ public class MailProcessor {
throws UpdateException, RestApiException {
try (ManualRequestContext ctx = oneOffRequestContext.openAs(sender)) {
List<ChangeData> changeDataList =
- queryProvider.get().byLegacyChangeId(new Change.Id(metadata.changeNumber));
+ queryProvider.get().byLegacyChangeId(Change.id(metadata.changeNumber));
if (changeDataList.size() != 1) {
logger.atSevere().log(
"Message %s references unique change %s,"
@@ -259,7 +276,22 @@ public class MailProcessor {
return;
}
- Op o = new Op(new PatchSet.Id(cd.getId(), metadata.patchSet), parsedComments, message.id());
+ ImmutableList<CommentForValidation> parsedCommentsForValidation =
+ parsedComments.stream()
+ .map(
+ comment ->
+ CommentForValidation.create(
+ MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE.get(comment.getType()),
+ comment.getMessage()))
+ .collect(ImmutableList.toImmutableList());
+ ImmutableList<CommentValidationFailure> commentValidationFailures =
+ PublishCommentUtil.findInvalidComments(commentValidators, parsedCommentsForValidation);
+ if (!commentValidationFailures.isEmpty()) {
+ sendRejectionEmail(message, InboundEmailRejectionSender.Error.COMMENT_REJECTED);
+ return;
+ }
+
+ Op o = new Op(PatchSet.id(cd.getId(), metadata.patchSet), parsedComments, message.id());
BatchUpdate batchUpdate = buf.create(project, ctx.getUser(), TimeUtil.nowTs());
batchUpdate.addOp(cd.getId(), o);
batchUpdate.execute();
@@ -302,7 +334,7 @@ public class MailProcessor {
persistentCommentFromMailComment(ctx, c, targetPatchSetForComment(ctx, c, patchSet)));
}
commentsUtil.putComments(
- ctx.getUpdate(ctx.getChange().currentPatchSetId()), Status.PUBLISHED, comments);
+ ctx.getUpdate(ctx.getChange().currentPatchSetId()), Comment.Status.PUBLISHED, comments);
return true;
}
@@ -330,7 +362,7 @@ public class MailProcessor {
approvalsUtil
.byPatchSetUser(
notes, psId, ctx.getAccountId(), ctx.getRevWalk(), ctx.getRepoView().getConfig())
- .forEach(a -> approvals.put(a.getLabel(), a.getValue()));
+ .forEach(a -> approvals.put(a.label(), a.value()));
// Fire Gerrit event. Note that approvals can't be granted via email, so old and new approvals
// are always the same here.
commentAdded.fire(
@@ -362,7 +394,7 @@ public class MailProcessor {
if (mailComment.getInReplyTo() != null) {
return psUtil.get(
ctx.getNotes(),
- new PatchSet.Id(ctx.getChange().getId(), mailComment.getInReplyTo().key.patchSetId));
+ PatchSet.id(ctx.getChange().getId(), mailComment.getInReplyTo().key.patchSetId));
}
return current;
}
@@ -386,7 +418,7 @@ public class MailProcessor {
commentsUtil.newComment(
ctx,
fileName,
- patchSetForComment.getId(),
+ patchSetForComment.id(),
(short) side.ordinal(),
mailComment.getMessage(),
false,
@@ -399,7 +431,7 @@ public class MailProcessor {
comment.range = mailComment.getInReplyTo().range;
comment.unresolved = mailComment.getInReplyTo().unresolved;
}
- CommentsUtil.setCommentRevId(comment, patchListCache, ctx.getChange(), patchSetForComment);
+ CommentsUtil.setCommentCommitId(comment, patchListCache, ctx.getChange(), patchSetForComment);
return comment;
}
}
diff --git a/java/com/google/gerrit/server/mail/send/AbandonedSender.java b/java/com/google/gerrit/server/mail/send/AbandonedSender.java
index 1017965d2e..a6fb4de07a 100644
--- a/java/com/google/gerrit/server/mail/send/AbandonedSender.java
+++ b/java/com/google/gerrit/server/mail/send/AbandonedSender.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -25,13 +25,13 @@ import com.google.inject.assistedinject.Assisted;
public class AbandonedSender extends ReplyToChangeSender {
public interface Factory extends ReplyToChangeSender.Factory<AbandonedSender> {
@Override
- AbandonedSender create(Project.NameKey project, Change.Id change);
+ AbandonedSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
public AbandonedSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "abandon", ChangeEmail.newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "abandon", ChangeEmail.newChangeData(args, project, changeId));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/AddKeySender.java b/java/com/google/gerrit/server/mail/send/AddKeySender.java
index 6a4918cf36..8b3d3f7348 100644
--- a/java/com/google/gerrit/server/mail/send/AddKeySender.java
+++ b/java/com/google/gerrit/server/mail/send/AddKeySender.java
@@ -37,8 +37,8 @@ public class AddKeySender extends OutgoingEmail {
@AssistedInject
public AddKeySender(
- EmailArguments ea, @Assisted IdentifiedUser user, @Assisted AccountSshKey sshKey) {
- super(ea, "addkey");
+ EmailArguments args, @Assisted IdentifiedUser user, @Assisted AccountSshKey sshKey) {
+ super(args, "addkey");
this.user = user;
this.sshKey = sshKey;
this.gpgKeys = null;
@@ -46,8 +46,8 @@ public class AddKeySender extends OutgoingEmail {
@AssistedInject
public AddKeySender(
- EmailArguments ea, @Assisted IdentifiedUser user, @Assisted List<String> gpgKeys) {
- super(ea, "addkey");
+ EmailArguments args, @Assisted IdentifiedUser user, @Assisted List<String> gpgKeys) {
+ super(args, "addkey");
this.user = user;
this.sshKey = null;
this.gpgKeys = gpgKeys;
@@ -79,7 +79,7 @@ public class AddKeySender extends OutgoingEmail {
}
public String getEmail() {
- return user.getAccount().getPreferredEmail();
+ return user.getAccount().preferredEmail();
}
public String getUserNameEmail() {
diff --git a/java/com/google/gerrit/server/mail/send/AddReviewerSender.java b/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
index 22abd9c38e..96d9483c38 100644
--- a/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
+++ b/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
@@ -14,22 +14,22 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
/** Asks a user to review a change. */
public class AddReviewerSender extends NewChangeSender {
public interface Factory {
- AddReviewerSender create(Project.NameKey project, Change.Id id);
+ AddReviewerSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
public AddReviewerSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, newChangeData(args, project, changeId));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index beb179c2a3..0ff607230b 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -20,18 +20,18 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.mail.MailHeader;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
@@ -84,11 +84,11 @@ public abstract class ChangeEmail extends NotificationEmail {
protected Set<Account.Id> authors;
protected boolean emailOnlyAuthors;
- protected ChangeEmail(EmailArguments ea, String mc, ChangeData cd) {
- super(ea, mc, cd);
- changeData = cd;
- change = cd.change();
- emailOnlyAuthors = false;
+ protected ChangeEmail(EmailArguments args, String messageClass, ChangeData changeData) {
+ super(args, messageClass, changeData);
+ this.changeData = changeData;
+ this.change = changeData.change();
+ this.emailOnlyAuthors = false;
}
@Override
@@ -156,10 +156,10 @@ public abstract class ChangeEmail extends NotificationEmail {
}
if (patchSet != null) {
- setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.getPatchSetId() + "");
+ setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.number() + "");
if (patchSetInfo == null) {
try {
- patchSetInfo = args.patchSetInfoFactory.get(changeData.notes(), patchSet.getId());
+ patchSetInfo = args.patchSetInfoFactory.get(changeData.notes(), patchSet.id());
} catch (PatchSetInfoNotAvailableException | StorageException err) {
patchSetInfo = null;
}
@@ -205,11 +205,8 @@ public abstract class ChangeEmail extends NotificationEmail {
}
private void setCommitIdHeader() {
- if (patchSet != null
- && patchSet.getRevision() != null
- && patchSet.getRevision().get() != null
- && patchSet.getRevision().get().length() > 0) {
- setHeader(MailHeader.COMMIT.fieldName(), patchSet.getRevision().get());
+ if (patchSet != null) {
+ setHeader(MailHeader.COMMIT.fieldName(), patchSet.commitId().name());
}
}
@@ -290,11 +287,11 @@ public abstract class ChangeEmail extends NotificationEmail {
/** Get the patch list corresponding to patch set patchSetId of this change. */
protected PatchList getPatchList(int patchSetId) throws PatchListNotAvailableException {
PatchSet ps;
- if (patchSetId == patchSet.getPatchSetId()) {
+ if (patchSetId == patchSet.number()) {
ps = patchSet;
} else {
try {
- ps = args.patchSetUtil.get(changeData.notes(), new PatchSet.Id(change.getId(), patchSetId));
+ ps = args.patchSetUtil.get(changeData.notes(), PatchSet.id(change.getId(), patchSetId));
} catch (StorageException e) {
throw new PatchListNotAvailableException("Failed to get patchSet", e);
}
@@ -338,7 +335,7 @@ public abstract class ChangeEmail extends NotificationEmail {
protected void removeUsersThatIgnoredTheChange() {
for (Map.Entry<Account.Id, Collection<String>> e : stars.asMap().entrySet()) {
if (e.getValue().contains(StarredChangesUtil.IGNORE_LABEL)) {
- args.accountCache.get(e.getKey()).ifPresent(a -> removeUser(a.getAccount()));
+ args.accountCache.get(e.getKey()).ifPresent(a -> removeUser(a.account()));
}
}
}
@@ -349,7 +346,7 @@ public abstract class ChangeEmail extends NotificationEmail {
return new Watchers();
}
- ProjectWatch watch = new ProjectWatch(args, branch.getParentKey(), projectState, changeData);
+ ProjectWatch watch = new ProjectWatch(args, branch.project(), projectState, changeData);
return watch.getWatchers(type, includeWatchersFromNotifyConfig);
}
@@ -415,7 +412,7 @@ public abstract class ChangeEmail extends NotificationEmail {
case ALL:
default:
if (patchSet != null) {
- authors.add(patchSet.getUploader());
+ authors.add(patchSet.uploader());
}
if (patchSetInfo != null) {
if (patchSetInfo.getAuthor().getAccount() != null) {
@@ -465,8 +462,8 @@ public abstract class ChangeEmail extends NotificationEmail {
soyContext.put("change", changeData);
Map<String, Object> patchSetData = new HashMap<>();
- patchSetData.put("patchSetId", patchSet.getPatchSetId());
- patchSetData.put("refName", patchSet.getRefName());
+ patchSetData.put("patchSetId", patchSet.number());
+ patchSetData.put("refName", patchSet.refName());
soyContext.put("patchSet", patchSetData);
Map<String, Object> patchSetInfoData = new HashMap<>();
@@ -476,7 +473,7 @@ public abstract class ChangeEmail extends NotificationEmail {
footers.add(MailHeader.CHANGE_ID.withDelimiter() + change.getKey().get());
footers.add(MailHeader.CHANGE_NUMBER.withDelimiter() + Integer.toString(change.getChangeId()));
- footers.add(MailHeader.PATCH_SET.withDelimiter() + patchSet.getPatchSetId());
+ footers.add(MailHeader.PATCH_SET.withDelimiter() + patchSet.number());
footers.add(MailHeader.OWNER.withDelimiter() + getNameEmailFor(change.getOwner()));
if (change.getAssignee() != null) {
footers.add(MailHeader.ASSIGNEE.withDelimiter() + getNameEmailFor(change.getAssignee()));
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index 7b11ce65a0..48d342e55b 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -17,21 +17,21 @@ package com.google.gerrit.server.mail.send;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
-import com.google.common.collect.Ordering;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.FilenameComparator;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.KeyUtil;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.NoSuchEntityException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.mail.MailHeader;
import com.google.gerrit.mail.MailProcessingUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -41,7 +41,6 @@ import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.util.LabelVote;
-import com.google.gwtorm.client.KeyUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -55,7 +54,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.Set;
import org.apache.james.mime4j.dom.field.FieldName;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -65,7 +63,7 @@ public class CommentSender extends ReplyToChangeSender {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
- CommentSender create(Project.NameKey project, Change.Id id);
+ CommentSender create(Project.NameKey project, Change.Id changeId);
}
private class FileCommentGroup {
@@ -113,12 +111,12 @@ public class CommentSender extends ReplyToChangeSender {
@Inject
public CommentSender(
- EmailArguments ea,
+ EmailArguments args,
CommentsUtil commentsUtil,
@GerritServerConfig Config cfg,
@Assisted Project.NameKey project,
- @Assisted Change.Id id) {
- super(ea, "comment", newChangeData(ea, project, id));
+ @Assisted Change.Id changeId) {
+ super(args, "comment", newChangeData(args, project, changeId));
this.commentsUtil = commentsUtil;
this.incomingEmailEnabled =
cfg.getEnum("receiveemail", null, "protocol", Protocol.NONE).ordinal()
@@ -128,14 +126,6 @@ public class CommentSender extends ReplyToChangeSender {
public void setComments(List<Comment> comments) {
inlineComments = comments;
-
- Set<String> paths = new HashSet<>();
- for (Comment c : comments) {
- if (!Patch.isMagic(c.key.filename)) {
- paths.add(c.key.filename);
- }
- }
- changeData.setCurrentFilePaths(Ordering.natural().sortedCopy(paths));
}
public void setPatchSetComment(String comment) {
diff --git a/java/com/google/gerrit/server/mail/send/CreateChangeSender.java b/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
index 9895e07069..1f58abb2ce 100644
--- a/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
+++ b/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.mail.send;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -34,18 +34,18 @@ public class CreateChangeSender extends NewChangeSender {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
- CreateChangeSender create(Project.NameKey project, Change.Id id);
+ CreateChangeSender create(Project.NameKey project, Change.Id changeId);
}
private final PermissionBackend permissionBackend;
@Inject
public CreateChangeSender(
- EmailArguments ea,
+ EmailArguments args,
PermissionBackend permissionBackend,
@Assisted Project.NameKey project,
- @Assisted Change.Id id) {
- super(ea, newChangeData(ea, project, id));
+ @Assisted Change.Id changeId) {
+ super(args, newChangeData(args, project, changeId));
this.permissionBackend = permissionBackend;
}
diff --git a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java b/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
index 39e21a52a2..c9bb1e4eeb 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
@@ -38,8 +38,8 @@ public class DeleteKeySender extends OutgoingEmail {
@AssistedInject
public DeleteKeySender(
- EmailArguments ea, @Assisted IdentifiedUser user, @Assisted AccountSshKey sshKey) {
- super(ea, "deletekey");
+ EmailArguments args, @Assisted IdentifiedUser user, @Assisted AccountSshKey sshKey) {
+ super(args, "deletekey");
this.user = user;
this.gpgKeyFingerprints = Collections.emptyList();
this.sshKey = sshKey;
@@ -47,8 +47,10 @@ public class DeleteKeySender extends OutgoingEmail {
@AssistedInject
public DeleteKeySender(
- EmailArguments ea, @Assisted IdentifiedUser user, @Assisted List<String> gpgKeyFingerprints) {
- super(ea, "deletekey");
+ EmailArguments args,
+ @Assisted IdentifiedUser user,
+ @Assisted List<String> gpgKeyFingerprints) {
+ super(args, "deletekey");
this.user = user;
this.gpgKeyFingerprints = gpgKeyFingerprints;
this.sshKey = null;
@@ -75,7 +77,7 @@ public class DeleteKeySender extends OutgoingEmail {
}
public String getEmail() {
- return user.getAccount().getPreferredEmail();
+ return user.getAccount().preferredEmail();
}
public String getUserNameEmail() {
diff --git a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
index f941acc582..4f42679255 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -36,13 +36,13 @@ public class DeleteReviewerSender extends ReplyToChangeSender {
public interface Factory extends ReplyToChangeSender.Factory<DeleteReviewerSender> {
@Override
- DeleteReviewerSender create(Project.NameKey project, Change.Id change);
+ DeleteReviewerSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
public DeleteReviewerSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "deleteReviewer", newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "deleteReviewer", newChangeData(args, project, changeId));
}
public void addReviewers(Collection<Account.Id> cc) {
diff --git a/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java b/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
index 195d53df1e..76f9b813d9 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -25,13 +25,13 @@ import com.google.inject.assistedinject.Assisted;
public class DeleteVoteSender extends ReplyToChangeSender {
public interface Factory extends ReplyToChangeSender.Factory<DeleteVoteSender> {
@Override
- DeleteVoteSender create(Project.NameKey project, Change.Id change);
+ DeleteVoteSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
protected DeleteVoteSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "deleteVote", newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "deleteVote", newChangeData(args, project, changeId));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/EmailArguments.java b/java/com/google/gerrit/server/mail/send/EmailArguments.java
index fe2f74b2da..ede5765993 100644
--- a/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -45,7 +45,7 @@ import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.template.soy.tofu.SoyTofu;
+import com.google.template.soy.jbcsrc.api.SoySauce;
import java.util.List;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
@@ -75,7 +75,7 @@ public class EmailArguments {
final ChangeQueryBuilder queryBuilder;
final ChangeData.Factory changeDataFactory;
- final SoyTofu soyTofu;
+ final SoySauce soySauce;
final EmailSettings settings;
final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners;
final Provider<InternalAccountQuery> accountQueryProvider;
@@ -105,7 +105,7 @@ public class EmailArguments {
AllProjectsName allProjectsName,
ChangeQueryBuilder queryBuilder,
ChangeData.Factory changeDataFactory,
- @MailTemplates SoyTofu soyTofu,
+ @MailTemplates SoySauce soySauce,
EmailSettings settings,
@SshAdvertisedAddresses List<String> sshAddresses,
SitePaths site,
@@ -134,7 +134,7 @@ public class EmailArguments {
this.allProjectsName = allProjectsName;
this.queryBuilder = queryBuilder;
this.changeDataFactory = changeDataFactory;
- this.soyTofu = soyTofu;
+ this.soySauce = soySauce;
this.settings = settings;
this.sshAddresses = sshAddresses;
this.site = site;
diff --git a/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java b/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
index 5baabe95c5..61fa50d268 100644
--- a/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
+++ b/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
/** Constructs an address to send email from. */
public interface FromAddressGenerator {
diff --git a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
index b77909e2f3..1bf1ff1f28 100644
--- a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
+++ b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.mail.send;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
@@ -123,9 +123,9 @@ public class FromAddressGeneratorProvider implements Provider<FromAddressGenerat
public Address from(Account.Id fromId) {
String senderName;
if (fromId != null) {
- Optional<Account> a = accountCache.get(fromId).map(AccountState::getAccount);
- String fullName = a.map(Account::getFullName).orElse(null);
- String userEmail = a.map(Account::getPreferredEmail).orElse(null);
+ Optional<Account> a = accountCache.get(fromId).map(AccountState::account);
+ String fullName = a.map(Account::fullName).orElse(null);
+ String userEmail = a.map(Account::preferredEmail).orElse(null);
if (canRelay(userEmail)) {
return new Address(fullName, userEmail);
}
@@ -208,8 +208,7 @@ public class FromAddressGeneratorProvider implements Provider<FromAddressGenerat
final String senderName;
if (fromId != null) {
- String fullName =
- accountCache.get(fromId).map(a -> a.getAccount().getFullName()).orElse(null);
+ String fullName = accountCache.get(fromId).map(a -> a.account().fullName()).orElse(null);
if (fullName == null || "".equals(fullName)) {
fullName = anonymousCowardName;
}
@@ -235,7 +234,7 @@ public class FromAddressGeneratorProvider implements Provider<FromAddressGenerat
byte[] bytes = hash.digest(data.getBytes(UTF_8));
return Base64.encodeBase64URLSafeString(bytes);
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("No MD5 available", e);
+ throw new IllegalStateException("No MD5 available", e);
}
}
}
diff --git a/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java b/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
index ca332ff275..2db2d6d21e 100644
--- a/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
+++ b/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
@@ -31,8 +31,8 @@ public class HttpPasswordUpdateSender extends OutgoingEmail {
@AssistedInject
public HttpPasswordUpdateSender(
- EmailArguments ea, @Assisted IdentifiedUser user, @Assisted String operation) {
- super(ea, "HttpPasswordUpdate");
+ EmailArguments args, @Assisted IdentifiedUser user, @Assisted String operation) {
+ super(args, "HttpPasswordUpdate");
this.user = user;
this.operation = operation;
}
@@ -59,7 +59,7 @@ public class HttpPasswordUpdateSender extends OutgoingEmail {
}
public String getEmail() {
- return user.getAccount().getPreferredEmail();
+ return user.getAccount().preferredEmail();
}
public String getUserNameEmail() {
diff --git a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
index b5d384d730..110f26ac22 100644
--- a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
+++ b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
@@ -32,7 +32,8 @@ public class InboundEmailRejectionSender extends OutgoingEmail {
PARSING_ERROR,
INACTIVE_ACCOUNT,
UNKNOWN_ACCOUNT,
- INTERNAL_EXCEPTION;
+ INTERNAL_EXCEPTION,
+ COMMENT_REJECTED
}
public interface Factory {
@@ -45,8 +46,11 @@ public class InboundEmailRejectionSender extends OutgoingEmail {
@Inject
public InboundEmailRejectionSender(
- EmailArguments ea, @Assisted Address to, @Assisted String threadId, @Assisted Error reason) {
- super(ea, "error");
+ EmailArguments args,
+ @Assisted Address to,
+ @Assisted String threadId,
+ @Assisted Error reason) {
+ super(args, "error");
this.to = requireNonNull(to);
this.threadId = requireNonNull(threadId);
this.reason = requireNonNull(reason);
diff --git a/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java b/java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java
index 3bb44c701a..92220eb890 100644
--- a/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
+++ b/java/com/google/gerrit/server/mail/send/MailSoySauceProvider.java
@@ -17,22 +17,23 @@ package com.google.gerrit.server.mail.send;
import com.google.common.io.CharStreams;
import com.google.common.io.Resources;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.template.soy.SoyFileSet;
+import com.google.template.soy.jbcsrc.api.SoySauce;
import com.google.template.soy.shared.SoyAstCache;
-import com.google.template.soy.tofu.SoyTofu;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-/** Configures Soy Tofu object for rendering email templates. */
+/** Configures Soy Sauce object for rendering email templates. */
@Singleton
-public class MailSoyTofuProvider implements Provider<SoyTofu> {
+public class MailSoySauceProvider implements Provider<SoySauce> {
// Note: will fail to construct the tofu object if this array is empty.
private static final String[] TEMPLATES = {
@@ -80,24 +81,32 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
private final SitePaths site;
private final SoyAstCache cache;
+ private final PluginSetContext<MailSoyTemplateProvider> templateProviders;
@Inject
- MailSoyTofuProvider(SitePaths site, SoyAstCache cache) {
+ MailSoySauceProvider(
+ SitePaths site,
+ SoyAstCache cache,
+ PluginSetContext<MailSoyTemplateProvider> templateProviders) {
this.site = site;
this.cache = cache;
+ this.templateProviders = templateProviders;
}
@Override
- public SoyTofu get() throws ProvisionException {
+ public SoySauce get() throws ProvisionException {
SoyFileSet.Builder builder = SoyFileSet.builder();
builder.setSoyAstCache(cache);
for (String name : TEMPLATES) {
- addTemplate(builder, name);
+ addTemplate(builder, "com/google/gerrit/server/mail/", name);
}
- return builder.build().compileToTofu();
+ templateProviders.runEach(
+ e -> e.getFileNames().forEach(p -> addTemplate(builder, e.getPath(), p)));
+ return builder.build().compileTemplates();
}
- private void addTemplate(SoyFileSet.Builder builder, String name) throws ProvisionException {
+ private void addTemplate(SoyFileSet.Builder builder, String resourcePath, String name)
+ throws ProvisionException {
// Load as a file in the mail templates directory if present.
Path tmpl = site.mail_dir.resolve(name);
if (Files.isRegularFile(tmpl)) {
@@ -115,7 +124,9 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
}
// Otherwise load the template as a resource.
- String resourcePath = "com/google/gerrit/server/mail/" + name;
- builder.add(Resources.getResource(resourcePath));
+ if (!resourcePath.endsWith("/")) {
+ resourcePath += "/";
+ }
+ builder.add(Resources.getResource(resourcePath + name));
}
}
diff --git a/java/com/google/gerrit/server/mail/send/MailSoyTemplateProvider.java b/java/com/google/gerrit/server/mail/send/MailSoyTemplateProvider.java
new file mode 100644
index 0000000000..3a6ff64c37
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/MailSoyTemplateProvider.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail.send;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import java.util.Set;
+
+/**
+ * Extension point to provide soy templates that should be registered so that they can be used for
+ * sending emails from a plugin.
+ */
+@ExtensionPoint
+public interface MailSoyTemplateProvider {
+ /**
+ * Return the name of the resource path that contains the soy template files that are returned by
+ * {@link #getFileNames()}.
+ *
+ * @return resource path of the templates
+ */
+ String getPath();
+
+ /**
+ * Return the names of the soy template files.
+ *
+ * <p>These files are expected to exist in the resource path that is returned by {@link
+ * #getPath()}.
+ *
+ * @return names of the template files, including the {@code .soy} file extension
+ */
+ Set<String> getFileNames();
+}
diff --git a/java/com/google/gerrit/server/mail/send/MergedSender.java b/java/com/google/gerrit/server/mail/send/MergedSender.java
index cbc4117e26..b28a4dc9e4 100644
--- a/java/com/google/gerrit/server/mail/send/MergedSender.java
+++ b/java/com/google/gerrit/server/mail/send/MergedSender.java
@@ -19,12 +19,12 @@ import com.google.common.collect.Table;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -32,15 +32,15 @@ import com.google.inject.assistedinject.Assisted;
/** Send notice about a change successfully merged. */
public class MergedSender extends ReplyToChangeSender {
public interface Factory {
- MergedSender create(Project.NameKey project, Change.Id id);
+ MergedSender create(Project.NameKey project, Change.Id changeId);
}
private final LabelTypes labelTypes;
@Inject
public MergedSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "merged", newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "merged", newChangeData(args, project, changeId));
labelTypes = changeData.getLabelTypes();
}
@@ -69,15 +69,15 @@ public class MergedSender extends ReplyToChangeSender {
Table<Account.Id, String, PatchSetApproval> pos = HashBasedTable.create();
Table<Account.Id, String, PatchSetApproval> neg = HashBasedTable.create();
for (PatchSetApproval ca :
- args.approvalsUtil.byPatchSet(changeData.notes(), patchSet.getId(), null, null)) {
- LabelType lt = labelTypes.byLabel(ca.getLabelId());
+ args.approvalsUtil.byPatchSet(changeData.notes(), patchSet.id(), null, null)) {
+ LabelType lt = labelTypes.byLabel(ca.labelId());
if (lt == null) {
continue;
}
- if (ca.getValue() > 0) {
- pos.put(ca.getAccountId(), lt.getName(), ca);
- } else if (ca.getValue() < 0) {
- neg.put(ca.getAccountId(), lt.getName(), ca);
+ if (ca.value() > 0) {
+ pos.put(ca.accountId(), lt.getName(), ca);
+ } else if (ca.value() < 0) {
+ neg.put(ca.accountId(), lt.getName(), ca);
}
}
@@ -117,7 +117,7 @@ public class MergedSender extends ReplyToChangeSender {
} else {
txt.append(lt.getName());
txt.append('=');
- txt.append(LabelValue.formatValue(ca.getValue()));
+ txt.append(LabelValue.formatValue(ca.value()));
}
}
txt.append('\n');
diff --git a/java/com/google/gerrit/server/mail/send/NewChangeSender.java b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
index a31596bdb0..83c3a944cb 100644
--- a/java/com/google/gerrit/server/mail/send/NewChangeSender.java
+++ b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.query.change.ChangeData;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,8 +32,8 @@ public abstract class NewChangeSender extends ChangeEmail {
private final Set<Account.Id> extraCC = new HashSet<>();
private final Set<Address> extraCCByEmail = new HashSet<>();
- protected NewChangeSender(EmailArguments ea, ChangeData cd) {
- super(ea, "newchange", cd);
+ protected NewChangeSender(EmailArguments args, ChangeData changeData) {
+ super(args, "newchange", changeData);
}
public void addReviewers(Collection<Account.Id> cc) {
diff --git a/java/com/google/gerrit/server/mail/send/NotificationEmail.java b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index 9ef8d872e6..18838753a1 100644
--- a/java/com/google/gerrit/server/mail/send/NotificationEmail.java
+++ b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.mail.send;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.mail.Address;
import com.google.gerrit.mail.MailHeader;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.cache.PerThreadCache.ReadonlyRequestWindow;
@@ -36,10 +36,10 @@ import java.util.Map;
public abstract class NotificationEmail extends OutgoingEmail {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- protected Branch.NameKey branch;
+ protected BranchNameKey branch;
- protected NotificationEmail(EmailArguments ea, String mc, ChangeData cd) {
- super(ea, mc);
+ protected NotificationEmail(EmailArguments args, String messageClass, ChangeData cd) {
+ super(args, messageClass);
try (ReadonlyRequestWindow window = PerThreadCache.openReadonlyRequestWindow()) {
this.branch = cd.change().getDest();
}
@@ -55,7 +55,7 @@ public abstract class NotificationEmail extends OutgoingEmail {
// Set a reasonable list id so that filters can be used to sort messages
setHeader(
"List-Id",
- "<gerrit-" + branch.getParentKey().get().replace('/', '-') + "." + getGerritHost() + ">");
+ "<gerrit-" + branch.project().get().replace('/', '-') + "." + getGerritHost() + ">");
if (getSettingsUrl() != null) {
setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
}
@@ -109,7 +109,7 @@ public abstract class NotificationEmail extends OutgoingEmail {
protected void setupSoyContext() {
super.setupSoyContext();
- String projectName = branch.getParentKey().get();
+ String projectName = branch.project().get();
soyContext.put("projectName", projectName);
// shortProjectName is the project name with the path abbreviated.
soyContext.put("shortProjectName", getShortProjectName(projectName));
@@ -123,11 +123,11 @@ public abstract class NotificationEmail extends OutgoingEmail {
soyContextEmailData.put("sshHost", getSshHost());
Map<String, String> branchData = new HashMap<>();
- branchData.put("shortName", branch.getShortName());
+ branchData.put("shortName", branch.shortName());
soyContext.put("branch", branchData);
- footers.add(MailHeader.PROJECT.withDelimiter() + branch.getParentKey().get());
- footers.add(MailHeader.BRANCH.withDelimiter() + branch.getShortName());
+ footers.add(MailHeader.PROJECT.withDelimiter() + branch.project().get());
+ footers.add(MailHeader.BRANCH.withDelimiter() + branch.shortName());
}
@VisibleForTesting
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index 949348df11..0448688db3 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -21,6 +21,8 @@ import static java.util.Objects.requireNonNull;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.UserIdentity;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -29,8 +31,6 @@ import com.google.gerrit.mail.Address;
import com.google.gerrit.mail.EmailHeader;
import com.google.gerrit.mail.EmailHeader.AddressList;
import com.google.gerrit.mail.MailHeader;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.cache.PerThreadCache.ReadonlyRequestWindow;
@@ -38,7 +38,7 @@ import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.gerrit.server.validators.ValidationException;
-import com.google.template.soy.data.SanitizedContent;
+import com.google.template.soy.jbcsrc.api.SoySauce;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
@@ -58,6 +58,7 @@ import org.eclipse.jgit.util.SystemReader;
/** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail {
+ private static final String SOY_TEMPLATE_NAMESPACE = "com.google.gerrit.server.mail.template.";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
protected String messageClass;
@@ -74,10 +75,10 @@ public abstract class OutgoingEmail {
protected Account.Id fromId;
protected NotifyResolver.Result notify = NotifyResolver.Result.all();
- protected OutgoingEmail(EmailArguments ea, String mc) {
- args = ea;
- messageClass = mc;
- headers = new LinkedHashMap<>();
+ protected OutgoingEmail(EmailArguments args, String messageClass) {
+ this.args = args;
+ this.messageClass = messageClass;
+ this.headers = new LinkedHashMap<>();
}
public void setFrom(Account.Id id) {
@@ -123,7 +124,7 @@ public abstract class OutgoingEmail {
if (fromId != null) {
Optional<AccountState> fromUser = args.accountCache.get(fromId);
if (fromUser.isPresent()) {
- GeneralPreferencesInfo senderPrefs = fromUser.get().getGeneralPreferences();
+ GeneralPreferencesInfo senderPrefs = fromUser.get().generalPreferences();
if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
// If we are impersonating a user, make sure they receive a CC of
// this message so they can always review and audit what we sent
@@ -134,7 +135,7 @@ public abstract class OutgoingEmail {
// If they don't want a copy, but we queued one up anyway,
// drop them from the recipient lists.
//
- removeUser(fromUser.get().getAccount());
+ removeUser(fromUser.get().account());
}
}
}
@@ -144,14 +145,14 @@ public abstract class OutgoingEmail {
for (Account.Id id : rcptTo) {
Optional<AccountState> thisUser = args.accountCache.get(id);
if (thisUser.isPresent()) {
- Account thisUserAccount = thisUser.get().getAccount();
- GeneralPreferencesInfo prefs = thisUser.get().getGeneralPreferences();
+ Account thisUserAccount = thisUser.get().account();
+ GeneralPreferencesInfo prefs = thisUser.get().generalPreferences();
if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
removeUser(thisUserAccount);
} else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
removeUser(thisUserAccount);
smtpRcptToPlaintextOnly.add(
- new Address(thisUserAccount.getFullName(), thisUserAccount.getPreferredEmail()));
+ new Address(thisUserAccount.fullName(), thisUserAccount.preferredEmail()));
}
}
if (smtpRcptTo.isEmpty() && smtpRcptToPlaintextOnly.isEmpty()) {
@@ -268,10 +269,10 @@ public abstract class OutgoingEmail {
protected String getFromLine() {
StringBuilder f = new StringBuilder();
- Optional<Account> account = args.accountCache.get(fromId).map(AccountState::getAccount);
+ Optional<Account> account = args.accountCache.get(fromId).map(AccountState::account);
if (account.isPresent()) {
- String name = account.get().getFullName();
- String email = account.get().getPreferredEmail();
+ String name = account.get().fullName();
+ String email = account.get().preferredEmail();
if ((name != null && !name.isEmpty()) || (email != null && !email.isEmpty())) {
f.append("From");
if (name != null && !name.isEmpty()) {
@@ -344,12 +345,12 @@ public abstract class OutgoingEmail {
return args.gerritPersonIdent.getName();
}
- Optional<Account> account = args.accountCache.get(accountId).map(AccountState::getAccount);
+ Optional<Account> account = args.accountCache.get(accountId).map(AccountState::account);
String name = null;
if (account.isPresent()) {
- name = account.get().getFullName();
+ name = account.get().fullName();
if (name == null) {
- name = account.get().getPreferredEmail();
+ name = account.get().preferredEmail();
}
}
if (name == null) {
@@ -373,10 +374,10 @@ public abstract class OutgoingEmail {
+ ">";
}
- Optional<Account> account = args.accountCache.get(accountId).map(AccountState::getAccount);
+ Optional<Account> account = args.accountCache.get(accountId).map(AccountState::account);
if (account.isPresent()) {
- String name = account.get().getFullName();
- String email = account.get().getPreferredEmail();
+ String name = account.get().fullName();
+ String email = account.get().preferredEmail();
if (name != null && email != null) {
return name + " <" + email + ">";
} else if (name != null) {
@@ -405,9 +406,9 @@ public abstract class OutgoingEmail {
return null;
}
- Account account = accountState.get().getAccount();
- String name = account.getFullName();
- String email = account.getPreferredEmail();
+ Account account = accountState.get().account();
+ String name = account.fullName();
+ String email = account.preferredEmail();
if (name != null && email != null) {
return name + " <" + email + ">";
} else if (email != null) {
@@ -415,7 +416,7 @@ public abstract class OutgoingEmail {
} else if (name != null) {
return name;
}
- return accountState.get().getUserName().orElse(null);
+ return accountState.get().userName().orElse(null);
}
protected boolean shouldSendMessage() {
@@ -537,17 +538,17 @@ public abstract class OutgoingEmail {
}
private Address toAddress(Account.Id id) {
- Optional<Account> accountState = args.accountCache.get(id).map(AccountState::getAccount);
+ Optional<Account> accountState = args.accountCache.get(id).map(AccountState::account);
if (!accountState.isPresent()) {
return null;
}
Account account = accountState.get();
- String e = account.getPreferredEmail();
+ String e = account.preferredEmail();
if (!account.isActive() || e == null) {
return null;
}
- return new Address(account.getFullName(), e);
+ return new Address(account.fullName(), e);
}
protected void setupSoyContext() {
@@ -569,24 +570,23 @@ public abstract class OutgoingEmail {
return args.instanceNameProvider.get();
}
- private String soyTemplate(String name, SanitizedContent.ContentKind kind) {
- return args.soyTofu
- .newRenderer("com.google.gerrit.server.mail.template." + name)
- .setContentKind(kind)
- .setData(soyContext)
- .render();
- }
-
+ /** Renders a soy template of kind="text". */
protected String textTemplate(String name) {
- return soyTemplate(name, SanitizedContent.ContentKind.TEXT);
+ return configureRenderer(name).renderText().get();
}
+ /** Renders a soy template of kind="html". */
protected String soyHtmlTemplate(String name) {
- return soyTemplate(name, SanitizedContent.ContentKind.HTML);
+ return configureRenderer(name).renderHtml().get().toString();
+ }
+
+ /** Configures a soy renderer for the given template name and rendering data map. */
+ private SoySauce.Renderer configureRenderer(String templateName) {
+ return args.soySauce.renderTemplate(SOY_TEMPLATE_NAMESPACE + templateName).setData(soyContext);
}
protected void removeUser(Account user) {
- String fromEmail = user.getPreferredEmail();
+ String fromEmail = user.preferredEmail();
for (Iterator<Address> j = smtpRcptTo.iterator(); j.hasNext(); ) {
if (j.next().getEmail().equals(fromEmail)) {
j.remove();
diff --git a/java/com/google/gerrit/server/mail/send/ProjectWatch.java b/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index 0a468b411c..2530d7e7fc 100644
--- a/java/com/google/gerrit/server/mail/send/ProjectWatch.java
+++ b/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -19,12 +19,12 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountState;
@@ -66,9 +66,8 @@ public class ProjectWatch {
Set<Account.Id> projectWatchers = new HashSet<>();
for (AccountState a : args.accountQueryProvider.get().byWatchedProject(project)) {
- Account.Id accountId = a.getAccount().getId();
- for (Map.Entry<ProjectWatchKey, ImmutableSet<NotifyType>> e :
- a.getProjectWatches().entrySet()) {
+ Account.Id accountId = a.account().id();
+ for (Map.Entry<ProjectWatchKey, ImmutableSet<NotifyType>> e : a.projectWatches().entrySet()) {
if (project.equals(e.getKey().project())
&& add(matching, accountId, e.getKey(), e.getValue(), type)) {
// We only want to prevent matching All-Projects if this filter hits
@@ -78,10 +77,9 @@ public class ProjectWatch {
}
for (AccountState a : args.accountQueryProvider.get().byWatchedProject(args.allProjectsName)) {
- for (Map.Entry<ProjectWatchKey, ImmutableSet<NotifyType>> e :
- a.getProjectWatches().entrySet()) {
+ for (Map.Entry<ProjectWatchKey, ImmutableSet<NotifyType>> e : a.projectWatches().entrySet()) {
if (args.allProjectsName.equals(e.getKey().project())) {
- Account.Id accountId = a.getAccount().getId();
+ Account.Id accountId = a.account().id();
if (!projectWatchers.contains(accountId)) {
add(matching, accountId, e.getKey(), e.getValue(), type);
}
@@ -97,9 +95,9 @@ public class ProjectWatch {
for (NotifyConfig nc : state.getConfig().getNotifyConfigs()) {
if (nc.isNotify(type)) {
try {
- add(matching, nc);
+ add(matching, state.getNameKey(), nc);
} catch (QueryParseException e) {
- logger.atWarning().log(
+ logger.atInfo().log(
"Project %s has invalid notify %s filter \"%s\": %s",
state.getName(), nc.getName(), nc.getFilter(), e.getMessage());
}
@@ -146,17 +144,27 @@ public class ProjectWatch {
}
}
- private void add(Watchers matching, NotifyConfig nc) throws QueryParseException {
- for (GroupReference ref : nc.getGroups()) {
- CurrentUser user = new SingleGroupUser(ref.getUUID());
+ private void add(Watchers matching, Project.NameKey projectName, NotifyConfig nc)
+ throws QueryParseException {
+ logger.atFine().log("Checking watchers for notify config %s from project %s", nc, projectName);
+ for (GroupReference groupRef : nc.getGroups()) {
+ CurrentUser user = new SingleGroupUser(groupRef.getUUID());
if (filterMatch(user, nc.getFilter())) {
- deliverToMembers(matching.list(nc.getHeader()), ref.getUUID());
+ deliverToMembers(matching.list(nc.getHeader()), groupRef.getUUID());
+ logger.atFine().log("Added watchers for group %s", groupRef);
+ } else {
+ logger.atFine().log("The filter did not match for group %s; skip notification", groupRef);
}
}
if (!nc.getAddresses().isEmpty()) {
if (filterMatch(null, nc.getFilter())) {
matching.list(nc.getHeader()).emails.addAll(nc.getAddresses());
+ logger.atFine().log("Added watchers for these addresses: %s", nc.getAddresses());
+ } else {
+ logger.atFine().log(
+ "The filter did not match; skip notification for these addresses: %s",
+ nc.getAddresses());
}
}
}
@@ -172,19 +180,24 @@ public class ProjectWatch {
AccountGroup.UUID uuid = q.remove(q.size() - 1);
GroupDescription.Basic group = args.groupBackend.get(uuid);
if (group == null) {
+ logger.atFine().log("group %s not found, skip notification", uuid);
continue;
}
if (!Strings.isNullOrEmpty(group.getEmailAddress())) {
// If the group has an email address, do not expand membership.
matching.emails.add(new Address(group.getEmailAddress()));
+ logger.atFine().log(
+ "notify group email address %s; skip expanding to members", group.getEmailAddress());
continue;
}
if (!(group instanceof GroupDescription.Internal)) {
// Non-internal groups cannot be expanded by the server.
+ logger.atFine().log("group %s is not an internal group, skip notification", uuid);
continue;
}
+ logger.atFine().log("adding the members of group %s as watchers", uuid);
GroupDescription.Internal ig = (GroupDescription.Internal) group;
matching.accounts.addAll(ig.getMembers());
for (AccountGroup.UUID m : ig.getSubgroups()) {
@@ -201,8 +214,9 @@ public class ProjectWatch {
ProjectWatchKey key,
Set<NotifyType> watchedTypes,
NotifyType type) {
- IdentifiedUser user = args.identifiedUserFactory.create(accountId);
+ logger.atFine().log("Checking project watch %s of account %s", key, accountId);
+ IdentifiedUser user = args.identifiedUserFactory.create(accountId);
try {
if (filterMatch(user, key.filter())) {
// If we are set to notify on this type, add the user.
@@ -210,10 +224,14 @@ public class ProjectWatch {
if (watchedTypes.contains(type)) {
matching.bcc.accounts.add(accountId);
}
+ logger.atFine().log("Added account %s as watcher", accountId);
return true;
}
+ logger.atFine().log("The filter did not match for account %s; skip notification", accountId);
} catch (QueryParseException e) {
// Ignore broken filter expressions.
+ logger.atInfo().log(
+ "Account %s has invalid filter in project watch %s: %s", accountId, key, e.getMessage());
}
return false;
}
diff --git a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java b/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
index 436736b560..91d8e817af 100644
--- a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
@@ -36,14 +36,14 @@ public class RegisterNewEmailSender extends OutgoingEmail {
@Inject
public RegisterNewEmailSender(
- EmailArguments ea,
- EmailTokenVerifier etv,
+ EmailArguments args,
+ EmailTokenVerifier tokenVerifier,
IdentifiedUser callingUser,
@Assisted final String address) {
- super(ea, "registernewemail");
- tokenVerifier = etv;
- user = callingUser;
- addr = address;
+ super(args, "registernewemail");
+ this.tokenVerifier = tokenVerifier;
+ this.user = callingUser;
+ this.addr = address;
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java b/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
index 30bcdeb1d4..909c52aef8 100644
--- a/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
+++ b/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -32,7 +32,7 @@ import java.util.Set;
/** Send notice of new patch sets for reviewers. */
public class ReplacePatchSetSender extends ReplyToChangeSender {
public interface Factory {
- ReplacePatchSetSender create(Project.NameKey project, Change.Id id);
+ ReplacePatchSetSender create(Project.NameKey project, Change.Id changeId);
}
private final Set<Account.Id> reviewers = new HashSet<>();
@@ -40,8 +40,8 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
@Inject
public ReplacePatchSetSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "newpatchset", newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "newpatchset", newChangeData(args, project, changeId));
}
public void addReviewers(Collection<Account.Id> cc) {
diff --git a/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java b/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
index 960c3a84d9..c765430437 100644
--- a/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
+++ b/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.query.change.ChangeData;
/** Alert a user to a reply to a change, usually commentary made during review. */
@@ -26,8 +26,8 @@ public abstract class ReplyToChangeSender extends ChangeEmail {
T create(Project.NameKey project, Change.Id id);
}
- protected ReplyToChangeSender(EmailArguments ea, String mc, ChangeData cd) {
- super(ea, mc, cd);
+ protected ReplyToChangeSender(EmailArguments args, String messageClass, ChangeData changeData) {
+ super(args, messageClass, changeData);
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/RestoredSender.java b/java/com/google/gerrit/server/mail/send/RestoredSender.java
index 0d998aa9b9..2a4c55646f 100644
--- a/java/com/google/gerrit/server/mail/send/RestoredSender.java
+++ b/java/com/google/gerrit/server/mail/send/RestoredSender.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -25,13 +25,13 @@ import com.google.inject.assistedinject.Assisted;
public class RestoredSender extends ReplyToChangeSender {
public interface Factory extends ReplyToChangeSender.Factory<RestoredSender> {
@Override
- RestoredSender create(Project.NameKey project, Change.Id id);
+ RestoredSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
public RestoredSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "restore", ChangeEmail.newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "restore", ChangeEmail.newChangeData(args, project, changeId));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/RevertedSender.java b/java/com/google/gerrit/server/mail/send/RevertedSender.java
index 48b5d9954f..dadd0d2ec1 100644
--- a/java/com/google/gerrit/server/mail/send/RevertedSender.java
+++ b/java/com/google/gerrit/server/mail/send/RevertedSender.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -24,13 +24,13 @@ import com.google.inject.assistedinject.Assisted;
/** Send notice about a change being reverted. */
public class RevertedSender extends ReplyToChangeSender {
public interface Factory {
- RevertedSender create(Project.NameKey project, Change.Id id);
+ RevertedSender create(Project.NameKey project, Change.Id changeId);
}
@Inject
public RevertedSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id) {
- super(ea, "revert", ChangeEmail.newChangeData(ea, project, id));
+ EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
+ super(args, "revert", ChangeEmail.newChangeData(args, project, changeId));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/SetAssigneeSender.java b/java/com/google/gerrit/server/mail/send/SetAssigneeSender.java
index a120769f61..850f775e1e 100644
--- a/java/com/google/gerrit/server/mail/send/SetAssigneeSender.java
+++ b/java/com/google/gerrit/server/mail/send/SetAssigneeSender.java
@@ -14,28 +14,28 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
public class SetAssigneeSender extends ChangeEmail {
public interface Factory {
- SetAssigneeSender create(Project.NameKey project, Change.Id id, Account.Id assignee);
+ SetAssigneeSender create(Project.NameKey project, Change.Id changeId, Account.Id assignee);
}
private final Account.Id assignee;
@Inject
public SetAssigneeSender(
- EmailArguments ea,
+ EmailArguments args,
@Assisted Project.NameKey project,
- @Assisted Change.Id id,
+ @Assisted Change.Id changeId,
@Assisted Account.Id assignee) {
- super(ea, "setassignee", newChangeData(ea, project, id));
+ super(args, "setassignee", newChangeData(args, project, changeId));
this.assignee = assignee;
}
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 1c6057d6ff..0acf20e7f3 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -20,11 +20,12 @@ import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -49,8 +50,8 @@ public abstract class AbstractChangeNotes<T> {
public final ChangeNoteJson changeNoteJson;
public final GitRepositoryManager repoManager;
public final AllUsersName allUsers;
- public final LegacyChangeNoteRead legacyChangeNoteRead;
public final NoteDbMetrics metrics;
+ public final String serverId;
// Providers required to avoid dependency cycles.
@@ -62,16 +63,16 @@ public abstract class AbstractChangeNotes<T> {
GitRepositoryManager repoManager,
AllUsersName allUsers,
ChangeNoteJson changeNoteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
NoteDbMetrics metrics,
- Provider<ChangeNotesCache> cache) {
+ Provider<ChangeNotesCache> cache,
+ @GerritServerId String serverId) {
this.failOnLoadForTest = new AtomicBoolean();
this.repoManager = repoManager;
this.allUsers = allUsers;
- this.legacyChangeNoteRead = legacyChangeNoteRead;
this.changeNoteJson = changeNoteJson;
this.metrics = metrics;
this.cache = cache;
+ this.serverId = serverId;
}
}
@@ -139,7 +140,7 @@ public abstract class AbstractChangeNotes<T> {
if (args.failOnLoadForTest.get()) {
throw new StorageException("Reading from NoteDb is disabled");
}
- try (Timer1.Context timer = args.metrics.readLatency.start(CHANGES);
+ try (Timer1.Context<NoteDbTable> timer = args.metrics.readLatency.start(CHANGES);
Repository repo = args.repoManager.openRepository(getProjectName());
// Call openHandle even if reading is disabled, to trigger
// auto-rebuilding before this object may get passed to a ChangeUpdate.
@@ -153,6 +154,7 @@ public abstract class AbstractChangeNotes<T> {
return self();
}
+ @Nullable
protected ObjectId readRef(Repository repo) throws IOException {
Ref ref = repo.getRefDatabase().exactRef(getRefName());
return ref != null ? ref.getObjectId() : null;
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
index daef7e7a3f..ee3ccd6d01 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeUpdate.java
@@ -19,11 +19,11 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
@@ -147,7 +147,7 @@ public abstract class AbstractChangeUpdate {
}
public void setPatchSetId(PatchSet.Id psId) {
- checkArgument(psId == null || psId.getParentKey().equals(getId()));
+ checkArgument(psId == null || psId.changeId().equals(getId()));
this.psId = psId;
}
@@ -193,6 +193,14 @@ public abstract class AbstractChangeUpdate {
}
/**
+ * Whether to allow bypassing the check that an update does not exceed the max update count on an
+ * object.
+ */
+ protected boolean bypassMaxUpdates() {
+ return false;
+ }
+
+ /**
* Apply this update to the given inserter.
*
* @param rw walk for reading back any objects needed for the update.
@@ -267,7 +275,7 @@ public abstract class AbstractChangeUpdate {
}
protected void verifyComment(Comment c) {
- checkArgument(c.revId != null, "RevId required for comment: %s", c);
+ checkArgument(c.getCommitId() != null, "commit ID required for comment: %s", c);
checkArgument(
c.author.getId().equals(getAccountId()),
"The author for the following comment does not match the author of this %s (%s): %s",
diff --git a/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
new file mode 100644
index 0000000000..112f6875b4
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.git.RefUpdateUtil;
+import com.google.gerrit.server.FanOutExecutor;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.transport.PushCertificate;
+
+/**
+ * Performs an update on {@code All-Users} asynchronously if required. No-op in case no updates were
+ * scheduled for asynchronous execution.
+ */
+public class AllUsersAsyncUpdate {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final ExecutorService executor;
+ private final AllUsersName allUsersName;
+ private final GitRepositoryManager repoManager;
+ private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
+
+ private PersonIdent serverIdent;
+
+ @Inject
+ AllUsersAsyncUpdate(
+ @FanOutExecutor ExecutorService executor,
+ AllUsersName allUsersName,
+ GitRepositoryManager repoManager) {
+ this.executor = executor;
+ this.allUsersName = allUsersName;
+ this.repoManager = repoManager;
+ this.draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
+ }
+
+ void setDraftUpdates(ListMultimap<String, ChangeDraftUpdate> draftUpdates) {
+ checkState(isEmpty(), "attempted to set draft comment updates for async execution twice");
+ boolean allPublishOnly =
+ draftUpdates.values().stream().allMatch(ChangeDraftUpdate::canRunAsync);
+ checkState(allPublishOnly, "not all updates can be run asynchronously");
+ // Add deep copies to avoid any threading issues.
+ for (Map.Entry<String, ChangeDraftUpdate> entry : draftUpdates.entries()) {
+ this.draftUpdates.put(entry.getKey(), entry.getValue().copy());
+ }
+ if (draftUpdates.size() > 0) {
+ // Save the PersonIdent for later so that we get consistent time stamps in the commit and ref
+ // log.
+ serverIdent = Iterables.get(draftUpdates.entries(), 0).getValue().serverIdent;
+ }
+ }
+
+ /** Returns true if no operations should be performed on the repo. */
+ boolean isEmpty() {
+ return draftUpdates.isEmpty();
+ }
+
+ /** Executes repository update asynchronously. No-op in case no updates were scheduled. */
+ void execute(PersonIdent refLogIdent, String refLogMessage, PushCertificate pushCert) {
+ if (isEmpty()) {
+ return;
+ }
+
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ executor.submit(
+ () -> {
+ try (OpenRepo allUsersRepo = OpenRepo.open(repoManager, allUsersName)) {
+ allUsersRepo.addUpdates(draftUpdates);
+ allUsersRepo.flush();
+ BatchRefUpdate bru = allUsersRepo.repo.getRefDatabase().newBatchUpdate();
+ bru.setPushCertificate(pushCert);
+ if (refLogMessage != null) {
+ bru.setRefLogMessage(refLogMessage, false);
+ } else {
+ bru.setRefLogMessage(
+ firstNonNull(NoteDbUtil.guessRestApiHandler(), "Update NoteDb refs async"),
+ false);
+ }
+ bru.setRefLogIdent(refLogIdent != null ? refLogIdent : serverIdent);
+ bru.setAtomic(true);
+ allUsersRepo.cmds.addTo(bru);
+ bru.setAllowNonFastForwards(true);
+ RefUpdateUtil.executeChecked(bru, allUsersRepo.rw);
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log(
+ "Failed to delete draft comments asynchronously after publishing them");
+ }
+ });
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 8773d9b4f4..05fdee9a6e 100644
--- a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -15,18 +15,17 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkState;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.auto.value.AutoValue;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllUsersName;
import com.google.inject.assistedinject.Assisted;
@@ -35,7 +34,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
-import java.util.HashSet;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -74,19 +73,25 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
@AutoValue
abstract static class Key {
- abstract String revId();
+ abstract ObjectId commitId();
abstract Comment.Key key();
}
+ enum DeleteReason {
+ DELETED,
+ PUBLISHED,
+ FIXED
+ }
+
private static Key key(Comment c) {
- return new AutoValue_ChangeDraftUpdate_Key(c.revId, c.key);
+ return new AutoValue_ChangeDraftUpdate_Key(c.getCommitId(), c.key);
}
private final AllUsersName draftsProject;
private List<Comment> put = new ArrayList<>();
- private Set<Key> delete = new HashSet<>();
+ private Map<Key, DeleteReason> delete = new HashMap<>();
@AssistedInject
private ChangeDraftUpdate(
@@ -117,40 +122,92 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
}
public void putComment(Comment c) {
+ checkState(!put.contains(c), "comment already added");
verifyComment(c);
put.add(c);
}
+ /**
+ * Marks a comment for deletion. Called when the comment is deleted because the user published it.
+ */
+ public void markCommentPublished(Comment c) {
+ checkState(!delete.containsKey(key(c)), "comment already marked for deletion");
+ verifyComment(c);
+ delete.put(key(c), DeleteReason.PUBLISHED);
+ }
+
+ /**
+ * Marks a comment for deletion. Called when the comment is deleted because the user removed it.
+ */
public void deleteComment(Comment c) {
+ checkState(!delete.containsKey(key(c)), "comment already marked for deletion");
verifyComment(c);
- delete.add(key(c));
+ delete.put(key(c), DeleteReason.DELETED);
+ }
+
+ /**
+ * Marks a comment for deletion. Called when the comment should have been deleted previously, but
+ * wasn't, so we're fixing it up.
+ */
+ public void deleteComment(ObjectId commitId, Comment.Key key) {
+ Key commentKey = new AutoValue_ChangeDraftUpdate_Key(commitId, key);
+ checkState(!delete.containsKey(commentKey), "comment already marked for deletion");
+ delete.put(commentKey, DeleteReason.FIXED);
+ }
+
+ /**
+ * Returns true if all we do in this operations is deletes caused by publishing or fixing up
+ * comments.
+ */
+ public boolean canRunAsync() {
+ return put.isEmpty()
+ && delete.values().stream()
+ .allMatch(r -> r == DeleteReason.PUBLISHED || r == DeleteReason.FIXED);
}
- public void deleteComment(String revId, Comment.Key key) {
- delete.add(new AutoValue_ChangeDraftUpdate_Key(revId, key));
+ /**
+ * Returns a copy of the current {@link ChangeDraftUpdate} that contains references to all
+ * deletions. Copying of {@link ChangeDraftUpdate} is only allowed if it contains no new comments.
+ */
+ ChangeDraftUpdate copy() {
+ checkState(
+ put.isEmpty(),
+ "copying ChangeDraftUpdate is allowed only if it doesn't contain new comments");
+ ChangeDraftUpdate clonedUpdate =
+ new ChangeDraftUpdate(
+ authorIdent,
+ draftsProject,
+ noteUtil,
+ new Change(getChange()),
+ accountId,
+ realAccountId,
+ authorIdent,
+ when);
+ clonedUpdate.delete.putAll(delete);
+ return clonedUpdate;
}
private CommitBuilder storeCommentsInNotes(
RevWalk rw, ObjectInserter ins, ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, IOException {
RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr);
- Set<RevId> updatedRevs = Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
+ Set<ObjectId> updatedCommits = Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
for (Comment c : put) {
- if (!delete.contains(key(c))) {
- cache.get(new RevId(c.revId)).putComment(c);
+ if (!delete.keySet().contains(key(c))) {
+ cache.get(c.getCommitId()).putComment(c);
}
}
- for (Key k : delete) {
- cache.get(new RevId(k.revId())).deleteComment(k.key());
+ for (Key k : delete.keySet()) {
+ cache.get(k.commitId()).deleteComment(k.key());
}
- Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
+ Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
boolean touchedAnyRevs = false;
- for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
- updatedRevs.add(e.getKey());
- ObjectId id = ObjectId.fromString(e.getKey().get());
+ for (Map.Entry<ObjectId, RevisionNoteBuilder> e : builders.entrySet()) {
+ updatedCommits.add(e.getKey());
+ ObjectId id = e.getKey();
byte[] data = e.getValue().build(noteUtil.getChangeNoteJson());
if (!Arrays.equals(data, e.getValue().baseRaw)) {
touchedAnyRevs = true;
@@ -204,12 +261,7 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
// Even though reading from changes might not be enabled, we need to
// parse any existing revision notes so we can merge them.
return RevisionNoteMap.parse(
- noteUtil.getChangeNoteJson(),
- noteUtil.getLegacyChangeNoteRead(),
- getId(),
- rw.getObjectReader(),
- noteMap,
- PatchLineComment.Status.DRAFT);
+ noteUtil.getChangeNoteJson(), rw.getObjectReader(), noteMap, Comment.Status.DRAFT);
}
@Override
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 628dfd2a73..b221ef51b3 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.notedb;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.config.GerritServerId;
import com.google.inject.Inject;
import java.util.Date;
@@ -63,22 +63,13 @@ public class ChangeNoteUtil {
static final String UNRESOLVED = "Unresolved";
static final String TAG = FOOTER_TAG.getName();
- private final LegacyChangeNoteRead legacyChangeNoteRead;
private final ChangeNoteJson changeNoteJson;
private final String serverId;
@Inject
- public ChangeNoteUtil(
- ChangeNoteJson changeNoteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
- @GerritServerId String serverId) {
+ public ChangeNoteUtil(ChangeNoteJson changeNoteJson, @GerritServerId String serverId) {
this.serverId = serverId;
this.changeNoteJson = changeNoteJson;
- this.legacyChangeNoteRead = legacyChangeNoteRead;
- }
-
- public LegacyChangeNoteRead getLegacyChangeNoteRead() {
- return legacyChangeNoteRead;
}
public ChangeNoteJson getChangeNoteJson() {
@@ -96,8 +87,8 @@ public class ChangeNoteUtil {
@VisibleForTesting
public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) {
return new PersonIdent(
- "Gerrit User " + author.getId(),
- author.getId().get() + "@" + serverId,
+ "Gerrit User " + author.id(),
+ author.id().get() + "@" + serverId,
when,
serverIdent.getTimeZone());
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index cda394c1c3..d6a32d323c 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -16,18 +16,21 @@ package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
@@ -36,18 +39,18 @@ import com.google.common.collect.Sets.SetView;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.RobotComment;
+import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
@@ -64,7 +67,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
@@ -79,7 +81,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final Ordering<PatchSetApproval> PSA_BY_TIME =
- Ordering.from(comparing(PatchSetApproval::getGranted));
+ Ordering.from(comparing(PatchSetApproval::granted));
public static final Ordering<ChangeMessage> MESSAGE_BY_TIME =
Ordering.from(comparing(ChangeMessage::getWrittenOn));
@@ -128,7 +130,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
public static Change newChange(Project.NameKey project, Change.Id changeId) {
return new Change(
- null, changeId, null, new Branch.NameKey(project, "INVALID_NOTE_DB_ONLY"), null);
+ null, changeId, null, BranchNameKey.create(project, "INVALID_NOTE_DB_ONLY"), null);
}
public ChangeNotes create(Project.NameKey project, Change.Id changeId) {
@@ -219,6 +221,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return idStream.map(id -> scanOneChange(project, sr, id)).filter(Objects::nonNull);
}
+ @Nullable
private ChangeNotesResult scanOneChange(Project.NameKey project, ScanResult sr, Change.Id id) {
if (!sr.fromMetaRefs().contains(id)) {
// Stray patch set refs can happen due to normal error conditions, e.g. failed
@@ -227,10 +230,15 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
}
// TODO(dborowitz): See discussion in BatchUpdate#newChangeContext.
- Change change = ChangeNotes.Factory.newChange(project, id);
-
- logger.atFine().log("adding change %s found in project %s", id, project);
- return toResult(change);
+ try {
+ Change change = ChangeNotes.Factory.newChange(project, id);
+ logger.atFine().log("adding change %s found in project %s", id, project);
+ return toResult(change);
+ } catch (InvalidServerIdException ise) {
+ logger.atWarning().withCause(ise).log(
+ "skipping change %d in project %s because of an invalid server id", id.get(), project);
+ return null;
+ }
}
@Nullable
@@ -339,9 +347,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
if (patchSets == null) {
ImmutableSortedMap.Builder<PatchSet.Id, PatchSet> b =
ImmutableSortedMap.orderedBy(comparing(PatchSet.Id::get));
- for (Map.Entry<PatchSet.Id, PatchSet> e : state.patchSets()) {
- b.put(e.getKey(), new PatchSet(e.getValue()));
- }
+ b.putAll(state.patchSets());
patchSets = b.build();
}
return patchSets;
@@ -349,12 +355,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
if (approvals == null) {
- ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> b =
- ImmutableListMultimap.builder();
- for (Map.Entry<PatchSet.Id, PatchSetApproval> e : state.approvals()) {
- b.put(e.getKey(), new PatchSetApproval(e.getValue()));
- }
- approvals = b.build();
+ approvals = ImmutableListMultimap.copyOf(state.approvals());
}
return approvals;
}
@@ -382,9 +383,24 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return state.reviewerUpdates();
}
- /** @return an ImmutableSet of Account.Ids of all users that have been assigned to this change. */
+ /**
+ * @return an ImmutableSet of Account.Ids of all users that have been assigned to this change. The
+ * order of the set is the order in which they were assigned.
+ */
public ImmutableSet<Account.Id> getPastAssignees() {
- return state.pastAssignees();
+ return Lists.reverse(state.assigneeUpdates()).stream()
+ .map(AssigneeStatusUpdate::currentAssignee)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableSet());
+ }
+
+ /**
+ * @return an ImmutableList of AssigneeStatusUpdate of all the updates to the assignee field to
+ * this change. The order of the list is from most recent updates to least recent.
+ */
+ public ImmutableList<AssigneeStatusUpdate> getAssigneeUpdates() {
+ return state.assigneeUpdates();
}
/** @return a ImmutableSet of all hashtags for this change sorted in alphabetical order. */
@@ -411,7 +427,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
}
/** @return inline comments on each revision. */
- public ImmutableListMultimap<RevId, Comment> getComments() {
+ public ImmutableListMultimap<ObjectId, Comment> getComments() {
return state.publishedComments();
}
@@ -426,11 +442,15 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return commentKeys;
}
- public ImmutableListMultimap<RevId, Comment> getDraftComments(Account.Id author) {
+ public int getUpdateCount() {
+ return state.updateCount();
+ }
+
+ public ImmutableListMultimap<ObjectId, Comment> getDraftComments(Account.Id author) {
return getDraftComments(author, null);
}
- public ImmutableListMultimap<RevId, Comment> getDraftComments(
+ public ImmutableListMultimap<ObjectId, Comment> getDraftComments(
Account.Id author, @Nullable Ref ref) {
loadDraftComments(author, ref);
// Filter out any zombie draft comments. These are drafts that are also in
@@ -441,7 +461,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
draftCommentNotes.getComments(), e -> !getCommentKeys().contains(e.getValue().key)));
}
- public ImmutableListMultimap<RevId, RobotComment> getRobotComments() {
+ public ImmutableListMultimap<ObjectId, RobotComment> getRobotComments() {
loadRobotComments();
return robotCommentNotes.getComments();
}
@@ -516,6 +536,19 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
ChangeNotesCache.Value v =
args.cache.get().get(getProjectName(), getChangeId(), rev, handle::walk);
state = v.state();
+
+ String stateServerId = state.serverId();
+ /**
+ * In earlier Gerrit versions serverId wasn't part of the change notes cache. That's why the
+ * earlier cached entries don't have the serverId attribute. That's fine because in earlier
+ * gerrit version serverId was already validated. Another approach to simplify the check would
+ * be to bump the cache version, but that would invalidate all persistent cache entries, what we
+ * rather try to avoid.
+ */
+ if (!Strings.isNullOrEmpty(stateServerId) && !args.serverId.equals(stateServerId)) {
+ throw new InvalidServerIdException(args.serverId, stateServerId);
+ }
+
state.copyColumnsTo(change);
revisionNoteMap = v.revisionNoteMap();
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index e2af855f45..7fde297f23 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -20,10 +20,10 @@ import com.google.common.cache.Cache;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.cache.CacheModule;
@@ -61,7 +61,7 @@ public class ChangeNotesCache {
.weigher(Weigher.class)
.maximumWeight(10 << 20)
.diskLimit(-1)
- .version(1)
+ .version(2)
.keySerializer(Key.Serializer.INSTANCE)
.valueSerializer(ChangeNotesState.Serializer.INSTANCE);
}
@@ -98,8 +98,8 @@ public class ChangeNotesCache {
public Key deserialize(byte[] in) {
ChangeNotesKeyProto proto = Protos.parseUnchecked(ChangeNotesKeyProto.parser(), in);
return Key.create(
- new Project.NameKey(proto.getProject()),
- new Change.Id(proto.getChangeId()),
+ Project.nameKey(proto.getProject()),
+ Change.id(proto.getChangeId()),
ObjectIdConverter.create().fromByteString(proto.getId()));
}
}
@@ -112,8 +112,11 @@ public class ChangeNotesCache {
// Single pointer overhead.
private static final int P = 8;
+ // Single int overhead.
+ private static final int I = 4;
+
// Single IntKey overhead.
- private static final int K = O + 4;
+ private static final int K = O + I;
// Single Timestamp overhead.
private static final int T = O + 8;
@@ -145,11 +148,8 @@ public class ChangeNotesCache {
+ str(state.columns().originalSubject())
+ P
+ str(state.columns().submissionId())
- + ptr(state.columns().assignee(), K) // assignee
+ P // status
+ P
- + set(state.pastAssignees(), K)
- + P
+ set(state.hashtags(), str(10))
+ P
+ list(state.patchSets(), patchSet())
@@ -166,6 +166,8 @@ public class ChangeNotesCache {
+ P
+ list(state.reviewerUpdates(), 4 * O + K + K + P)
+ P
+ + list(state.assigneeUpdates(), 4 * O + K + K)
+ + P
+ list(state.submitRecords(), P + list(2, str(4) + P + K) + P)
+ P
+ list(state.changeMessages(), changeMessage())
@@ -173,11 +175,8 @@ public class ChangeNotesCache {
+ map(state.publishedComments().asMap(), comment())
+ 1 // isPrivate
+ 1 // workInProgress
- + 1; // reviewStarted
- }
-
- private static int ptr(Object o, int size) {
- return o != null ? P + size : P;
+ + 1 // reviewStarted
+ + I; // updateCount
}
private static int str(String s) {
@@ -351,12 +350,7 @@ public class ChangeNotesCache {
"Load change notes for change %s of project %s", key.changeId(), key.project());
ChangeNotesParser parser =
new ChangeNotesParser(
- key.changeId(),
- key.id(),
- walkSupplier.get(),
- args.changeNoteJson,
- args.legacyChangeNoteRead,
- args.metrics);
+ key.changeId(), key.id(), walkSupplier.get(), args.changeNoteJson, args.metrics);
ChangeNotesState result = parser.parseAll();
// This assignment only happens if call() was actually called, which only
// happens when Cache#get(K, Callable<V>) incurs a cache miss.
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index d3b34b9d60..60162cdfbd 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -40,7 +40,6 @@ import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
-import com.google.auto.value.AutoValue;
import com.google.common.base.Enums;
import com.google.common.base.Splitter;
import com.google.common.collect.HashBasedTable;
@@ -48,6 +47,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
@@ -56,18 +56,17 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.mail.Address;
import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
@@ -101,26 +100,8 @@ import org.eclipse.jgit.util.RawParseUtils;
class ChangeNotesParser {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- // Sentinel RevId indicating a mutable field on a patch set was parsed, but
- // the parser does not yet know its commit SHA-1.
- private static final RevId PARTIAL_PATCH_SET = new RevId("INVALID PARTIAL PATCH SET");
-
- @AutoValue
- abstract static class ApprovalKey {
- abstract PatchSet.Id psId();
-
- abstract Account.Id accountId();
-
- abstract String label();
-
- private static ApprovalKey create(PatchSet.Id psId, Account.Id accountId, String label) {
- return new AutoValue_ChangeNotesParser_ApprovalKey(psId, accountId, label);
- }
- }
-
// Private final members initialized in the constructor.
private final ChangeNoteJson changeNoteJson;
- private final LegacyChangeNoteRead legacyChangeNoteRead;
private final NoteDbMetrics metrics;
private final Change.Id id;
@@ -133,26 +114,26 @@ class ChangeNotesParser {
private final Table<Address, ReviewerStateInternal, Timestamp> reviewersByEmail;
private final List<Account.Id> allPastReviewers;
private final List<ReviewerStatusUpdate> reviewerUpdates;
+ private final List<AssigneeStatusUpdate> assigneeUpdates;
private final List<SubmitRecord> submitRecords;
- private final ListMultimap<RevId, Comment> comments;
- private final Map<PatchSet.Id, PatchSet> patchSets;
+ private final ListMultimap<ObjectId, Comment> comments;
+ private final Map<PatchSet.Id, PatchSet.Builder> patchSets;
private final Set<PatchSet.Id> deletedPatchSets;
private final Map<PatchSet.Id, PatchSetState> patchSetStates;
private final List<PatchSet.Id> currentPatchSets;
- private final Map<ApprovalKey, PatchSetApproval> approvals;
- private final List<PatchSetApproval> bufferedApprovals;
+ private final Map<PatchSetApproval.Key, PatchSetApproval.Builder> approvals;
+ private final List<PatchSetApproval.Builder> bufferedApprovals;
private final List<ChangeMessage> allChangeMessages;
// Non-final private members filled in during the parsing process.
private String branch;
private Change.Status status;
private String topic;
- private Optional<Account.Id> assignee;
- private List<Account.Id> pastAssignees;
private Set<String> hashtags;
private Timestamp createdOn;
private Timestamp lastUpdatedOn;
private Account.Id ownerId;
+ private String serverId;
private String changeId;
private String subject;
private String originalSubject;
@@ -166,19 +147,18 @@ class ChangeNotesParser {
private ReviewerSet pendingReviewers;
private ReviewerByEmailSet pendingReviewersByEmail;
private Change.Id revertOf;
+ private int updateCount;
ChangeNotesParser(
Change.Id changeId,
ObjectId tip,
ChangeNotesRevWalk walk,
ChangeNoteJson changeNoteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
NoteDbMetrics metrics) {
this.id = changeId;
this.tip = tip;
this.walk = walk;
this.changeNoteJson = changeNoteJson;
- this.legacyChangeNoteRead = legacyChangeNoteRead;
this.metrics = metrics;
approvals = new LinkedHashMap<>();
bufferedApprovals = new ArrayList<>();
@@ -188,6 +168,7 @@ class ChangeNotesParser {
pendingReviewersByEmail = ReviewerByEmailSet.empty();
allPastReviewers = new ArrayList<>();
reviewerUpdates = new ArrayList<>();
+ assigneeUpdates = new ArrayList<>();
submitRecords = Lists.newArrayListWithExpectedSize(1);
allChangeMessages = new ArrayList<>();
comments = MultimapBuilder.hashKeys().arrayListValues().build();
@@ -204,7 +185,7 @@ class ChangeNotesParser {
walk.reset();
walk.markStart(walk.parseCommit(tip));
- try (Timer1.Context timer = metrics.parseLatency.start(CHANGES)) {
+ try (Timer1.Context<NoteDbTable> timer = metrics.parseLatency.start(CHANGES)) {
ChangeNotesCommit commit;
while ((commit = walk.next()) != null) {
parse(commit);
@@ -232,25 +213,24 @@ class ChangeNotesParser {
return revisionNoteMap;
}
- private ChangeNotesState buildState() {
+ private ChangeNotesState buildState() throws ConfigInvalidException {
return ChangeNotesState.create(
tip.copy(),
id,
- new Change.Key(changeId),
+ Change.key(changeId),
createdOn,
lastUpdatedOn,
ownerId,
+ serverId,
branch,
buildCurrentPatchSetId(),
subject,
topic,
originalSubject,
submissionId,
- assignee != null ? assignee.orElse(null) : null,
status,
- Sets.newLinkedHashSet(Lists.reverse(pastAssignees)),
firstNonNull(hashtags, ImmutableSet.of()),
- patchSets,
+ buildPatchSets(),
buildApprovals(),
ReviewerSet.fromTable(Tables.transpose(reviewers)),
ReviewerByEmailSet.fromTable(Tables.transpose(reviewersByEmail)),
@@ -258,20 +238,37 @@ class ChangeNotesParser {
pendingReviewersByEmail,
allPastReviewers,
buildReviewerUpdates(),
+ assigneeUpdates,
submitRecords,
buildAllMessages(),
comments,
firstNonNull(isPrivate, false),
firstNonNull(workInProgress, false),
firstNonNull(hasReviewStarted, true),
- revertOf);
+ revertOf,
+ updateCount);
+ }
+
+ private Map<PatchSet.Id, PatchSet> buildPatchSets() throws ConfigInvalidException {
+ Map<PatchSet.Id, PatchSet> result = Maps.newHashMapWithExpectedSize(patchSets.size());
+ for (Map.Entry<PatchSet.Id, PatchSet.Builder> e : patchSets.entrySet()) {
+ try {
+ PatchSet ps = e.getValue().build();
+ result.put(ps.id(), ps);
+ } catch (Exception ex) {
+ ConfigInvalidException cie = parseException("Error building patch set %s", e.getKey());
+ cie.initCause(ex);
+ throw cie;
+ }
+ }
+ return result;
}
private PatchSet.Id buildCurrentPatchSetId() {
// currentPatchSets are in parse order, i.e. newest first. Pick the first
// patch set that was marked as current, excluding deleted patch sets.
for (PatchSet.Id psId : currentPatchSets) {
- if (patchSets.containsKey(psId)) {
+ if (patchSetCommitParsed(psId)) {
return psId;
}
}
@@ -281,14 +278,14 @@ class ChangeNotesParser {
private ListMultimap<PatchSet.Id, PatchSetApproval> buildApprovals() {
ListMultimap<PatchSet.Id, PatchSetApproval> result =
MultimapBuilder.hashKeys().arrayListValues().build();
- for (PatchSetApproval a : approvals.values()) {
- if (!patchSets.containsKey(a.getPatchSetId())) {
+ for (PatchSetApproval.Builder a : approvals.values()) {
+ if (!patchSetCommitParsed(a.key().patchSetId())) {
continue; // Patch set deleted or missing.
- } else if (allPastReviewers.contains(a.getAccountId())
- && !reviewers.containsRow(a.getAccountId())) {
+ } else if (allPastReviewers.contains(a.key().accountId())
+ && !reviewers.containsRow(a.key().accountId())) {
continue; // Reviewer was explicitly removed.
}
- result.put(a.getPatchSetId(), a);
+ result.put(a.key().patchSetId(), a.build());
}
result.keySet().forEach(k -> result.get(k).sort(ChangeNotes.PSA_BY_TIME));
return result;
@@ -311,6 +308,7 @@ class ChangeNotesParser {
}
private void parse(ChangeNotesCommit commit) throws ConfigInvalidException {
+ updateCount++;
Timestamp ts = new Timestamp(commit.getCommitterIdent().getWhen().getTime());
createdOn = ts;
@@ -334,6 +332,10 @@ class ChangeNotesParser {
Account.Id accountId = parseIdent(commit);
if (accountId != null) {
ownerId = accountId;
+ PersonIdent personIdent = commit.getAuthorIdent();
+ serverId = NoteDbUtil.extractHostPartFromPersonIdent(personIdent);
+ } else {
+ serverId = "UNKNOWN_SERVER_ID";
}
Account.Id realAccountId = parseRealAccountId(commit, accountId);
@@ -355,17 +357,29 @@ class ChangeNotesParser {
}
parseHashtags(commit);
- parseAssignee(commit);
+ parseAssigneeUpdates(ts, commit);
if (submissionId == null) {
submissionId = parseSubmissionId(commit);
}
+ if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
+ lastUpdatedOn = ts;
+ }
+
+ if (deletedPatchSets.contains(psId)) {
+ // Do not update PS details as PS was deleted and this meta data is of no relevance.
+ return;
+ }
+
+ // Parse mutable patch set fields first so they can be recorded in the PendingPatchSetFields.
+ parseDescription(psId, commit);
+ parseGroups(psId, commit);
+
ObjectId currRev = parseRevision(commit);
if (currRev != null) {
parsePatchSet(psId, currRev, accountId, ts);
}
- parseGroups(psId, commit);
parseCurrentPatchSet(psId, commit);
if (submitRecords.isEmpty()) {
@@ -405,12 +419,6 @@ class ChangeNotesParser {
previousWorkInProgressFooter = null;
parseWorkInProgress(commit);
-
- if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
- lastUpdatedOn = ts;
- }
-
- parseDescription(psId, commit);
}
private String parseSubmissionId(ChangeNotesCommit commit) throws ConfigInvalidException {
@@ -437,7 +445,7 @@ class ChangeNotesParser {
return effectiveAccountId;
}
PersonIdent ident = RawParseUtils.parsePersonIdent(realUser);
- return legacyChangeNoteRead.parseIdent(ident, id);
+ return parseIdent(ident);
}
private String parseTopic(ChangeNotesCommit commit) throws ConfigInvalidException {
@@ -483,24 +491,23 @@ class ChangeNotesParser {
if (accountId == null) {
throw parseException("patch set %s requires an identified user as uploader", psId.get());
}
- PatchSet ps = patchSets.get(psId);
- if (ps == null) {
- ps = new PatchSet(psId);
- patchSets.put(psId, ps);
- } else if (!ps.getRevision().equals(PARTIAL_PATCH_SET)) {
- if (deletedPatchSets.contains(psId)) {
- // Do not update PS details as PS was deleted and this meta data is of
- // no relevance
- return;
- }
+ if (patchSetCommitParsed(psId)) {
+ ObjectId commitId = patchSets.get(psId).commitId().orElseThrow(IllegalStateException::new);
throw new ConfigInvalidException(
String.format(
"Multiple revisions parsed for patch set %s: %s and %s",
- psId.get(), patchSets.get(psId).getRevision(), rev.name()));
- }
- ps.setRevision(new RevId(rev.name()));
- ps.setUploader(accountId);
- ps.setCreatedOn(ts);
+ psId.get(), commitId.name(), rev.name()));
+ }
+ patchSets
+ .computeIfAbsent(psId, id -> PatchSet.builder())
+ .id(psId)
+ .commitId(rev)
+ .uploader(accountId)
+ .createdOn(ts);
+ // Fields not set here:
+ // * Groups, parsed earlier in parseGroups.
+ // * Description, parsed earlier in parseDescription.
+ // * Push certificate, parsed later in parseNotes.
}
private void parseGroups(PatchSet.Id psId, ChangeNotesCommit commit)
@@ -509,15 +516,11 @@ class ChangeNotesParser {
if (groupsStr == null) {
return;
}
- PatchSet ps = patchSets.get(psId);
- if (ps == null) {
- ps = new PatchSet(psId);
- ps.setRevision(PARTIAL_PATCH_SET);
- patchSets.put(psId, ps);
- } else if (!ps.getGroups().isEmpty()) {
- return;
+ checkPatchSetCommitNotParsed(psId, FOOTER_GROUPS);
+ PatchSet.Builder pending = patchSets.computeIfAbsent(psId, id -> PatchSet.builder());
+ if (pending.groups().isEmpty()) {
+ pending.groups(PatchSet.splitGroups(groupsStr));
}
- ps.setGroups(PatchSet.splitGroups(groupsStr));
}
private void parseCurrentPatchSet(PatchSet.Id psId, ChangeNotesCommit commit)
@@ -560,10 +563,8 @@ class ChangeNotesParser {
}
}
- private void parseAssignee(ChangeNotesCommit commit) throws ConfigInvalidException {
- if (pastAssignees == null) {
- pastAssignees = Lists.newArrayList();
- }
+ private void parseAssigneeUpdates(Timestamp ts, ChangeNotesCommit commit)
+ throws ConfigInvalidException {
String assigneeValue = parseOneFooter(commit, FOOTER_ASSIGNEE);
if (assigneeValue != null) {
Optional<Account.Id> parsedAssignee;
@@ -572,14 +573,9 @@ class ChangeNotesParser {
parsedAssignee = Optional.empty();
} else {
PersonIdent ident = RawParseUtils.parsePersonIdent(assigneeValue);
- parsedAssignee = Optional.ofNullable(legacyChangeNoteRead.parseIdent(ident, id));
- }
- if (assignee == null) {
- assignee = parsedAssignee;
- }
- if (parsedAssignee.isPresent()) {
- pastAssignees.add(parsedAssignee.get());
+ parsedAssignee = Optional.ofNullable(parseIdent(ident));
}
+ assigneeUpdates.add(AssigneeStatusUpdate.create(ts, ownerId, parsedAssignee));
}
}
@@ -612,9 +608,9 @@ class ChangeNotesParser {
// exception is the legacy SUBM approval, which is never considered post-submit, but might end
// up sorted after the submit during rebuilding.
if (status == Change.Status.MERGED) {
- for (PatchSetApproval psa : bufferedApprovals) {
- if (!psa.isLegacySubmit()) {
- psa.setPostSubmit(true);
+ for (PatchSetApproval.Builder psa : bufferedApprovals) {
+ if (!psa.key().isLegacySubmit()) {
+ psa.postSubmit(true);
}
}
}
@@ -630,7 +626,7 @@ class ChangeNotesParser {
if (psId == null) {
throw invalidFooter(FOOTER_PATCH_SET, psIdStr);
}
- return new PatchSet.Id(id, psId);
+ return PatchSet.id(id, psId);
}
private PatchSetState parsePatchSetState(ChangeNotesCommit commit) throws ConfigInvalidException {
@@ -658,16 +654,14 @@ class ChangeNotesParser {
List<String> descLines = commit.getFooterLineValues(FOOTER_PATCH_SET_DESCRIPTION);
if (descLines.isEmpty()) {
return;
- } else if (descLines.size() == 1) {
+ }
+
+ checkPatchSetCommitNotParsed(psId, FOOTER_PATCH_SET_DESCRIPTION);
+ if (descLines.size() == 1) {
String desc = descLines.get(0).trim();
- PatchSet ps = patchSets.get(psId);
- if (ps == null) {
- ps = new PatchSet(psId);
- ps.setRevision(PARTIAL_PATCH_SET);
- patchSets.put(psId, ps);
- }
- if (ps.getDescription() == null) {
- ps.setDescription(desc);
+ PatchSet.Builder pending = patchSets.computeIfAbsent(psId, p -> PatchSet.builder());
+ if (!pending.description().isPresent()) {
+ pending.description(Optional.of(desc));
}
} else {
throw expectedOneFooter(FOOTER_PATCH_SET_DESCRIPTION, descLines);
@@ -686,8 +680,7 @@ class ChangeNotesParser {
}
ChangeMessage changeMessage =
- new ChangeMessage(
- new ChangeMessage.Key(psId.getParentKey(), commit.name()), accountId, ts, psId);
+ new ChangeMessage(ChangeMessage.key(psId.changeId(), commit.name()), accountId, ts, psId);
changeMessage.setMessage(changeMsgString.get());
changeMessage.setTag(tag);
changeMessage.setRealAuthor(realAccountId);
@@ -713,24 +706,24 @@ class ChangeNotesParser {
ChangeNotesCommit tipCommit = walk.parseCommit(tip);
revisionNoteMap =
RevisionNoteMap.parse(
- changeNoteJson,
- legacyChangeNoteRead,
- id,
- reader,
- NoteMap.read(reader, tipCommit),
- PatchLineComment.Status.PUBLISHED);
- Map<RevId, ChangeRevisionNote> rns = revisionNoteMap.revisionNotes;
-
- for (Map.Entry<RevId, ChangeRevisionNote> e : rns.entrySet()) {
+ changeNoteJson, reader, NoteMap.read(reader, tipCommit), Comment.Status.PUBLISHED);
+ Map<ObjectId, ChangeRevisionNote> rns = revisionNoteMap.revisionNotes;
+
+ for (Map.Entry<ObjectId, ChangeRevisionNote> e : rns.entrySet()) {
for (Comment c : e.getValue().getEntities()) {
comments.put(e.getKey(), c);
}
}
- for (PatchSet ps : patchSets.values()) {
- ChangeRevisionNote rn = rns.get(ps.getRevision());
+ for (PatchSet.Builder b : patchSets.values()) {
+ ObjectId commitId =
+ b.commitId()
+ .orElseThrow(
+ () ->
+ new IllegalStateException("never parsed commit ID for patch set " + b.id()));
+ ChangeRevisionNote rn = rns.get(commitId);
if (rn != null && rn.getPushCert() != null) {
- ps.setPushCertificate(rn.getPushCert());
+ b.pushCertificate(Optional.of(rn.getPushCert()));
}
}
}
@@ -741,7 +734,7 @@ class ChangeNotesParser {
if (accountId == null) {
throw parseException("patch set %s requires an identified user as uploader", psId.get());
}
- PatchSetApproval psa;
+ PatchSetApproval.Builder psa;
if (line.startsWith("-")) {
psa = parseRemoveApproval(psId, accountId, realAccountId, ts, line);
} else {
@@ -750,7 +743,7 @@ class ChangeNotesParser {
bufferedApprovals.add(psa);
}
- private PatchSetApproval parseAddApproval(
+ private PatchSetApproval.Builder parseAddApproval(
PatchSet.Id psId, Account.Id committerId, Account.Id realAccountId, Timestamp ts, String line)
throws ConfigInvalidException {
// There are potentially 3 accounts involved here:
@@ -772,7 +765,7 @@ class ChangeNotesParser {
labelVoteStr = line.substring(0, s);
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
checkFooter(ident != null, FOOTER_LABEL, line);
- effectiveAccountId = legacyChangeNoteRead.parseIdent(ident, id);
+ effectiveAccountId = parseIdent(ident);
} else {
labelVoteStr = line;
effectiveAccountId = committerId;
@@ -787,23 +780,20 @@ class ChangeNotesParser {
throw pe;
}
- PatchSetApproval psa =
- new PatchSetApproval(
- new PatchSetApproval.Key(psId, effectiveAccountId, new LabelId(l.label())),
- l.value(),
- ts);
- psa.setTag(tag);
+ PatchSetApproval.Builder psa =
+ PatchSetApproval.builder()
+ .key(PatchSetApproval.key(psId, effectiveAccountId, LabelId.create(l.label())))
+ .value(l.value())
+ .granted(ts)
+ .tag(Optional.ofNullable(tag));
if (!Objects.equals(realAccountId, committerId)) {
- psa.setRealAccountId(realAccountId);
- }
- ApprovalKey k = ApprovalKey.create(psId, effectiveAccountId, l.label());
- if (!approvals.containsKey(k)) {
- approvals.put(k, psa);
+ psa.realAccountId(realAccountId);
}
+ approvals.putIfAbsent(psa.key(), psa);
return psa;
}
- private PatchSetApproval parseRemoveApproval(
+ private PatchSetApproval.Builder parseRemoveApproval(
PatchSet.Id psId, Account.Id committerId, Account.Id realAccountId, Timestamp ts, String line)
throws ConfigInvalidException {
// See comments in parseAddApproval about the various users involved.
@@ -814,7 +804,7 @@ class ChangeNotesParser {
label = line.substring(1, s);
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
checkFooter(ident != null, FOOTER_LABEL, line);
- effectiveAccountId = legacyChangeNoteRead.parseIdent(ident, id);
+ effectiveAccountId = parseIdent(ident);
} else {
label = line.substring(1);
effectiveAccountId = committerId;
@@ -830,16 +820,15 @@ class ChangeNotesParser {
// Store an actual 0-vote approval in the map for a removed approval, because ApprovalCopier
// needs an actual approval in order to block copying an earlier approval over a later delete.
- PatchSetApproval remove =
- new PatchSetApproval(
- new PatchSetApproval.Key(psId, effectiveAccountId, new LabelId(label)), (short) 0, ts);
+ PatchSetApproval.Builder remove =
+ PatchSetApproval.builder()
+ .key(PatchSetApproval.key(psId, effectiveAccountId, LabelId.create(label)))
+ .value(0)
+ .granted(ts);
if (!Objects.equals(realAccountId, committerId)) {
- remove.setRealAccountId(realAccountId);
- }
- ApprovalKey k = ApprovalKey.create(psId, effectiveAccountId, label);
- if (!approvals.containsKey(k)) {
- approvals.put(k, remove);
+ remove.realAccountId(realAccountId);
}
+ approvals.putIfAbsent(remove.key(), remove);
return remove;
}
@@ -874,7 +863,7 @@ class ChangeNotesParser {
label.label = line.substring(c + 2, c2);
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
- label.appliedBy = legacyChangeNoteRead.parseIdent(ident, id);
+ label.appliedBy = parseIdent(ident);
} else {
label.label = line.substring(c + 2);
}
@@ -890,7 +879,7 @@ class ChangeNotesParser {
if (a.getName().equals(c.getName()) && a.getEmailAddress().equals(c.getEmailAddress())) {
return null;
}
- return legacyChangeNoteRead.parseIdent(commit.getAuthorIdent(), id);
+ return parseIdent(commit.getAuthorIdent());
}
private void parseReviewer(Timestamp ts, ReviewerStateInternal state, String line)
@@ -899,7 +888,7 @@ class ChangeNotesParser {
if (ident == null) {
throw invalidFooter(state.getFooterKey(), line);
}
- Account.Id accountId = legacyChangeNoteRead.parseIdent(ident, id);
+ Account.Id accountId = parseIdent(ident);
reviewerUpdates.add(ReviewerStatusUpdate.create(ts, ownerId, accountId, state));
if (!reviewers.containsRow(accountId)) {
reviewers.put(accountId, state, ts);
@@ -976,7 +965,7 @@ class ChangeNotesParser {
if (revertOf == null) {
throw invalidFooter(FOOTER_REVERT_OF, footer);
}
- return new Change.Id(revertOf);
+ return Change.id(revertOf);
}
private void pruneReviewers() {
@@ -1003,13 +992,8 @@ class ChangeNotesParser {
private void updatePatchSetStates() {
Set<PatchSet.Id> missing = new TreeSet<>(comparing(PatchSet.Id::get));
- for (Iterator<PatchSet> it = patchSets.values().iterator(); it.hasNext(); ) {
- PatchSet ps = it.next();
- if (ps.getRevision().equals(PARTIAL_PATCH_SET)) {
- missing.add(ps.getId());
- it.remove();
- }
- }
+ patchSets.keySet().stream().filter(p -> !patchSetCommitParsed(p)).forEach(p -> missing.add(p));
+
for (Map.Entry<PatchSet.Id, PatchSetState> e : patchSetStates.entrySet()) {
switch (e.getValue()) {
case PUBLISHED:
@@ -1030,10 +1014,10 @@ class ChangeNotesParser {
pruneEntitiesForMissingPatchSets(allChangeMessages, ChangeMessage::getPatchSetId, missing);
pruned +=
pruneEntitiesForMissingPatchSets(
- comments.values(), c -> new PatchSet.Id(id, c.key.patchSetId), missing);
+ comments.values(), c -> PatchSet.id(id, c.key.patchSetId), missing);
pruned +=
pruneEntitiesForMissingPatchSets(
- approvals.values(), PatchSetApproval::getPatchSetId, missing);
+ approvals.values(), psa -> psa.key().patchSetId(), missing);
if (!missing.isEmpty()) {
logger.atWarning().log(
@@ -1046,7 +1030,7 @@ class ChangeNotesParser {
int pruned = 0;
for (Iterator<T> it = ents.iterator(); it.hasNext(); ) {
PatchSet.Id psId = psIdFunc.apply(it.next());
- if (!patchSets.containsKey(psId)) {
+ if (!patchSetCommitParsed(psId)) {
pruned++;
missing.add(psId);
it.remove();
@@ -1089,7 +1073,27 @@ class ChangeNotesParser {
}
}
+ private void checkPatchSetCommitNotParsed(PatchSet.Id psId, FooterKey footer)
+ throws ConfigInvalidException {
+ if (patchSetCommitParsed(psId)) {
+ throw parseException(
+ "%s field found for patch set %s before patch set was originally defined",
+ footer.getName(), psId.get());
+ }
+ }
+
+ private boolean patchSetCommitParsed(PatchSet.Id psId) {
+ PatchSet.Builder pending = patchSets.get(psId);
+ return pending != null && pending.commitId().isPresent();
+ }
+
private ConfigInvalidException parseException(String fmt, Object... args) {
return ChangeNotes.parseException(id, fmt, args);
}
+
+ private Account.Id parseIdent(PersonIdent ident) throws ConfigInvalidException {
+ return NoteDbUtil.parseIdent(ident)
+ .orElseThrow(
+ () -> parseException("cannot retrieve account id: %s", ident.getEmailAddress()));
+ }
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index fd260e7449..896cca3ba4 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
@@ -35,26 +34,27 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.converter.ChangeMessageProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetProtoConverter;
+import com.google.gerrit.entities.converter.ProtoConverter;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.mail.Address;
import com.google.gerrit.proto.Protos;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.converter.ChangeMessageProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
-import com.google.gerrit.reviewdb.converter.ProtoConverter;
+import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
+import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
@@ -68,6 +68,7 @@ import com.google.protobuf.MessageLite;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
@@ -97,15 +98,14 @@ public abstract class ChangeNotesState {
Timestamp createdOn,
Timestamp lastUpdatedOn,
Account.Id owner,
+ String serverId,
String branch,
@Nullable PatchSet.Id currentPatchSetId,
String subject,
@Nullable String topic,
@Nullable String originalSubject,
@Nullable String submissionId,
- @Nullable Account.Id assignee,
@Nullable Change.Status status,
- Set<Account.Id> pastAssignees,
Set<String> hashtags,
Map<PatchSet.Id, PatchSet> patchSets,
ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
@@ -115,13 +115,15 @@ public abstract class ChangeNotesState {
ReviewerByEmailSet pendingReviewersByEmail,
List<Account.Id> allPastReviewers,
List<ReviewerStatusUpdate> reviewerUpdates,
+ List<AssigneeStatusUpdate> assigneeUpdates,
List<SubmitRecord> submitRecords,
List<ChangeMessage> changeMessages,
- ListMultimap<RevId, Comment> publishedComments,
+ ListMultimap<ObjectId, Comment> publishedComments,
boolean isPrivate,
boolean workInProgress,
boolean reviewStarted,
- @Nullable Change.Id revertOf) {
+ @Nullable Change.Id revertOf,
+ int updateCount) {
requireNonNull(
metaId,
() ->
@@ -146,14 +148,13 @@ public abstract class ChangeNotesState {
.topic(topic)
.originalSubject(originalSubject)
.submissionId(submissionId)
- .assignee(assignee)
.isPrivate(isPrivate)
.workInProgress(workInProgress)
.reviewStarted(reviewStarted)
.revertOf(revertOf)
.build())
- .pastAssignees(pastAssignees)
.hashtags(hashtags)
+ .serverId(serverId)
.patchSets(patchSets.entrySet())
.approvals(approvals.entries())
.reviewers(reviewers)
@@ -162,9 +163,11 @@ public abstract class ChangeNotesState {
.pendingReviewersByEmail(pendingReviewersByEmail)
.allPastReviewers(allPastReviewers)
.reviewerUpdates(reviewerUpdates)
+ .assigneeUpdates(assigneeUpdates)
.submitRecords(submitRecords)
.changeMessages(changeMessages)
.publishedComments(publishedComments)
+ .updateCount(updateCount)
.build();
}
@@ -208,9 +211,6 @@ public abstract class ChangeNotesState {
@Nullable
abstract String submissionId();
- @Nullable
- abstract Account.Id assignee();
-
abstract boolean isPrivate();
abstract boolean workInProgress();
@@ -244,8 +244,6 @@ public abstract class ChangeNotesState {
abstract Builder submissionId(@Nullable String submissionId);
- abstract Builder assignee(@Nullable Account.Id assignee);
-
abstract Builder status(@Nullable Change.Status status);
abstract Builder isPrivate(boolean isPrivate);
@@ -271,10 +269,11 @@ public abstract class ChangeNotesState {
abstract ChangeColumns columns();
// Other related to this Change.
- abstract ImmutableSet<Account.Id> pastAssignees();
-
abstract ImmutableSet<String> hashtags();
+ @Nullable
+ abstract String serverId();
+
abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSet>> patchSets();
abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals();
@@ -291,11 +290,15 @@ public abstract class ChangeNotesState {
abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
+ abstract ImmutableList<AssigneeStatusUpdate> assigneeUpdates();
+
abstract ImmutableList<SubmitRecord> submitRecords();
abstract ImmutableList<ChangeMessage> changeMessages();
- abstract ImmutableListMultimap<RevId, Comment> publishedComments();
+ abstract ImmutableListMultimap<ObjectId, Comment> publishedComments();
+
+ abstract int updateCount();
Change newChange(Project.NameKey project) {
ChangeColumns c = requireNonNull(columns(), "columns are required");
@@ -304,7 +307,7 @@ public abstract class ChangeNotesState {
c.changeKey(),
changeId(),
c.owner(),
- new Branch.NameKey(project, c.branch()),
+ BranchNameKey.create(project, c.branch()),
c.createdOn());
copyNonConstructorColumnsTo(change);
return change;
@@ -318,7 +321,7 @@ public abstract class ChangeNotesState {
this);
change.setKey(c.changeKey());
change.setOwner(c.owner());
- change.setDest(new Branch.NameKey(change.getProject(), c.branch()));
+ change.setDest(BranchNameKey.create(change.getProject(), c.branch()));
change.setCreatedOn(c.createdOn());
copyNonConstructorColumnsTo(change);
}
@@ -331,7 +334,9 @@ public abstract class ChangeNotesState {
change.setTopic(Strings.emptyToNull(c.topic()));
change.setLastUpdatedOn(c.lastUpdatedOn());
change.setSubmissionId(c.submissionId());
- change.setAssignee(c.assignee());
+ if (!assigneeUpdates().isEmpty()) {
+ change.setAssignee(assigneeUpdates().get(0).currentAssignee().orElse(null));
+ }
change.setPrivate(c.isPrivate());
change.setWorkInProgress(c.workInProgress());
change.setReviewStarted(c.reviewStarted());
@@ -351,7 +356,6 @@ public abstract class ChangeNotesState {
static Builder empty(Change.Id changeId) {
return new AutoValue_ChangeNotesState.Builder()
.changeId(changeId)
- .pastAssignees(ImmutableSet.of())
.hashtags(ImmutableSet.of())
.patchSets(ImmutableList.of())
.approvals(ImmutableList.of())
@@ -361,9 +365,11 @@ public abstract class ChangeNotesState {
.pendingReviewersByEmail(ReviewerByEmailSet.empty())
.allPastReviewers(ImmutableList.of())
.reviewerUpdates(ImmutableList.of())
+ .assigneeUpdates(ImmutableList.of())
.submitRecords(ImmutableList.of())
.changeMessages(ImmutableList.of())
- .publishedComments(ImmutableListMultimap.of());
+ .publishedComments(ImmutableListMultimap.of())
+ .updateCount(0);
}
abstract Builder metaId(ObjectId metaId);
@@ -372,7 +378,7 @@ public abstract class ChangeNotesState {
abstract Builder columns(ChangeColumns columns);
- abstract Builder pastAssignees(Set<Account.Id> pastAssignees);
+ abstract Builder serverId(String serverId);
abstract Builder hashtags(Iterable<String> hashtags);
@@ -392,11 +398,15 @@ public abstract class ChangeNotesState {
abstract Builder reviewerUpdates(List<ReviewerStatusUpdate> reviewerUpdates);
+ abstract Builder assigneeUpdates(List<AssigneeStatusUpdate> assigneeUpdates);
+
abstract Builder submitRecords(List<SubmitRecord> submitRecords);
abstract Builder changeMessages(List<ChangeMessage> changeMessages);
- abstract Builder publishedComments(ListMultimap<RevId, Comment> publishedComments);
+ abstract Builder publishedComments(ListMultimap<ObjectId, Comment> publishedComments);
+
+ abstract Builder updateCount(int updateCount);
abstract ChangeNotesState build();
}
@@ -421,7 +431,10 @@ public abstract class ChangeNotesState {
.setChangeId(object.changeId().get())
.setColumns(toChangeColumnsProto(object.columns()));
- object.pastAssignees().forEach(a -> b.addPastAssignee(a.get()));
+ if (object.serverId() != null) {
+ b.setServerId(object.serverId());
+ b.setHasServerId(true);
+ }
object.hashtags().forEach(b::addHashtag);
object
.patchSets()
@@ -452,6 +465,7 @@ public abstract class ChangeNotesState {
object.allPastReviewers().forEach(a -> b.addPastReviewer(a.get()));
object.reviewerUpdates().forEach(u -> b.addReviewerUpdate(toReviewerStatusUpdateProto(u)));
+ object.assigneeUpdates().forEach(u -> b.addAssigneeUpdate(toAssigneeStatusUpdateProto(u)));
object
.submitRecords()
.forEach(r -> b.addSubmitRecord(GSON.toJson(new StoredSubmitRecord(r))));
@@ -459,6 +473,7 @@ public abstract class ChangeNotesState {
.changeMessages()
.forEach(m -> b.addChangeMessage(toByteString(m, ChangeMessageProtoConverter.INSTANCE)));
object.publishedComments().values().forEach(c -> b.addPublishedComment(GSON.toJson(c)));
+ b.setUpdateCount(object.updateCount());
return Protos.toByteArray(b.build());
}
@@ -490,9 +505,6 @@ public abstract class ChangeNotesState {
if (cols.submissionId() != null) {
b.setSubmissionId(cols.submissionId()).setHasSubmissionId(true);
}
- if (cols.assignee() != null) {
- b.setAssignee(cols.assignee().get()).setHasAssignee(true);
- }
if (cols.status() != null) {
b.setStatus(STATUS_CONVERTER.reverse().convert(cols.status())).setHasStatus(true);
}
@@ -532,40 +544,47 @@ public abstract class ChangeNotesState {
.build();
}
+ private static AssigneeStatusUpdateProto toAssigneeStatusUpdateProto(AssigneeStatusUpdate u) {
+ AssigneeStatusUpdateProto.Builder builder =
+ AssigneeStatusUpdateProto.newBuilder()
+ .setDate(u.date().getTime())
+ .setUpdatedBy(u.updatedBy().get())
+ .setHasCurrentAssignee(u.currentAssignee().isPresent());
+
+ u.currentAssignee().ifPresent(assignee -> builder.setCurrentAssignee(assignee.get()));
+ return builder.build();
+ }
+
@Override
public ChangeNotesState deserialize(byte[] in) {
ChangeNotesStateProto proto = Protos.parseUnchecked(ChangeNotesStateProto.parser(), in);
- Change.Id changeId = new Change.Id(proto.getChangeId());
+ Change.Id changeId = Change.id(proto.getChangeId());
ChangeNotesState.Builder b =
builder()
.metaId(ObjectIdConverter.create().fromByteString(proto.getMetaId()))
.changeId(changeId)
.columns(toChangeColumns(changeId, proto.getColumns()))
- .pastAssignees(
- proto.getPastAssigneeList().stream()
- .map(Account.Id::new)
- .collect(toImmutableSet()))
+ .serverId(proto.getHasServerId() ? proto.getServerId() : null)
.hashtags(proto.getHashtagList())
.patchSets(
proto.getPatchSetList().stream()
.map(bytes -> parseProtoFrom(PatchSetProtoConverter.INSTANCE, bytes))
- .map(ps -> Maps.immutableEntry(ps.getId(), ps))
+ .map(ps -> Maps.immutableEntry(ps.id(), ps))
.collect(toImmutableList()))
.approvals(
proto.getApprovalList().stream()
.map(bytes -> parseProtoFrom(PatchSetApprovalProtoConverter.INSTANCE, bytes))
- .map(a -> Maps.immutableEntry(a.getPatchSetId(), a))
+ .map(a -> Maps.immutableEntry(a.patchSetId(), a))
.collect(toImmutableList()))
.reviewers(toReviewerSet(proto.getReviewerList()))
.reviewersByEmail(toReviewerByEmailSet(proto.getReviewerByEmailList()))
.pendingReviewers(toReviewerSet(proto.getPendingReviewerList()))
.pendingReviewersByEmail(toReviewerByEmailSet(proto.getPendingReviewerByEmailList()))
.allPastReviewers(
- proto.getPastReviewerList().stream()
- .map(Account.Id::new)
- .collect(toImmutableList()))
+ proto.getPastReviewerList().stream().map(Account::id).collect(toImmutableList()))
.reviewerUpdates(toReviewerStatusUpdateList(proto.getReviewerUpdateList()))
+ .assigneeUpdates(toAssigneeStatusUpdateList(proto.getAssigneeUpdateList()))
.submitRecords(
proto.getSubmitRecordList().stream()
.map(r -> GSON.fromJson(r, StoredSubmitRecord.class).toSubmitRecord())
@@ -577,7 +596,8 @@ public abstract class ChangeNotesState {
.publishedComments(
proto.getPublishedCommentList().stream()
.map(r -> GSON.fromJson(r, Comment.class))
- .collect(toImmutableListMultimap(c -> new RevId(c.revId), c -> c)));
+ .collect(toImmutableListMultimap(Comment::getCommitId, c -> c)))
+ .updateCount(proto.getUpdateCount());
return b.build();
}
@@ -590,13 +610,13 @@ public abstract class ChangeNotesState {
private static ChangeColumns toChangeColumns(Change.Id changeId, ChangeColumnsProto proto) {
ChangeColumns.Builder b =
ChangeColumns.builder()
- .changeKey(new Change.Key(proto.getChangeKey()))
+ .changeKey(Change.key(proto.getChangeKey()))
.createdOn(new Timestamp(proto.getCreatedOn()))
.lastUpdatedOn(new Timestamp(proto.getLastUpdatedOn()))
- .owner(new Account.Id(proto.getOwner()))
+ .owner(Account.id(proto.getOwner()))
.branch(proto.getBranch());
if (proto.getHasCurrentPatchSetId()) {
- b.currentPatchSetId(new PatchSet.Id(changeId, proto.getCurrentPatchSetId()));
+ b.currentPatchSetId(PatchSet.id(changeId, proto.getCurrentPatchSetId()));
}
b.subject(proto.getSubject());
if (proto.getHasTopic()) {
@@ -608,9 +628,6 @@ public abstract class ChangeNotesState {
if (proto.getHasSubmissionId()) {
b.submissionId(proto.getSubmissionId());
}
- if (proto.getHasAssignee()) {
- b.assignee(new Account.Id(proto.getAssignee()));
- }
if (proto.getHasStatus()) {
b.status(STATUS_CONVERTER.convert(proto.getStatus()));
}
@@ -618,7 +635,7 @@ public abstract class ChangeNotesState {
.workInProgress(proto.getWorkInProgress())
.reviewStarted(proto.getReviewStarted());
if (proto.getHasRevertOf()) {
- b.revertOf(new Change.Id(proto.getRevertOf()));
+ b.revertOf(Change.id(proto.getRevertOf()));
}
return b.build();
}
@@ -629,7 +646,7 @@ public abstract class ChangeNotesState {
for (ReviewerSetEntryProto e : protos) {
b.put(
REVIEWER_STATE_CONVERTER.convert(e.getState()),
- new Account.Id(e.getAccountId()),
+ Account.id(e.getAccountId()),
new Timestamp(e.getTimestamp()));
}
return ReviewerSet.fromTable(b.build());
@@ -655,11 +672,26 @@ public abstract class ChangeNotesState {
b.add(
ReviewerStatusUpdate.create(
new Timestamp(proto.getDate()),
- new Account.Id(proto.getUpdatedBy()),
- new Account.Id(proto.getReviewer()),
+ Account.id(proto.getUpdatedBy()),
+ Account.id(proto.getReviewer()),
REVIEWER_STATE_CONVERTER.convert(proto.getState())));
}
return b.build();
}
+
+ private static ImmutableList<AssigneeStatusUpdate> toAssigneeStatusUpdateList(
+ List<AssigneeStatusUpdateProto> protos) {
+ ImmutableList.Builder<AssigneeStatusUpdate> b = ImmutableList.builder();
+ for (AssigneeStatusUpdateProto proto : protos) {
+ b.add(
+ AssigneeStatusUpdate.create(
+ new Timestamp(proto.getDate()),
+ Account.id(proto.getUpdatedBy()),
+ proto.getHasCurrentAssignee()
+ ? Optional.of(Account.id(proto.getCurrentAssignee()))
+ : Optional.empty()));
+ }
+ return b.build();
+ }
}
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
index 66dd5e805a..b6443f15b9 100644
--- a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -16,10 +16,7 @@ package com.google.gerrit.server.notedb;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.primitives.Bytes;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.entities.Comment;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -30,30 +27,16 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.util.MutableInteger;
-import org.eclipse.jgit.util.RawParseUtils;
class ChangeRevisionNote extends RevisionNote<Comment> {
- private static final byte[] CERT_HEADER = "certificate version ".getBytes(UTF_8);
- // See org.eclipse.jgit.transport.PushCertificateParser.END_SIGNATURE
- private static final byte[] END_SIGNATURE = "-----END PGP SIGNATURE-----\n".getBytes(UTF_8);
-
private final ChangeNoteJson noteJson;
- private final LegacyChangeNoteRead legacyChangeNoteRead;
- private final Change.Id changeId;
- private final PatchLineComment.Status status;
+ private final Comment.Status status;
private String pushCert;
ChangeRevisionNote(
- ChangeNoteJson noteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
- Change.Id changeId,
- ObjectReader reader,
- ObjectId noteId,
- PatchLineComment.Status status) {
+ ChangeNoteJson noteJson, ObjectReader reader, ObjectId noteId, Comment.Status status) {
super(reader, noteId);
- this.legacyChangeNoteRead = legacyChangeNoteRead;
this.noteJson = noteJson;
- this.changeId = changeId;
this.status = status;
}
@@ -67,29 +50,13 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
MutableInteger p = new MutableInteger();
p.value = offset;
- if (isJson(raw, p.value)) {
- RevisionNoteData data = parseJson(noteJson, raw, p.value);
- if (status == PatchLineComment.Status.PUBLISHED) {
- pushCert = data.pushCert;
- } else {
- pushCert = null;
- }
- return data.comments;
- }
-
- if (status == PatchLineComment.Status.PUBLISHED) {
- pushCert = parsePushCert(changeId, raw, p);
- trimLeadingEmptyLines(raw, p);
+ RevisionNoteData data = parseJson(noteJson, raw, p.value);
+ if (status == Comment.Status.PUBLISHED) {
+ pushCert = data.pushCert;
} else {
pushCert = null;
}
- List<Comment> comments = legacyChangeNoteRead.parseNote(raw, p, changeId);
- comments.forEach(c -> c.legacyFormat = true);
- return comments;
- }
-
- static boolean isJson(byte[] raw, int offset) {
- return raw[offset] == '{' || raw[offset] == '[';
+ return data.comments;
}
private RevisionNoteData parseJson(ChangeNoteJson noteUtil, byte[] raw, int offset)
@@ -99,18 +66,4 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
return noteUtil.getGson().fromJson(r, RevisionNoteData.class);
}
}
-
- private static String parsePushCert(Change.Id changeId, byte[] bytes, MutableInteger p)
- throws ConfigInvalidException {
- if (RawParseUtils.match(bytes, p.value, CERT_HEADER) < 0) {
- return null;
- }
- int end = Bytes.indexOf(bytes, END_SIGNATURE);
- if (end < 0) {
- throw ChangeNotes.parseException(changeId, "invalid push certificate in note");
- }
- int start = p.value;
- p.value = end + END_SIGNATURE.length;
- return new String(bytes, start, p.value, UTF_8);
- }
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 0cde36350b..b2f85fcf62 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.notedb;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
@@ -39,7 +39,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_WORK_IN_PROGRESS;
import static com.google.gerrit.server.notedb.NoteDbUtil.sanitizeFooter;
-import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -50,21 +50,18 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.LabelVote;
-import com.google.gwtorm.client.IntKey;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
@@ -173,7 +170,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private static Table<String, Account.Id, Optional<Short>> approvals(
Comparator<String> nameComparator) {
- return TreeBasedTable.create(nameComparator, comparing(IntKey::get));
+ return TreeBasedTable.create(nameComparator, naturalOrder());
}
@AssistedInject
@@ -250,11 +247,6 @@ public class ChangeUpdate extends AbstractChangeUpdate {
checkArgument(!this.submitRecords.isEmpty(), "no submit records specified at submit time");
}
- @Deprecated // Only until we improve ChangeRebuilder to call merge().
- public void setSubmissionId(String submissionId) {
- this.submissionId = submissionId;
- }
-
public void setSubjectForCommit(String commitSubject) {
this.commitSubject = commitSubject;
}
@@ -280,18 +272,14 @@ public class ChangeUpdate extends AbstractChangeUpdate {
this.psDescription = psDescription;
}
- public void putComment(PatchLineComment.Status status, Comment c) {
+ public void putComment(Comment.Status status, Comment c) {
verifyComment(c);
createDraftUpdateIfNull();
- if (status == PatchLineComment.Status.DRAFT) {
+ if (status == Comment.Status.DRAFT) {
draftUpdate.putComment(c);
} else {
comments.add(c);
- // Always delete the corresponding comment from drafts. Published comments
- // are immutable, meaning in normal operation we only hit this path when
- // publishing a comment. It's exactly in that case that we have to delete
- // the draft.
- draftUpdate.deleteComment(c);
+ draftUpdate.markCommentPublished(c);
}
}
@@ -421,7 +409,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
}
public void setRevertOf(int revertOf) {
- int ownId = getChange().getId().get();
+ int ownId = getId().get();
checkArgument(ownId != revertOf, "A change cannot revert itself");
this.revertOf = revertOf;
rootOnly = true;
@@ -438,18 +426,18 @@ public class ChangeUpdate extends AbstractChangeUpdate {
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
for (Comment c : comments) {
c.tag = tag;
- cache.get(new RevId(c.revId)).putComment(c);
+ cache.get(c.getCommitId()).putComment(c);
}
if (pushCert != null) {
checkState(commit != null);
- cache.get(new RevId(commit)).setPushCertificate(pushCert);
+ cache.get(ObjectId.fromString(commit)).setPushCertificate(pushCert);
}
- Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
+ Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
checkComments(rnm.revisionNotes, builders);
- for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
+ for (Map.Entry<ObjectId, RevisionNoteBuilder> e : builders.entrySet()) {
ObjectId data = inserter.insert(OBJ_BLOB, e.getValue().build(noteUtil.getChangeNoteJson()));
- rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
+ rnm.noteMap.set(e.getKey(), data);
}
return rnm.noteMap.writeTree(inserter);
@@ -473,16 +461,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
// Even though reading from changes might not be enabled, we need to
// parse any existing revision notes so we can merge them.
return RevisionNoteMap.parse(
- noteUtil.getChangeNoteJson(),
- noteUtil.getLegacyChangeNoteRead(),
- getId(),
- rw.getObjectReader(),
- noteMap,
- PatchLineComment.Status.PUBLISHED);
+ noteUtil.getChangeNoteJson(), rw.getObjectReader(), noteMap, Comment.Status.PUBLISHED);
}
private void checkComments(
- Map<RevId, ChangeRevisionNote> existingNotes, Map<RevId, RevisionNoteBuilder> toUpdate) {
+ Map<ObjectId, ChangeRevisionNote> existingNotes,
+ Map<ObjectId, RevisionNoteBuilder> toUpdate) {
// Prohibit various kinds of illegal operations on comments.
Set<Comment.Key> existing = new HashSet<>();
for (ChangeRevisionNote rn : existingNotes.values()) {
@@ -504,7 +488,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
// separate commit. But note that we don't care much about the commit
// graph of the draft ref, particularly because the ref is completely
// deleted when all drafts are gone.
- draftUpdate.deleteComment(c.revId, c.key);
+ draftUpdate.deleteComment(c.getCommitId(), c.key);
}
}
}
@@ -524,6 +508,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
}
@Override
+ protected boolean bypassMaxUpdates() {
+ // Allow abandoning or submitting a change even if it would exceed the max update count.
+ return status != null && status.isClosed();
+ }
+
+ @Override
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins, ObjectId curr)
throws IOException {
checkState(
diff --git a/java/com/google/gerrit/server/notedb/DeleteChangeMessageRewriter.java b/java/com/google/gerrit/server/notedb/DeleteChangeMessageRewriter.java
index 8fb28e1bc9..b555fdbf29 100644
--- a/java/com/google/gerrit/server/notedb/DeleteChangeMessageRewriter.java
+++ b/java/com/google/gerrit/server/notedb/DeleteChangeMessageRewriter.java
@@ -15,13 +15,13 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.parseCommitMessageRange;
+import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.util.RawParseUtils.decode;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Optional;
@@ -46,7 +46,7 @@ public class DeleteChangeMessageRewriter implements NoteDbRewriter {
DeleteChangeMessageRewriter(Change.Id changeId, String targetMessageId, String newChangeMessage) {
this.changeId = changeId;
- this.targetMessageId = checkNotNull(targetMessageId);
+ this.targetMessageId = requireNonNull(targetMessageId);
this.newChangeMessage = newChangeMessage;
}
diff --git a/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java b/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
index c100550f90..9c8b369ee8 100644
--- a/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
+++ b/java/com/google/gerrit/server/notedb/DeleteCommentRewriter.java
@@ -15,16 +15,15 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
+import static com.google.gerrit.entities.Comment.Status;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.RefNames;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -96,13 +95,13 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
ObjectReader reader = revWalk.getObjectReader();
RevCommit newTipCommit = revWalk.next(); // The first commit will not be rewritten.
Map<String, Comment> parentComments =
- getPublishedComments(noteUtil, changeId, reader, NoteMap.read(reader, newTipCommit));
+ getPublishedComments(noteUtil, reader, NoteMap.read(reader, newTipCommit));
boolean rewrite = false;
RevCommit originalCommit;
while ((originalCommit = revWalk.next()) != null) {
NoteMap noteMap = NoteMap.read(reader, originalCommit);
- Map<String, Comment> currComments = getPublishedComments(noteUtil, changeId, reader, noteMap);
+ Map<String, Comment> currComments = getPublishedComments(noteUtil, reader, noteMap);
if (!rewrite && currComments.containsKey(uuid)) {
rewrite = true;
@@ -132,28 +131,18 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
*/
@VisibleForTesting
public static Map<String, Comment> getPublishedComments(
- ChangeNoteJson changeNoteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
- Change.Id changeId,
- ObjectReader reader,
- NoteMap noteMap)
+ ChangeNoteJson changeNoteJson, ObjectReader reader, NoteMap noteMap)
throws IOException, ConfigInvalidException {
- return RevisionNoteMap.parse(
- changeNoteJson, legacyChangeNoteRead, changeId, reader, noteMap, PUBLISHED)
- .revisionNotes.values().stream()
+ return RevisionNoteMap.parse(changeNoteJson, reader, noteMap, Status.PUBLISHED).revisionNotes
+ .values().stream()
.flatMap(n -> n.getEntities().stream())
.collect(toMap(c -> c.key.uuid, Function.identity()));
}
public static Map<String, Comment> getPublishedComments(
- ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap)
+ ChangeNoteUtil noteUtil, ObjectReader reader, NoteMap noteMap)
throws IOException, ConfigInvalidException {
- return getPublishedComments(
- noteUtil.getChangeNoteJson(),
- noteUtil.getLegacyChangeNoteRead(),
- changeId,
- reader,
- noteMap);
+ return getPublishedComments(noteUtil.getChangeNoteJson(), reader, noteMap);
}
/**
* Gets the comments put in by the current commit. The message of the target comment will be
@@ -216,24 +205,22 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
RevisionNoteMap<ChangeRevisionNote> revNotesMap =
RevisionNoteMap.parse(
noteUtil.getChangeNoteJson(),
- noteUtil.getLegacyChangeNoteRead(),
- changeId,
reader,
NoteMap.read(reader, parentCommit),
- PUBLISHED);
+ Status.PUBLISHED);
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
for (Comment c : putInComments) {
- cache.get(new RevId(c.revId)).putComment(c);
+ cache.get(c.getCommitId()).putComment(c);
}
for (Comment c : deletedComments) {
- cache.get(new RevId(c.revId)).deleteComment(c.key);
+ cache.get(c.getCommitId()).deleteComment(c.key);
}
- Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
- for (Map.Entry<RevId, RevisionNoteBuilder> entry : builders.entrySet()) {
- ObjectId objectId = ObjectId.fromString(entry.getKey().get());
+ Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
+ for (Map.Entry<ObjectId, RevisionNoteBuilder> entry : builders.entrySet()) {
+ ObjectId objectId = entry.getKey();
byte[] data = entry.getValue().build(noteUtil.getChangeNoteJson());
if (data.length == 0) {
revNotesMap.noteMap.remove(objectId);
diff --git a/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java
index 495ac659dd..128e185bb6 100644
--- a/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java
+++ b/java/com/google/gerrit/server/notedb/DeleteZombieCommentsRefs.java
@@ -18,8 +18,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 213613ec9f..3966396961 100644
--- a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static com.google.gerrit.entities.RefNames.refsDraftComments;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
@@ -24,12 +24,10 @@ import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Project;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
@@ -52,7 +50,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
private final Account.Id author;
private final Ref ref;
- private ImmutableListMultimap<RevId, Comment> comments;
+ private ImmutableListMultimap<ObjectId, Comment> comments;
private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
@AssistedInject
@@ -82,7 +80,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
return author;
}
- public ImmutableListMultimap<RevId, Comment> getComments() {
+ public ImmutableListMultimap<ObjectId, Comment> getComments() {
return comments;
}
@@ -122,16 +120,11 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
ObjectReader reader = handle.walk().getObjectReader();
revisionNoteMap =
RevisionNoteMap.parse(
- args.changeNoteJson,
- args.legacyChangeNoteRead,
- getChangeId(),
- reader,
- NoteMap.read(reader, tipCommit),
- PatchLineComment.Status.DRAFT);
- ListMultimap<RevId, Comment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
+ args.changeNoteJson, reader, NoteMap.read(reader, tipCommit), Comment.Status.DRAFT);
+ ListMultimap<ObjectId, Comment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
for (ChangeRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
for (Comment c : rn.getEntities()) {
- cs.put(new RevId(c.revId), c);
+ cs.put(c.getCommitId(), c);
}
}
comments = ImmutableListMultimap.copyOf(cs);
diff --git a/java/com/google/gerrit/server/notedb/IntBlob.java b/java/com/google/gerrit/server/notedb/IntBlob.java
index cbc933cbc7..5efc2b0c5a 100644
--- a/java/com/google/gerrit/server/notedb/IntBlob.java
+++ b/java/com/google/gerrit/server/notedb/IntBlob.java
@@ -23,9 +23,9 @@ import com.google.common.base.CharMatcher;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import java.io.IOException;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/notedb/InvalidServerIdException.java b/java/com/google/gerrit/server/notedb/InvalidServerIdException.java
new file mode 100644
index 0000000000..f79e07c8cd
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/InvalidServerIdException.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+public class InvalidServerIdException extends IllegalStateException {
+ private static final long serialVersionUID = 5302751510361680907L;
+
+ public InvalidServerIdException(String expectedServerId, String actualServerId) {
+ super(
+ String.format(
+ "invalid server id, expected %s: actual: %s", expectedServerId, actualServerId));
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java b/java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java
deleted file mode 100644
index 916cc16d79..0000000000
--- a/java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java
+++ /dev/null
@@ -1,401 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.notedb;
-
-import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.CommentRange;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.inject.Inject;
-import java.sql.Timestamp;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.util.GitDateParser;
-import org.eclipse.jgit.util.MutableInteger;
-import org.eclipse.jgit.util.QuotedString;
-import org.eclipse.jgit.util.RawParseUtils;
-
-public class LegacyChangeNoteRead {
- private final String serverId;
-
- @Inject
- public LegacyChangeNoteRead(@GerritServerId String serverId) {
- this.serverId = serverId;
- }
-
- public Account.Id parseIdent(PersonIdent ident, Change.Id changeId)
- throws ConfigInvalidException {
- return NoteDbUtil.parseIdent(ident, serverId)
- .orElseThrow(
- () ->
- parseException(
- changeId,
- "invalid identity, expected <id>@%s: %s",
- serverId,
- ident.getEmailAddress()));
- }
-
- private static boolean match(byte[] note, MutableInteger p, byte[] expected) {
- int m = RawParseUtils.match(note, p.value, expected);
- return m == p.value + expected.length;
- }
-
- public List<Comment> parseNote(byte[] note, MutableInteger p, Change.Id changeId)
- throws ConfigInvalidException {
- if (p.value >= note.length) {
- return ImmutableList.of();
- }
- Set<Comment.Key> seen = new HashSet<>();
- List<Comment> result = new ArrayList<>();
- int sizeOfNote = note.length;
- byte[] psb = ChangeNoteUtil.PATCH_SET.getBytes(UTF_8);
- byte[] bpsb = ChangeNoteUtil.BASE_PATCH_SET.getBytes(UTF_8);
- byte[] bpn = ChangeNoteUtil.PARENT_NUMBER.getBytes(UTF_8);
-
- RevId revId = new RevId(parseStringField(note, p, changeId, ChangeNoteUtil.REVISION));
- String fileName = null;
- PatchSet.Id psId = null;
- boolean isForBase = false;
- Integer parentNumber = null;
-
- while (p.value < sizeOfNote) {
- boolean matchPs = match(note, p, psb);
- boolean matchBase = match(note, p, bpsb);
- if (matchPs) {
- fileName = null;
- psId = parsePsId(note, p, changeId, ChangeNoteUtil.PATCH_SET);
- isForBase = false;
- } else if (matchBase) {
- fileName = null;
- psId = parsePsId(note, p, changeId, ChangeNoteUtil.BASE_PATCH_SET);
- isForBase = true;
- if (match(note, p, bpn)) {
- parentNumber = parseParentNumber(note, p, changeId);
- }
- } else if (psId == null) {
- throw parseException(
- changeId,
- "missing %s or %s header",
- ChangeNoteUtil.PATCH_SET,
- ChangeNoteUtil.BASE_PATCH_SET);
- }
-
- Comment c = parseComment(note, p, fileName, psId, revId, isForBase, parentNumber);
- fileName = c.key.filename;
- if (!seen.add(c.key)) {
- throw parseException(changeId, "multiple comments for %s in note", c.key);
- }
- result.add(c);
- }
- return result;
- }
-
- private Comment parseComment(
- byte[] note,
- MutableInteger curr,
- String currentFileName,
- PatchSet.Id psId,
- RevId revId,
- boolean isForBase,
- Integer parentNumber)
- throws ConfigInvalidException {
- Change.Id changeId = psId.getParentKey();
-
- // Check if there is a new file.
- boolean newFile =
- (RawParseUtils.match(note, curr.value, ChangeNoteUtil.FILE.getBytes(UTF_8))) != -1;
- if (newFile) {
- // If so, parse the new file name.
- currentFileName = parseFilename(note, curr, changeId);
- } else if (currentFileName == null) {
- throw parseException(changeId, "could not parse %s", ChangeNoteUtil.FILE);
- }
-
- CommentRange range = parseCommentRange(note, curr);
- if (range == null) {
- throw parseException(changeId, "could not parse %s", ChangeNoteUtil.COMMENT_RANGE);
- }
-
- Timestamp commentTime = parseTimestamp(note, curr, changeId);
- Account.Id aId = parseAuthor(note, curr, changeId, ChangeNoteUtil.AUTHOR);
- boolean hasRealAuthor =
- (RawParseUtils.match(note, curr.value, ChangeNoteUtil.REAL_AUTHOR.getBytes(UTF_8))) != -1;
- Account.Id raId = null;
- if (hasRealAuthor) {
- raId = parseAuthor(note, curr, changeId, ChangeNoteUtil.REAL_AUTHOR);
- }
-
- boolean hasParent =
- (RawParseUtils.match(note, curr.value, ChangeNoteUtil.PARENT.getBytes(UTF_8))) != -1;
- String parentUUID = null;
- boolean unresolved = false;
- if (hasParent) {
- parentUUID = parseStringField(note, curr, changeId, ChangeNoteUtil.PARENT);
- }
- boolean hasUnresolved =
- (RawParseUtils.match(note, curr.value, ChangeNoteUtil.UNRESOLVED.getBytes(UTF_8))) != -1;
- if (hasUnresolved) {
- unresolved = parseBooleanField(note, curr, changeId, ChangeNoteUtil.UNRESOLVED);
- }
-
- String uuid = parseStringField(note, curr, changeId, ChangeNoteUtil.UUID);
-
- boolean hasTag =
- (RawParseUtils.match(note, curr.value, ChangeNoteUtil.TAG.getBytes(UTF_8))) != -1;
- String tag = null;
- if (hasTag) {
- tag = parseStringField(note, curr, changeId, ChangeNoteUtil.TAG);
- }
-
- int commentLength = parseCommentLength(note, curr, changeId);
-
- String message = RawParseUtils.decode(UTF_8, note, curr.value, curr.value + commentLength);
- checkResult(message, "message contents", changeId);
-
- Comment c =
- new Comment(
- new Comment.Key(uuid, currentFileName, psId.get()),
- aId,
- commentTime,
- isForBase ? (short) (parentNumber == null ? 0 : -parentNumber) : (short) 1,
- message,
- serverId,
- unresolved);
- c.lineNbr = range.getEndLine();
- c.parentUuid = parentUUID;
- c.tag = tag;
- c.setRevId(revId);
- if (raId != null) {
- c.setRealAuthor(raId);
- }
-
- if (range.getStartCharacter() != -1) {
- c.setRange(range);
- }
-
- curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
- curr.value = RawParseUtils.nextLF(note, curr.value);
- return c;
- }
-
- private static String parseStringField(
- byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
- throws ConfigInvalidException {
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- checkHeaderLineFormat(note, curr, fieldName, changeId);
- int startOfField = RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
- curr.value = endOfLine;
- return RawParseUtils.decode(UTF_8, note, startOfField, endOfLine - 1);
- }
-
- /**
- * @return a comment range. If the comment range line in the note only has one number, we return a
- * CommentRange with that one number as the end line and the other fields as -1. If the
- * comment range line in the note contains a whole comment range, then we return a
- * CommentRange with all fields set. If the line is not correctly formatted, return null.
- */
- private static CommentRange parseCommentRange(byte[] note, MutableInteger ptr) {
- CommentRange range = new CommentRange(-1, -1, -1, -1);
-
- int last = ptr.value;
- int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
- if (ptr.value == last) {
- return null;
- } else if (note[ptr.value] == '\n') {
- range.setEndLine(startLine);
- ptr.value += 1;
- return range;
- } else if (note[ptr.value] == ':') {
- range.setStartLine(startLine);
- ptr.value += 1;
- } else {
- return null;
- }
-
- last = ptr.value;
- int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
- if (ptr.value == last) {
- return null;
- } else if (note[ptr.value] == '-') {
- range.setStartCharacter(startChar);
- ptr.value += 1;
- } else {
- return null;
- }
-
- last = ptr.value;
- int endLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
- if (ptr.value == last) {
- return null;
- } else if (note[ptr.value] == ':') {
- range.setEndLine(endLine);
- ptr.value += 1;
- } else {
- return null;
- }
-
- last = ptr.value;
- int endChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
- if (ptr.value == last) {
- return null;
- } else if (note[ptr.value] == '\n') {
- range.setEndCharacter(endChar);
- ptr.value += 1;
- } else {
- return null;
- }
- return range;
- }
-
- private static PatchSet.Id parsePsId(
- byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
- throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, fieldName, changeId);
- int startOfPsId = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
- MutableInteger i = new MutableInteger();
- int patchSetId = RawParseUtils.parseBase10(note, startOfPsId, i);
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- if (i.value != endOfLine - 1) {
- throw parseException(changeId, "could not parse %s", fieldName);
- }
- checkResult(patchSetId, "patchset id", changeId);
- curr.value = endOfLine;
- return new PatchSet.Id(changeId, patchSetId);
- }
-
- private static Integer parseParentNumber(byte[] note, MutableInteger curr, Change.Id changeId)
- throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, ChangeNoteUtil.PARENT_NUMBER, changeId);
-
- int start = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
- MutableInteger i = new MutableInteger();
- int parentNumber = RawParseUtils.parseBase10(note, start, i);
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- if (i.value != endOfLine - 1) {
- throw parseException(changeId, "could not parse %s", ChangeNoteUtil.PARENT_NUMBER);
- }
- checkResult(parentNumber, "parent number", changeId);
- curr.value = endOfLine;
- return Integer.valueOf(parentNumber);
- }
-
- private static String parseFilename(byte[] note, MutableInteger curr, Change.Id changeId)
- throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, ChangeNoteUtil.FILE, changeId);
- int startOfFileName = RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- curr.value = endOfLine;
- curr.value = RawParseUtils.nextLF(note, curr.value);
- return QuotedString.GIT_PATH.dequote(
- RawParseUtils.decode(UTF_8, note, startOfFileName, endOfLine - 1));
- }
-
- private static Timestamp parseTimestamp(byte[] note, MutableInteger curr, Change.Id changeId)
- throws ConfigInvalidException {
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- Timestamp commentTime;
- String dateString = RawParseUtils.decode(UTF_8, note, curr.value, endOfLine - 1);
- try {
- commentTime = new Timestamp(GitDateParser.parse(dateString, null, Locale.US).getTime());
- } catch (ParseException e) {
- throw new ConfigInvalidException("could not parse comment timestamp", e);
- }
- curr.value = endOfLine;
- return checkResult(commentTime, "comment timestamp", changeId);
- }
-
- private Account.Id parseAuthor(
- byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
- throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, fieldName, changeId);
- int startOfAccountId = RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
- PersonIdent ident = RawParseUtils.parsePersonIdent(note, startOfAccountId);
- Account.Id aId = parseIdent(ident, changeId);
- curr.value = RawParseUtils.nextLF(note, curr.value);
- return checkResult(aId, fieldName, changeId);
- }
-
- private static int parseCommentLength(byte[] note, MutableInteger curr, Change.Id changeId)
- throws ConfigInvalidException {
- checkHeaderLineFormat(note, curr, ChangeNoteUtil.LENGTH, changeId);
- int startOfLength = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
- MutableInteger i = new MutableInteger();
- i.value = startOfLength;
- int commentLength = RawParseUtils.parseBase10(note, startOfLength, i);
- if (i.value == startOfLength) {
- throw parseException(changeId, "could not parse %s", ChangeNoteUtil.LENGTH);
- }
- int endOfLine = RawParseUtils.nextLF(note, curr.value);
- if (i.value != endOfLine - 1) {
- throw parseException(changeId, "could not parse %s", ChangeNoteUtil.LENGTH);
- }
- curr.value = endOfLine;
- return checkResult(commentLength, "comment length", changeId);
- }
-
- private boolean parseBooleanField(
- byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
- throws ConfigInvalidException {
- String str = parseStringField(note, curr, changeId, fieldName);
- if ("true".equalsIgnoreCase(str)) {
- return true;
- } else if ("false".equalsIgnoreCase(str)) {
- return false;
- }
- throw parseException(changeId, "invalid boolean for %s: %s", fieldName, str);
- }
-
- private static <T> T checkResult(T o, String fieldName, Change.Id changeId)
- throws ConfigInvalidException {
- if (o == null) {
- throw parseException(changeId, "could not parse %s", fieldName);
- }
- return o;
- }
-
- private static int checkResult(int i, String fieldName, Change.Id changeId)
- throws ConfigInvalidException {
- if (i <= 0) {
- throw parseException(changeId, "could not parse %s", fieldName);
- }
- return i;
- }
-
- private static void checkHeaderLineFormat(
- byte[] note, MutableInteger curr, String fieldName, Change.Id changeId)
- throws ConfigInvalidException {
- boolean correct = RawParseUtils.match(note, curr.value, fieldName.getBytes(UTF_8)) != -1;
- int p = curr.value + fieldName.length();
- correct &= (p < note.length && note[p] == ':');
- p++;
- correct &= (p < note.length && note[p] == ' ');
- if (!correct) {
- throw parseException(changeId, "could not parse %s", fieldName);
- }
- }
-}
diff --git a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java b/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
deleted file mode 100644
index 2d5293f0e2..0000000000
--- a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.notedb;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.server.CommentsUtil.COMMENT_ORDER;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ListMultimap;
-import com.google.gerrit.common.UsedAt;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.inject.Inject;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.sql.Timestamp;
-import java.util.Date;
-import java.util.List;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.util.QuotedString;
-
-public class LegacyChangeNoteWrite {
-
- private final PersonIdent serverIdent;
- private final String serverId;
-
- @Inject
- public LegacyChangeNoteWrite(
- @GerritPersonIdent PersonIdent serverIdent, @GerritServerId String serverId) {
- this.serverIdent = serverIdent;
- this.serverId = serverId;
- }
-
- public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) {
- return new PersonIdent(
- authorId.toString(), authorId.get() + "@" + serverId, when, serverIdent.getTimeZone());
- }
-
- public String getServerId() {
- return serverId;
- }
-
- private void appendHeaderField(PrintWriter writer, String field, String value) {
- writer.print(field);
- writer.print(": ");
- writer.print(value);
- writer.print('\n');
- }
-
- /**
- * Build a note that contains the metadata for and the contents of all of the comments in the
- * given comments.
- *
- * @param comments Comments to be written to the output stream, keyed by patch set ID; multiple
- * patch sets are allowed since base revisions may be shared across patch sets. All of the
- * comments must share the same RevId, and all the comments for a given patch set must have
- * the same side.
- * @param out output stream to write to.
- */
- @UsedAt(UsedAt.Project.GOOGLE)
- public void buildNote(ListMultimap<Integer, Comment> comments, OutputStream out) {
- if (comments.isEmpty()) {
- return;
- }
-
- ImmutableList<Integer> psIds = comments.keySet().stream().sorted().collect(toImmutableList());
-
- OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
- try (PrintWriter writer = new PrintWriter(streamWriter)) {
- String revId = comments.values().iterator().next().revId;
- appendHeaderField(writer, ChangeNoteUtil.REVISION, revId);
-
- for (int psId : psIds) {
- List<Comment> psComments = COMMENT_ORDER.sortedCopy(comments.get(psId));
- Comment first = psComments.get(0);
-
- short side = first.side;
- appendHeaderField(
- writer,
- side <= 0 ? ChangeNoteUtil.BASE_PATCH_SET : ChangeNoteUtil.PATCH_SET,
- Integer.toString(psId));
- if (side < 0) {
- appendHeaderField(writer, ChangeNoteUtil.PARENT_NUMBER, Integer.toString(-side));
- }
-
- String currentFilename = null;
-
- for (Comment c : psComments) {
- checkArgument(
- revId.equals(c.revId),
- "All comments being added must have all the same RevId. The "
- + "comment below does not have the same RevId as the others "
- + "(%s).\n%s",
- revId,
- c);
- checkArgument(
- side == c.side,
- "All comments being added must all have the same side. The "
- + "comment below does not have the same side as the others "
- + "(%s).\n%s",
- side,
- c);
- String commentFilename = QuotedString.GIT_PATH.quote(c.key.filename);
-
- if (!commentFilename.equals(currentFilename)) {
- currentFilename = commentFilename;
- writer.print("File: ");
- writer.print(commentFilename);
- writer.print("\n\n");
- }
-
- appendOneComment(writer, c);
- }
- }
- }
- }
-
- private void appendOneComment(PrintWriter writer, Comment c) {
- // The CommentRange field for a comment is allowed to be null. If it is
- // null, then in the first line, we simply use the line number field for a
- // comment instead. If it isn't null, we write the comment range itself.
- Comment.Range range = c.range;
- if (range != null) {
- writer.print(range.startLine);
- writer.print(':');
- writer.print(range.startChar);
- writer.print('-');
- writer.print(range.endLine);
- writer.print(':');
- writer.print(range.endChar);
- } else {
- writer.print(c.lineNbr);
- }
- writer.print("\n");
-
- writer.print(NoteDbUtil.formatTime(serverIdent, c.writtenOn));
- writer.print("\n");
-
- appendIdent(writer, ChangeNoteUtil.AUTHOR, c.author.getId(), c.writtenOn);
- if (!c.getRealAuthor().equals(c.author)) {
- appendIdent(writer, ChangeNoteUtil.REAL_AUTHOR, c.getRealAuthor().getId(), c.writtenOn);
- }
-
- String parent = c.parentUuid;
- if (parent != null) {
- appendHeaderField(writer, ChangeNoteUtil.PARENT, parent);
- }
-
- appendHeaderField(writer, ChangeNoteUtil.UNRESOLVED, Boolean.toString(c.unresolved));
- appendHeaderField(writer, ChangeNoteUtil.UUID, c.key.uuid);
-
- if (c.tag != null) {
- appendHeaderField(writer, ChangeNoteUtil.TAG, c.tag);
- }
-
- byte[] messageBytes = c.message.getBytes(UTF_8);
- appendHeaderField(writer, ChangeNoteUtil.LENGTH, Integer.toString(messageBytes.length));
-
- writer.print(c.message);
- writer.print("\n\n");
- }
-
- private void appendIdent(PrintWriter writer, String header, Account.Id id, Timestamp ts) {
- PersonIdent ident = newIdent(id, ts, serverIdent);
- StringBuilder name = new StringBuilder();
- PersonIdent.appendSanitized(name, ident.getName());
- name.append(" <");
- PersonIdent.appendSanitized(name, ident.getEmailAddress());
- name.append('>');
- appendHeaderField(writer, header, name.toString());
- }
-}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
index 61f475f3a9..18ffd178f9 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
@@ -19,6 +19,7 @@ import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -45,7 +46,8 @@ class NoteDbMetrics {
@Inject
NoteDbMetrics(MetricMaker metrics) {
- Field<NoteDbTable> view = Field.ofEnum(NoteDbTable.class, "table");
+ Field<NoteDbTable> tableField =
+ Field.ofEnum(NoteDbTable.class, "table", Metadata.Builder::noteDbTable).build();
updateLatency =
metrics.newTimer(
@@ -53,7 +55,7 @@ class NoteDbMetrics {
new Description("NoteDb update latency by table")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- view);
+ tableField);
stageUpdateLatency =
metrics.newTimer(
@@ -61,7 +63,7 @@ class NoteDbMetrics {
new Description("Latency for staging updates to NoteDb by table")
.setCumulative()
.setUnit(Units.MICROSECONDS),
- view);
+ tableField);
readLatency =
metrics.newTimer(
@@ -69,7 +71,7 @@ class NoteDbMetrics {
new Description("NoteDb read latency by table")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- view);
+ tableField);
parseLatency =
metrics.newTimer(
@@ -77,6 +79,6 @@ class NoteDbMetrics {
new Description("NoteDb parse latency by table")
.setCumulative()
.setUnit(Units.MICROSECONDS),
- view);
+ tableField);
}
}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index ea42a9dcd5..7022cdc29a 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -18,27 +18,21 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
-import static java.util.Objects.requireNonNull;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.git.RefUpdateUtil;
import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.InMemoryInserter;
-import com.google.gerrit.server.git.InsertedObject;
import com.google.gerrit.server.update.ChainedReceiveCommands;
-import com.google.gerrit.server.update.RetryingRestModifyView;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@@ -50,9 +44,9 @@ import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -70,85 +64,16 @@ import org.eclipse.jgit.transport.ReceiveCommand;
* {@link #stage()}.
*/
public class NoteDbUpdateManager implements AutoCloseable {
- private static final ImmutableList<String> PACKAGE_PREFIXES =
- ImmutableList.of("com.google.gerrit.server.", "com.google.gerrit.");
- private static final ImmutableSet<String> SERVLET_NAMES =
- ImmutableSet.of(
- "com.google.gerrit.httpd.restapi.RestApiServlet", RetryingRestModifyView.class.getName());
-
public interface Factory {
NoteDbUpdateManager create(Project.NameKey projectName);
}
- public static class OpenRepo implements AutoCloseable {
- public final Repository repo;
- public final RevWalk rw;
- public final ChainedReceiveCommands cmds;
-
- private final InMemoryInserter inMemIns;
- private final ObjectInserter tempIns;
- @Nullable private final ObjectInserter finalIns;
-
- private final boolean close;
-
- private OpenRepo(
- Repository repo,
- RevWalk rw,
- @Nullable ObjectInserter ins,
- ChainedReceiveCommands cmds,
- boolean close) {
- ObjectReader reader = rw.getObjectReader();
- checkArgument(
- ins == null || reader.getCreatedFromInserter() == ins,
- "expected reader to be created from %s, but was %s",
- ins,
- reader.getCreatedFromInserter());
- this.repo = requireNonNull(repo);
-
- this.inMemIns = new InMemoryInserter(rw.getObjectReader());
- this.tempIns = inMemIns;
-
- this.rw = new RevWalk(tempIns.newReader());
- this.finalIns = ins;
- this.cmds = requireNonNull(cmds);
- this.close = close;
- }
-
- public Optional<ObjectId> getObjectId(String refName) throws IOException {
- return cmds.get(refName);
- }
-
- void flush() throws IOException {
- flushToFinalInserter();
- finalIns.flush();
- }
-
- void flushToFinalInserter() throws IOException {
- checkState(finalIns != null);
- for (InsertedObject obj : inMemIns.getInsertedObjects()) {
- finalIns.insert(obj.type(), obj.data().toByteArray());
- }
- inMemIns.clear();
- }
-
- @Override
- public void close() {
- rw.getObjectReader().close();
- rw.close();
- if (close) {
- if (finalIns != null) {
- finalIns.close();
- }
- repo.close();
- }
- }
- }
-
private final Provider<PersonIdent> serverIdent;
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final NoteDbMetrics metrics;
private final Project.NameKey projectName;
+ private final int maxUpdates;
private final ListMultimap<String, ChangeUpdate> changeUpdates;
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
private final ListMultimap<String, RobotCommentUpdate> robotCommentUpdates;
@@ -157,6 +82,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
private OpenRepo changeRepo;
private OpenRepo allUsersRepo;
+ private AllUsersAsyncUpdate updateAllUsersAsync;
private boolean executed;
private String refLogMessage;
private PersonIdent refLogIdent;
@@ -164,16 +90,20 @@ public class NoteDbUpdateManager implements AutoCloseable {
@Inject
NoteDbUpdateManager(
+ @GerritServerConfig Config cfg,
@GerritPersonIdent Provider<PersonIdent> serverIdent,
GitRepositoryManager repoManager,
AllUsersName allUsersName,
NoteDbMetrics metrics,
+ AllUsersAsyncUpdate updateAllUsersAsync,
@Assisted Project.NameKey projectName) {
this.serverIdent = serverIdent;
this.repoManager = repoManager;
this.allUsersName = allUsersName;
this.metrics = metrics;
+ this.updateAllUsersAsync = updateAllUsersAsync;
this.projectName = projectName;
+ maxUpdates = cfg.getInt("change", null, "maxUpdates", 1000);
changeUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
robotCommentUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
@@ -236,28 +166,13 @@ public class NoteDbUpdateManager implements AutoCloseable {
private void initChangeRepo() throws IOException {
if (changeRepo == null) {
- changeRepo = openRepo(projectName);
+ changeRepo = OpenRepo.open(repoManager, projectName);
}
}
private void initAllUsersRepo() throws IOException {
if (allUsersRepo == null) {
- allUsersRepo = openRepo(allUsersName);
- }
- }
-
- private OpenRepo openRepo(Project.NameKey p) throws IOException {
- Repository repo = repoManager.openRepository(p); // Closed by OpenRepo#close.
- ObjectInserter ins = repo.newObjectInserter(); // Closed by OpenRepo#close.
- ObjectReader reader = ins.newReader(); // Not closed by OpenRepo#close.
- try (RevWalk rw = new RevWalk(reader)) { // Doesn't escape OpenRepo constructor.
- return new OpenRepo(repo, rw, ins, new ChainedReceiveCommands(repo), true) {
- @Override
- public void close() {
- reader.close();
- super.close();
- }
- };
+ allUsersRepo = OpenRepo.open(repoManager, allUsersName);
}
}
@@ -268,7 +183,8 @@ public class NoteDbUpdateManager implements AutoCloseable {
&& rewriters.isEmpty()
&& toDelete.isEmpty()
&& !hasCommands(changeRepo)
- && !hasCommands(allUsersRepo);
+ && !hasCommands(allUsersRepo)
+ && updateAllUsersAsync.isEmpty();
}
private static boolean hasCommands(@Nullable OpenRepo or) {
@@ -351,7 +267,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
* @throws IOException if a storage layer error occurs.
*/
private void stage() throws IOException {
- try (Timer1.Context timer = metrics.stageUpdateLatency.start(CHANGES)) {
+ try (Timer1.Context<NoteDbTable> timer = metrics.stageUpdateLatency.start(CHANGES)) {
if (isEmpty()) {
return;
}
@@ -364,16 +280,6 @@ public class NoteDbUpdateManager implements AutoCloseable {
}
}
- public void flush() throws IOException {
- checkNotExecuted();
- if (changeRepo != null) {
- changeRepo.flush();
- }
- if (allUsersRepo != null) {
- allUsersRepo.flush();
- }
- }
-
@Nullable
public BatchRefUpdate execute() throws IOException {
return execute(false);
@@ -386,7 +292,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
executed = true;
return null;
}
- try (Timer1.Context timer = metrics.updateLatency.start(CHANGES)) {
+ try (Timer1.Context<NoteDbTable> timer = metrics.updateLatency.start(CHANGES)) {
stage();
// ChangeUpdates must execute before ChangeDraftUpdates.
//
@@ -398,6 +304,13 @@ public class NoteDbUpdateManager implements AutoCloseable {
// comments can only go from DRAFT to PUBLISHED, not vice versa.
BatchRefUpdate result = execute(changeRepo, dryrun, pushCert);
execute(allUsersRepo, dryrun, null);
+ if (!dryrun) {
+ // Only execute the asynchronous operation if we are not in dry-run mode: The dry run would
+ // have to run synchronous to be of any value at all. For the removal of draft comments from
+ // All-Users we don't care much of the operation succeeds, so we are skipping the dry run
+ // altogether.
+ updateAllUsersAsync.execute(refLogIdent, refLogMessage, pushCert);
+ }
executed = true;
return result;
} finally {
@@ -423,7 +336,8 @@ public class NoteDbUpdateManager implements AutoCloseable {
if (refLogMessage != null) {
bru.setRefLogMessage(refLogMessage, false);
} else {
- bru.setRefLogMessage(firstNonNull(guessRestApiHandler(), "Update NoteDb refs"), false);
+ bru.setRefLogMessage(
+ firstNonNull(NoteDbUtil.guessRestApiHandler(), "Update NoteDb refs"), false);
}
bru.setRefLogIdent(refLogIdent != null ? refLogIdent : serverIdent.get());
bru.setAtomic(true);
@@ -436,59 +350,18 @@ public class NoteDbUpdateManager implements AutoCloseable {
return bru;
}
- private static String guessRestApiHandler() {
- StackTraceElement[] trace = Thread.currentThread().getStackTrace();
- int i = findRestApiServlet(trace);
- if (i < 0) {
- return null;
- }
- try {
- for (i--; i >= 0; i--) {
- String cn = trace[i].getClassName();
- Class<?> cls = Class.forName(cn);
- if (RestModifyView.class.isAssignableFrom(cls) && cls != RetryingRestModifyView.class) {
- return viewName(cn);
- }
- }
- return null;
- } catch (ClassNotFoundException e) {
- return null;
- }
- }
-
- private static String viewName(String cn) {
- String impl = cn.replace('$', '.');
- for (String p : PACKAGE_PREFIXES) {
- if (impl.startsWith(p)) {
- return impl.substring(p.length());
- }
- }
- return impl;
- }
-
- private static int findRestApiServlet(StackTraceElement[] trace) {
- for (int i = 0; i < trace.length; i++) {
- if (SERVLET_NAMES.contains(trace[i].getClassName())) {
- return i;
- }
- }
- return -1;
- }
-
private void addCommands() throws IOException {
- if (isEmpty()) {
- return;
- }
- checkState(changeRepo != null, "must set change repo");
- if (!draftUpdates.isEmpty()) {
- checkState(allUsersRepo != null, "must set all users repo");
- }
- addUpdates(changeUpdates, changeRepo);
+ changeRepo.addUpdates(changeUpdates, Optional.of(maxUpdates));
if (!draftUpdates.isEmpty()) {
- addUpdates(draftUpdates, allUsersRepo);
+ boolean publishOnly = draftUpdates.values().stream().allMatch(ChangeDraftUpdate::canRunAsync);
+ if (publishOnly) {
+ updateAllUsersAsync.setDraftUpdates(draftUpdates);
+ } else {
+ allUsersRepo.addUpdates(draftUpdates);
+ }
}
if (!robotCommentUpdates.isEmpty()) {
- addUpdates(robotCommentUpdates, changeRepo);
+ changeRepo.addUpdates(robotCommentUpdates);
}
if (!rewriters.isEmpty()) {
addRewrites(rewriters, changeRepo);
@@ -520,36 +393,6 @@ public class NoteDbUpdateManager implements AutoCloseable {
checkState(!executed, "update has already been executed");
}
- private static <U extends AbstractChangeUpdate> void addUpdates(
- ListMultimap<String, U> all, OpenRepo or) throws IOException {
- for (Map.Entry<String, Collection<U>> e : all.asMap().entrySet()) {
- String refName = e.getKey();
- Collection<U> updates = e.getValue();
- ObjectId old = or.cmds.get(refName).orElse(ObjectId.zeroId());
- // Only actually write to the ref if one of the updates explicitly allows
- // us to do so, i.e. it is known to represent a new change. This avoids
- // writing partial change meta if the change hasn't been backfilled yet.
- if (!allowWrite(updates, old)) {
- continue;
- }
-
- ObjectId curr = old;
- for (U u : updates) {
- if (u.isRootOnly() && !old.equals(ObjectId.zeroId())) {
- throw new StorageException("Given ChangeUpdate is only allowed on initial commit");
- }
- ObjectId next = u.apply(or.rw, or.tempIns, curr);
- if (next == null) {
- continue;
- }
- curr = next;
- }
- if (!old.equals(curr)) {
- or.cmds.add(new ReceiveCommand(old, curr, refName));
- }
- }
- }
-
private static void addRewrites(ListMultimap<String, NoteDbRewriter> rewriters, OpenRepo openRepo)
throws IOException {
for (Map.Entry<String, Collection<NoteDbRewriter>> entry : rewriters.asMap().entrySet()) {
@@ -578,12 +421,4 @@ public class NoteDbUpdateManager implements AutoCloseable {
}
}
}
-
- private static <U extends AbstractChangeUpdate> boolean allowWrite(
- Collection<U> updates, ObjectId old) {
- if (!old.equals(ObjectId.zeroId())) {
- return true;
- }
- return updates.iterator().next().allowWriteToNewRef();
- }
}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUtil.java b/java/com/google/gerrit/server/notedb/NoteDbUtil.java
index 667ceaba4d..58a33c8501 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUtil.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUtil.java
@@ -15,8 +15,12 @@
package com.google.gerrit.server.notedb;
import com.google.common.base.CharMatcher;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.update.RetryingRestModifyView;
import java.sql.Timestamp;
import java.util.Optional;
import org.eclipse.jgit.lib.PersonIdent;
@@ -25,26 +29,35 @@ import org.eclipse.jgit.util.GitDateFormatter.Format;
public class NoteDbUtil {
- /**
- * Returns an AccountId for the given email address. Returns empty if the address isn't on this
- * server.
- */
- public static Optional<Account.Id> parseIdent(PersonIdent ident, String serverId) {
+ private static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0");
+
+ private static final ImmutableList<String> PACKAGE_PREFIXES =
+ ImmutableList.of("com.google.gerrit.server.", "com.google.gerrit.");
+ private static final ImmutableSet<String> SERVLET_NAMES =
+ ImmutableSet.of(
+ "com.google.gerrit.httpd.restapi.RestApiServlet", RetryingRestModifyView.class.getName());
+
+ /** Returns an AccountId for the given email address. */
+ public static Optional<Account.Id> parseIdent(PersonIdent ident) {
String email = ident.getEmailAddress();
int at = email.indexOf('@');
if (at >= 0) {
- String host = email.substring(at + 1);
- if (host.equals(serverId)) {
- Integer id = Ints.tryParse(email.substring(0, at));
- if (id != null) {
- return Optional.of(new Account.Id(id));
- }
+ Integer id = Ints.tryParse(email.substring(0, at));
+ if (id != null) {
+ return Optional.of(Account.id(id));
}
}
return Optional.empty();
}
- private NoteDbUtil() {}
+ public static String extractHostPartFromPersonIdent(PersonIdent ident) {
+ String email = ident.getEmailAddress();
+ int at = email.indexOf('@');
+ if (at >= 0) {
+ return email.substring(at + 1);
+ }
+ throw new IllegalArgumentException("No host part found: " + email);
+ }
public static String formatTime(PersonIdent ident, Timestamp t) {
GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT);
@@ -53,7 +66,29 @@ public class NoteDbUtil {
return dateFormatter.formatDate(newIdent);
}
- private static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0");
+ /**
+ * Returns the name of the REST API handler that is in the stack trace of the caller of this
+ * method.
+ */
+ static String guessRestApiHandler() {
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ int i = findRestApiServlet(trace);
+ if (i < 0) {
+ return null;
+ }
+ try {
+ for (i--; i >= 0; i--) {
+ String cn = trace[i].getClassName();
+ Class<?> cls = Class.forName(cn);
+ if (RestModifyView.class.isAssignableFrom(cls) && cls != RetryingRestModifyView.class) {
+ return viewName(cn);
+ }
+ }
+ return null;
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
static String sanitizeFooter(String value) {
// Remove characters that would confuse JGit's footer parser if they were
@@ -65,4 +100,25 @@ public class NoteDbUtil {
// empty paragraph for the purposes of footer parsing.
return INVALID_FOOTER_CHARS.trimAndCollapseFrom(value, ' ');
}
+
+ private static int findRestApiServlet(StackTraceElement[] trace) {
+ for (int i = 0; i < trace.length; i++) {
+ if (SERVLET_NAMES.contains(trace[i].getClassName())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static String viewName(String cn) {
+ String impl = cn.replace('$', '.');
+ for (String p : PACKAGE_PREFIXES) {
+ if (impl.startsWith(p)) {
+ return impl.substring(p.length());
+ }
+ }
+ return impl;
+ }
+
+ private NoteDbUtil() {}
}
diff --git a/java/com/google/gerrit/server/notedb/OpenRepo.java b/java/com/google/gerrit/server/notedb/OpenRepo.java
new file mode 100644
index 0000000000..de88684610
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/OpenRepo.java
@@ -0,0 +1,176 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ListMultimap;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.InMemoryInserter;
+import com.google.gerrit.server.git.InsertedObject;
+import com.google.gerrit.server.update.ChainedReceiveCommands;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * Wrapper around {@link Repository} that keeps track of related {@link ObjectInserter}s and other
+ * objects that are jointly closed when invoking {@link #close}.
+ */
+class OpenRepo implements AutoCloseable {
+ /** Returns a {@link OpenRepo} wrapping around an open {@link Repository}. */
+ static OpenRepo open(GitRepositoryManager repoManager, Project.NameKey project)
+ throws IOException {
+ Repository repo = repoManager.openRepository(project); // Closed by OpenRepo#close.
+ ObjectInserter ins = repo.newObjectInserter(); // Closed by OpenRepo#close.
+ ObjectReader reader = ins.newReader(); // Not closed by OpenRepo#close.
+ try (RevWalk rw = new RevWalk(reader)) { // Doesn't escape OpenRepo constructor.
+ return new OpenRepo(repo, rw, ins, new ChainedReceiveCommands(repo), true) {
+ @Override
+ public void close() {
+ reader.close();
+ super.close();
+ }
+ };
+ }
+ }
+
+ final Repository repo;
+ final RevWalk rw;
+ final ChainedReceiveCommands cmds;
+ final ObjectInserter tempIns;
+
+ private final InMemoryInserter inMemIns;
+ @Nullable private final ObjectInserter finalIns;
+ private final boolean close;
+
+ OpenRepo(
+ Repository repo,
+ RevWalk rw,
+ @Nullable ObjectInserter ins,
+ ChainedReceiveCommands cmds,
+ boolean close) {
+ ObjectReader reader = rw.getObjectReader();
+ checkArgument(
+ ins == null || reader.getCreatedFromInserter() == ins,
+ "expected reader to be created from %s, but was %s",
+ ins,
+ reader.getCreatedFromInserter());
+ this.repo = requireNonNull(repo);
+
+ this.inMemIns = new InMemoryInserter(rw.getObjectReader());
+ this.tempIns = inMemIns;
+
+ this.rw = new RevWalk(tempIns.newReader());
+ this.finalIns = ins;
+ this.cmds = requireNonNull(cmds);
+ this.close = close;
+ }
+
+ @Override
+ public void close() {
+ rw.getObjectReader().close();
+ rw.close();
+ if (close) {
+ if (finalIns != null) {
+ finalIns.close();
+ }
+ repo.close();
+ }
+ }
+
+ void flush() throws IOException {
+ flushToFinalInserter();
+ finalIns.flush();
+ }
+
+ void flushToFinalInserter() throws IOException {
+ checkState(finalIns != null);
+ for (InsertedObject obj : inMemIns.getInsertedObjects()) {
+ finalIns.insert(obj.type(), obj.data().toByteArray());
+ }
+ inMemIns.clear();
+ }
+
+ private static <U extends AbstractChangeUpdate> boolean allowWrite(
+ Collection<U> updates, ObjectId old) {
+ if (!old.equals(ObjectId.zeroId())) {
+ return true;
+ }
+ return updates.iterator().next().allowWriteToNewRef();
+ }
+
+ <U extends AbstractChangeUpdate> void addUpdates(ListMultimap<String, U> all) throws IOException {
+ addUpdates(all, Optional.empty());
+ }
+
+ <U extends AbstractChangeUpdate> void addUpdates(
+ ListMultimap<String, U> all, Optional<Integer> maxUpdates) throws IOException {
+ for (Map.Entry<String, Collection<U>> e : all.asMap().entrySet()) {
+ String refName = e.getKey();
+ Collection<U> updates = e.getValue();
+ ObjectId old = cmds.get(refName).orElse(ObjectId.zeroId());
+ // Only actually write to the ref if one of the updates explicitly allows
+ // us to do so, i.e. it is known to represent a new change. This avoids
+ // writing partial change meta if the change hasn't been backfilled yet.
+ if (!allowWrite(updates, old)) {
+ continue;
+ }
+
+ int updateCount;
+ U first = updates.iterator().next();
+ if (maxUpdates.isPresent()) {
+ checkState(first.getNotes() != null, "expected ChangeNotes on %s", first);
+ updateCount = first.getNotes().getUpdateCount();
+ } else {
+ updateCount = 0;
+ }
+
+ ObjectId curr = old;
+ for (U u : updates) {
+ if (u.isRootOnly() && !old.equals(ObjectId.zeroId())) {
+ throw new StorageException("Given ChangeUpdate is only allowed on initial commit");
+ }
+ ObjectId next = u.apply(rw, tempIns, curr);
+ if (next == null) {
+ continue;
+ }
+ if (maxUpdates.isPresent()
+ && !Objects.equals(next, curr)
+ && ++updateCount > maxUpdates.get()
+ && !u.bypassMaxUpdates()) {
+ throw new TooManyUpdatesException(u.getId(), maxUpdates.get());
+ }
+ curr = next;
+ }
+ if (!old.equals(curr)) {
+ cmds.add(new ReceiveCommand(old, curr, refName));
+ }
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/RepoSequence.java b/java/com/google/gerrit/server/notedb/RepoSequence.java
index 68b2b95399..e52b84c148 100644
--- a/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_SEQUENCES;
+import static com.google.gerrit.entities.RefNames.REFS;
+import static com.google.gerrit.entities.RefNames.REFS_SEQUENCES;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -29,19 +29,20 @@ import com.github.rholder.retry.WaitStrategies;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Runnables;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@@ -72,9 +73,12 @@ public class RepoSequence {
}
@VisibleForTesting
- static RetryerBuilder<RefUpdate> retryerBuilder() {
- return RetryerBuilder.<RefUpdate>newBuilder()
- .retryIfResult(ru -> ru != null && RefUpdate.Result.LOCK_FAILURE.equals(ru.getResult()))
+ static RetryerBuilder<ImmutableList<Integer>> retryerBuilder() {
+ return RetryerBuilder.<ImmutableList<Integer>>newBuilder()
+ .retryIfException(
+ t ->
+ t instanceof StorageException
+ && ((StorageException) t).getCause() instanceof LockFailureException)
.withWaitStrategy(
WaitStrategies.join(
WaitStrategies.exponentialWait(5, TimeUnit.SECONDS),
@@ -82,7 +86,7 @@ public class RepoSequence {
.withStopStrategy(StopStrategies.stopAfterDelay(30, TimeUnit.SECONDS));
}
- private static final Retryer<RefUpdate> RETRYER = retryerBuilder().build();
+ private static final Retryer<ImmutableList<Integer>> RETRYER = retryerBuilder().build();
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated;
@@ -92,7 +96,7 @@ public class RepoSequence {
private final int floor;
private final int batchSize;
private final Runnable afterReadRef;
- private final Retryer<RefUpdate> retryer;
+ private final Retryer<ImmutableList<Integer>> retryer;
// Protects all non-final fields.
private final Lock counterLock;
@@ -150,7 +154,7 @@ public class RepoSequence {
Seed seed,
int batchSize,
Runnable afterReadRef,
- Retryer<RefUpdate> retryer) {
+ Retryer<ImmutableList<Integer>> retryer) {
this(repoManager, gitRefUpdated, projectName, name, seed, batchSize, afterReadRef, retryer, 0);
}
@@ -162,7 +166,7 @@ public class RepoSequence {
Seed seed,
int batchSize,
Runnable afterReadRef,
- Retryer<RefUpdate> retryer,
+ Retryer<ImmutableList<Integer>> retryer,
int floor) {
this.repoManager = requireNonNull(repoManager, "repoManager");
this.gitRefUpdated = requireNonNull(gitRefUpdated, "gitRefUpdated");
@@ -188,79 +192,86 @@ public class RepoSequence {
counterLock = new ReentrantLock(true);
}
+ /**
+ * Retrieves the next available sequence number.
+ *
+ * <p>This method is thread-safe.
+ *
+ * @return the next available sequence number
+ */
public int next() {
- counterLock.lock();
- try {
- if (counter >= limit) {
- acquire(batchSize);
- }
- return counter++;
- } finally {
- counterLock.unlock();
- }
+ return Iterables.getOnlyElement(next(1));
}
+ /**
+ * Retrieves the next N available sequence number.
+ *
+ * <p>This method is thread-safe.
+ *
+ * @param count the number of sequence numbers which should be returned
+ * @return the next N available sequence numbers
+ */
public ImmutableList<Integer> next(int count) {
if (count == 0) {
return ImmutableList.of();
}
checkArgument(count > 0, "count is negative: %s", count);
- counterLock.lock();
+
try {
- List<Integer> ids = new ArrayList<>(count);
- while (counter < limit) {
- ids.add(counter++);
- if (ids.size() == count) {
- return ImmutableList.copyOf(ids);
- }
- }
- acquire(Math.max(count - ids.size(), batchSize));
- while (ids.size() < count) {
- ids.add(counter++);
- }
- return ImmutableList.copyOf(ids);
- } finally {
- counterLock.unlock();
- }
- }
+ return retryer.call(
+ () -> {
+ counterLock.lock();
+ try {
+ if (count == 1) {
+ if (counter >= limit) {
+ acquire(batchSize);
+ }
+ return ImmutableList.of(counter++);
+ }
- private void acquire(int count) {
- try (Repository repo = repoManager.openRepository(projectName);
- RevWalk rw = new RevWalk(repo)) {
- logger.atFine().log("acquire %d ids on %s in %s", count, refName, projectName);
- TryAcquire attempt = new TryAcquire(repo, rw, count);
- RefUpdateUtil.checkResult(retryer.call(attempt));
- counter = attempt.next;
- limit = counter + count;
- acquireCount++;
+ List<Integer> ids = new ArrayList<>(count);
+ while (counter < limit) {
+ ids.add(counter++);
+ if (ids.size() == count) {
+ return ImmutableList.copyOf(ids);
+ }
+ }
+ acquire(Math.max(count - ids.size(), batchSize));
+ while (ids.size() < count) {
+ ids.add(counter++);
+ }
+ return ImmutableList.copyOf(ids);
+ } finally {
+ counterLock.unlock();
+ }
+ });
} catch (ExecutionException | RetryException e) {
if (e.getCause() != null) {
Throwables.throwIfInstanceOf(e.getCause(), StorageException.class);
}
throw new StorageException(e);
- } catch (IOException e) {
- throw new StorageException(e);
}
}
- private class TryAcquire implements Callable<RefUpdate> {
- private final Repository repo;
- private final RevWalk rw;
- private final int count;
-
- private int next;
-
- private TryAcquire(Repository repo, RevWalk rw, int count) {
- this.repo = repo;
- this.rw = rw;
- this.count = count;
- }
-
- @Override
- public RefUpdate call() throws Exception {
+ /**
+ * Updates the next available sequence number in NoteDb in order to have a batch of sequence
+ * numbers available that can be handed out. {@link #counter} stores the next sequence number that
+ * can be handed out. When {@link #limit} is reached a new batch of sequence numbers needs to be
+ * retrieved by calling this method.
+ *
+ * <p><strong>Note:</strong> Callers are required to acquire the {@link #counterLock} before
+ * calling this method.
+ *
+ * @param count the number of sequence numbers which should be retrieved
+ */
+ private void acquire(int count) {
+ try (Repository repo = repoManager.openRepository(projectName);
+ RevWalk rw = new RevWalk(repo)) {
+ logger.atFine().log("acquire %d ids on %s in %s", count, refName, projectName);
Optional<IntBlob> blob = IntBlob.parse(repo, refName, rw);
afterReadRef.run();
ObjectId oldId;
+ int next;
if (!blob.isPresent()) {
oldId = ObjectId.zeroId();
next = seed.get();
@@ -269,7 +280,14 @@ public class RepoSequence {
next = blob.get().value();
}
next = Math.max(floor, next);
- return IntBlob.tryStore(repo, rw, projectName, refName, oldId, next + count, gitRefUpdated);
+ RefUpdate refUpdate =
+ IntBlob.tryStore(repo, rw, projectName, refName, oldId, next + count, gitRefUpdated);
+ RefUpdateUtil.checkResult(refUpdate);
+ counter = next;
+ limit = counter + count;
+ acquireCount++;
+ } catch (IOException e) {
+ throw new StorageException(e);
}
}
diff --git a/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java b/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java
index fad9832d90..d5a7259417 100644
--- a/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java
+++ b/java/com/google/gerrit/server/notedb/ReviewerStateInternal.java
@@ -21,13 +21,13 @@ import org.eclipse.jgit.revwalk.FooterKey;
/** State of a reviewer on a change. */
public enum ReviewerStateInternal {
/** The user has contributed at least one nonzero vote on the change. */
- REVIEWER(new FooterKey("Reviewer"), ReviewerState.REVIEWER),
+ REVIEWER("Reviewer", ReviewerState.REVIEWER),
/** The reviewer was added to the change, but has not voted. */
- CC(new FooterKey("CC"), ReviewerState.CC),
+ CC("CC", ReviewerState.CC),
/** The user was previously a reviewer on the change, but was removed. */
- REMOVED(new FooterKey("Removed"), ReviewerState.REMOVED);
+ REMOVED("Removed", ReviewerState.REMOVED);
public static ReviewerStateInternal fromReviewerState(ReviewerState state) {
return ReviewerStateInternal.values()[state.ordinal()];
@@ -50,20 +50,20 @@ public enum ReviewerStateInternal {
}
}
- private final FooterKey footerKey;
+ private final String footer;
private final ReviewerState state;
- ReviewerStateInternal(FooterKey footerKey, ReviewerState state) {
- this.footerKey = footerKey;
+ ReviewerStateInternal(String footer, ReviewerState state) {
+ this.footer = footer;
this.state = state;
}
FooterKey getFooterKey() {
- return footerKey;
+ return new FooterKey(footer);
}
FooterKey getByEmailFooterKey() {
- return new FooterKey(footerKey.getName() + "-email");
+ return new FooterKey(footer + "-email");
}
public ReviewerState asReviewerState() {
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
index ac7a89d904..e63737c85a 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -21,8 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Comment;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -33,27 +32,29 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
class RevisionNoteBuilder {
static class Cache {
private final RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap;
- private final Map<RevId, RevisionNoteBuilder> builders;
+ private final Map<ObjectId, RevisionNoteBuilder> builders;
Cache(RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap) {
this.revisionNoteMap = revisionNoteMap;
this.builders = new HashMap<>();
}
- RevisionNoteBuilder get(RevId revId) {
- RevisionNoteBuilder b = builders.get(revId);
+ RevisionNoteBuilder get(AnyObjectId commitId) {
+ RevisionNoteBuilder b = builders.get(commitId);
if (b == null) {
- b = new RevisionNoteBuilder(revisionNoteMap.revisionNotes.get(revId));
- builders.put(revId, b);
+ b = new RevisionNoteBuilder(revisionNoteMap.revisionNotes.get(commitId));
+ builders.put(commitId.copy(), b);
}
return b;
}
- Map<RevId, RevisionNoteBuilder> getBuilders() {
+ Map<ObjectId, RevisionNoteBuilder> getBuilders() {
return Collections.unmodifiableMap(builders);
}
}
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteData.java b/java/com/google/gerrit/server/notedb/RevisionNoteData.java
index 1e16b2221b..c0e09ed343 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteData.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteData.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.notedb;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.List;
/**
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
index da790e248b..3e1bad1951 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -15,37 +15,28 @@
package com.google.gerrit.server.notedb;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Comment;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMap;
class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
final NoteMap noteMap;
- final ImmutableMap<RevId, T> revisionNotes;
+ final ImmutableMap<ObjectId, T> revisionNotes;
static RevisionNoteMap<ChangeRevisionNote> parse(
- ChangeNoteJson noteJson,
- LegacyChangeNoteRead legacyChangeNoteRead,
- Change.Id changeId,
- ObjectReader reader,
- NoteMap noteMap,
- PatchLineComment.Status status)
+ ChangeNoteJson noteJson, ObjectReader reader, NoteMap noteMap, Comment.Status status)
throws ConfigInvalidException, IOException {
- Map<RevId, ChangeRevisionNote> result = new HashMap<>();
+ Map<ObjectId, ChangeRevisionNote> result = new HashMap<>();
for (Note note : noteMap) {
- ChangeRevisionNote rn =
- new ChangeRevisionNote(
- noteJson, legacyChangeNoteRead, changeId, reader, note.getData(), status);
+ ChangeRevisionNote rn = new ChangeRevisionNote(noteJson, reader, note.getData(), status);
rn.parse();
- result.put(new RevId(note.name()), rn);
+ result.put(note.copy(), rn);
}
return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
}
@@ -53,12 +44,12 @@ class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
ChangeNoteJson changeNoteJson, ObjectReader reader, NoteMap noteMap)
throws ConfigInvalidException, IOException {
- Map<RevId, RobotCommentsRevisionNote> result = new HashMap<>();
+ Map<ObjectId, RobotCommentsRevisionNote> result = new HashMap<>();
for (Note note : noteMap) {
RobotCommentsRevisionNote rn =
new RobotCommentsRevisionNote(changeNoteJson, reader, note.getData());
rn.parse();
- result.put(new RevId(note.name()), rn);
+ result.put(note.copy(), rn);
}
return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
}
@@ -67,7 +58,7 @@ class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
return new RevisionNoteMap<>(NoteMap.newEmptyMap(), ImmutableMap.of());
}
- private RevisionNoteMap(NoteMap noteMap, ImmutableMap<RevId, T> revisionNotes) {
+ private RevisionNoteMap(NoteMap noteMap, ImmutableMap<ObjectId, T> revisionNotes) {
this.noteMap = noteMap;
this.revisionNotes = revisionNotes;
}
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
index 92dd7d8b7e..fe0564356e 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
@@ -19,11 +19,10 @@ import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.RobotComment;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.RobotComment;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -42,7 +41,7 @@ public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
private final Change change;
- private ImmutableListMultimap<RevId, RobotComment> comments;
+ private ImmutableListMultimap<ObjectId, RobotComment> comments;
private RevisionNoteMap<RobotCommentsRevisionNote> revisionNoteMap;
private ObjectId metaId;
@@ -56,7 +55,7 @@ public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
return revisionNoteMap;
}
- public ImmutableListMultimap<RevId, RobotComment> getComments() {
+ public ImmutableListMultimap<ObjectId, RobotComment> getComments() {
return comments;
}
@@ -95,10 +94,10 @@ public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
revisionNoteMap =
RevisionNoteMap.parseRobotComments(
args.changeNoteJson, reader, NoteMap.read(reader, tipCommit));
- ListMultimap<RevId, RobotComment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
+ ListMultimap<ObjectId, RobotComment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
for (RobotCommentsRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
for (RobotComment c : rn.getEntities()) {
- cs.put(new RevId(c.revId), c);
+ cs.put(c.getCommitId(), c);
}
}
comments = ImmutableListMultimap.copyOf(cs);
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java b/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
index 0304ab81d7..895f378452 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentUpdate.java
@@ -15,16 +15,15 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.gerrit.reviewdb.client.RefNames.robotCommentsRef;
+import static com.google.gerrit.entities.RefNames.robotCommentsRef;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -103,19 +102,19 @@ public class RobotCommentUpdate extends AbstractChangeUpdate {
RevWalk rw, ObjectInserter ins, ObjectId curr, CommitBuilder cb)
throws ConfigInvalidException, IOException {
RevisionNoteMap<RobotCommentsRevisionNote> rnm = getRevisionNoteMap(rw, curr);
- Set<RevId> updatedRevs = Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
+ Set<ObjectId> updatedRevs = Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
for (RobotComment c : put) {
- cache.get(new RevId(c.revId)).putComment(c);
+ cache.get(c.getCommitId()).putComment(c);
}
- Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
+ Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
boolean touchedAnyRevs = false;
boolean hasComments = false;
- for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
- updatedRevs.add(e.getKey());
- ObjectId id = ObjectId.fromString(e.getKey().get());
+ for (Map.Entry<ObjectId, RevisionNoteBuilder> e : builders.entrySet()) {
+ ObjectId id = e.getKey();
+ updatedRevs.add(id);
byte[] data = e.getValue().build(noteUtil);
if (!Arrays.equals(data, e.getValue().baseRaw)) {
touchedAnyRevs = true;
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
index 6c3cc8627e..97a8ad4a4e 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.notedb;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.gerrit.reviewdb.client.RobotComment;
+import com.google.gerrit.entities.RobotComment;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java
index 116b30e624..3619c432c1 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNoteData.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.notedb;
-import com.google.gerrit.reviewdb.client.RobotComment;
+import com.google.gerrit.entities.RobotComment;
import java.util.List;
public class RobotCommentsRevisionNoteData {
diff --git a/java/com/google/gerrit/server/notedb/Sequences.java b/java/com/google/gerrit/server/notedb/Sequences.java
index d4144cea06..9f1972535f 100644
--- a/java/com/google/gerrit/server/notedb/Sequences.java
+++ b/java/com/google/gerrit/server/notedb/Sequences.java
@@ -25,6 +25,7 @@ import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
@@ -110,30 +111,35 @@ public class Sequences {
new Description("Latency of requesting IDs from repo sequences")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- Field.ofEnum(SequenceType.class, "sequence"),
- Field.ofBoolean("multiple"));
+ Field.ofEnum(SequenceType.class, "sequence", Metadata.Builder::noteDbSequenceType)
+ .build(),
+ Field.ofBoolean("multiple", Metadata.Builder::multiple).build());
}
public int nextAccountId() {
- try (Timer2.Context timer = nextIdLatency.start(SequenceType.ACCOUNTS, false)) {
+ try (Timer2.Context<SequenceType, Boolean> timer =
+ nextIdLatency.start(SequenceType.ACCOUNTS, false)) {
return accountSeq.next();
}
}
public int nextChangeId() {
- try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, false)) {
+ try (Timer2.Context<SequenceType, Boolean> timer =
+ nextIdLatency.start(SequenceType.CHANGES, false)) {
return changeSeq.next();
}
}
public ImmutableList<Integer> nextChangeIds(int count) {
- try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
+ try (Timer2.Context<SequenceType, Boolean> timer =
+ nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
return changeSeq.next(count);
}
}
public int nextGroupId() {
- try (Timer2.Context timer = nextIdLatency.start(SequenceType.GROUPS, false)) {
+ try (Timer2.Context<SequenceType, Boolean> timer =
+ nextIdLatency.start(SequenceType.GROUPS, false)) {
return groupSeq.next();
}
}
diff --git a/java/com/google/gerrit/server/notedb/TooManyUpdatesException.java b/java/com/google/gerrit/server/notedb/TooManyUpdatesException.java
new file mode 100644
index 0000000000..9c6faaf6c8
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/TooManyUpdatesException.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.exceptions.StorageException;
+
+/**
+ * Exception indicating that the change has received too many updates. Further actions apart from
+ * {@code abandon} or {@code submit} are blocked.
+ */
+public class TooManyUpdatesException extends StorageException {
+ @VisibleForTesting
+ public static String message(Change.Id id, int maxUpdates) {
+ return "Change "
+ + id
+ + " may not exceed "
+ + maxUpdates
+ + " updates. It may still be abandoned or submitted. To continue working on this "
+ + "change, recreate it with a new Change-Id, then abandon this one.";
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ TooManyUpdatesException(Change.Id id, int maxUpdates) {
+ super(message(id, maxUpdates));
+ }
+}
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index 18aa8b9fe0..a9cc9b5ac4 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.InMemoryInserter;
diff --git a/java/com/google/gerrit/server/patch/DiffSummaryLoader.java b/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
index 9153638199..b61e0c7bb8 100644
--- a/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
+++ b/java/com/google/gerrit/server/patch/DiffSummaryLoader.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
diff --git a/java/com/google/gerrit/server/patch/IntraLineDiff.java b/java/com/google/gerrit/server/patch/IntraLineDiff.java
index 1c3d78a91b..fb13207b75 100644
--- a/java/com/google/gerrit/server/patch/IntraLineDiff.java
+++ b/java/com/google/gerrit/server/patch/IntraLineDiff.java
@@ -21,8 +21,8 @@ import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.CodedEnum;
import com.google.gerrit.jgit.diff.ReplaceEdit;
-import com.google.gerrit.reviewdb.client.CodedEnum;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
diff --git a/java/com/google/gerrit/server/patch/IntraLineDiffArgs.java b/java/com/google/gerrit/server/patch/IntraLineDiffArgs.java
index 4661485ddf..2ebae02c7d 100644
--- a/java/com/google/gerrit/server/patch/IntraLineDiffArgs.java
+++ b/java/com/google/gerrit/server/patch/IntraLineDiffArgs.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.patch;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.diff.Edit;
diff --git a/java/com/google/gerrit/server/patch/PatchFile.java b/java/com/google/gerrit/server/patch/PatchFile.java
index 0e5bcc17a0..ca5223d971 100644
--- a/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/java/com/google/gerrit/server/patch/PatchFile.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.patch;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.exceptions.NoSuchEntityException;
-import com.google.gerrit.reviewdb.client.Patch;
import java.io.IOException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -39,6 +39,15 @@ public class PatchFile {
private final RevTree aTree;
private final RevTree bTree;
+ // Full text of both sides of the file. For standard files, these are not directly reconstructable
+ // from the PatchListEntry, which comes from the PatchListCache and only contains the diff between
+ // the two blobs. This is intentional, to avoid storing entire large blobs in the cache. For
+ // regular files, the full text is initialized from the repo lazily only when necessary, e.g. in
+ // getLine. Although it's a safe assumption that any caller constructing a PatchSet will want to
+ // read some content, we don't know in advance which side they are interested in.
+ //
+ // For special files like COMMIT_MSG, the full text is loaded eagerly during the constructor.
+ // TODO(dborowitz): I see why the logic is different, but I don't see why it needs to be eager.
private Text a;
private Text b;
diff --git a/java/com/google/gerrit/server/patch/PatchList.java b/java/com/google/gerrit/server/patch/PatchList.java
index dd717ba37e..fee9088ce9 100644
--- a/java/com/google/gerrit/server/patch/PatchList.java
+++ b/java/com/google/gerrit/server/patch/PatchList.java
@@ -25,8 +25,9 @@ import static org.eclipse.jgit.lib.ObjectIdSerializer.writeWithoutMarker;
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.git.ObjectIds;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -78,7 +79,7 @@ public class PatchList implements Serializable {
boolean isMerge,
ComparisonType comparisonType,
PatchListEntry[] patches) {
- this.oldId = oldId != null ? oldId.copy() : null;
+ this.oldId = ObjectIds.copyOrNull(oldId);
this.newId = newId.copy();
this.isMerge = isMerge;
this.comparisonType = comparisonType;
diff --git a/java/com/google/gerrit/server/patch/PatchListCache.java b/java/com/google/gerrit/server/patch/PatchListCache.java
index 728d227123..63cac0e9ec 100644
--- a/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.patch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import org.eclipse.jgit.lib.ObjectId;
/** Provides a cached list of {@link PatchListEntry}. */
diff --git a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 20f0f720b8..15fa0f4ec9 100644
--- a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -18,10 +18,10 @@ package com.google.gerrit.server.patch;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheBackend;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -137,10 +137,7 @@ public class PatchListCacheImpl implements PatchListCache {
private PatchList get(Change change, PatchSet patchSet, Integer parentNum)
throws PatchListNotAvailableException {
Project.NameKey project = change.getProject();
- if (patchSet.getRevision() == null) {
- throw new PatchListNotAvailableException("revision is null for " + patchSet.getId());
- }
- ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
+ ObjectId b = patchSet.commitId();
Whitespace ws = Whitespace.IGNORE_NONE;
if (parentNum != null) {
return get(PatchListKey.againstParentNum(parentNum, b, ws), project);
diff --git a/java/com/google/gerrit/server/patch/PatchListEntry.java b/java/com/google/gerrit/server/patch/PatchListEntry.java
index 6b1a153edc..625f56c253 100644
--- a/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -30,10 +30,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
-import com.google.gerrit.reviewdb.client.Patch.PatchType;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Patch.ChangeType;
+import com.google.gerrit.entities.Patch.PatchType;
+import com.google.gerrit.entities.PatchSet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -229,7 +229,7 @@ public class PatchListEntry {
}
Patch toPatch(PatchSet.Id setId) {
- final Patch p = new Patch(new Patch.Key(setId, getNewName()));
+ final Patch p = new Patch(Patch.key(setId, getNewName()));
p.setChangeType(getChangeType());
p.setPatchType(getPatchType());
p.setSourceFileName(getOldName());
diff --git a/java/com/google/gerrit/server/patch/PatchListKey.java b/java/com/google/gerrit/server/patch/PatchListKey.java
index 2df6d66450..bf38029d00 100644
--- a/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -23,6 +23,7 @@ import static org.eclipse.jgit.lib.ObjectIdSerializer.writeWithoutMarker;
import com.google.common.collect.ImmutableBiMap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.git.ObjectIds;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -82,7 +83,7 @@ public class PatchListKey implements Serializable {
private transient Whitespace whitespace;
private PatchListKey(AnyObjectId a, AnyObjectId b, Whitespace ws) {
- oldId = a != null ? a.copy() : null;
+ oldId = ObjectIds.copyOrNull(a);
newId = b.copy();
whitespace = ws;
}
diff --git a/java/com/google/gerrit/server/patch/PatchListLoader.java b/java/com/google/gerrit/server/patch/PatchListLoader.java
index 08de537305..c3d9a1d3e8 100644
--- a/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -314,12 +314,12 @@ public class PatchListLoader implements Callable<PatchList> {
}
private static boolean areParentChild(RevCommit commitA, RevCommit commitB) {
- return ObjectId.equals(commitA.getParent(0), commitB)
- || ObjectId.equals(commitB.getParent(0), commitA);
+ return ObjectId.isEqual(commitA.getParent(0), commitB)
+ || ObjectId.isEqual(commitB.getParent(0), commitA);
}
private static boolean haveCommonParent(RevCommit commitA, RevCommit commitB) {
- return ObjectId.equals(commitA.getParent(0), commitB.getParent(0));
+ return ObjectId.isEqual(commitA.getParent(0), commitB.getParent(0));
}
private static Set<String> getTouchedFilePaths(PatchListEntry patchListEntry) {
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index c2caa01b75..1c1c639c50 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -21,14 +21,14 @@ import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.inject.Inject;
import eu.medsea.mimeutil.MimeType;
@@ -37,6 +37,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.errors.CorruptObjectException;
@@ -120,7 +121,6 @@ class PatchScriptBuilder {
private PatchScript build(PatchListEntry content, CommentDetail comments, List<Patch> history)
throws IOException {
- boolean intralineDifferenceIsPossible = true;
boolean intralineFailure = false;
boolean intralineTimeout = false;
@@ -146,21 +146,17 @@ class PatchScriptBuilder {
break;
case DISABLED:
- intralineDifferenceIsPossible = false;
break;
case ERROR:
- intralineDifferenceIsPossible = false;
intralineFailure = true;
break;
case TIMEOUT:
- intralineDifferenceIsPossible = false;
intralineTimeout = true;
break;
}
} else {
- intralineDifferenceIsPossible = false;
intralineFailure = true;
}
}
@@ -220,7 +216,6 @@ class PatchScriptBuilder {
comments,
history,
hugeFile,
- intralineDifferenceIsPossible,
intralineFailure,
intralineTimeout,
content.getPatchType() == Patch.PatchType.BINARY,
@@ -514,8 +509,7 @@ class PatchScriptBuilder {
try {
final boolean reuse;
if (Patch.COMMIT_MSG.equals(path)) {
- if (comparisonType.isAgainstParentOrAutoMerge()
- && (aId == within || within.equals(aId))) {
+ if (comparisonType.isAgainstParentOrAutoMerge() && Objects.equals(aId, within)) {
id = ObjectId.zeroId();
src = Text.EMPTY;
srcContent = Text.NO_BYTES;
@@ -534,8 +528,7 @@ class PatchScriptBuilder {
}
reuse = false;
} else if (Patch.MERGE_LIST.equals(path)) {
- if (comparisonType.isAgainstParentOrAutoMerge()
- && (aId == within || within.equals(aId))) {
+ if (comparisonType.isAgainstParentOrAutoMerge() && Objects.equals(aId, within)) {
id = ObjectId.zeroId();
src = Text.EMPTY;
srcContent = Text.NO_BYTES;
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index a7e77383ac..319a5bcbde 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -15,20 +15,21 @@
package com.google.gerrit.server.patch;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Patch.ChangeType;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
@@ -136,7 +137,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.psb = patchSetB;
this.diffPrefs = diffPrefs;
- changeId = patchSetB.getParentKey();
+ changeId = patchSetB.changeId();
}
@AssistedInject
@@ -172,7 +173,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.psb = patchSetB;
this.diffPrefs = diffPrefs;
- changeId = patchSetB.getParentKey();
+ changeId = patchSetB.changeId();
checkArgument(parentNum >= 0, "parentNum must be >= 0");
}
@@ -188,19 +189,28 @@ public class PatchScriptFactory implements Callable<PatchScript> {
public PatchScript call()
throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
PermissionBackendException {
- if (parentNum < 0) {
- validatePatchSetId(psa);
- }
+ validatePatchSetId(psa);
validatePatchSetId(psb);
- PatchSet psEntityA = psa != null ? psUtil.get(notes, psa) : null;
- PatchSet psEntityB = psb.get() == 0 ? new PatchSet(psb) : psUtil.get(notes, psb);
- if (psEntityA != null || psEntityB != null) {
- try {
- permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
- } catch (AuthException e) {
- throw new NoSuchChangeException(changeId, e);
- }
+ if (psa != null) {
+ checkState(parentNum < 0, "expected no parentNum when psa is present");
+ checkArgument(psa.get() != 0, "edit not supported for left side");
+ aId = getCommitId(psa);
+ } else {
+ aId = null;
+ }
+
+ if (psb.get() != 0) {
+ bId = getCommitId(psb);
+ } else {
+ // Change edit: create synthetic PatchSet corresponding to the edit.
+ bId = getEditRev();
+ }
+
+ try {
+ permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
+ } catch (AuthException e) {
+ throw new NoSuchChangeException(changeId, e);
}
if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
@@ -208,11 +218,6 @@ public class PatchScriptFactory implements Callable<PatchScript> {
}
try (Repository git = repoManager.openRepository(notes.getProjectName())) {
- bId = toObjectId(psEntityB);
- if (parentNum < 0) {
- aId = psEntityA != null ? toObjectId(psEntityA) : null;
- }
-
try {
final PatchList list = listFor(keyFor(diffPrefs.ignoreWhitespace));
final PatchScriptBuilder b = newBuilder(list, git);
@@ -258,20 +263,12 @@ public class PatchScriptFactory implements Callable<PatchScript> {
return b;
}
- private ObjectId toObjectId(PatchSet ps) throws AuthException, IOException {
- if (ps.getId().get() == 0) {
- return getEditRev();
- }
- if (ps.getRevision() == null || ps.getRevision().get() == null) {
- throw new NoSuchChangeException(changeId);
- }
-
- try {
- return ObjectId.fromString(ps.getRevision().get());
- } catch (IllegalArgumentException e) {
- logger.atSevere().log("Patch set %s has invalid revision", ps.getId());
- throw new NoSuchChangeException(changeId, e);
+ private ObjectId getCommitId(PatchSet.Id psId) {
+ PatchSet ps = psUtil.get(notes, psId);
+ if (ps == null) {
+ throw new NoSuchChangeException(psId.changeId());
}
+ return ps.commitId();
}
private ObjectId getEditRev() throws AuthException, IOException {
@@ -284,7 +281,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private void validatePatchSetId(PatchSet.Id psId) throws NoSuchChangeException {
if (psId == null) { // OK, means use base;
- } else if (changeId.equals(psId.getParentKey())) { // OK, same change;
+ } else if (changeId.equals(psId.changeId())) { // OK, same change;
} else {
throw new NoSuchChangeException(changeId);
}
@@ -306,7 +303,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
switch (changeType) {
case COPIED:
case RENAMED:
- if (ps.getId().equals(psa)) {
+ if (ps.id().equals(psa)) {
name = oldName;
}
break;
@@ -319,12 +316,12 @@ public class PatchScriptFactory implements Callable<PatchScript> {
}
}
- Patch p = new Patch(new Patch.Key(ps.getId(), name));
+ Patch p = new Patch(Patch.key(ps.id(), name));
history.add(p);
byKey.put(p.getKey(), p);
}
if (edit != null && edit.isPresent()) {
- Patch p = new Patch(new Patch.Key(new PatchSet.Id(psb.getParentKey(), 0), fileName));
+ Patch p = new Patch(Patch.key(PatchSet.id(psb.changeId(), 0), fileName));
history.add(p);
byKey.put(p.getKey(), p);
}
@@ -385,8 +382,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private void loadPublished(Map<Patch.Key, Patch> byKey, String file) {
for (Comment c : commentsUtil.publishedByChangeFile(notes, file)) {
comments.include(notes.getChangeId(), c);
- PatchSet.Id psId = new PatchSet.Id(notes.getChangeId(), c.key.patchSetId);
- Patch.Key pKey = new Patch.Key(psId, c.key.filename);
+ PatchSet.Id psId = PatchSet.id(notes.getChangeId(), c.key.patchSetId);
+ Patch.Key pKey = Patch.key(psId, c.key.filename);
Patch p = byKey.get(pKey);
if (p != null) {
p.setCommentCount(p.getCommentCount() + 1);
@@ -397,8 +394,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private void loadDrafts(Map<Patch.Key, Patch> byKey, Account.Id me, String file) {
for (Comment c : commentsUtil.draftByChangeFileAuthor(notes, file, me)) {
comments.include(notes.getChangeId(), c);
- PatchSet.Id psId = new PatchSet.Id(notes.getChangeId(), c.key.patchSetId);
- Patch.Key pKey = new Patch.Key(psId, c.key.filename);
+ PatchSet.Id psId = PatchSet.id(notes.getChangeId(), c.key.patchSetId);
+ Patch.Key pKey = Patch.key(psId, c.key.filename);
Patch p = byKey.get(pKey);
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);
diff --git a/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 0eb5588cb8..019fe1574a 100644
--- a/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -14,13 +14,10 @@
package com.google.gerrit.server.patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.Emails;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -28,13 +25,9 @@ import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -58,9 +51,9 @@ public class PatchSetInfoFactory {
PatchSetInfo info = new PatchSetInfo(psi);
info.setSubject(src.getShortMessage());
info.setMessage(src.getFullMessage());
- info.setAuthor(toUserIdentity(src.getAuthorIdent()));
- info.setCommitter(toUserIdentity(src.getCommitterIdent()));
- info.setRevId(src.getName());
+ info.setAuthor(emails.toUserIdentity(src.getAuthorIdent()));
+ info.setCommitter(emails.toUserIdentity(src.getCommitterIdent()));
+ info.setCommitId(src);
return info;
}
@@ -78,8 +71,8 @@ public class PatchSetInfoFactory {
throws PatchSetInfoNotAvailableException {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
- final RevCommit src = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- PatchSetInfo info = get(rw, src, patchSet.getId());
+ RevCommit src = rw.parseCommit(patchSet.commitId());
+ PatchSetInfo info = get(rw, src, patchSet.id());
info.setParents(toParentInfos(src.getParents(), rw));
return info;
} catch (IOException | StorageException e) {
@@ -87,33 +80,13 @@ public class PatchSetInfoFactory {
}
}
- // TODO: The same method exists in EventFactory, find a common place for it
- private UserIdentity toUserIdentity(PersonIdent who) throws IOException {
- final UserIdentity u = new UserIdentity();
- u.setName(who.getName());
- u.setEmail(who.getEmailAddress());
- u.setDate(new Timestamp(who.getWhen().getTime()));
- u.setTimeZone(who.getTimeZoneOffset());
-
- // If only one account has access to this email address, select it
- // as the identity of the user.
- //
- Set<Account.Id> a = emails.getAccountFor(u.getEmail());
- if (a.size() == 1) {
- u.setAccount(a.iterator().next());
- }
-
- return u;
- }
-
private List<PatchSetInfo.ParentInfo> toParentInfos(RevCommit[] parents, RevWalk walk)
throws IOException, MissingObjectException {
List<PatchSetInfo.ParentInfo> pInfos = new ArrayList<>(parents.length);
for (RevCommit parent : parents) {
walk.parseBody(parent);
- RevId rev = new RevId(parent.getId().name());
String msg = parent.getShortMessage();
- pInfos.add(new PatchSetInfo.ParentInfo(rev, msg));
+ pInfos.add(new PatchSetInfo.ParentInfo(parent, msg));
}
return pInfos;
}
diff --git a/java/com/google/gerrit/server/patch/Text.java b/java/com/google/gerrit/server/patch/Text.java
index 011185d2fc..cc0a5e40e4 100644
--- a/java/com/google/gerrit/server/patch/Text.java
+++ b/java/com/google/gerrit/server/patch/Text.java
@@ -18,6 +18,7 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.git.ObjectIds;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
@@ -62,7 +63,7 @@ public class Text extends RawText {
RevCommit p = c.getParent(0);
rw.parseBody(p);
b.append("Parent: ");
- b.append(reader.abbreviate(p, 8).name());
+ b.append(abbreviateName(p, reader));
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
@@ -73,7 +74,7 @@ public class Text extends RawText {
RevCommit p = c.getParent(i);
rw.parseBody(p);
b.append(i == 0 ? "Merge Of: " : " ");
- b.append(reader.abbreviate(p, 8).name());
+ b.append(abbreviateName(p, reader));
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
@@ -106,7 +107,7 @@ public class Text extends RawText {
b.append("Merge List:\n\n");
for (RevCommit commit : MergeListBuilder.build(rw, c, uniterestingParent)) {
b.append("* ");
- b.append(reader.abbreviate(commit, 8).name());
+ b.append(abbreviateName(commit, reader));
b.append(" ");
b.append(commit.getShortMessage());
b.append("\n");
@@ -116,6 +117,10 @@ public class Text extends RawText {
}
}
+ private static String abbreviateName(RevCommit p, ObjectReader reader) throws IOException {
+ return ObjectIds.abbreviateName(p, 8, reader);
+ }
+
private static void appendPersonIdent(StringBuilder b, String field, PersonIdent person) {
if (person != null) {
b.append(field).append(": ");
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index c3be7f2609..cecdb959a6 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -22,12 +22,12 @@ import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
@@ -79,6 +79,13 @@ class ChangeControl {
return new ForChangeImpl();
}
+ ChangeControl setChangeData(@Nullable ChangeData cd) {
+ if (cd != null) {
+ this.cd = cd;
+ }
+ return this;
+ }
+
private CurrentUser getUser() {
return refControl.getUser();
}
@@ -98,13 +105,6 @@ class ChangeControl {
return cd;
}
- ChangeControl setChangeData(@Nullable ChangeData cd) {
- if (cd != null) {
- this.cd = cd;
- }
- return this;
- }
-
/** Can this user see this change? */
boolean isVisible() {
if (getChange().isPrivate() && !isPrivateVisible(changeData())) {
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
index b23c85f246..6142bc0245 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -22,13 +22,13 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index 2d1f6fc5fe..f887f61fba 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -16,8 +16,7 @@ package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS_SELF;
+import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
import static java.util.stream.Collectors.toMap;
import com.google.auto.value.AutoValue;
@@ -28,20 +27,22 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TagMatcher;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
@@ -59,7 +60,6 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.SymbolicRef;
class DefaultRefFilter {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -81,7 +81,7 @@ class DefaultRefFilter {
private final Counter0 skipFilterCount;
private final boolean skipFullRefEvaluationIfAllRefsAreVisible;
- private Map<Change.Id, Branch.NameKey> visibleChanges;
+ private Map<Change.Id, BranchNameKey> visibleChanges;
@Inject
DefaultRefFilter(
@@ -122,69 +122,100 @@ class DefaultRefFilter {
/** Filters given refs and tags by visibility. */
Map<String, Ref> filter(Map<String, Ref> refs, Repository repo, RefFilterOptions opts)
throws PermissionBackendException {
+ logger.atFinest().log(
+ "Filter refs for repository %s by visibility (options = %s, refs = %s)",
+ projectState.getNameKey(), opts, refs);
+ logger.atFinest().log("Calling user: %s", user.getLoggableName());
+ logger.atFinest().log("Groups: %s", user.getEffectiveGroups().getKnownGroups());
+ logger.atFinest().log(
+ "auth.skipFullRefEvaluationIfAllRefsAreVisible = %s",
+ skipFullRefEvaluationIfAllRefsAreVisible);
+ logger.atFinest().log(
+ "Project state %s permits read = %s",
+ projectState.getProject().getState(), projectState.statePermitsRead());
+
// See if we can get away with a single, cheap ref evaluation.
if (refs.size() == 1) {
String refName = Iterables.getOnlyElement(refs.values()).getName();
if (opts.filterMeta() && isMetadata(refName)) {
+ logger.atFinest().log("Filter out metadata ref %s", refName);
return ImmutableMap.of();
}
if (RefNames.isRefsChanges(refName)) {
- return canSeeSingleChangeRef(refName) ? refs : ImmutableMap.of();
+ boolean isChangeRefVisisble = canSeeSingleChangeRef(refName);
+ if (isChangeRefVisisble) {
+ logger.atFinest().log("Change ref %s is visible", refName);
+ return refs;
+ }
+ logger.atFinest().log("Filter out non-visible change ref %s", refName);
+ return ImmutableMap.of();
}
}
// Perform an initial ref filtering with all the refs the caller asked for. If we find tags that
- // we have to investigate (deferred tags) separately then perform a reachability check starting
+ // we have to investigate separately (deferred tags) then perform a reachability check starting
// from all visible branches (refs/heads/*).
Result initialRefFilter = filterRefs(refs, repo, opts);
Map<String, Ref> visibleRefs = initialRefFilter.visibleRefs();
if (!initialRefFilter.deferredTags().isEmpty()) {
- Result allVisibleBranches = filterRefs(getTaggableRefsMap(repo), repo, opts);
- checkState(
- allVisibleBranches.deferredTags().isEmpty(),
- "unexpected tags found when filtering refs/heads/* " + allVisibleBranches.deferredTags());
-
- TagMatcher tags =
- tagCache
- .get(projectState.getNameKey())
- .matcher(tagCache, repo, allVisibleBranches.visibleRefs().values());
- for (Ref tag : initialRefFilter.deferredTags()) {
- try {
- if (tags.isReachable(tag)) {
- visibleRefs.put(tag.getName(), tag);
+ try (TraceTimer traceTimer = TraceContext.newTimer("Check visibility of deferred tags")) {
+ Result allVisibleBranches = filterRefs(getTaggableRefsMap(repo), repo, opts);
+ checkState(
+ allVisibleBranches.deferredTags().isEmpty(),
+ "unexpected tags found when filtering refs/heads/* "
+ + allVisibleBranches.deferredTags());
+
+ TagMatcher tags =
+ tagCache
+ .get(projectState.getNameKey())
+ .matcher(tagCache, repo, allVisibleBranches.visibleRefs().values());
+ for (Ref tag : initialRefFilter.deferredTags()) {
+ try {
+ if (tags.isReachable(tag)) {
+ logger.atFinest().log("Include reachable tag %s", tag.getName());
+ visibleRefs.put(tag.getName(), tag);
+ } else {
+ logger.atFinest().log("Filter out non-reachable tag %s", tag.getName());
+ }
+ } catch (IOException e) {
+ throw new PermissionBackendException(e);
}
- } catch (IOException e) {
- throw new PermissionBackendException(e);
}
}
}
+
+ logger.atFinest().log("visible refs = %s", visibleRefs);
return visibleRefs;
}
/**
* Filters refs by visibility. Returns tags where visibility can't be trivially computed
- * separately for later ref-walk-based visibility computation. Tags where visibility is trivial to
+ * separately for later rev-walk-based visibility computation. Tags where visibility is trivial to
* compute will be returned as part of {@link Result#visibleRefs()}.
*/
Result filterRefs(Map<String, Ref> refs, Repository repo, RefFilterOptions opts)
throws PermissionBackendException {
- if (projectState.isAllUsers()) {
- refs = addUsersSelfSymref(refs);
- }
+ logger.atFinest().log("Filter refs (refs = %s)", refs);
// TODO(hiesel): Remove when optimization is done.
boolean hasReadOnRefsStar =
checkProjectPermission(permissionBackendForProject, ProjectPermission.READ);
+ logger.atFinest().log("User has READ on refs/* = %s", hasReadOnRefsStar);
if (skipFullRefEvaluationIfAllRefsAreVisible && !projectState.isAllUsers()) {
if (projectState.statePermitsRead() && hasReadOnRefsStar) {
skipFilterCount.increment();
+ logger.atFinest().log(
+ "Fast path, all refs are visible because user has READ on refs/*: %s", refs);
return new AutoValue_DefaultRefFilter_Result(refs, ImmutableList.of());
} else if (projectControl.allRefsAreVisible(ImmutableSet.of(RefNames.REFS_CONFIG))) {
skipFilterCount.increment();
- return new AutoValue_DefaultRefFilter_Result(
- fastHideRefsMetaConfig(refs), ImmutableList.of());
+ refs = fastHideRefsMetaConfig(refs);
+ logger.atFinest().log(
+ "Fast path, all refs except %s are visible: %s", RefNames.REFS_CONFIG, refs);
+ return new AutoValue_DefaultRefFilter_Result(refs, ImmutableList.of());
}
}
+ logger.atFinest().log("Doing full ref filtering");
fullFilterCount.increment();
boolean hasAccessDatabase =
@@ -239,7 +270,9 @@ class DefaultRefFilter {
resultRefs.put(refName, ref);
}
}
- return new AutoValue_DefaultRefFilter_Result(resultRefs, deferredTags);
+ Result result = new AutoValue_DefaultRefFilter_Result(resultRefs, deferredTags);
+ logger.atFinest().log("Result of ref filtering = %s", result);
+ return result;
}
/**
@@ -272,18 +305,6 @@ class DefaultRefFilter {
return refs;
}
- private Map<String, Ref> addUsersSelfSymref(Map<String, Ref> refs) {
- if (user.isIdentifiedUser()) {
- Ref r = refs.get(RefNames.refsUsers(user.getAccountId()));
- if (r != null) {
- SymbolicRef s = new SymbolicRef(REFS_USERS_SELF, r);
- refs = new HashMap<>(refs);
- refs.put(s.getName(), s);
- }
- }
- return refs;
- }
-
private boolean visible(Repository repo, Change.Id changeId) throws PermissionBackendException {
if (visibleChanges == null) {
if (changeCache == null) {
@@ -291,44 +312,51 @@ class DefaultRefFilter {
} else {
visibleChanges = visibleChangesBySearch();
}
+ logger.atFinest().log("Visible changes: %s", visibleChanges.keySet());
}
return visibleChanges.containsKey(changeId);
}
private boolean visibleEdit(Repository repo, String name) throws PermissionBackendException {
Change.Id id = Change.Id.fromEditRefPart(name);
- // Initialize if it wasn't yet
- if (visibleChanges == null) {
- visible(repo, id);
- }
if (id == null) {
+ logger.atWarning().log("Couldn't extract change ID from edit ref %s", name);
return false;
}
if (user.isIdentifiedUser()
&& name.startsWith(RefNames.refsEditPrefix(user.asIdentifiedUser().getAccountId()))
&& visible(repo, id)) {
+ logger.atFinest().log("Own change edit ref is visible: %s", name);
return true;
}
+
+ // Initialize visibleChanges if it wasn't initialized yet.
+ if (visibleChanges == null) {
+ visible(repo, id);
+ }
if (visibleChanges.containsKey(id)) {
try {
// Default to READ_PRIVATE_CHANGES as there is no special permission for reading edits.
permissionBackendForProject
- .ref(visibleChanges.get(id).get())
+ .ref(visibleChanges.get(id).branch())
.check(RefPermission.READ_PRIVATE_CHANGES);
+ logger.atFinest().log("Foreign change edit ref is visible: %s", name);
return true;
} catch (AuthException e) {
+ logger.atFinest().log("Foreign change edit ref is not visible: %s", name);
return false;
}
}
+
+ logger.atFinest().log("Change %d of change edit ref %s is not visible", id.get(), name);
return false;
}
- private Map<Change.Id, Branch.NameKey> visibleChangesBySearch()
- throws PermissionBackendException {
+ private Map<Change.Id, BranchNameKey> visibleChangesBySearch() throws PermissionBackendException {
Project.NameKey project = projectState.getNameKey();
try {
- Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
+ Map<Change.Id, BranchNameKey> visibleChanges = new HashMap<>();
for (ChangeData cd : changeCache.getChangeData(project)) {
ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change());
if (!projectState.statePermitsRead()) {
@@ -349,7 +377,7 @@ class DefaultRefFilter {
}
}
- private Map<Change.Id, Branch.NameKey> visibleChangesByScan(Repository repo)
+ private Map<Change.Id, BranchNameKey> visibleChangesByScan(Repository repo)
throws PermissionBackendException {
Project.NameKey p = projectState.getNameKey();
ImmutableList<ChangeNotesResult> changes;
@@ -361,7 +389,7 @@ class DefaultRefFilter {
return Collections.emptyMap();
}
- Map<Change.Id, Branch.NameKey> result = Maps.newHashMapWithExpectedSize(changes.size());
+ Map<Change.Id, BranchNameKey> result = Maps.newHashMapWithExpectedSize(changes.size());
for (ChangeNotesResult notesResult : changes) {
ChangeNotes notes = toNotes(notesResult);
if (notes != null) {
@@ -393,7 +421,9 @@ class DefaultRefFilter {
}
private boolean isMetadata(String name) {
- return RefNames.isRefsChanges(name) || RefNames.isRefsEdit(name);
+ boolean isMetaData = RefNames.isRefsChanges(name) || RefNames.isRefsEdit(name);
+ logger.atFinest().log("ref %s is " + (isMetaData ? "" : "not ") + "a metadata ref", name);
+ return isMetaData;
}
private static boolean isTag(Ref ref) {
diff --git a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
index 5c7ee0d81d..0800d6b696 100644
--- a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.permissions;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.permissions.PermissionBackend.ForProject;
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 7960c65f1e..e0c5927f96 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -23,15 +23,15 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.query.change.ChangeData;
@@ -157,8 +157,8 @@ public abstract class PermissionBackend {
public abstract ForProject project(Project.NameKey project);
/** Returns an instance scoped for the {@code ref}, and its parent project. */
- public ForRef ref(Branch.NameKey ref) {
- return project(ref.getParentKey()).ref(ref.get());
+ public ForRef ref(BranchNameKey ref) {
+ return project(ref.project()).ref(ref.branch());
}
/** Returns an instance scoped for the change, and its destination ref and project. */
@@ -280,7 +280,7 @@ public abstract class PermissionBackend {
/** Returns an instance scoped for the change, and its destination ref and project. */
public ForChange change(ChangeData cd) {
try {
- return ref(cd.change().getDest().get()).change(cd);
+ return ref(cd.change().getDest().branch()).change(cd);
} catch (StorageException e) {
return FailedPermissionBackend.change("unavailable", e);
}
@@ -288,7 +288,7 @@ public abstract class PermissionBackend {
/** Returns an instance scoped for the change, and its destination ref and project. */
public ForChange change(ChangeNotes notes) {
- return ref(notes.getChange().getDest().get()).change(notes);
+ return ref(notes.getChange().getDest().branch()).change(notes);
}
/**
@@ -297,7 +297,7 @@ public abstract class PermissionBackend {
* stale data from the index is acceptable.
*/
public ForChange indexedChange(ChangeData cd, ChangeNotes notes) {
- return ref(notes.getChange().getDest().get()).indexedChange(cd, notes);
+ return ref(notes.getChange().getDest().branch()).indexedChange(cd, notes);
}
/** Verify scoped user can {@code perm}, throwing if denied. */
diff --git a/java/com/google/gerrit/server/permissions/PermissionCollection.java b/java/com/google/gerrit/server/permissions/PermissionCollection.java
index 8ecb274be6..1f0370b74f 100644
--- a/java/com/google/gerrit/server/permissions/PermissionCollection.java
+++ b/java/com/google/gerrit/server/permissions/PermissionCollection.java
@@ -27,12 +27,12 @@ import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.project.RefPatternMatcher.ExpandParameters;
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 20eeaa69dc..c0fbf364de 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -17,23 +17,23 @@ package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.common.data.AccessSection.ALL;
import static com.google.gerrit.common.data.AccessSection.REGEX_PREFIX;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS;
+import static com.google.gerrit.entities.RefNames.REFS_TAGS;
import static com.google.gerrit.server.util.MagicBranch.NEW_CHANGE;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
import com.google.gerrit.extensions.api.access.PluginProjectPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllUsersName;
@@ -122,8 +122,8 @@ class ProjectControl {
return changeControlFactory.create(controlForRef(notes.getChange().getDest()), notes);
}
- RefControl controlForRef(Branch.NameKey ref) {
- return controlForRef(ref.get());
+ RefControl controlForRef(BranchNameKey ref) {
+ return controlForRef(ref.branch());
}
public RefControl controlForRef(String refName) {
diff --git a/java/com/google/gerrit/server/permissions/ProjectPermission.java b/java/com/google/gerrit/server/permissions/ProjectPermission.java
index 653303a29c..fc31e964d9 100644
--- a/java/com/google/gerrit/server/permissions/ProjectPermission.java
+++ b/java/com/google/gerrit/server/permissions/ProjectPermission.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.permissions;
import static java.util.Objects.requireNonNull;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
import com.google.gerrit.extensions.api.access.GerritPermission;
-import com.google.gerrit.reviewdb.client.RefNames;
public enum ProjectPermission implements CoreOrPluginProjectPermission {
/**
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 10ded3c220..945ae06c9d 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -22,12 +22,12 @@ import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.logging.CallerFinder;
@@ -426,7 +426,7 @@ class RefControl {
withForce,
projectControl.getProject().getName(),
refName,
- callerFinder.findCaller());
+ callerFinder.findCallerLazy());
return false;
}
@@ -439,7 +439,7 @@ class RefControl {
withForce,
projectControl.getProject().getName(),
refName,
- callerFinder.findCaller());
+ callerFinder.findCallerLazy());
return true;
}
}
@@ -451,7 +451,7 @@ class RefControl {
withForce,
projectControl.getProject().getName(),
refName,
- callerFinder.findCaller());
+ callerFinder.findCallerLazy());
return false;
}
diff --git a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
index 6db505138a..23a7db19a2 100644
--- a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
+++ b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
@@ -15,16 +15,17 @@
package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.entities.RefNames.REFS_CACHE_AUTOMERGE;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -71,7 +72,7 @@ class RefVisibilityControl {
return projectControl.controlForRef(refName).hasReadPermissionOnRef(false);
}
- if (refName.startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
+ if (refName.startsWith(REFS_CACHE_AUTOMERGE)) {
// Internal cache state that is accessible to no one.
return false;
}
diff --git a/java/com/google/gerrit/server/plugincontext/PluginContext.java b/java/com/google/gerrit/server/plugincontext/PluginContext.java
index 70b23e3b8b..90d56c8903 100644
--- a/java/com/google/gerrit/server/plugincontext/PluginContext.java
+++ b/java/com/google/gerrit/server/plugincontext/PluginContext.java
@@ -30,6 +30,7 @@ import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer3;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -118,25 +119,32 @@ public class PluginContext<T> {
@Inject
PluginMetrics(MetricMaker metricMaker) {
+ Field<String> pluginNameField =
+ Field.ofString("plugin_name", Metadata.Builder::pluginName).build();
+ Field<String> classNameField =
+ Field.ofString("class_name", Metadata.Builder::className).build();
+ Field<String> exportValueField =
+ Field.ofString("export_value", Metadata.Builder::exportValue).build();
+
this.latency =
metricMaker.newTimer(
"plugin/latency",
new Description("Latency for plugin invocation")
.setCumulative()
.setUnit(Units.MILLISECONDS),
- Field.ofString("plugin_name"),
- Field.ofString("class_name"),
- Field.ofString("export_name"));
+ pluginNameField,
+ classNameField,
+ exportValueField);
this.errorCount =
metricMaker.newCounter(
"plugin/error_count",
new Description("Number of plugin errors").setCumulative().setUnit("errors"),
- Field.ofString("plugin_name"),
- Field.ofString("class_name"),
- Field.ofString("export_name"));
+ pluginNameField,
+ classNameField,
+ exportValueField);
}
- Timer3.Context startLatency(Extension<?> extension) {
+ Timer3.Context<String, String, String> startLatency(Extension<?> extension) {
return latency.start(
extension.getPluginName(),
extension.get().getClass().getName(),
@@ -194,7 +202,7 @@ public class PluginContext<T> {
return;
}
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionImplConsumer.run(extensionImpl);
} catch (Throwable e) {
pluginMetrics.incrementErrorCount(extension);
@@ -223,7 +231,7 @@ public class PluginContext<T> {
}
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionConsumer.run(extension);
} catch (Throwable e) {
pluginMetrics.incrementErrorCount(extension);
@@ -257,14 +265,14 @@ public class PluginContext<T> {
}
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionImplConsumer.run(extensionImpl);
} catch (Throwable e) {
Throwables.throwIfInstanceOf(e, exceptionClass);
Throwables.throwIfUnchecked(e);
pluginMetrics.incrementErrorCount(extension);
logger.atWarning().withCause(e).log(
- "Failure in %s of plugin invoke%s", extensionImpl.getClass(), extension.getPluginName());
+ "Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName());
}
}
@@ -294,7 +302,7 @@ public class PluginContext<T> {
}
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
extensionConsumer.run(extension);
} catch (Throwable e) {
Throwables.throwIfInstanceOf(e, exceptionClass);
@@ -320,7 +328,7 @@ public class PluginContext<T> {
Extension<T> extension,
ExtensionImplFunction<T, R> extensionImplFunction) {
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
return extensionImplFunction.call(extension.get());
}
}
@@ -345,7 +353,7 @@ public class PluginContext<T> {
Class<X> exceptionClass)
throws X {
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
try {
return checkedExtensionImplFunction.call(extension.get());
} catch (Exception e) {
@@ -374,7 +382,7 @@ public class PluginContext<T> {
Extension<T> extension,
ExtensionFunction<Extension<T>, R> extensionFunction) {
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
return extensionFunction.call(extension);
}
}
@@ -400,7 +408,7 @@ public class PluginContext<T> {
Class<X> exceptionClass)
throws X {
try (TraceContext traceContext = newTrace(extension);
- Timer3.Context ctx = pluginMetrics.startLatency(extension)) {
+ Timer3.Context<String, String, String> ctx = pluginMetrics.startLatency(extension)) {
try {
return checkedExtensionFunction.call(extension);
} catch (Exception e) {
diff --git a/java/com/google/gerrit/server/plugins/CopyConfigModule.java b/java/com/google/gerrit/server/plugins/CopyConfigModule.java
index 090d257849..9b74341a9d 100644
--- a/java/com/google/gerrit/server/plugins/CopyConfigModule.java
+++ b/java/com/google/gerrit/server/plugins/CopyConfigModule.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.plugins;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.GerritIsReplica;
+import com.google.gerrit.server.config.GerritIsReplicaProvider;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
@@ -101,6 +103,14 @@ class CopyConfigModule extends AbstractModule {
return secureStore;
}
+ @Inject private GerritIsReplicaProvider isReplicaProvider;
+
+ @Provides
+ @GerritIsReplica
+ boolean getIsReplica() {
+ return isReplicaProvider.get();
+ }
+
@Inject
CopyConfigModule() {}
diff --git a/java/com/google/gerrit/server/plugins/DisablePlugin.java b/java/com/google/gerrit/server/plugins/DisablePlugin.java
index 62eb993118..8adae5223c 100644
--- a/java/com/google/gerrit/server/plugins/DisablePlugin.java
+++ b/java/com/google/gerrit/server/plugins/DisablePlugin.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.plugins;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -30,15 +32,20 @@ public class DisablePlugin implements RestModifyView<PluginResource, Input> {
private final PluginLoader loader;
private final PermissionBackend permissionBackend;
+ private final MandatoryPluginsCollection mandatoryPluginsCollection;
@Inject
- DisablePlugin(PluginLoader loader, PermissionBackend permissionBackend) {
+ DisablePlugin(
+ PluginLoader loader,
+ PermissionBackend permissionBackend,
+ MandatoryPluginsCollection mandatoryPluginsCollection) {
this.loader = loader;
this.permissionBackend = permissionBackend;
+ this.mandatoryPluginsCollection = mandatoryPluginsCollection;
}
@Override
- public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ public Response<PluginInfo> apply(PluginResource resource, Input input) throws RestApiException {
try {
permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
} catch (PermissionBackendException e) {
@@ -46,7 +53,10 @@ public class DisablePlugin implements RestModifyView<PluginResource, Input> {
}
loader.checkRemoteAdminEnabled();
String name = resource.getName();
+ if (mandatoryPluginsCollection.contains(name)) {
+ throw new MethodNotAllowedException("Plugin " + name + " is mandatory");
+ }
loader.disablePlugins(ImmutableSet.of(name));
- return ListPlugins.toPluginInfo(loader.get(name));
+ return Response.ok(ListPlugins.toPluginInfo(loader.get(name)));
}
}
diff --git a/java/com/google/gerrit/server/plugins/EnablePlugin.java b/java/com/google/gerrit/server/plugins/EnablePlugin.java
index 569bc39307..b45aaf1f69 100644
--- a/java/com/google/gerrit/server/plugins/EnablePlugin.java
+++ b/java/com/google/gerrit/server/plugins/EnablePlugin.java
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.inject.Inject;
@@ -39,7 +40,7 @@ public class EnablePlugin implements RestModifyView<PluginResource, Input> {
}
@Override
- public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ public Response<PluginInfo> apply(PluginResource resource, Input input) throws RestApiException {
loader.checkRemoteAdminEnabled();
String name = resource.getName();
try {
@@ -52,6 +53,6 @@ public class EnablePlugin implements RestModifyView<PluginResource, Input> {
pw.flush();
throw new ResourceConflictException(buf.toString());
}
- return ListPlugins.toPluginInfo(loader.get(name));
+ return Response.ok(ListPlugins.toPluginInfo(loader.get(name)));
}
}
diff --git a/java/com/google/gerrit/server/plugins/GetStatus.java b/java/com/google/gerrit/server/plugins/GetStatus.java
index cbd864a2ce..5fcc96a526 100644
--- a/java/com/google/gerrit/server/plugins/GetStatus.java
+++ b/java/com/google/gerrit/server/plugins/GetStatus.java
@@ -15,13 +15,14 @@
package com.google.gerrit.server.plugins;
import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.inject.Singleton;
@Singleton
public class GetStatus implements RestReadView<PluginResource> {
@Override
- public PluginInfo apply(PluginResource resource) {
- return ListPlugins.toPluginInfo(resource.getPlugin());
+ public Response<PluginInfo> apply(PluginResource resource) {
+ return Response.ok(ListPlugins.toPluginInfo(resource.getPlugin()));
}
}
diff --git a/java/com/google/gerrit/server/plugins/ListPlugins.java b/java/com/google/gerrit/server/plugins/ListPlugins.java
index 438a0c4a10..0408efc1b8 100644
--- a/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -23,6 +23,7 @@ import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.plugins.Plugins;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
@@ -111,7 +112,8 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
}
@Override
- public SortedMap<String, PluginInfo> apply(TopLevelResource resource) throws BadRequestException {
+ public Response<SortedMap<String, PluginInfo>> apply(TopLevelResource resource)
+ throws BadRequestException {
Stream<Plugin> s = Streams.stream(pluginLoader.getPlugins(all));
if (matchPrefix != null) {
checkMatchOptions(matchSubstring == null && matchRegex == null);
@@ -132,7 +134,7 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
if (limit > 0) {
s = s.limit(limit);
}
- return new TreeMap<>(s.collect(toMap(Plugin::getName, ListPlugins::toPluginInfo)));
+ return Response.ok(new TreeMap<>(s.collect(toMap(Plugin::getName, ListPlugins::toPluginInfo))));
}
private void checkMatchOptions(boolean cond) throws BadRequestException {
diff --git a/java/com/google/gerrit/server/plugins/MandatoryPluginsCollection.java b/java/com/google/gerrit/server/plugins/MandatoryPluginsCollection.java
new file mode 100644
index 0000000000..70a0fff640
--- /dev/null
+++ b/java/com/google/gerrit/server/plugins/MandatoryPluginsCollection.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+public class MandatoryPluginsCollection {
+ private final CopyOnWriteArraySet<String> members;
+
+ @Inject
+ MandatoryPluginsCollection(@GerritServerConfig Config cfg) {
+ members = Sets.newCopyOnWriteArraySet();
+ members.addAll(Arrays.asList(cfg.getStringList("plugins", null, "mandatory")));
+ }
+
+ public boolean contains(String name) {
+ return members.contains(name);
+ }
+
+ public Set<String> asSet() {
+ return ImmutableSet.copyOf(members);
+ }
+
+ @VisibleForTesting
+ public void add(String name) {
+ members.add(name);
+ }
+}
diff --git a/java/com/google/gerrit/server/plugins/MissingMandatoryPluginsException.java b/java/com/google/gerrit/server/plugins/MissingMandatoryPluginsException.java
new file mode 100644
index 0000000000..1c23550261
--- /dev/null
+++ b/java/com/google/gerrit/server/plugins/MissingMandatoryPluginsException.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import java.util.Collection;
+
+/** Raised when one or more mandatory plugins are missing. */
+public class MissingMandatoryPluginsException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public MissingMandatoryPluginsException(Collection<String> pluginNames) {
+ super(getMessage(pluginNames));
+ }
+
+ private static String getMessage(Collection<String> pluginNames) {
+ return String.format("Cannot find or load the following mandatory plugins: %s", pluginNames);
+ }
+}
diff --git a/java/com/google/gerrit/server/plugins/PluginLoader.java b/java/com/google/gerrit/server/plugins/PluginLoader.java
index 9279f0fee4..c4f4a1f22f 100644
--- a/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -52,6 +52,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -87,6 +88,7 @@ public class PluginLoader implements LifecycleListener {
private final Provider<String> urlProvider;
private final PersistentCacheFactory persistentCacheFactory;
private final boolean remoteAdmin;
+ private final MandatoryPluginsCollection mandatoryPlugins;
private final UniversalServerPluginProvider serverPluginFactory;
private final GerritRuntime gerritRuntime;
@@ -101,6 +103,7 @@ public class PluginLoader implements LifecycleListener {
@CanonicalWebUrl Provider<String> provider,
PersistentCacheFactory cacheFactory,
UniversalServerPluginProvider pluginFactory,
+ MandatoryPluginsCollection mpc,
GerritRuntime gerritRuntime) {
pluginsDir = sitePaths.plugins_dir;
dataDir = sitePaths.data_dir;
@@ -114,6 +117,7 @@ public class PluginLoader implements LifecycleListener {
serverPluginFactory = pluginFactory;
remoteAdmin = cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
+ mandatoryPlugins = mpc;
this.gerritRuntime = gerritRuntime;
long checkFrequency =
@@ -226,6 +230,11 @@ public class PluginLoader implements LifecycleListener {
continue;
}
+ if (mandatoryPlugins.contains(name)) {
+ logger.atWarning().log("Mandatory plugin %s cannot be disabled", name);
+ continue;
+ }
+
logger.atInfo().log("Disabling plugin %s", active.getName());
Path off =
active.getSrcFile().resolveSibling(active.getSrcFile().getFileName() + ".disabled");
@@ -381,52 +390,59 @@ public class PluginLoader implements LifecycleListener {
}
public synchronized void rescan() {
+ Set<String> loadedPlugins = new HashSet<>();
SetMultimap<String, Path> pluginsFiles = prunePlugins(pluginsDir);
- if (pluginsFiles.isEmpty()) {
- return;
- }
- syncDisabledPlugins(pluginsFiles);
-
- Map<String, Path> activePlugins = filterDisabled(pluginsFiles);
- for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
- String name = entry.getKey();
- Path path = entry.getValue();
- String fileName = path.getFileName().toString();
- if (!isUiPlugin(fileName) && !serverPluginFactory.handles(path)) {
- logger.atWarning().log(
- "No Plugin provider was found that handles this file format: %s", fileName);
- continue;
- }
+ if (!pluginsFiles.isEmpty()) {
+ syncDisabledPlugins(pluginsFiles);
+
+ Map<String, Path> activePlugins = filterDisabled(pluginsFiles);
+ for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
+ String name = entry.getKey();
+ Path path = entry.getValue();
+ String fileName = path.getFileName().toString();
+ if (!isUiPlugin(fileName) && !serverPluginFactory.handles(path)) {
+ logger.atWarning().log(
+ "No Plugin provider was found that handles this file format: %s", fileName);
+ continue;
+ }
- FileSnapshot brokenTime = broken.get(name);
- if (brokenTime != null && !brokenTime.isModified(path.toFile())) {
- continue;
- }
+ FileSnapshot brokenTime = broken.get(name);
+ if (brokenTime != null && !brokenTime.isModified(path.toFile())) {
+ continue;
+ }
- Plugin active = running.get(name);
- if (active != null && !active.isModified(path)) {
- continue;
- }
+ Plugin active = running.get(name);
+ if (active != null && !active.isModified(path)) {
+ loadedPlugins.add(name);
+ continue;
+ }
- if (active != null) {
- logger.atInfo().log("Reloading plugin %s", active.getName());
- }
+ if (active != null) {
+ logger.atInfo().log("Reloading plugin %s", active.getName());
+ }
- try {
- Plugin loadedPlugin = runPlugin(name, path, active);
- if (!loadedPlugin.isDisabled()) {
- logger.atInfo().log(
- "%s plugin %s, version %s",
- active == null ? "Loaded" : "Reloaded",
- loadedPlugin.getName(),
- loadedPlugin.getVersion());
+ try {
+ Plugin loadedPlugin = runPlugin(name, path, active);
+ if (!loadedPlugin.isDisabled()) {
+ loadedPlugins.add(name);
+ logger.atInfo().log(
+ "%s plugin %s, version %s",
+ active == null ? "Loaded" : "Reloaded",
+ loadedPlugin.getName(),
+ loadedPlugin.getVersion());
+ }
+ } catch (PluginInstallException e) {
+ logger.atWarning().withCause(e.getCause()).log("Cannot load plugin %s", name);
}
- } catch (PluginInstallException e) {
- logger.atWarning().withCause(e.getCause()).log("Cannot load plugin %s", name);
}
}
+ Set<String> missingMandatory = Sets.difference(mandatoryPlugins.asSet(), loadedPlugins);
+ if (!missingMandatory.isEmpty()) {
+ throw new MissingMandatoryPluginsException(missingMandatory);
+ }
+
cleanInBackground();
}
@@ -471,6 +487,12 @@ public class PluginLoader implements LifecycleListener {
throws PluginInstallException {
FileSnapshot snapshot = FileSnapshot.save(plugin.toFile());
try {
+ boolean restartRequired = oldPlugin != null && !oldPlugin.canReload();
+ if (restartRequired && mandatoryPlugins.contains(name)) {
+ logger.atWarning().log("Restarting mandatory plugin %s not allowed", name);
+ return oldPlugin;
+ }
+
Plugin newPlugin = loadPlugin(name, plugin, snapshot);
if (newPlugin.getCleanupHandle() != null) {
cleanupHandles.put(newPlugin, newPlugin.getCleanupHandle());
diff --git a/java/com/google/gerrit/server/plugins/PluginModule.java b/java/com/google/gerrit/server/plugins/PluginModule.java
index 6bc37bd792..71186e51e7 100644
--- a/java/com/google/gerrit/server/plugins/PluginModule.java
+++ b/java/com/google/gerrit/server/plugins/PluginModule.java
@@ -34,6 +34,7 @@ public class PluginModule extends LifecycleModule {
bind(PluginLoader.class);
bind(CopyConfigModule.class);
listener().to(PluginLoader.class);
+ bind(MandatoryPluginsCollection.class);
DynamicSet.setOf(binder(), ServerPluginProvider.class);
DynamicSet.bind(binder(), ServerPluginProvider.class).to(JarPluginProvider.class);
diff --git a/java/com/google/gerrit/server/plugins/ReloadPlugin.java b/java/com/google/gerrit/server/plugins/ReloadPlugin.java
index 1134f50800..490c4aab25 100644
--- a/java/com/google/gerrit/server/plugins/ReloadPlugin.java
+++ b/java/com/google/gerrit/server/plugins/ReloadPlugin.java
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -38,7 +39,8 @@ public class ReloadPlugin implements RestModifyView<PluginResource, Input> {
}
@Override
- public PluginInfo apply(PluginResource resource, Input input) throws ResourceConflictException {
+ public Response<PluginInfo> apply(PluginResource resource, Input input)
+ throws ResourceConflictException {
String name = resource.getName();
try {
loader.reload(ImmutableList.of(name));
@@ -52,6 +54,6 @@ public class ReloadPlugin implements RestModifyView<PluginResource, Input> {
pw.flush();
throw new ResourceConflictException(buf.toString());
}
- return ListPlugins.toPluginInfo(loader.get(name));
+ return Response.ok(ListPlugins.toPluginInfo(loader.get(name)));
}
}
diff --git a/java/com/google/gerrit/server/project/AccessControlModule.java b/java/com/google/gerrit/server/project/AccessControlModule.java
index 6d772676f8..89ab8ee27d 100644
--- a/java/com/google/gerrit/server/project/AccessControlModule.java
+++ b/java/com/google/gerrit/server/project/AccessControlModule.java
@@ -18,8 +18,8 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.AdministrateServerGroups;
import com.google.gerrit.server.config.AdministrateServerGroupsProvider;
import com.google.gerrit.server.config.GitReceivePackGroups;
diff --git a/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java b/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
index dc9cf9cafd..ae9828aaa8 100644
--- a/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
+++ b/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
@@ -16,11 +16,11 @@ package com.google.gerrit.server.project;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInfo.InheritedBooleanInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import java.util.Arrays;
import java.util.HashSet;
diff --git a/java/com/google/gerrit/server/project/BranchResource.java b/java/com/google/gerrit/server/project/BranchResource.java
index 622b1ddad4..a8936ac220 100644
--- a/java/com/google/gerrit/server/project/BranchResource.java
+++ b/java/com/google/gerrit/server/project/BranchResource.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.Ref;
@@ -33,8 +33,8 @@ public class BranchResource extends RefResource {
this.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
}
- public Branch.NameKey getBranchKey() {
- return new Branch.NameKey(getNameKey(), refName);
+ public BranchNameKey getBranchKey() {
+ return BranchNameKey.create(getNameKey(), refName);
}
@Override
diff --git a/java/com/google/gerrit/server/project/ChildProjects.java b/java/com/google/gerrit/server/project/ChildProjects.java
index ce9992eeba..2069a48185 100644
--- a/java/com/google/gerrit/server/project/ChildProjects.java
+++ b/java/com/google/gerrit/server/project/ChildProjects.java
@@ -18,8 +18,8 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ProjectInfo;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
index b33fcb5c9c..a2de4ef704 100644
--- a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
+++ b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
@@ -20,15 +20,15 @@ import static java.util.Objects.requireNonNull;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.UrlFormatter;
@@ -117,7 +117,7 @@ public class ContributorAgreementsChecker {
if ((rule.getAction() == Action.ALLOW)
&& (rule.getGroup() != null)
&& (rule.getGroup().getUUID() != null)) {
- groupIds.add(new AccountGroup.UUID(rule.getGroup().getUUID().get()));
+ groupIds.add(AccountGroup.uuid(rule.getGroup().getUUID().get()));
}
}
}
diff --git a/java/com/google/gerrit/server/project/CreateProjectArgs.java b/java/com/google/gerrit/server/project/CreateProjectArgs.java
index df31c193d0..c1b7b86987 100644
--- a/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.List;
public class CreateProjectArgs {
@@ -61,7 +61,7 @@ public class CreateProjectArgs {
}
public void setProjectName(String n) {
- projectName = n != null ? new Project.NameKey(n) : null;
+ projectName = n != null ? Project.nameKey(n) : null;
}
public void setProjectName(Project.NameKey n) {
diff --git a/java/com/google/gerrit/server/project/CreateRefControl.java b/java/com/google/gerrit/server/project/CreateRefControl.java
index 7a7598bf89..abbd9f6e64 100644
--- a/java/com/google/gerrit/server/project/CreateRefControl.java
+++ b/java/com/google/gerrit/server/project/CreateRefControl.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.project;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -65,15 +65,12 @@ public class CreateRefControl {
* @throws ResourceConflictException if the project state does not permit the operation
*/
public void checkCreateRef(
- Provider<? extends CurrentUser> user,
- Repository repo,
- Branch.NameKey branch,
- RevObject object)
+ Provider<? extends CurrentUser> user, Repository repo, BranchNameKey branch, RevObject object)
throws AuthException, PermissionBackendException, NoSuchProjectException, IOException,
ResourceConflictException {
- ProjectState ps = projectCache.checkedGet(branch.getParentKey());
+ ProjectState ps = projectCache.checkedGet(branch.project());
if (ps == null) {
- throw new NoSuchProjectException(branch.getParentKey());
+ throw new NoSuchProjectException(branch.project());
}
ps.checkStatePermitsWrite();
@@ -86,8 +83,7 @@ public class CreateRefControl {
try (RevWalk rw = new RevWalk(repo)) {
rw.parseBody(tag);
} catch (IOException e) {
- logger.atSevere().withCause(e).log(
- "RevWalk(%s) parsing %s:", branch.getParentKey(), tag.name());
+ logger.atSevere().withCause(e).log("RevWalk(%s) parsing %s:", branch.project(), tag.name());
throw e;
}
diff --git a/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java b/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java
index 3fb5d2af7c..000fb094d2 100644
--- a/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java
+++ b/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.project;
import com.google.common.util.concurrent.Striped;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import java.util.concurrent.locks.Lock;
diff --git a/java/com/google/gerrit/server/project/GroupList.java b/java/com/google/gerrit/server/project/GroupList.java
index fdb87406fb..fe59012889 100644
--- a/java/com/google/gerrit/server/project/GroupList.java
+++ b/java/com/google/gerrit/server/project/GroupList.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.project;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.TabFile;
import java.io.IOException;
@@ -48,7 +48,7 @@ public class GroupList extends TabFile {
logger.atWarning().log("null field in group list for %s:\n%s", project, text);
continue;
}
- AccountGroup.UUID uuid = new AccountGroup.UUID(row.left);
+ AccountGroup.UUID uuid = AccountGroup.uuid(row.left);
String name = row.right;
GroupReference ref = new GroupReference(uuid, name);
diff --git a/java/com/google/gerrit/server/project/NoSuchChangeException.java b/java/com/google/gerrit/server/project/NoSuchChangeException.java
index 6f65659f04..93cf03a5aa 100644
--- a/java/com/google/gerrit/server/project/NoSuchChangeException.java
+++ b/java/com/google/gerrit/server/project/NoSuchChangeException.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Change;
/** Indicates the change does not exist. */
public class NoSuchChangeException extends StorageException {
diff --git a/java/com/google/gerrit/server/project/NoSuchProjectException.java b/java/com/google/gerrit/server/project/NoSuchProjectException.java
index 23d8d80b95..d4a8a5ca41 100644
--- a/java/com/google/gerrit/server/project/NoSuchProjectException.java
+++ b/java/com/google/gerrit/server/project/NoSuchProjectException.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
/** Indicates the project does not exist. */
public class NoSuchProjectException extends Exception {
diff --git a/java/com/google/gerrit/server/project/ProjectCache.java b/java/com/google/gerrit/server/project/ProjectCache.java
index 17250fa2de..0baaa11ecc 100644
--- a/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/java/com/google/gerrit/server/project/ProjectCache.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.project;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import java.io.IOException;
import java.util.Set;
@@ -35,7 +35,7 @@ public interface ProjectCache {
* @param projectName name of the project.
* @return the cached data; null if no such project exists, projectName is null or an error
* occurred.
- * @see #checkedGet(com.google.gerrit.reviewdb.client.Project.NameKey)
+ * @see #checkedGet(com.google.gerrit.entities.Project.NameKey)
*/
ProjectState get(@Nullable Project.NameKey projectName);
@@ -57,7 +57,7 @@ public interface ProjectCache {
* errors.
* @return the cached data or null when strict = false
*/
- public ProjectState checkedGet(Project.NameKey projectName, boolean strict) throws Exception;
+ ProjectState checkedGet(Project.NameKey projectName, boolean strict) throws Exception;
/**
* Invalidate the cached information about the given project, and triggers reindexing for it
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index bd26a8fa56..e88bfc63ec 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -24,18 +24,19 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.inject.Inject;
@@ -262,8 +263,8 @@ public class ProjectCacheImpl implements ProjectCache {
@Override
public ImmutableSortedSet<Project.NameKey> byName(String pfx) {
- Project.NameKey start = new Project.NameKey(pfx);
- Project.NameKey end = new Project.NameKey(pfx + Character.MAX_VALUE);
+ Project.NameKey start = Project.nameKey(pfx);
+ Project.NameKey end = Project.nameKey(pfx + Character.MAX_VALUE);
try {
// Right endpoint is exclusive, but U+FFFF is a non-character so no project ends with it.
return list.get(ListKey.ALL).subSet(start, end);
@@ -293,9 +294,11 @@ public class ProjectCacheImpl implements ProjectCache {
@Override
public ProjectState load(String projectName) throws Exception {
- try (TraceTimer timer = TraceContext.newTimer("Loading project %s", projectName)) {
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "Loading project", Metadata.builder().projectName(projectName).build())) {
long now = clock.read();
- Project.NameKey key = new Project.NameKey(projectName);
+ Project.NameKey key = Project.nameKey(projectName);
try (Repository git = mgr.openRepository(key)) {
ProjectConfig cfg = projectConfigFactory.create(key);
cfg.load(key, git);
diff --git a/java/com/google/gerrit/server/project/ProjectCacheWarmer.java b/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
index 10cf2de23a..d1f31a3729 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheWarmer.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.project;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 09279fabb5..e6b8d44fa1 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.common.data.Permission.isPermission;
-import static com.google.gerrit.reviewdb.client.Project.DEFAULT_SUBMIT_TYPE;
+import static com.google.gerrit.entities.Project.DEFAULT_SUBMIT_TYPE;
import static com.google.gerrit.server.permissions.PluginPermissionsUtil.isValidPluginPermission;
import static java.util.stream.Collectors.toList;
@@ -26,6 +26,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.primitives.Shorts;
import com.google.gerrit.common.Nullable;
@@ -42,15 +43,15 @@ import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
import com.google.gerrit.common.data.SubscribeSection;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.config.AllProjectsName;
@@ -100,6 +101,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
public static final String KEY_COPY_MIN_SCORE = "copyMinScore";
public static final String KEY_ALLOW_POST_SUBMIT = "allowPostSubmit";
public static final String KEY_IGNORE_SELF_APPROVAL = "ignoreSelfApproval";
+ public static final String KEY_COPY_ANY_SCORE = "copyAnyScore";
public static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
public static final String KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE =
"copyAllScoresOnMergeFirstParentUpdate";
@@ -337,6 +339,10 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
return as;
}
+ public ImmutableSet<String> getAccessSectionNames() {
+ return ImmutableSet.copyOf(accessSections.keySet());
+ }
+
public Collection<AccessSection> getAccessSections() {
return sort(accessSections.values());
}
@@ -349,7 +355,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
return subscribeSections;
}
- public Collection<SubscribeSection> getSubscribeSections(Branch.NameKey branch) {
+ public Collection<SubscribeSection> getSubscribeSections(BranchNameKey branch) {
Collection<SubscribeSection> ret = new ArrayList<>();
for (SubscribeSection s : subscribeSections.values()) {
if (s.appliesTo(branch)) {
@@ -717,7 +723,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
if (groupName != null) {
GroupReference ref = groupsByName.get(groupName);
if (ref == null) {
- ref = new GroupReference(null, groupName);
+ ref = new GroupReference(groupName);
groupsByName.put(ref.getName(), ref);
}
if (ref.getUUID() != null) {
@@ -972,6 +978,8 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
rc.getBoolean(LABEL, name, KEY_ALLOW_POST_SUBMIT, LabelType.DEF_ALLOW_POST_SUBMIT));
label.setIgnoreSelfApproval(
rc.getBoolean(LABEL, name, KEY_IGNORE_SELF_APPROVAL, LabelType.DEF_IGNORE_SELF_APPROVAL));
+ label.setCopyAnyScore(
+ rc.getBoolean(LABEL, name, KEY_COPY_ANY_SCORE, LabelType.DEF_COPY_ANY_SCORE));
label.setCopyMinScore(
rc.getBoolean(LABEL, name, KEY_COPY_MIN_SCORE, LabelType.DEF_COPY_MIN_SCORE));
label.setCopyMaxScore(
@@ -1051,7 +1059,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
subscribeSections = new HashMap<>();
try {
for (String projectName : subsections) {
- Project.NameKey p = new Project.NameKey(projectName);
+ Project.NameKey p = Project.nameKey(projectName);
SubscribeSection ss = new SubscribeSection(p);
for (String s :
rc.getStringList(SUBSCRIBE_SECTION, projectName, SUBSCRIBE_MULTI_MATCH_REFS)) {
@@ -1203,6 +1211,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void saveAccountsSection(Config rc, Set<AccountGroup.UUID> keepGroups) {
+ unsetSection(rc, ACCOUNTS);
if (accountsSection != null) {
rc.setStringList(
ACCOUNTS,
@@ -1213,6 +1222,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void saveCommentLinkSections(Config rc) {
+ unsetSection(rc, COMMENTLINK);
if (commentLinkSections != null) {
for (CommentLinkInfoImpl cm : commentLinkSections.values()) {
rc.setString(COMMENTLINK, cm.name, KEY_MATCH, cm.match);
@@ -1230,6 +1240,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void saveContributorAgreements(Config rc, Set<AccountGroup.UUID> keepGroups) {
+ unsetSection(rc, CONTRIBUTOR_AGREEMENT);
for (ContributorAgreement ca : sort(contributorAgreements.values())) {
set(rc, CONTRIBUTOR_AGREEMENT, ca.getName(), KEY_DESCRIPTION, ca.getDescription());
set(rc, CONTRIBUTOR_AGREEMENT, ca.getName(), KEY_AGREEMENT_URL, ca.getAgreementUrl());
@@ -1263,6 +1274,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void saveNotifySections(Config rc, Set<AccountGroup.UUID> keepGroups) {
+ unsetSection(rc, NOTIFY);
for (NotifyConfig nc : sort(notifySections.values())) {
nc.getGroups().stream()
.map(GroupReference::getUUID)
@@ -1317,6 +1329,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void saveAccessSections(Config rc, Set<AccountGroup.UUID> keepGroups) {
+ unsetSection(rc, CAPABILITY);
AccessSection capability = accessSections.get(AccessSection.GLOBAL_CAPABILITIES);
if (capability != null) {
Set<String> have = new HashSet<>();
@@ -1399,9 +1412,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
List<String> existing = new ArrayList<>(rc.getSubsections(LABEL));
if (!new ArrayList<>(labelSections.keySet()).equals(existing)) {
// Order of sections changed, remove and rewrite them all.
- for (String name : existing) {
- rc.unsetSection(LABEL, name);
- }
+ unsetSection(rc, LABEL);
}
Set<String> toUnset = new HashSet<>(existing);
@@ -1430,6 +1441,13 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
rc,
LABEL,
name,
+ KEY_COPY_ANY_SCORE,
+ label.isCopyAnyScore(),
+ LabelType.DEF_COPY_ANY_SCORE);
+ setBooleanConfigKey(
+ rc,
+ LABEL,
+ name,
KEY_COPY_MIN_SCORE,
label.isCopyMinScore(),
LabelType.DEF_COPY_MIN_SCORE);
@@ -1497,11 +1515,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
private void savePluginSections(Config rc, Set<AccountGroup.UUID> keepGroups) {
- List<String> existing = new ArrayList<>(rc.getSubsections(PLUGIN));
- for (String name : existing) {
- rc.unsetSection(PLUGIN, name);
- }
-
+ unsetSection(rc, PLUGIN);
for (Map.Entry<String, Config> e : pluginConfigs.entrySet()) {
String plugin = e.getKey();
Config pluginConfig = e.getValue();
@@ -1542,6 +1556,13 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
}
}
+ private void unsetSection(Config rc, String sectionName) {
+ for (String subSectionName : rc.getSubsections(sectionName)) {
+ rc.unsetSection(sectionName, subSectionName);
+ }
+ rc.unsetSection(sectionName, null);
+ }
+
private <E extends Enum<?>> E getEnum(
Config rc, String section, String subsection, String name, E defaultValue) {
try {
diff --git a/java/com/google/gerrit/server/project/ProjectCreator.java b/java/com/google/gerrit/server/project/ProjectCreator.java
index 35ecd7cff8..c9eb73e266 100644
--- a/java/com/google/gerrit/server/project/ProjectCreator.java
+++ b/java/com/google/gerrit/server/project/ProjectCreator.java
@@ -21,13 +21,13 @@ import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
diff --git a/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java b/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
index 27bde7205a..694c5417f2 100644
--- a/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
+++ b/java/com/google/gerrit/server/project/ProjectHierarchyIterator.java
@@ -18,7 +18,7 @@ import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import java.util.Iterator;
import java.util.List;
diff --git a/java/com/google/gerrit/server/project/ProjectJson.java b/java/com/google/gerrit/server/project/ProjectJson.java
index 449f6073f2..fab6c11fa6 100644
--- a/java/com/google/gerrit/server/project/ProjectJson.java
+++ b/java/com/google/gerrit/server/project/ProjectJson.java
@@ -17,20 +17,20 @@ package com.google.gerrit.server.project;
import static java.util.stream.Collectors.toMap;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.LabelTypeInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.HashMap;
-import java.util.List;
@Singleton
public class ProjectJson {
@@ -77,7 +77,7 @@ public class ProjectJson {
info.description = Strings.emptyToNull(p.getDescription());
info.state = p.getState();
info.id = Url.encode(info.name);
- List<WebLinkInfo> links = webLinks.getProjectLinks(p.getName());
+ ImmutableList<WebLinkInfo> links = webLinks.getProjectLinks(p.getName());
info.webLinks = links.isEmpty() ? null : links;
return info;
}
diff --git a/java/com/google/gerrit/server/project/ProjectLevelConfig.java b/java/com/google/gerrit/server/project/ProjectLevelConfig.java
index 961d1fcaf6..4e0261ccaf 100644
--- a/java/com/google/gerrit/server/project/ProjectLevelConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectLevelConfig.java
@@ -18,7 +18,7 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import java.util.Arrays;
diff --git a/java/com/google/gerrit/server/project/ProjectNameLockManager.java b/java/com/google/gerrit/server/project/ProjectNameLockManager.java
index 4666c32fa0..72036a7456 100644
--- a/java/com/google/gerrit/server/project/ProjectNameLockManager.java
+++ b/java/com/google/gerrit/server/project/ProjectNameLockManager.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.project;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.util.concurrent.locks.Lock;
public interface ProjectNameLockManager {
diff --git a/java/com/google/gerrit/server/project/ProjectResource.java b/java/com/google/gerrit/server/project/ProjectResource.java
index 22b7bd989e..8802758949 100644
--- a/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/java/com/google/gerrit/server/project/ProjectResource.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 0e3a94027d..4b879a1865 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -28,6 +28,11 @@ import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.SubscribeSection;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -37,17 +42,13 @@ import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.BranchOrderSection;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -69,7 +70,7 @@ import org.eclipse.jgit.lib.Repository;
/**
* Cached information on a project. Must not contain any data derived from parents other than it's
- * immediate parent's {@link com.google.gerrit.reviewdb.client.Project.NameKey}.
+ * immediate parent's {@link com.google.gerrit.entities.Project.NameKey}.
*/
public class ProjectState {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -135,7 +136,7 @@ public class ProjectState {
new Description("Latency for access computations in ProjectState")
.setCumulative()
.setUnit(Units.NANOSECONDS),
- Field.ofString("method"));
+ Field.ofString("method", Metadata.Builder::methodName).build());
if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
localOwners = Collections.emptySet();
@@ -354,14 +355,15 @@ public class ProjectState {
* cached. Callers should try to cache this result per-request as much as possible.
*/
public List<SectionMatcher> getAllSections() {
- try (Timer1.Context ignored = computationLatency.start("getAllSections")) {
+ try (Timer1.Context<String> ignored = computationLatency.start("getAllSections")) {
if (isAllProjects) {
return getLocalAccessSections();
}
List<SectionMatcher> all = new ArrayList<>();
Iterable<ProjectState> tree = tree();
- try (Timer1.Context ignored2 = computationLatency.start("getAllSections-parsing-only")) {
+ try (Timer1.Context<String> ignored2 =
+ computationLatency.start("getAllSections-parsing-only")) {
for (ProjectState s : tree) {
all.addAll(s.getLocalAccessSections());
}
@@ -476,7 +478,7 @@ public class ProjectState {
}
/** All available label types for this branch. */
- public LabelTypes getLabelTypes(Branch.NameKey destination) {
+ public LabelTypes getLabelTypes(BranchNameKey destination) {
List<LabelType> all = getLabelTypes().getLabelTypes();
List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
@@ -537,7 +539,7 @@ public class ProjectState {
return null;
}
- public Collection<SubscribeSection> getSubscribeSections(Branch.NameKey branch) {
+ public Collection<SubscribeSection> getSubscribeSections(BranchNameKey branch) {
Collection<SubscribeSection> ret = new ArrayList<>();
for (ProjectState s : tree()) {
ret.addAll(s.getConfig().getSubscribeSections(branch));
@@ -584,7 +586,7 @@ public class ProjectState {
return project;
}
- private boolean match(Branch.NameKey destination, String refPattern) {
- return RefPatternMatcher.getMatcher(refPattern).match(destination.get(), null);
+ private boolean match(BranchNameKey destination, String refPattern) {
+ return RefPatternMatcher.getMatcher(refPattern).match(destination.branch(), null);
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
index 17fd69c4f8..a3b4126c55 100644
--- a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
@@ -24,6 +24,10 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.api.projects.CheckProjectInput;
@@ -38,9 +42,6 @@ import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
@@ -225,7 +226,7 @@ public class ProjectsConsistencyChecker {
// important thing for callers is that auto-closable changes are closed. Which of the
// commits is used to auto-close a change if there are several candidates is of minor
// importance and hence can be non-deterministic.
- Change.Key changeKey = new Change.Key(changeId);
+ Change.Key changeKey = Change.key(changeId);
if (!changeIdToMergedSha1.containsKey(changeKey)) {
changeIdToMergedSha1.put(changeKey, commitId);
}
@@ -295,7 +296,7 @@ public class ProjectsConsistencyChecker {
// Auto-close by commit
for (ObjectId patchSetSha1 :
autoCloseableChange.patchSets().stream()
- .map(ps -> ObjectId.fromString(ps.getRevision().get()))
+ .map(PatchSet::commitId)
.collect(toSet())) {
if (mergedSha1s.contains(patchSetSha1)) {
autoCloseableChangesByBranch.add(
diff --git a/java/com/google/gerrit/server/project/Reachable.java b/java/com/google/gerrit/server/project/Reachable.java
index 93cbf4b1f7..b12f43b286 100644
--- a/java/com/google/gerrit/server/project/Reachable.java
+++ b/java/com/google/gerrit/server/project/Reachable.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.project;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.IncludedInResolver;
import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/project/RefFilter.java b/java/com/google/gerrit/server/project/RefFilter.java
index 76bafc0113..cdabcbe064 100644
--- a/java/com/google/gerrit/server/project/RefFilter.java
+++ b/java/com/google/gerrit/server/project/RefFilter.java
@@ -14,15 +14,17 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Predicate;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.common.base.Strings;
-import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.api.projects.RefInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Stream;
public class RefFilter<T extends RefInfo> {
private final String prefix;
@@ -55,15 +57,17 @@ public class RefFilter<T extends RefInfo> {
return this;
}
- public List<T> filter(List<T> refs) throws BadRequestException {
+ public ImmutableList<T> filter(List<T> refs) throws BadRequestException {
if (!Strings.isNullOrEmpty(matchSubstring) && !Strings.isNullOrEmpty(matchRegex)) {
throw new BadRequestException("specify exactly one of m/r");
}
- FluentIterable<T> results = FluentIterable.from(refs);
+ Stream<T> results = refs.stream();
if (!Strings.isNullOrEmpty(matchSubstring)) {
- results = results.filter(new SubstringPredicate(matchSubstring));
+ String lowercaseSubstring = matchSubstring.toLowerCase(Locale.US);
+ results = results.filter(refInfo -> matchesSubstring(prefix, lowercaseSubstring, refInfo));
} else if (!Strings.isNullOrEmpty(matchRegex)) {
- results = results.filter(new RegexPredicate(matchRegex));
+ RunAutomaton a = parseRegex(matchRegex);
+ results = results.filter(refInfo -> matchesRegex(prefix, a, refInfo));
}
if (start > 0) {
results = results.skip(start);
@@ -71,51 +75,39 @@ public class RefFilter<T extends RefInfo> {
if (limit > 0) {
results = results.limit(limit);
}
- return results.toList();
+ return results.collect(toImmutableList());
}
- private class SubstringPredicate implements Predicate<T> {
- private final String substring;
-
- private SubstringPredicate(String substring) {
- this.substring = substring.toLowerCase(Locale.US);
- }
-
- @Override
- public boolean apply(T in) {
- String ref = in.ref;
- if (ref.startsWith(prefix)) {
- ref = ref.substring(prefix.length());
- }
- ref = ref.toLowerCase(Locale.US);
- return ref.contains(substring);
+ private static <T extends RefInfo> boolean matchesSubstring(
+ String prefix, String lowercaseSubstring, T refInfo) {
+ String ref = refInfo.ref;
+ if (ref.startsWith(prefix)) {
+ ref = ref.substring(prefix.length());
}
+ ref = ref.toLowerCase(Locale.US);
+ return ref.contains(lowercaseSubstring);
}
- private class RegexPredicate implements Predicate<T> {
- private final RunAutomaton a;
-
- private RegexPredicate(String regex) throws BadRequestException {
- if (regex.startsWith("^")) {
- regex = regex.substring(1);
- if (regex.endsWith("$") && !regex.endsWith("\\$")) {
- regex = regex.substring(0, regex.length() - 1);
- }
- }
- try {
- a = new RunAutomaton(new RegExp(regex).toAutomaton());
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage());
+ private static RunAutomaton parseRegex(String regex) throws BadRequestException {
+ if (regex.startsWith("^")) {
+ regex = regex.substring(1);
+ if (regex.endsWith("$") && !regex.endsWith("\\$")) {
+ regex = regex.substring(0, regex.length() - 1);
}
}
+ try {
+ return new RunAutomaton(new RegExp(regex).toAutomaton());
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
- @Override
- public boolean apply(T in) {
- String ref = in.ref;
- if (ref.startsWith(prefix)) {
- ref = ref.substring(prefix.length());
- }
- return a.run(ref);
+ private static <T extends RefInfo> boolean matchesRegex(
+ String prefix, RunAutomaton a, T refInfo) {
+ String ref = refInfo.ref;
+ if (ref.startsWith(prefix)) {
+ ref = ref.substring(prefix.length());
}
+ return a.run(ref);
}
}
diff --git a/java/com/google/gerrit/server/project/RefPatternMatcher.java b/java/com/google/gerrit/server/project/RefPatternMatcher.java
index 4032524296..b9076b3db6 100644
--- a/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -22,8 +22,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.CurrentUser;
import dk.brics.automaton.Automaton;
import java.util.HashMap;
diff --git a/java/com/google/gerrit/server/project/RefUtil.java b/java/com/google/gerrit/server/project/RefUtil.java
index a9c964dd51..dc8cdc7864 100644
--- a/java/com/google/gerrit/server/project/RefUtil.java
+++ b/java/com/google/gerrit/server/project/RefUtil.java
@@ -20,9 +20,9 @@ import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import java.io.IOException;
import java.util.Collections;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
diff --git a/java/com/google/gerrit/server/project/RefValidationHelper.java b/java/com/google/gerrit/server/project/RefValidationHelper.java
index 0a5980ca97..9b297f91ab 100644
--- a/java/com/google/gerrit/server/project/RefValidationHelper.java
+++ b/java/com/google/gerrit/server/project/RefValidationHelper.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.validators.ValidationException;
@@ -43,7 +43,7 @@ public class RefValidationHelper {
throws ResourceConflictException {
RefOperationValidators refValidators =
refValidatorsFactory.create(
- new Project(new Project.NameKey(projectName)),
+ new Project(Project.nameKey(projectName)),
user,
RefOperationValidators.getCommand(update, operationType));
try {
diff --git a/java/com/google/gerrit/server/project/RemoveReviewerControl.java b/java/com/google/gerrit/server/project/RemoveReviewerControl.java
index eeb2a65419..6bf3beb968 100644
--- a/java/com/google/gerrit/server/project/RemoveReviewerControl.java
+++ b/java/com/google/gerrit/server/project/RemoveReviewerControl.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -47,7 +47,7 @@ public class RemoveReviewerControl {
public void checkRemoveReviewer(
ChangeNotes notes, CurrentUser currentUser, PatchSetApproval approval)
throws PermissionBackendException, AuthException {
- checkRemoveReviewer(notes, currentUser, approval.getAccountId(), approval.getValue());
+ checkRemoveReviewer(notes, currentUser, approval.accountId(), approval.value());
}
/**
@@ -108,7 +108,7 @@ public class RemoveReviewerControl {
// owner and site admin can remove anyone
PermissionBackend.WithUser withUser = permissionBackend.user(currentUser);
PermissionBackend.ForProject forProject = withUser.project(change.getProject());
- if (check(forProject.ref(change.getDest().get()), RefPermission.WRITE_CONFIG)
+ if (check(forProject.ref(change.getDest().branch()), RefPermission.WRITE_CONFIG)
|| check(withUser, GlobalPermission.ADMINISTRATE_SERVER)) {
return true;
}
diff --git a/java/com/google/gerrit/server/project/SectionMatcher.java b/java/com/google/gerrit/server/project/SectionMatcher.java
index a8ebd98d62..6de8eec3f6 100644
--- a/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
/**
diff --git a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 1b1869ca2e..cc7591c9dd 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -18,8 +18,12 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.server.index.OnlineReindexMode;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
@@ -27,9 +31,9 @@ import com.google.gerrit.server.rules.PrologRule;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -44,6 +48,8 @@ public class SubmitRuleEvaluator {
private final ProjectCache projectCache;
private final PrologRule prologRule;
private final PluginSetContext<SubmitRule> submitRules;
+ private final Timer0 submitRuleEvaluationLatency;
+ private final Timer0 submitTypeEvaluationLatency;
private final SubmitRuleOptions opts;
public interface Factory {
@@ -56,23 +62,36 @@ public class SubmitRuleEvaluator {
ProjectCache projectCache,
PrologRule prologRule,
PluginSetContext<SubmitRule> submitRules,
+ MetricMaker metricMaker,
@Assisted SubmitRuleOptions options) {
this.projectCache = projectCache;
this.prologRule = prologRule;
this.submitRules = submitRules;
+ this.submitRuleEvaluationLatency =
+ metricMaker.newTimer(
+ "change/submit_rule_evaluation",
+ new Description("Latency for evaluating submit rules on a change.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
+ this.submitTypeEvaluationLatency =
+ metricMaker.newTimer(
+ "change/submit_type_evaluation",
+ new Description("Latency for evaluating the submit type on a change.")
+ .setCumulative()
+ .setUnit(Units.MILLISECONDS));
this.opts = options;
}
- public static List<SubmitRecord> defaultRuleError() {
+ public static SubmitRecord defaultRuleError() {
return createRuleError(DEFAULT_MSG);
}
- public static List<SubmitRecord> createRuleError(String err) {
+ public static SubmitRecord createRuleError(String err) {
SubmitRecord rec = new SubmitRecord();
rec.status = SubmitRecord.Status.RULE_ERROR;
rec.errorMessage = err;
- return Collections.singletonList(rec);
+ return rec;
}
public static SubmitTypeRecord defaultTypeError() {
@@ -87,46 +106,42 @@ public class SubmitRuleEvaluator {
* @param cd ChangeData to evaluate
*/
public List<SubmitRecord> evaluate(ChangeData cd) {
- Change change;
- ProjectState projectState;
- try {
- change = cd.change();
- if (change == null) {
- throw new StorageException("Change not found");
+ try (Timer0.Context ignored = submitRuleEvaluationLatency.start()) {
+ Change change;
+ ProjectState projectState;
+ try {
+ change = cd.change();
+ if (change == null) {
+ throw new StorageException("Change not found");
+ }
+
+ projectState = projectCache.get(cd.project());
+ if (projectState == null) {
+ throw new NoSuchProjectException(cd.project());
+ }
+ } catch (StorageException | NoSuchProjectException e) {
+ return Collections.singletonList(ruleError("Error looking up change " + cd.getId(), e));
}
- projectState = projectCache.get(cd.project());
- if (projectState == null) {
- throw new NoSuchProjectException(cd.project());
+ if ((!opts.allowClosed() || OnlineReindexMode.isActive()) && change.isClosed()) {
+ SubmitRecord rec = new SubmitRecord();
+ rec.status = SubmitRecord.Status.CLOSED;
+ return Collections.singletonList(rec);
}
- } catch (StorageException | NoSuchProjectException e) {
- return ruleError("Error looking up change " + cd.getId(), e);
- }
- if ((!opts.allowClosed() || OnlineReindexMode.isActive()) && change.isClosed()) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.CLOSED;
- return Collections.singletonList(rec);
+ // We evaluate all the plugin-defined evaluators,
+ // and then we collect the results in one list.
+ return Streams.stream(submitRules)
+ .map(c -> c.call(s -> s.evaluate(cd)))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
}
-
- // We evaluate all the plugin-defined evaluators,
- // and then we collect the results in one list.
- return Streams.stream(submitRules)
- .map(c -> c.call(s -> s.evaluate(cd, opts)))
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
}
- private List<SubmitRecord> ruleError(String err, Exception e) {
- if (opts.logErrors()) {
- if (e == null) {
- logger.atSevere().log(err);
- } else {
- logger.atSevere().withCause(e).log(err);
- }
- return defaultRuleError();
- }
- return createRuleError(err);
+ private SubmitRecord ruleError(String err, Exception e) {
+ logger.atSevere().withCause(e).log(err);
+ return defaultRuleError();
}
/**
@@ -136,24 +151,23 @@ public class SubmitRuleEvaluator {
* @param cd
*/
public SubmitTypeRecord getSubmitType(ChangeData cd) {
- ProjectState projectState;
- try {
- projectState = projectCache.get(cd.project());
- if (projectState == null) {
- throw new NoSuchProjectException(cd.project());
+ try (Timer0.Context ignored = submitTypeEvaluationLatency.start()) {
+ ProjectState projectState;
+ try {
+ projectState = projectCache.get(cd.project());
+ if (projectState == null) {
+ throw new NoSuchProjectException(cd.project());
+ }
+ } catch (NoSuchProjectException e) {
+ return typeError("Error looking up change " + cd.getId(), e);
}
- } catch (NoSuchProjectException e) {
- return typeError("Error looking up change " + cd.getId(), e);
- }
- return prologRule.getSubmitType(cd, opts);
+ return prologRule.getSubmitType(cd);
+ }
}
private SubmitTypeRecord typeError(String err, Exception e) {
- if (opts.logErrors()) {
- logger.atSevere().withCause(e).log(err);
- return defaultTypeError();
- }
- return SubmitTypeRecord.error(err);
+ logger.atSevere().withCause(e).log(err);
+ return defaultTypeError();
}
}
diff --git a/java/com/google/gerrit/server/project/SubmitRuleOptions.java b/java/com/google/gerrit/server/project/SubmitRuleOptions.java
index a4340b2bad..ad077c0aaa 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleOptions.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleOptions.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.project;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.common.Nullable;
/**
* Stable identifier for options passed to a particular submit rule evaluator.
@@ -26,12 +25,7 @@ import com.google.gerrit.common.Nullable;
@AutoValue
public abstract class SubmitRuleOptions {
private static final SubmitRuleOptions defaults =
- new AutoValue_SubmitRuleOptions.Builder()
- .allowClosed(false)
- .skipFilters(false)
- .logErrors(true)
- .rule(null)
- .build();
+ new AutoValue_SubmitRuleOptions.Builder().allowClosed(false).build();
public static SubmitRuleOptions defaults() {
return defaults;
@@ -43,25 +37,12 @@ public abstract class SubmitRuleOptions {
public abstract boolean allowClosed();
- public abstract boolean skipFilters();
-
- public abstract boolean logErrors();
-
- @Nullable
- public abstract String rule();
-
public abstract Builder toBuilder();
@AutoValue.Builder
public abstract static class Builder {
public abstract SubmitRuleOptions.Builder allowClosed(boolean allowClosed);
- public abstract SubmitRuleOptions.Builder skipFilters(boolean skipFilters);
-
- public abstract SubmitRuleOptions.Builder rule(@Nullable String rule);
-
- public abstract SubmitRuleOptions.Builder logErrors(boolean logErrors);
-
public abstract SubmitRuleOptions build();
}
}
diff --git a/java/com/google/gerrit/server/project/SuggestParentCandidates.java b/java/com/google/gerrit/server/project/SuggestParentCandidates.java
index d3dfdcdf04..fdc8b50765 100644
--- a/java/com/google/gerrit/server/project/SuggestParentCandidates.java
+++ b/java/com/google/gerrit/server/project/SuggestParentCandidates.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.project;
import static java.util.stream.Collectors.toList;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/project/testing/BUILD b/java/com/google/gerrit/server/project/testing/BUILD
index 968e3da744..988a89f1e3 100644
--- a/java/com/google/gerrit/server/project/testing/BUILD
+++ b/java/com/google/gerrit/server/project/testing/BUILD
@@ -5,9 +5,5 @@ java_library(
testonly = True,
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
- deps = [
- "//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/reviewdb:server",
- "//java/com/google/gerrit/server",
- ],
+ deps = ["//java/com/google/gerrit/common:server"],
)
diff --git a/java/com/google/gerrit/server/project/testing/TestLabels.java b/java/com/google/gerrit/server/project/testing/TestLabels.java
new file mode 100644
index 0000000000..6c2dddeb00
--- /dev/null
+++ b/java/com/google/gerrit/server/project/testing/TestLabels.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project.testing;
+
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
+import java.util.Arrays;
+
+public class TestLabels {
+ public static LabelType codeReview() {
+ return label(
+ "Code-Review",
+ value(2, "Looks good to me, approved"),
+ value(1, "Looks good to me, but someone else must approve"),
+ value(0, "No score"),
+ value(-1, "I would prefer this is not merged as is"),
+ value(-2, "This shall not be merged"));
+ }
+
+ public static LabelType verified() {
+ return label("Verified", value(1, "Verified"), value(0, "No score"), value(-1, "Fails"));
+ }
+
+ public static LabelType patchSetLock() {
+ LabelType label =
+ label("Patch-Set-Lock", value(1, "Patch Set Locked"), value(0, "Patch Set Unlocked"));
+ label.setFunction(LabelFunction.PATCH_SET_LOCK);
+ return label;
+ }
+
+ public static LabelValue value(int value, String text) {
+ return new LabelValue((short) value, text);
+ }
+
+ public static LabelType label(String name, LabelValue... values) {
+ return new LabelType(name, Arrays.asList(values));
+ }
+
+ private TestLabels() {}
+}
diff --git a/java/com/google/gerrit/server/project/testing/Util.java b/java/com/google/gerrit/server/project/testing/Util.java
deleted file mode 100644
index 204fa7b8f3..0000000000
--- a/java/com/google/gerrit/server/project/testing/Util.java
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.project.testing;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.LabelFunction;
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRange;
-import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.project.ProjectConfig;
-import java.util.Arrays;
-
-public class Util {
- public static final AccountGroup.UUID ADMIN = new AccountGroup.UUID("test.admin");
- public static final AccountGroup.UUID DEVS = new AccountGroup.UUID("test.devs");
-
- public static final LabelType codeReview() {
- return category(
- "Code-Review",
- value(2, "Looks good to me, approved"),
- value(1, "Looks good to me, but someone else must approve"),
- value(0, "No score"),
- value(-1, "I would prefer this is not merged as is"),
- value(-2, "This shall not be merged"));
- }
-
- public static final LabelType verified() {
- return category("Verified", value(1, "Verified"), value(0, "No score"), value(-1, "Fails"));
- }
-
- public static final LabelType patchSetLock() {
- LabelType label =
- category("Patch-Set-Lock", value(1, "Patch Set Locked"), value(0, "Patch Set Unlocked"));
- label.setFunction(LabelFunction.PATCH_SET_LOCK);
- return label;
- }
-
- public static LabelValue value(int value, String text) {
- return new LabelValue((short) value, text);
- }
-
- public static LabelType category(String name, LabelValue... values) {
- return new LabelType(name, Arrays.asList(values));
- }
-
- public static PermissionRule newRule(ProjectConfig project, AccountGroup.UUID groupUUID) {
- GroupReference group = new GroupReference(groupUUID, groupUUID.get());
- group = project.resolve(group);
-
- return new PermissionRule(group);
- }
-
- public static PermissionRule allow(
- ProjectConfig project,
- String permissionName,
- int min,
- int max,
- AccountGroup.UUID group,
- String ref) {
- PermissionRule rule = newRule(project, group);
- rule.setMin(min);
- rule.setMax(max);
- return grant(project, permissionName, rule, ref);
- }
-
- public static PermissionRule allowExclusive(
- ProjectConfig project,
- String permissionName,
- int min,
- int max,
- AccountGroup.UUID group,
- String ref) {
- PermissionRule rule = newRule(project, group);
- rule.setMin(min);
- rule.setMax(max);
- return grant(project, permissionName, rule, ref, true);
- }
-
- public static PermissionRule block(
- ProjectConfig project,
- String permissionName,
- int min,
- int max,
- AccountGroup.UUID group,
- String ref) {
- PermissionRule rule = newRule(project, group);
- rule.setMin(min);
- rule.setMax(max);
- PermissionRule r = grant(project, permissionName, rule, ref);
- r.setBlock();
- return r;
- }
-
- public static PermissionRule allow(
- ProjectConfig project, String permissionName, AccountGroup.UUID group, String ref) {
- return grant(project, permissionName, newRule(project, group), ref);
- }
-
- public static PermissionRule allow(
- ProjectConfig project,
- String permissionName,
- AccountGroup.UUID group,
- String ref,
- boolean exclusive) {
- return grant(project, permissionName, newRule(project, group), ref, exclusive);
- }
-
- public static PermissionRule allow(
- ProjectConfig project, String capabilityName, AccountGroup.UUID group) {
- return allow(project, capabilityName, group, (PermissionRange) null);
- }
-
- public static PermissionRule allow(
- ProjectConfig project,
- String capabilityName,
- AccountGroup.UUID group,
- PermissionRange customRange) {
- PermissionRule rule = newRule(project, group);
- project
- .getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
- .getPermission(capabilityName, true)
- .add(rule);
- if (GlobalCapability.hasRange(capabilityName)) {
- if (customRange == null) {
- PermissionRange.WithDefaults range = GlobalCapability.getRange(capabilityName);
- if (range != null) {
- rule.setRange(range.getDefaultMin(), range.getDefaultMax());
- }
- return rule;
- }
- rule.setRange(customRange.getMin(), customRange.getMax());
- }
- return rule;
- }
-
- public static PermissionRule remove(
- ProjectConfig project, String capabilityName, AccountGroup.UUID group) {
- PermissionRule rule = newRule(project, group);
- project
- .getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
- .getPermission(capabilityName, true)
- .remove(rule);
- return rule;
- }
-
- public static PermissionRule remove(
- ProjectConfig project, String permissionName, AccountGroup.UUID group, String ref) {
- PermissionRule rule = newRule(project, group);
- project.getAccessSection(ref, true).getPermission(permissionName, true).remove(rule);
- return rule;
- }
-
- public static PermissionRule block(
- ProjectConfig project, String capabilityName, AccountGroup.UUID group) {
- PermissionRule rule = newRule(project, group);
- project
- .getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
- .getPermission(capabilityName, true)
- .add(rule);
- return rule;
- }
-
- public static PermissionRule block(
- ProjectConfig project, String permissionName, AccountGroup.UUID group, String ref) {
- PermissionRule r = grant(project, permissionName, newRule(project, group), ref);
- r.setBlock();
- return r;
- }
-
- public static PermissionRule blockLabel(
- ProjectConfig project, String labelName, AccountGroup.UUID group, String ref) {
- return blockLabel(project, labelName, -1, 1, group, ref);
- }
-
- public static PermissionRule blockLabel(
- ProjectConfig project,
- String labelName,
- int min,
- int max,
- AccountGroup.UUID group,
- String ref) {
- PermissionRule r = grant(project, Permission.LABEL + labelName, newRule(project, group), ref);
- r.setBlock();
- r.setRange(min, max);
- return r;
- }
-
- public static PermissionRule deny(
- ProjectConfig project, String permissionName, AccountGroup.UUID group, String ref) {
- PermissionRule r = grant(project, permissionName, newRule(project, group), ref);
- r.setDeny();
- return r;
- }
-
- public static void doNotInherit(ProjectConfig project, String permissionName, String ref) {
- project
- .getAccessSection(ref, true) //
- .getPermission(permissionName, true) //
- .setExclusiveGroup(true);
- }
-
- private static PermissionRule grant(
- ProjectConfig project, String permissionName, PermissionRule rule, String ref) {
- return grant(project, permissionName, rule, ref, false);
- }
-
- private static PermissionRule grant(
- ProjectConfig project,
- String permissionName,
- PermissionRule rule,
- String ref,
- boolean exclusive) {
- Permission permission = project.getAccessSection(ref, true).getPermission(permissionName, true);
- if (exclusive) {
- permission.setExclusiveGroup(exclusive);
- }
- permission.add(rule);
- return rule;
- }
-
- private Util() {}
-}
diff --git a/java/com/google/gerrit/server/query/account/AccountPredicates.java b/java/com/google/gerrit/server/query/account/AccountPredicates.java
index cb96bc5eaa..1eed7ea29e 100644
--- a/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -16,14 +16,14 @@ package com.google.gerrit.server.query.account;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.IndexPredicate;
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -44,7 +44,7 @@ public class AccountPredicates {
List<Predicate<AccountState>> preds = Lists.newArrayListWithCapacity(3);
Integer id = Ints.tryParse(query);
if (id != null) {
- preds.add(id(new Account.Id(id)));
+ preds.add(id(schema, Account.id(id)));
}
if (canSeeSecondaryEmails) {
preds.add(equalsNameIncludingSecondaryEmails(query));
@@ -64,9 +64,11 @@ public class AccountPredicates {
return Predicate.or(preds);
}
- public static Predicate<AccountState> id(Account.Id accountId) {
+ public static Predicate<AccountState> id(Schema<AccountState> schema, Account.Id accountId) {
return new AccountPredicate(
- AccountField.ID, AccountQueryBuilder.FIELD_ACCOUNT, accountId.toString());
+ schema.useLegacyNumericFields() ? AccountField.ID : AccountField.ID_STR,
+ AccountQueryBuilder.FIELD_ACCOUNT,
+ accountId.toString());
}
public static Predicate<AccountState> emailIncludingSecondaryEmails(String email) {
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 38336c1c17..42a83106e6 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -17,6 +17,7 @@ import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.NotSignedInException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -26,7 +27,6 @@ import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountState;
@@ -204,7 +204,7 @@ public class AccountQueryBuilder extends QueryBuilder<AccountState, AccountQuery
if ("self".equalsIgnoreCase(query) || "me".equalsIgnoreCase(query)) {
try {
- return Predicate.or(defaultPredicate, AccountPredicates.id(self()));
+ return Predicate.or(defaultPredicate, AccountPredicates.id(args.schema(), self()));
} catch (QueryParseException e) {
// Skip.
}
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index 7a7381d6d9..2e29bbd4f6 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -77,6 +77,6 @@ public class AccountQueryProcessor extends QueryProcessor<AccountState> {
@Override
protected String formatForLogging(AccountState accountState) {
- return accountState.getAccount().getId().toString();
+ return accountState.account().id().toString();
}
}
diff --git a/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java b/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
index 8bbeb24018..0252a0689c 100644
--- a/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
+++ b/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
@@ -37,7 +37,7 @@ public class CanSeeChangePredicate extends PostFilterPredicate<AccountState> {
public boolean match(AccountState accountState) {
try {
permissionBackend
- .absentUser(accountState.getAccount().getId())
+ .absentUser(accountState.account().id())
.change(changeNotes)
.check(ChangePermission.READ);
return true;
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index f2385ec789..091edca081 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -24,17 +24,16 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.InternalQuery;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.inject.Inject;
-import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -77,7 +76,7 @@ public class InternalAccountQuery extends InternalQuery<AccountState, InternalAc
msg.append("Ambiguous external ID ").append(externalId).append(" for accounts: ");
Joiner.on(", ")
.appendTo(
- msg, accountStates.stream().map(AccountState.ACCOUNT_ID_FUNCTION).collect(toList()));
+ msg, accountStates.stream().map(a -> a.account().id().toString()).collect(toList()));
logger.atWarning().log(msg.toString());
}
return null;
@@ -103,7 +102,7 @@ public class InternalAccountQuery extends InternalQuery<AccountState, InternalAc
}
return query(AccountPredicates.preferredEmail(email)).stream()
- .filter(a -> a.getAccount().getPreferredEmail().equals(email))
+ .filter(a -> a.account().preferredEmail().equals(email))
.collect(toList());
}
@@ -114,15 +113,13 @@ public class InternalAccountQuery extends InternalQuery<AccountState, InternalAc
* @return multimap of the given emails to accounts that have a preferred email that exactly
* matches this email
*/
- public Multimap<String, AccountState> byPreferredEmail(String... emails) {
- List<String> emailList = Arrays.asList(emails);
-
+ public Multimap<String, AccountState> byPreferredEmail(List<String> emails) {
if (hasPreferredEmailExact()) {
List<List<AccountState>> r =
- query(emailList.stream().map(AccountPredicates::preferredEmailExact).collect(toList()));
+ query(emails.stream().map(AccountPredicates::preferredEmailExact).collect(toList()));
Multimap<String, AccountState> accountsByEmail = ArrayListMultimap.create();
- for (int i = 0; i < emailList.size(); i++) {
- accountsByEmail.putAll(emailList.get(i), r.get(i));
+ for (int i = 0; i < emails.size(); i++) {
+ accountsByEmail.putAll(emails.get(i), r.get(i));
}
return accountsByEmail;
}
@@ -132,13 +129,13 @@ public class InternalAccountQuery extends InternalQuery<AccountState, InternalAc
}
List<List<AccountState>> r =
- query(emailList.stream().map(AccountPredicates::preferredEmail).collect(toList()));
+ query(emails.stream().map(AccountPredicates::preferredEmail).collect(toList()));
Multimap<String, AccountState> accountsByEmail = ArrayListMultimap.create();
- for (int i = 0; i < emailList.size(); i++) {
- String email = emailList.get(i);
+ for (int i = 0; i < emails.size(); i++) {
+ String email = emails.get(i);
Set<AccountState> matchingAccounts =
r.get(i).stream()
- .filter(a -> a.getAccount().getPreferredEmail().equals(email))
+ .filter(a -> a.account().preferredEmail().equals(email))
.collect(toSet());
accountsByEmail.putAll(email, matchingAccounts);
}
diff --git a/java/com/google/gerrit/server/query/change/AgePredicate.java b/java/com/google/gerrit/server/query/change/AgePredicate.java
index 1cf2c2f9e6..36eb5b7c06 100644
--- a/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.query.change;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.util.time.TimeUtil;
diff --git a/java/com/google/gerrit/server/query/change/AssigneePredicate.java b/java/com/google/gerrit/server/query/change/AssigneePredicate.java
index fb19e850bb..35a91c98ef 100644
--- a/java/com/google/gerrit/server/query/change/AssigneePredicate.java
+++ b/java/com/google/gerrit/server/query/change/AssigneePredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.index.change.ChangeField;
public class AssigneePredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index de58456a2d..8459b9f525 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -19,6 +19,7 @@ import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -34,19 +35,19 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
@@ -74,6 +75,7 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -222,12 +224,18 @@ public class ChangeData {
* @return instance for testing.
*/
public static ChangeData createForTest(
- Project.NameKey project, Change.Id id, int currentPatchSetId) {
+ Project.NameKey project, Change.Id id, int currentPatchSetId, ObjectId commitId) {
ChangeData cd =
new ChangeData(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, project, id, null, null);
- cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
+ cd.currentPatchSet =
+ PatchSet.builder()
+ .id(PatchSet.id(id, currentPatchSetId))
+ .commitId(commitId)
+ .uploader(Account.id(1000))
+ .createdOn(TimeUtil.nowTs())
+ .build();
return cd;
}
@@ -357,6 +365,7 @@ public class ChangeData {
return allUsersName;
}
+ @VisibleForTesting
public void setCurrentFilePaths(List<String> filePaths) {
PatchSet ps = currentPatchSet();
if (ps != null) {
@@ -387,7 +396,7 @@ public class ChangeData {
return Optional.empty();
}
- ObjectId id = ObjectId.fromString(ps.getRevision().get());
+ ObjectId id = ps.commitId();
Whitespace ws = Whitespace.IGNORE_NONE;
PatchListKey pk =
parentCount > 1
@@ -497,7 +506,7 @@ public class ChangeData {
return null;
}
for (PatchSet p : patchSets()) {
- if (p.getId().equals(c.currentPatchSetId())) {
+ if (p.id().equals(c.currentPatchSetId())) {
currentPatchSet = p;
return p;
}
@@ -580,10 +589,9 @@ public class ChangeData {
if (ps == null) {
return false;
}
- String sha1 = ps.getRevision().get();
try (Repository repo = repoManager.openRepository(project());
RevWalk walk = new RevWalk(repo)) {
- RevCommit c = walk.parseCommit(ObjectId.fromString(sha1));
+ RevCommit c = walk.parseCommit(ps.commitId());
commitMessage = c.getFullMessage();
commitFooters = c.getFooterLines();
author = c.getAuthorIdent();
@@ -610,11 +618,11 @@ public class ChangeData {
/** @return patch with the given ID, or null if it does not exist. */
public PatchSet patchSet(PatchSet.Id psId) {
- if (currentPatchSet != null && currentPatchSet.getId().equals(psId)) {
+ if (currentPatchSet != null && currentPatchSet.id().equals(psId)) {
return currentPatchSet;
}
for (PatchSet ps : patchSets()) {
- if (ps.getId().equals(psId)) {
+ if (ps.id().equals(psId)) {
return ps;
}
}
@@ -901,7 +909,7 @@ public class ChangeData {
}
try (Repository repo = repoManager.openRepository(project())) {
- Ref ref = repo.getRefDatabase().exactRef(c.getDest().get());
+ Ref ref = repo.getRefDatabase().exactRef(c.getDest().branch());
SubmitTypeRecord str = submitTypeRecord();
if (!str.isOk()) {
// If submit type rules are broken, it's definitely not mergeable.
@@ -911,13 +919,7 @@ public class ChangeData {
String mergeStrategy =
mergeUtilFactory.create(projectCache.get(project())).mergeStrategyName();
mergeable =
- mergeabilityCache.get(
- ObjectId.fromString(ps.getRevision().get()),
- ref,
- str.type,
- mergeStrategy,
- c.getDest(),
- repo);
+ mergeabilityCache.get(ps.commitId(), ref, str.type, mergeStrategy, c.getDest(), repo);
} catch (IOException e) {
throw new StorageException(e);
}
@@ -995,11 +997,11 @@ public class ChangeData {
PatchSet ps = currentPatchSet();
if (ps != null) {
- if (stars.contains(StarredChangesUtil.REVIEWED_LABEL + "/" + ps.getPatchSetId())) {
+ if (stars.contains(StarredChangesUtil.REVIEWED_LABEL + "/" + ps.number())) {
return true;
}
- if (stars.contains(StarredChangesUtil.UNREVIEWED_LABEL + "/" + ps.getPatchSetId())) {
+ if (stars.contains(StarredChangesUtil.UNREVIEWED_LABEL + "/" + ps.number())) {
return false;
}
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index 74ad0ef52d..05cc6ca1e2 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
/** Predicate over Change-Id strings (aka Change.Key). */
diff --git a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 8015a33af7..819fc2b65b 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.query.change;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.index.query.IsVisibleToPredicate;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f998ad3fc6..6f278ab712 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
+import static com.google.gerrit.entities.Change.CHANGE_ID_PATTERN;
import static com.google.gerrit.server.account.AccountResolver.isSelf;
import static com.google.gerrit.server.query.change.ChangeData.asChanges;
import static java.util.stream.Collectors.toList;
@@ -29,6 +29,11 @@ import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.NotSignedInException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -41,11 +46,6 @@ import com.google.gerrit.index.query.QueryBuilder;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryRequiresAuthException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -62,10 +62,7 @@ import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -94,7 +91,6 @@ import java.util.function.Function;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
/** Parses a query string meant to be applied to change objects. */
@@ -189,7 +185,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
public static final String ARG_ID_USER = "user";
public static final String ARG_ID_GROUP = "group";
public static final String ARG_ID_OWNER = "owner";
- public static final Account.Id OWNER_ACCOUNT_ID = new Account.Id(0);
+ public static final Account.Id OWNER_ACCOUNT_ID = Account.id(0);
private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
new QueryBuilder.Definition<>(ChangeQueryBuilder.class);
@@ -400,8 +396,6 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
private final Arguments args;
- private @Inject @GerritServerConfig Config cfg;
-
@Inject
ChangeQueryBuilder(Arguments args) {
this(mydef, args);
@@ -452,13 +446,15 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
if (triplet.isPresent()) {
return Predicate.and(
project(triplet.get().project().get()),
- branch(triplet.get().branch().get()),
+ branch(triplet.get().branch().branch()),
new ChangeIdPredicate(parseChangeId(triplet.get().id().get())));
}
if (PAT_LEGACY_ID.matcher(query).matches()) {
Integer id = Ints.tryParse(query);
if (id != null) {
- return new LegacyChangeIdPredicate(new Change.Id(id));
+ return args.getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(Change.id(id))
+ : new LegacyChangeIdStrPredicate(Change.id(id));
}
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
return new ChangeIdPredicate(parseChangeId(query));
@@ -566,15 +562,23 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
}
if ("assigned".equalsIgnoreCase(value)) {
- return Predicate.not(new AssigneePredicate(new Account.Id(ChangeField.NO_ASSIGNEE)));
+ return Predicate.not(new AssigneePredicate(Account.id(ChangeField.NO_ASSIGNEE)));
}
if ("unassigned".equalsIgnoreCase(value)) {
- return new AssigneePredicate(new Account.Id(ChangeField.NO_ASSIGNEE));
+ return new AssigneePredicate(Account.id(ChangeField.NO_ASSIGNEE));
}
if ("submittable".equalsIgnoreCase(value)) {
- return new SubmittablePredicate(SubmitRecord.Status.OK);
+ // SubmittablePredicate will match if *any* of the submit records are OK,
+ // but we need to check that they're *all* OK, so check that none of the
+ // submit records match any of the negative cases. To avoid checking yet
+ // more negative cases for CLOSED and FORCED, instead make sure at least
+ // one submit record is OK.
+ return Predicate.and(
+ new SubmittablePredicate(SubmitRecord.Status.OK),
+ Predicate.not(new SubmittablePredicate(SubmitRecord.Status.NOT_READY)),
+ Predicate.not(new SubmittablePredicate(SubmitRecord.Status.RULE_ERROR)));
}
if ("ignored".equalsIgnoreCase(value)) {
@@ -733,10 +737,6 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
@Operator
public Predicate<ChangeData> extension(String ext) throws QueryParseException {
if (args.getSchema().hasField(ChangeField.EXTENSION)) {
- if (ext.isEmpty()
- && IndexModule.getIndexType(cfg).equalsIgnoreCase(IndexType.ELASTICSEARCH.name())) {
- return new FileWithNoExtensionInElasticPredicate();
- }
return new FileExtensionPredicate(ext);
}
throw new QueryParseException("'extension' operator is not supported by change index version");
@@ -775,11 +775,6 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
if (directory.startsWith("^")) {
return new RegexDirectoryPredicate(directory);
}
-
- if (IndexModule.getIndexType(cfg).equalsIgnoreCase(IndexType.ELASTICSEARCH.name())
- && (directory.isEmpty() || directory.equals("/"))) {
- return Predicate.any();
- }
return new DirectoryPredicate(directory);
}
throw new QueryParseException("'directory' operator is not supported by change index version");
@@ -1184,7 +1179,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
try (Repository git = args.repoManager.openRepository(args.allUsersName)) {
VersionedAccountDestinations d = VersionedAccountDestinations.forUser(self());
d.load(args.allUsersName, git);
- Set<Branch.NameKey> destinations = d.getDestinationList().getDestinations(name);
+ Set<BranchNameKey> destinations = d.getDestinationList().getDestinations(name);
if (destinations != null && !destinations.isEmpty()) {
return new DestinationPredicate(destinations, name);
}
@@ -1321,7 +1316,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
private Set<Account.Id> getMembers(AccountGroup.UUID g) throws IOException {
Set<Account.Id> accounts;
Set<Account.Id> allMembers =
- args.groupMembers.listAccounts(g).stream().map(Account::getId).collect(toSet());
+ args.groupMembers.listAccounts(g).stream().map(Account::id).collect(toSet());
int maxTerms = args.indexConfig.maxTerms();
if (allMembers.size() > maxTerms) {
// limit the number of query terms otherwise Gerrit will barf
diff --git a/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index 66790e7579..88e93d9d25 100644
--- a/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -18,9 +18,9 @@ import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Change.Status;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.server.index.change.ChangeField;
import java.util.ArrayList;
import java.util.List;
diff --git a/java/com/google/gerrit/server/query/change/CommentByPredicate.java b/java/com/google/gerrit/server/query/change/CommentByPredicate.java
index 0747bb2784..bd7981c107 100644
--- a/java/com/google/gerrit/server/query/change/CommentByPredicate.java
+++ b/java/com/google/gerrit/server/query/change/CommentByPredicate.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.server.index.change.ChangeField;
import java.util.Objects;
diff --git a/java/com/google/gerrit/server/query/change/CommentPredicate.java b/java/com/google/gerrit/server/query/change/CommentPredicate.java
index d193bb68bc..4b14f081a0 100644
--- a/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
@@ -32,9 +33,15 @@ public class CommentPredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData object) {
try {
- Predicate<ChangeData> p = Predicate.and(new LegacyChangeIdPredicate(object.getId()), this);
+ Change.Id id = object.getId();
+ Predicate<ChangeData> p =
+ Predicate.and(
+ index.getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(id)
+ : new LegacyChangeIdStrPredicate(id),
+ this);
for (ChangeData cData : index.getSource(p, IndexedChangeQuery.oneResult()).read()) {
- if (cData.getId().equals(object.getId())) {
+ if (cData.getId().equals(id)) {
return true;
}
}
diff --git a/java/com/google/gerrit/server/query/change/CommitPredicate.java b/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 567f58d59a..b54ee64441 100644
--- a/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -14,16 +14,17 @@
package com.google.gerrit.server.query.change;
+import static com.google.gerrit.git.ObjectIds.matchesAbbreviation;
import static com.google.gerrit.server.index.change.ChangeField.COMMIT;
import static com.google.gerrit.server.index.change.ChangeField.EXACT_COMMIT;
-import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.index.FieldDef;
-import com.google.gerrit.reviewdb.client.PatchSet;
public class CommitPredicate extends ChangeIndexPredicate {
static FieldDef<ChangeData, ?> commitField(String id) {
- if (id.length() == OBJECT_ID_STRING_LENGTH) {
+ if (id.length() == ObjectIds.STR_LEN) {
return EXACT_COMMIT;
}
return COMMIT;
@@ -45,9 +46,10 @@ public class CommitPredicate extends ChangeIndexPredicate {
}
protected boolean equals(PatchSet p, String id) {
- boolean exact = getField() == EXACT_COMMIT;
- String rev = p.getRevision() != null ? p.getRevision().get() : null;
- return (exact && id.equals(rev)) || (!exact && rev != null && rev.startsWith(id));
+ if (getField() == EXACT_COMMIT) {
+ return p.commitId().name().equals(id);
+ }
+ return matchesAbbreviation(p.commitId(), id);
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index d415f71627..17e4a59647 100644
--- a/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -20,14 +20,14 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.PostFilterPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -86,8 +86,12 @@ public class ConflictsPredicate {
List<Predicate<ChangeData>> and = new ArrayList<>(5);
and.add(new ProjectPredicate(c.getProject().get()));
- and.add(new RefPredicate(c.getDest().get()));
- and.add(Predicate.not(new LegacyChangeIdPredicate(c.getId())));
+ and.add(new RefPredicate(c.getDest().branch()));
+ and.add(
+ Predicate.not(
+ args.getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(c.getId())
+ : new LegacyChangeIdStrPredicate(c.getId())));
and.add(Predicate.or(filePredicates));
ChangeDataCache changeDataCache = new ChangeDataCache(cd, args.projectCache);
@@ -97,7 +101,7 @@ public class ConflictsPredicate {
private static final class CheckConflict extends PostFilterPredicate<ChangeData> {
private final Arguments args;
- private final Branch.NameKey dest;
+ private final BranchNameKey dest;
private final ChangeDataCache changeDataCache;
CheckConflict(String value, Arguments args, Change c, ChangeDataCache changeDataCache) {
@@ -131,7 +135,7 @@ public class ConflictsPredicate {
return false;
}
- other = ObjectId.fromString(object.currentPatchSet().getRevision().get());
+ other = object.currentPatchSet().commitId();
ConflictKey conflictsKey =
ConflictKey.create(
changeDataCache.getTestAgainst(),
@@ -207,7 +211,7 @@ public class ConflictsPredicate {
ObjectId getTestAgainst() {
if (testAgainst == null) {
- testAgainst = ObjectId.fromString(cd.currentPatchSet().getRevision().get());
+ testAgainst = cd.currentPatchSet().commitId();
}
return testAgainst;
}
diff --git a/java/com/google/gerrit/server/query/change/DestinationPredicate.java b/java/com/google/gerrit/server/query/change/DestinationPredicate.java
index 702745e9b4..3c3d70f872 100644
--- a/java/com/google/gerrit/server/query/change/DestinationPredicate.java
+++ b/java/com/google/gerrit/server/query/change/DestinationPredicate.java
@@ -14,15 +14,15 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.query.PostFilterPredicate;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
import java.util.Set;
public class DestinationPredicate extends PostFilterPredicate<ChangeData> {
- protected Set<Branch.NameKey> destinations;
+ protected Set<BranchNameKey> destinations;
- public DestinationPredicate(Set<Branch.NameKey> destinations, String value) {
+ public DestinationPredicate(Set<BranchNameKey> destinations, String value) {
super(ChangeQueryBuilder.FIELD_DESTINATION, value);
this.destinations = destinations;
}
diff --git a/java/com/google/gerrit/server/query/change/EditByPredicate.java b/java/com/google/gerrit/server/query/change/EditByPredicate.java
index dfe73105e2..0fd66f8b9a 100644
--- a/java/com/google/gerrit/server/query/change/EditByPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EditByPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.index.change.ChangeField;
public class EditByPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index 2e7f34cce3..6997e0228a 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -16,11 +16,11 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -60,7 +60,7 @@ public class EqualsLabelPredicate extends ChangeIndexPredicate {
return false;
}
- ProjectState project = projectCache.get(c.getDest().getParentKey());
+ ProjectState project = projectCache.get(c.getDest().project());
if (project == null) {
// The project has disappeared.
//
@@ -76,7 +76,7 @@ public class EqualsLabelPredicate extends ChangeIndexPredicate {
for (PatchSetApproval p : object.currentApprovals()) {
if (labelType.matches(p)) {
hasVote = true;
- if (match(object, p.getValue(), p.getAccountId())) {
+ if (match(object, p.value(), p.accountId())) {
return true;
}
}
diff --git a/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java b/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
index c6ade75e94..6683c91a64 100644
--- a/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.index.change.ChangeField.EXACT_TOPIC;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
public class ExactTopicPredicate extends ChangeIndexPredicate {
public ExactTopicPredicate(String topic) {
diff --git a/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java b/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
index 140f26b165..d558b0f1c8 100644
--- a/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
+++ b/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.index.change.ChangeField.FUZZY_TOPIC;
import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.IndexedChangeQuery;
@@ -43,7 +43,10 @@ public class FuzzyTopicPredicate extends ChangeIndexPredicate {
return false;
}
try {
- Predicate<ChangeData> thisId = new LegacyChangeIdPredicate(cd.getId());
+ Predicate<ChangeData> thisId =
+ index.getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(cd.getId())
+ : new LegacyChangeIdStrPredicate(cd.getId());
Iterable<ChangeData> results =
index.getSource(and(thisId, this), IndexedChangeQuery.oneResult()).read();
return !Iterables.isEmpty(results);
diff --git a/java/com/google/gerrit/server/query/change/GroupPredicate.java b/java/com/google/gerrit/server/query/change/GroupPredicate.java
index 7f7bcff528..99f37d629f 100644
--- a/java/com/google/gerrit/server/query/change/GroupPredicate.java
+++ b/java/com/google/gerrit/server/query/change/GroupPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.index.change.ChangeField;
import java.util.List;
@@ -26,7 +26,7 @@ public class GroupPredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData cd) {
for (PatchSet ps : cd.patchSets()) {
- List<String> groups = ps.getGroups();
+ List<String> groups = ps.groups();
if (groups != null && groups.contains(getValue())) {
return true;
}
diff --git a/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index e57a8b39d0..6d1576fdd1 100644
--- a/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.index.change.ChangeField;
public class HasDraftByPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/HasStarsPredicate.java b/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
index 0c99cdf0bc..2fbd1e81ff 100644
--- a/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
+++ b/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.index.change.ChangeField;
public class HasStarsPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 74a0f71d1a..6605c23da2 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -25,13 +25,13 @@ import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.InternalQuery;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.inject.Inject;
@@ -55,8 +55,13 @@ import org.eclipse.jgit.lib.Repository;
* holding on to a single instance.
*/
public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChangeQuery> {
- private static Predicate<ChangeData> ref(Branch.NameKey branch) {
- return new RefPredicate(branch.get());
+ @FunctionalInterface
+ static interface ChangeIdPredicateFactory {
+ Predicate<ChangeData> create(Change.Id id);
+ }
+
+ private static Predicate<ChangeData> ref(BranchNameKey branch) {
+ return new RefPredicate(branch.branch());
}
private static Predicate<ChangeData> change(Change.Key key) {
@@ -78,6 +83,9 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
private final ChangeData.Factory changeDataFactory;
private final ChangeNotes.Factory notesFactory;
+ // TODO(davido): Remove the below fields when support for legacy numeric fields is removed.
+ private final ChangeIdPredicateFactory predicateFactory;
+
@Inject
InternalChangeQuery(
ChangeQueryProcessor queryProcessor,
@@ -88,6 +96,11 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
super(queryProcessor, indexes, indexConfig);
this.changeDataFactory = changeDataFactory;
this.notesFactory = notesFactory;
+ predicateFactory =
+ (id) ->
+ schema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(id)
+ : new LegacyChangeIdStrPredicate(id);
}
public List<ChangeData> byKey(Change.Key key) {
@@ -99,48 +112,48 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
}
public List<ChangeData> byLegacyChangeId(Change.Id id) {
- return query(new LegacyChangeIdPredicate(id));
+ return query(predicateFactory.create(id));
}
public List<ChangeData> byLegacyChangeIds(Collection<Change.Id> ids) {
List<Predicate<ChangeData>> preds = new ArrayList<>(ids.size());
for (Change.Id id : ids) {
- preds.add(new LegacyChangeIdPredicate(id));
+ preds.add(predicateFactory.create(id));
}
return query(or(preds));
}
- public List<ChangeData> byBranchKey(Branch.NameKey branch, Change.Key key) {
+ public List<ChangeData> byBranchKey(BranchNameKey branch, Change.Key key) {
return query(byBranchKeyPred(branch, key));
}
public List<ChangeData> byBranchKeyOpen(Project.NameKey project, String branch, Change.Key key) {
- return query(and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open()));
+ return query(and(byBranchKeyPred(BranchNameKey.create(project, branch), key), open()));
}
public static Predicate<ChangeData> byBranchKeyOpenPred(
Project.NameKey project, String branch, Change.Key key) {
- return and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open());
+ return and(byBranchKeyPred(BranchNameKey.create(project, branch), key), open());
}
- private static Predicate<ChangeData> byBranchKeyPred(Branch.NameKey branch, Change.Key key) {
- return and(ref(branch), project(branch.getParentKey()), change(key));
+ private static Predicate<ChangeData> byBranchKeyPred(BranchNameKey branch, Change.Key key) {
+ return and(ref(branch), project(branch.project()), change(key));
}
public List<ChangeData> byProject(Project.NameKey project) {
return query(project(project));
}
- public List<ChangeData> byBranchOpen(Branch.NameKey branch) {
- return query(and(ref(branch), project(branch.getParentKey()), open()));
+ public List<ChangeData> byBranchOpen(BranchNameKey branch) {
+ return query(and(ref(branch), project(branch.project()), open()));
}
- public List<ChangeData> byBranchNew(Branch.NameKey branch) {
- return query(and(ref(branch), project(branch.getParentKey()), status(Change.Status.NEW)));
+ public List<ChangeData> byBranchNew(BranchNameKey branch) {
+ return query(and(ref(branch), project(branch.project()), status(Change.Status.NEW)));
}
public Iterable<ChangeData> byCommitsOnBranchNotMerged(
- Repository repo, Branch.NameKey branch, Collection<String> hashes) throws IOException {
+ Repository repo, BranchNameKey branch, Collection<String> hashes) throws IOException {
return byCommitsOnBranchNotMerged(
repo,
branch,
@@ -151,7 +164,7 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
@VisibleForTesting
Iterable<ChangeData> byCommitsOnBranchNotMerged(
- Repository repo, Branch.NameKey branch, Collection<String> hashes, int indexLimit)
+ Repository repo, BranchNameKey branch, Collection<String> hashes, int indexLimit)
throws IOException {
if (hashes.size() > indexLimit) {
return byCommitsOnBranchNotMergedFromDatabase(repo, branch, hashes);
@@ -160,7 +173,7 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
}
private Iterable<ChangeData> byCommitsOnBranchNotMergedFromDatabase(
- Repository repo, Branch.NameKey branch, Collection<String> hashes) throws IOException {
+ Repository repo, BranchNameKey branch, Collection<String> hashes) throws IOException {
Set<Change.Id> changeIds = Sets.newHashSetWithExpectedSize(hashes.size());
String lastPrefix = null;
for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) {
@@ -180,7 +193,7 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
List<ChangeNotes> notes =
notesFactory.create(
- branch.getParentKey(),
+ branch.project(),
changeIds,
cn -> {
Change c = cn.getChange();
@@ -190,11 +203,11 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
}
private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex(
- Branch.NameKey branch, Collection<String> hashes) {
+ BranchNameKey branch, Collection<String> hashes) {
return query(
and(
ref(branch),
- project(branch.getParentKey()),
+ project(branch.project()),
not(status(Change.Status.MERGED)),
or(commits(hashes))));
}
@@ -241,8 +254,8 @@ public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChang
return query(byBranchCommitPred(project, branch, hash));
}
- public List<ChangeData> byBranchCommit(Branch.NameKey branch, String hash) {
- return byBranchCommit(branch.getParentKey().get(), branch.get(), hash);
+ public List<ChangeData> byBranchCommit(BranchNameKey branch, String hash) {
+ return byBranchCommit(branch.project().get(), branch.branch(), hash);
}
public List<ChangeData> byBranchCommitOpen(String project, String branch, String hash) {
diff --git a/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index ddb6f329b9..4b32b066f0 100644
--- a/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.index.change.ChangeField.REVIEWEDBY;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.index.change.ChangeField;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,7 +25,7 @@ import java.util.List;
import java.util.Set;
public class IsReviewedPredicate extends ChangeIndexPredicate {
- protected static final Account.Id NOT_REVIEWED = new Account.Id(ChangeField.NOT_REVIEWED);
+ protected static final Account.Id NOT_REVIEWED = Account.id(ChangeField.NOT_REVIEWED);
public static Predicate<ChangeData> create() {
return Predicate.not(new IsReviewedPredicate(NOT_REVIEWED));
diff --git a/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index c52d2a925c..3a43fd3669 100644
--- a/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -93,7 +93,7 @@ public class IsWatchedByPredicate extends AndPredicate<ChangeData> {
throws QueryParseException {
CurrentUser user = args.getUser();
if (user.isIdentifiedUser()) {
- return user.asIdentifiedUser().state().getProjectWatches().keySet();
+ return user.asIdentifiedUser().state().projectWatches().keySet();
}
return Collections.emptySet();
}
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index b5d375cd64..38d1dbed8b 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.query.OrPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.RangeUtil;
import com.google.gerrit.index.query.RangeUtil.Range;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectCache;
diff --git a/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java b/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
index fe4d4e10ca..d531236286 100644
--- a/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
/** Predicate over change number (aka legacy ID or Change.Id). */
public class LegacyChangeIdPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java b/java/com/google/gerrit/server/query/change/LegacyChangeIdStrPredicate.java
index d886bafa0c..bae9c0d1fd 100644
--- a/java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LegacyChangeIdStrPredicate.java
@@ -14,20 +14,22 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.index.query.PostFilterPredicate;
-import com.google.gerrit.server.index.change.ChangeField;
+import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID_STR;
-public class FileWithNoExtensionInElasticPredicate extends PostFilterPredicate<ChangeData> {
+import com.google.gerrit.entities.Change;
- private static final String NO_EXT = "";
+/** Predicate over change number (aka legacy ID or Change.Id). */
+public class LegacyChangeIdStrPredicate extends ChangeIndexPredicate {
+ protected final Change.Id id;
- public FileWithNoExtensionInElasticPredicate() {
- super(ChangeField.EXTENSION.getName(), NO_EXT);
+ public LegacyChangeIdStrPredicate(Change.Id id) {
+ super(LEGACY_ID_STR, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
+ this.id = id;
}
@Override
- public boolean match(ChangeData cd) {
- return ChangeField.getExtensions(cd).contains(NO_EXT);
+ public boolean match(ChangeData object) {
+ return id.equals(object.getId());
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/MessagePredicate.java b/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 0bd8c881c4..44cbd8eed3 100644
--- a/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -33,7 +33,12 @@ public class MessagePredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData object) {
try {
- Predicate<ChangeData> p = Predicate.and(new LegacyChangeIdPredicate(object.getId()), this);
+ Predicate<ChangeData> p =
+ Predicate.and(
+ index.getSchema().useLegacyNumericFields()
+ ? new LegacyChangeIdPredicate(object.getId())
+ : new LegacyChangeIdStrPredicate(object.getId()),
+ this);
for (ChangeData cData : index.getSource(p, IndexedChangeQuery.oneResult()).read()) {
if (cData.getId().equals(object.getId())) {
return true;
diff --git a/java/com/google/gerrit/server/query/change/OrSource.java b/java/com/google/gerrit/server/query/change/OrSource.java
index ba06b89f15..983d9b4cd4 100644
--- a/java/com/google/gerrit/server/query/change/OrSource.java
+++ b/java/com/google/gerrit/server/query/change/OrSource.java
@@ -14,17 +14,21 @@
package com.google.gerrit.server.query.change;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.FieldBundle;
-import com.google.gerrit.index.query.ListResultSet;
+import com.google.gerrit.index.query.LazyResultSet;
import com.google.gerrit.index.query.OrPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.ResultSet;
-import com.google.gerrit.reviewdb.client.Change;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
public class OrSource extends OrPredicate<ChangeData> implements ChangeDataSource {
@@ -36,22 +40,29 @@ public class OrSource extends OrPredicate<ChangeData> implements ChangeDataSourc
@Override
public ResultSet<ChangeData> read() {
- // TODO(spearce) This probably should be more lazy.
- //
- List<ChangeData> r = new ArrayList<>();
- Set<Change.Id> have = new HashSet<>();
- for (Predicate<ChangeData> p : getChildren()) {
- if (p instanceof ChangeDataSource) {
- for (ChangeData cd : ((ChangeDataSource) p).read()) {
- if (have.add(cd.getId())) {
- r.add(cd);
- }
- }
- } else {
- throw new StorageException("No ChangeDataSource: " + p);
- }
+ Optional<Predicate<ChangeData>> nonChangeDataSource =
+ getChildren().stream().filter(p -> !(p instanceof ChangeDataSource)).findAny();
+ if (nonChangeDataSource.isPresent()) {
+ throw new StorageException("No ChangeDataSource: " + nonChangeDataSource.get());
}
- return new ListResultSet<>(r);
+
+ // ResultSets are lazy. Calling #read here first and then dealing with ResultSets only when
+ // requested allows the index to run asynchronous queries.
+ List<ResultSet<ChangeData>> results =
+ getChildren().stream().map(p -> ((ChangeDataSource) p).read()).collect(toImmutableList());
+ return new LazyResultSet<>(
+ () -> {
+ List<ChangeData> r = new ArrayList<>();
+ Set<Change.Id> have = new HashSet<>();
+ for (ResultSet<ChangeData> resultSet : results) {
+ for (ChangeData result : resultSet) {
+ if (have.add(result.getId())) {
+ r.add(result);
+ }
+ }
+ }
+ return ImmutableList.copyOf(r);
+ });
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 08e6f3339b..e8754995d5 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -19,11 +19,11 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.DynamicOptions;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.data.ChangeAttribute;
diff --git a/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/java/com/google/gerrit/server/query/change/OwnerPredicate.java
index 100a66c7a7..923a9ca924 100644
--- a/java/com/google/gerrit/server/query/change/OwnerPredicate.java
+++ b/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
public class OwnerPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
index 41b32040aa..c2bc5d9514 100644
--- a/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
+++ b/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.query.PostFilterPredicate;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
public class OwnerinPredicate extends PostFilterPredicate<ChangeData> {
diff --git a/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 17d6448915..5deb7f5368 100644
--- a/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -15,10 +15,10 @@
package com.google.gerrit.server.query.change;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.index.query.OrPredicate;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChildProjects;
import com.google.gerrit.server.project.ProjectCache;
@@ -40,7 +40,7 @@ public class ParentProjectPredicate extends OrPredicate<ChangeData> {
protected static List<Predicate<ChangeData>> predicates(
ProjectCache projectCache, ChildProjects childProjects, String value) {
- ProjectState projectState = projectCache.get(new Project.NameKey(value));
+ ProjectState projectState = projectCache.get(Project.nameKey(value));
if (projectState == null) {
return Collections.emptyList();
}
diff --git a/java/com/google/gerrit/server/query/change/ProjectPredicate.java b/java/com/google/gerrit/server/query/change/ProjectPredicate.java
index febd6c64f5..db5a9327f0 100644
--- a/java/com/google/gerrit/server/query/change/ProjectPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ProjectPredicate.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.index.change.ChangeField;
public class ProjectPredicate extends ChangeIndexPredicate {
@@ -24,7 +24,7 @@ public class ProjectPredicate extends ChangeIndexPredicate {
}
protected Project.NameKey getValueKey() {
- return new Project.NameKey(getValue());
+ return Project.nameKey(getValue());
}
@Override
@@ -34,7 +34,7 @@ public class ProjectPredicate extends ChangeIndexPredicate {
return false;
}
- Project.NameKey p = change.getDest().getParentKey();
+ Project.NameKey p = change.getDest().project();
return p.equals(getValueKey());
}
diff --git a/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java b/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
index 744f4d27ef..c23a17570c 100644
--- a/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
public class ProjectPrefixPredicate extends ChangeIndexPredicate {
@@ -25,7 +25,7 @@ public class ProjectPrefixPredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData object) {
Change c = object.change();
- return c != null && c.getDest().getParentKey().get().startsWith(getValue());
+ return c != null && c.getDest().project().get().startsWith(getValue());
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/RefPredicate.java b/java/com/google/gerrit/server/query/change/RefPredicate.java
index 2ed4c99dd4..1c70a62d9f 100644
--- a/java/com/google/gerrit/server/query/change/RefPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
public class RefPredicate extends ChangeIndexPredicate {
@@ -28,7 +28,7 @@ public class RefPredicate extends ChangeIndexPredicate {
if (change == null) {
return false;
}
- return getValue().equals(change.getDest().get());
+ return getValue().equals(change.getDest().branch());
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java b/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
index 46a17f6798..bbdfc663f0 100644
--- a/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.index.change.ChangeField;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
@@ -44,7 +44,7 @@ public class RegexProjectPredicate extends ChangeRegexPredicate {
return false;
}
- Project.NameKey p = change.getDest().getParentKey();
+ Project.NameKey p = change.getDest().project();
return pattern.run(p.get());
}
diff --git a/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
index af211e630a..6d404b4631 100644
--- a/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
@@ -42,7 +42,7 @@ public class RegexRefPredicate extends ChangeRegexPredicate {
if (change == null) {
return false;
}
- return pattern.run(change.getDest().get());
+ return pattern.run(change.getDest().branch());
}
@Override
diff --git a/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 0441afa4b5..7713f24223 100644
--- a/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.query.change;
import static com.google.gerrit.server.index.change.ChangeField.EXACT_TOPIC;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
diff --git a/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index 19104d31ef..d783f76aa5 100644
--- a/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.query.change;
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
diff --git a/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index 542a357804..e5c6647803 100644
--- a/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.query.PostFilterPredicate;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
diff --git a/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/java/com/google/gerrit/server/query/change/SingleGroupUser.java
index a49e8c5642..c451d46d29 100644
--- a/java/com/google/gerrit/server/query/change/SingleGroupUser.java
+++ b/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.query.change;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
diff --git a/java/com/google/gerrit/server/query/change/StarPredicate.java b/java/com/google/gerrit/server/query/change/StarPredicate.java
index 6c5fd78798..788f1a32f6 100644
--- a/java/com/google/gerrit/server/query/change/StarPredicate.java
+++ b/java/com/google/gerrit/server/query/change/StarPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.index.change.ChangeField;
diff --git a/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java b/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
index 0995a590f6..093447e095 100644
--- a/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
+++ b/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.index.change.ChangeField;
public class SubmissionIdPredicate extends ChangeIndexPredicate {
diff --git a/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java b/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
index e59ae436bf..bc65422f27 100644
--- a/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
+++ b/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
@@ -17,8 +17,8 @@ package com.google.gerrit.server.query.change;
import static java.util.stream.Collectors.toList;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.index.change.ChangeField;
import java.util.Set;
diff --git a/java/com/google/gerrit/server/query/group/GroupPredicates.java b/java/com/google/gerrit/server/query/group/GroupPredicates.java
index d02f6a46fa..17a7000348 100644
--- a/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.query.group;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.query.IndexPredicate;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.group.GroupField;
import java.util.Locale;
diff --git a/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java b/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
index 215e36f6d0..4e60db5dbe 100644
--- a/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
@@ -20,13 +20,13 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryRequiresAuthException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException;
import com.google.gerrit.server.account.GroupBackend;
@@ -75,7 +75,7 @@ public class GroupQueryBuilder extends QueryBuilder<InternalGroup, GroupQueryBui
@Operator
public Predicate<InternalGroup> uuid(String uuid) {
- return GroupPredicates.uuid(new AccountGroup.UUID(uuid));
+ return GroupPredicates.uuid(AccountGroup.uuid(uuid));
}
@Operator
@@ -169,7 +169,7 @@ public class GroupQueryBuilder extends QueryBuilder<InternalGroup, GroupQueryBui
}
private AccountGroup.UUID parseGroup(String groupNameOrUuid) throws QueryParseException {
- Optional<InternalGroup> group = args.groupCache.get(new AccountGroup.UUID(groupNameOrUuid));
+ Optional<InternalGroup> group = args.groupCache.get(AccountGroup.uuid(groupNameOrUuid));
if (group.isPresent()) {
return group.get().getGroupUUID();
}
diff --git a/java/com/google/gerrit/server/query/group/InternalGroupQuery.java b/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
index 57498099b2..57328736b0 100644
--- a/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
+++ b/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
@@ -19,11 +19,11 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.InternalQuery;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.group.GroupIndexCollection;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index 5f13236fd3..4e56fac76d 100644
--- a/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.query.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectField;
import com.google.gerrit.index.project.ProjectPredicate;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.reviewdb.client.Project;
import java.util.Locale;
public class ProjectPredicates {
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index 8b4cb538ae..d23454644e 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.query.project;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import java.util.List;
@@ -41,12 +41,12 @@ public class ProjectQueryBuilder extends QueryBuilder<ProjectData, ProjectQueryB
@Operator
public Predicate<ProjectData> name(String name) {
- return ProjectPredicates.name(new Project.NameKey(name));
+ return ProjectPredicates.name(Project.nameKey(name));
}
@Operator
public Predicate<ProjectData> parent(String parentName) {
- return ProjectPredicates.parent(new Project.NameKey(parentName));
+ return ProjectPredicates.parent(Project.nameKey(parentName));
}
@Operator
diff --git a/java/com/google/gerrit/server/quota/DefaultQuotaBackend.java b/java/com/google/gerrit/server/quota/DefaultQuotaBackend.java
index d39e55ca80..c6599759ed 100644
--- a/java/com/google/gerrit/server/quota/DefaultQuotaBackend.java
+++ b/java/com/google/gerrit/server/quota/DefaultQuotaBackend.java
@@ -18,9 +18,9 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
diff --git a/java/com/google/gerrit/server/quota/QuotaBackend.java b/java/com/google/gerrit/server/quota/QuotaBackend.java
index 11ce61f74e..694f4f1070 100644
--- a/java/com/google/gerrit/server/quota/QuotaBackend.java
+++ b/java/com/google/gerrit/server/quota/QuotaBackend.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.quota;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.ImplementedBy;
diff --git a/java/com/google/gerrit/server/quota/QuotaRequestContext.java b/java/com/google/gerrit/server/quota/QuotaRequestContext.java
index 90b501c39f..bfa7a35419 100644
--- a/java/com/google/gerrit/server/quota/QuotaRequestContext.java
+++ b/java/com/google/gerrit/server/quota/QuotaRequestContext.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.quota;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index dfb95940ff..fd341e9dfd 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -10,8 +10,10 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/index/project",
@@ -20,7 +22,6 @@ java_library(
"//java/com/google/gerrit/mail",
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/prettify:server",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/util/time",
@@ -29,6 +30,7 @@ java_library(
"//lib:blame-cache",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
@@ -38,6 +40,5 @@ java_library(
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/java/com/google/gerrit/server/restapi/access/ListAccess.java b/java/com/google/gerrit/server/restapi/access/ListAccess.java
index a3e95301b4..2520821ad1 100644
--- a/java/com/google/gerrit/server/restapi/access/ListAccess.java
+++ b/java/com/google/gerrit/server/restapi/access/ListAccess.java
@@ -14,16 +14,13 @@
package com.google.gerrit.server.restapi.access;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.project.GetAccess;
import com.google.inject.Inject;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -47,13 +44,12 @@ public class ListAccess implements RestReadView<TopLevelResource> {
}
@Override
- public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
- throws ResourceNotFoundException, ResourceConflictException, IOException,
- PermissionBackendException {
+ public Response<Map<String, ProjectAccessInfo>> apply(TopLevelResource resource)
+ throws Exception {
Map<String, ProjectAccessInfo> access = new TreeMap<>();
for (String p : projects) {
- access.put(p, getAccess.apply(new Project.NameKey(p)));
+ access.put(p, getAccess.apply(Project.nameKey(p)));
}
- return access;
+ return Response.ok(access);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/AddSshKey.java b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
index 7c05f4e07f..1fcf0bd1cd 100644
--- a/java/com/google/gerrit/server/restapi/account/AddSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
@@ -104,7 +104,7 @@ public class AddSshKey
addKeyFactory.create(user, sshKey).send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
- "Cannot send SSH key added message to %s", user.getAccount().getPreferredEmail());
+ "Cannot send SSH key added message to %s", user.getAccount().preferredEmail());
}
user.getUserName().ifPresent(sshKeyCache::evict);
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index bf50e10e76..6ad6d97743 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -22,6 +22,8 @@ import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.InvalidSshKeyException;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -34,8 +36,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountExternalIdCreator;
import com.google.gerrit.server.account.AccountLoader;
@@ -44,6 +44,7 @@ import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.group.GroupResolver;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupUpdate;
@@ -59,6 +60,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -75,6 +77,7 @@ public class CreateAccount
private final PluginSetContext<AccountExternalIdCreator> externalIdCreators;
private final Provider<GroupsUpdate> groupsUpdate;
private final OutgoingEmailValidator validator;
+ private final AuthConfig authConfig;
@Inject
CreateAccount(
@@ -86,7 +89,8 @@ public class CreateAccount
AccountLoader.Factory infoLoader,
PluginSetContext<AccountExternalIdCreator> externalIdCreators,
@UserInitiated Provider<GroupsUpdate> groupsUpdate,
- OutgoingEmailValidator validator) {
+ OutgoingEmailValidator validator,
+ AuthConfig authConfig) {
this.seq = seq;
this.groupResolver = groupResolver;
this.authorizedKeys = authorizedKeys;
@@ -96,6 +100,7 @@ public class CreateAccount
this.externalIdCreators = externalIdCreators;
this.groupsUpdate = groupsUpdate;
this.validator = validator;
+ this.authConfig = authConfig;
}
@Override
@@ -109,17 +114,21 @@ public class CreateAccount
public Response<AccountInfo> apply(IdString id, AccountInput input)
throws BadRequestException, ResourceConflictException, UnprocessableEntityException,
IOException, ConfigInvalidException, PermissionBackendException {
- String username = id.get();
- if (input.username != null && !username.equals(input.username)) {
+ String username = applyCaseOfUsername(id.get());
+ if (input.username != null && !username.equals(applyCaseOfUsername(input.username))) {
throw new BadRequestException("username must match URL");
}
if (!ExternalId.isValidUsername(username)) {
throw new BadRequestException("Invalid username '" + username + "'");
}
+ if (input.name == null) {
+ input.name = input.username;
+ }
+
Set<AccountGroup.UUID> groups = parseGroups(input.groups);
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
List<ExternalId> extIds = new ArrayList<>();
if (input.email != null) {
@@ -175,6 +184,10 @@ public class CreateAccount
return Response.created(info);
}
+ private String applyCaseOfUsername(String username) {
+ return authConfig.isUserNameToLowerCase() ? username.toLowerCase(Locale.US) : username;
+ }
+
private Set<AccountGroup.UUID> parseGroups(List<String> groups)
throws UnprocessableEntityException {
Set<AccountGroup.UUID> groupUuids = new HashSet<>();
diff --git a/java/com/google/gerrit/server/restapi/account/CreateEmail.java b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
index cc4cf21af7..ae45b68b02 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
@@ -97,11 +97,11 @@ public class CreateEmail
throw new MethodNotAllowedException("realm does not allow adding emails");
}
- return apply(rsrc.getUser(), id, input);
+ return Response.created(apply(rsrc.getUser(), id, input));
}
/** To be used from plugins that want to create emails without permission checks. */
- public Response<EmailInfo> apply(IdentifiedUser user, IdString id, EmailInput input)
+ public EmailInfo apply(IdentifiedUser user, IdString id, EmailInput input)
throws RestApiException, EmailException, MethodNotAllowedException, IOException,
ConfigInvalidException, PermissionBackendException {
String email = id.get().trim();
@@ -146,6 +146,6 @@ public class CreateEmail
throw e;
}
}
- return Response.created(info);
+ return info;
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
index 478830143e..a6fbb10e17 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
@@ -15,26 +15,27 @@
package com.google.gerrit.server.restapi.account;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
import com.google.gerrit.extensions.api.accounts.DeletedDraftCommentInfo;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
@@ -107,7 +108,7 @@ public class DeleteDraftComments
}
@Override
- public ImmutableList<DeletedDraftCommentInfo> apply(
+ public Response<ImmutableList<DeletedDraftCommentInfo>> apply(
AccountResource rsrc, DeleteDraftCommentsInput input)
throws RestApiException, UpdateException {
CurrentUser user = userProvider.get();
@@ -147,7 +148,8 @@ public class DeleteDraftComments
// allowing partial failure would have little value.
BatchUpdate.execute(updates.values(), BatchUpdateListener.NONE, false);
- return ops.stream().map(Op::getResult).filter(Objects::nonNull).collect(toImmutableList());
+ return Response.ok(
+ ops.stream().map(Op::getResult).filter(Objects::nonNull).collect(toImmutableList()));
}
private Predicate<ChangeData> predicate(Account.Id accountId, DeleteDraftCommentsInput input)
@@ -180,8 +182,8 @@ public class DeleteDraftComments
boolean dirty = false;
for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getNotes(), accountId)) {
dirty = true;
- PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), c.key.patchSetId);
- setCommentRevId(c, patchListCache, ctx.getChange(), psUtil.get(ctx.getNotes(), psId));
+ PatchSet.Id psId = PatchSet.id(ctx.getChange().getId(), c.key.patchSetId);
+ setCommentCommitId(c, patchListCache, ctx.getChange(), psUtil.get(ctx.getNotes(), psId));
commentsUtil.deleteComments(ctx.getUpdate(psId), Collections.singleton(c));
comments.add(commentFormatter.format(c));
}
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java
index 72a67db858..5cdf4ae9b6 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteEmail.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.account;
import static java.util.stream.Collectors.toSet;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.common.Input;
@@ -25,7 +26,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java b/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
index 054f4bc984..b470be8e75 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
@@ -74,7 +74,7 @@ public class DeleteSshKey implements RestModifyView<AccountResource.SshKey, Inpu
deleteKeySenderFactory.create(user, rsrc.getSshKey()).send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
- "Cannot send SSH key deletion message to %s", user.getAccount().getPreferredEmail());
+ "Cannot send SSH key deletion message to %s", user.getAccount().preferredEmail());
}
user.getUserName().ifPresent(sshKeyCache::evict);
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
index 798aad17b3..9f38b97dcc 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
@@ -16,13 +16,13 @@ package com.google.gerrit.server.restapi.account;
import static java.util.stream.Collectors.toList;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResource;
@@ -77,7 +77,7 @@ public class DeleteWatchedProjects
u.deleteProjectWatches(
input.stream()
.filter(Objects::nonNull)
- .map(w -> ProjectWatchKey.create(new Project.NameKey(w.project), w.filter))
+ .map(w -> ProjectWatchKey.create(Project.nameKey(w.project), w.filter))
.collect(toList())));
return Response.none();
}
diff --git a/java/com/google/gerrit/server/restapi/account/EmailsCollection.java b/java/com/google/gerrit/server/restapi/account/EmailsCollection.java
index 434b9d6174..7a498f46ac 100644
--- a/java/com/google/gerrit/server/restapi/account/EmailsCollection.java
+++ b/java/com/google/gerrit/server/restapi/account/EmailsCollection.java
@@ -63,7 +63,7 @@ public class EmailsCollection implements ChildCollection<AccountResource, Accoun
}
if ("preferred".equals(id.get())) {
- String email = rsrc.getUser().getAccount().getPreferredEmail();
+ String email = rsrc.getUser().getAccount().preferredEmail();
if (Strings.isNullOrEmpty(email)) {
throw new ResourceNotFoundException(id);
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetAccount.java b/java/com/google/gerrit/server/restapi/account/GetAccount.java
index 6544f8dced..898b0bbfbc 100644
--- a/java/com/google/gerrit/server/restapi/account/GetAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/GetAccount.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountResource;
@@ -32,10 +33,10 @@ public class GetAccount implements RestReadView<AccountResource> {
}
@Override
- public AccountInfo apply(AccountResource rsrc) throws PermissionBackendException {
+ public Response<AccountInfo> apply(AccountResource rsrc) throws PermissionBackendException {
AccountLoader loader = infoFactory.create(true);
AccountInfo info = loader.get(rsrc.getUser().getAccountId());
loader.fill();
- return info;
+ return Response.ok(info);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetAgreements.java b/java/com/google/gerrit/server/restapi/account/GetAgreements.java
index edcbc3560f..5feca6696c 100644
--- a/java/com/google/gerrit/server/restapi/account/GetAgreements.java
+++ b/java/com/google/gerrit/server/restapi/account/GetAgreements.java
@@ -18,12 +18,13 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.common.AgreementInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
@@ -61,7 +62,7 @@ public class GetAgreements implements RestReadView<AccountResource> {
}
@Override
- public List<AgreementInfo> apply(AccountResource resource)
+ public Response<List<AgreementInfo>> apply(AccountResource resource)
throws RestApiException, PermissionBackendException {
if (!agreementsEnabled) {
throw new MethodNotAllowedException("contributor agreements disabled");
@@ -97,6 +98,6 @@ public class GetAgreements implements RestReadView<AccountResource> {
results.add(agreementJson.format(ca));
}
}
- return results;
+ return Response.ok(results);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetAvatarChangeUrl.java b/java/com/google/gerrit/server/restapi/account/GetAvatarChangeUrl.java
index 904b15fc68..e97e0a0e7e 100644
--- a/java/com/google/gerrit/server/restapi/account/GetAvatarChangeUrl.java
+++ b/java/com/google/gerrit/server/restapi/account/GetAvatarChangeUrl.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.avatar.AvatarProvider;
@@ -33,7 +34,7 @@ public class GetAvatarChangeUrl implements RestReadView<AccountResource> {
}
@Override
- public String apply(AccountResource rsrc) throws ResourceNotFoundException {
+ public Response<String> apply(AccountResource rsrc) throws ResourceNotFoundException {
AvatarProvider impl = avatarProvider.get();
if (impl == null) {
throw new ResourceNotFoundException();
@@ -43,6 +44,6 @@ public class GetAvatarChangeUrl implements RestReadView<AccountResource> {
if (Strings.isNullOrEmpty(url)) {
throw new ResourceNotFoundException();
}
- return url;
+ return Response.ok(url);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
index 77f166899b..fa9ab18bd2 100644
--- a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
@@ -28,9 +28,9 @@ import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.account.AccountLimits;
@@ -40,7 +40,6 @@ import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -79,7 +78,7 @@ public class GetCapabilities implements RestReadView<AccountResource> {
}
@Override
- public Object apply(AccountResource resource)
+ public Response<Map<String, Object>> apply(AccountResource resource)
throws RestApiException, PermissionBackendException {
permissionBackend.checkUsesDefaultCapabilities();
PermissionBackend.WithUser perm = permissionBackend.currentUser();
@@ -97,9 +96,7 @@ public class GetCapabilities implements RestReadView<AccountResource> {
addRanges(have, limits);
addPriority(have, limits);
- return OutputFormat.JSON
- .newGson()
- .toJsonTree(have, new TypeToken<Map<String, Object>>() {}.getType());
+ return Response.ok(have);
}
private Set<GlobalOrPluginPermission> permissionsToTest() {
@@ -172,9 +169,9 @@ public class GetCapabilities implements RestReadView<AccountResource> {
}
@Override
- public BinaryResult apply(Capability resource) throws ResourceNotFoundException {
+ public Response<BinaryResult> apply(Capability resource) throws ResourceNotFoundException {
permissionBackend.checkUsesDefaultCapabilities();
- return BinaryResult.create("ok\n");
+ return Response.ok(BinaryResult.create("ok\n"));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetDetail.java b/java/com/google/gerrit/server/restapi/account/GetDetail.java
index 72044c40d1..b19559e3e3 100644
--- a/java/com/google/gerrit/server/restapi/account/GetDetail.java
+++ b/java/com/google/gerrit/server/restapi/account/GetDetail.java
@@ -14,9 +14,10 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountDetailInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.InternalAccountDirectory;
@@ -36,12 +37,12 @@ public class GetDetail implements RestReadView<AccountResource> {
}
@Override
- public AccountDetailInfo apply(AccountResource rsrc) throws PermissionBackendException {
+ public Response<AccountDetailInfo> apply(AccountResource rsrc) throws PermissionBackendException {
Account a = rsrc.getUser().getAccount();
- AccountDetailInfo info = new AccountDetailInfo(a.getId().get());
- info.registeredOn = a.getRegisteredOn();
+ AccountDetailInfo info = new AccountDetailInfo(a.id().get());
+ info.registeredOn = a.registeredOn();
info.inactive = !a.isActive() ? true : null;
directory.fillAccountInfo(Collections.singleton(info), EnumSet.allOf(FillOptions.class));
- return info;
+ return Response.ok(info);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetDiffPreferences.java b/java/com/google/gerrit/server/restapi/account/GetDiffPreferences.java
index 40201a879a..670ef3b83d 100644
--- a/java/com/google/gerrit/server/restapi/account/GetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/GetDiffPreferences.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
@@ -48,16 +49,17 @@ public class GetDiffPreferences implements RestReadView<AccountResource> {
}
@Override
- public DiffPreferencesInfo apply(AccountResource rsrc)
+ public Response<DiffPreferencesInfo> apply(AccountResource rsrc)
throws RestApiException, ConfigInvalidException, IOException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
}
Account.Id id = rsrc.getUser().getAccountId();
- return accountCache
- .get(id)
- .map(AccountState::getDiffPreferences)
- .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
+ return Response.ok(
+ accountCache
+ .get(id)
+ .map(AccountState::diffPreferences)
+ .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString()))));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetEditPreferences.java b/java/com/google/gerrit/server/restapi/account/GetEditPreferences.java
index 0ecd6eaddd..409209ee41 100644
--- a/java/com/google/gerrit/server/restapi/account/GetEditPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/GetEditPreferences.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
@@ -48,16 +49,17 @@ public class GetEditPreferences implements RestReadView<AccountResource> {
}
@Override
- public EditPreferencesInfo apply(AccountResource rsrc)
+ public Response<EditPreferencesInfo> apply(AccountResource rsrc)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
}
Account.Id id = rsrc.getUser().getAccountId();
- return accountCache
- .get(id)
- .map(AccountState::getEditPreferences)
- .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
+ return Response.ok(
+ accountCache
+ .get(id)
+ .map(AccountState::editPreferences)
+ .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString()))));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetEmail.java b/java/com/google/gerrit/server/restapi/account/GetEmail.java
index 31183805a3..afcdac2e50 100644
--- a/java/com/google/gerrit/server/restapi/account/GetEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/GetEmail.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.common.EmailInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.inject.Inject;
@@ -26,10 +27,10 @@ public class GetEmail implements RestReadView<AccountResource.Email> {
public GetEmail() {}
@Override
- public EmailInfo apply(AccountResource.Email rsrc) {
+ public Response<EmailInfo> apply(AccountResource.Email rsrc) {
EmailInfo e = new EmailInfo();
e.email = rsrc.getEmail();
- e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
- return e;
+ e.preferred(rsrc.getUser().getAccount().preferredEmail());
+ return Response.ok(e);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetEmails.java b/java/com/google/gerrit/server/restapi/account/GetEmails.java
index 8c21536ed3..9db9f05cc2 100644
--- a/java/com/google/gerrit/server/restapi/account/GetEmails.java
+++ b/java/com/google/gerrit/server/restapi/account/GetEmails.java
@@ -19,6 +19,7 @@ import static java.util.stream.Collectors.toList;
import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
@@ -43,22 +44,23 @@ public class GetEmails implements RestReadView<AccountResource> {
}
@Override
- public List<EmailInfo> apply(AccountResource rsrc)
+ public Response<List<EmailInfo>> apply(AccountResource rsrc)
throws AuthException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
}
- return rsrc.getUser().getEmailAddresses().stream()
- .filter(Objects::nonNull)
- .map(e -> toEmailInfo(rsrc, e))
- .sorted(comparing((EmailInfo e) -> e.email))
- .collect(toList());
+ return Response.ok(
+ rsrc.getUser().getEmailAddresses().stream()
+ .filter(Objects::nonNull)
+ .map(e -> toEmailInfo(rsrc, e))
+ .sorted(comparing((EmailInfo e) -> e.email))
+ .collect(toList()));
}
private static EmailInfo toEmailInfo(AccountResource rsrc, String email) {
EmailInfo e = new EmailInfo();
e.email = email;
- e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
+ e.preferred(rsrc.getUser().getAccount().preferredEmail());
return e;
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
index ef448dc7cc..0e52af239c 100644
--- a/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
+++ b/java/com/google/gerrit/server/restapi/account/GetExternalIds.java
@@ -19,6 +19,7 @@ import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USE
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
@@ -58,7 +59,7 @@ public class GetExternalIds implements RestReadView<AccountResource> {
}
@Override
- public List<AccountExternalIdInfo> apply(AccountResource resource)
+ public Response<List<AccountExternalIdInfo>> apply(AccountResource resource)
throws RestApiException, IOException, PermissionBackendException {
if (!self.get().hasSameAccountId(resource.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
@@ -66,7 +67,7 @@ public class GetExternalIds implements RestReadView<AccountResource> {
Collection<ExternalId> ids = externalIds.byAccount(resource.getUser().getAccountId());
if (ids.isEmpty()) {
- return ImmutableList.of();
+ return Response.ok(ImmutableList.of());
}
List<AccountExternalIdInfo> result = Lists.newArrayListWithCapacity(ids.size());
for (ExternalId id : ids) {
@@ -83,7 +84,7 @@ public class GetExternalIds implements RestReadView<AccountResource> {
}
result.add(info);
}
- return result;
+ return Response.ok(result);
}
private static Boolean toBoolean(boolean v) {
diff --git a/java/com/google/gerrit/server/restapi/account/GetGroups.java b/java/com/google/gerrit/server/restapi/account/GetGroups.java
index 569ff76a4c..22492a751f 100644
--- a/java/com/google/gerrit/server/restapi/account/GetGroups.java
+++ b/java/com/google/gerrit/server/restapi/account/GetGroups.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.GroupControl;
@@ -42,7 +43,8 @@ public class GetGroups implements RestReadView<AccountResource> {
}
@Override
- public List<GroupInfo> apply(AccountResource resource) throws PermissionBackendException {
+ public Response<List<GroupInfo>> apply(AccountResource resource)
+ throws PermissionBackendException {
IdentifiedUser user = resource.getUser();
Account.Id userId = user.getAccountId();
Set<AccountGroup.UUID> knownGroups = user.getEffectiveGroups().getKnownGroups();
@@ -58,6 +60,6 @@ public class GetGroups implements RestReadView<AccountResource> {
visibleGroups.add(json.format(ctl.getGroup()));
}
}
- return visibleGroups;
+ return Response.ok(visibleGroups);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetName.java b/java/com/google/gerrit/server/restapi/account/GetName.java
index bdf379e52c..ca33887eb0 100644
--- a/java/com/google/gerrit/server/restapi/account/GetName.java
+++ b/java/com/google/gerrit/server/restapi/account/GetName.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetName implements RestReadView<AccountResource> {
@Override
- public String apply(AccountResource rsrc) {
- return Strings.nullToEmpty(rsrc.getUser().getAccount().getFullName());
+ public Response<String> apply(AccountResource rsrc) {
+ return Response.ok(Strings.nullToEmpty(rsrc.getUser().getAccount().fullName()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetOAuthToken.java b/java/com/google/gerrit/server/restapi/account/GetOAuthToken.java
index 395c1599db..24682c02c3 100644
--- a/java/com/google/gerrit/server/restapi/account/GetOAuthToken.java
+++ b/java/com/google/gerrit/server/restapi/account/GetOAuthToken.java
@@ -18,6 +18,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
@@ -50,7 +51,7 @@ public class GetOAuthToken implements RestReadView<AccountResource> {
}
@Override
- public OAuthTokenInfo apply(AccountResource rsrc)
+ public Response<OAuthTokenInfo> apply(AccountResource rsrc)
throws AuthException, ResourceNotFoundException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
throw new AuthException("not allowed to get access token");
@@ -66,7 +67,7 @@ public class GetOAuthToken implements RestReadView<AccountResource> {
accessTokenInfo.providerId = accessToken.getProviderId();
accessTokenInfo.expiresAt = Long.toString(accessToken.getExpiresAt());
accessTokenInfo.type = BEARER_TYPE;
- return accessTokenInfo;
+ return Response.ok(accessTokenInfo);
}
private static String getHostName(String canonicalWebUrl) {
diff --git a/java/com/google/gerrit/server/restapi/account/GetPreferences.java b/java/com/google/gerrit/server/restapi/account/GetPreferences.java
index 3d2064232f..d4d73c5107 100644
--- a/java/com/google/gerrit/server/restapi/account/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/GetPreferences.java
@@ -14,15 +14,16 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
@@ -54,7 +55,7 @@ public class GetPreferences implements RestReadView<AccountResource> {
}
@Override
- public GeneralPreferencesInfo apply(AccountResource rsrc)
+ public Response<GeneralPreferencesInfo> apply(AccountResource rsrc)
throws RestApiException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
@@ -64,9 +65,9 @@ public class GetPreferences implements RestReadView<AccountResource> {
GeneralPreferencesInfo preferencesInfo =
accountCache
.get(id)
- .map(AccountState::getGeneralPreferences)
+ .map(AccountState::generalPreferences)
.orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
- return unsetDownloadSchemeIfUnsupported(preferencesInfo);
+ return Response.ok(unsetDownloadSchemeIfUnsupported(preferencesInfo));
}
private GeneralPreferencesInfo unsetDownloadSchemeIfUnsupported(
diff --git a/java/com/google/gerrit/server/restapi/account/GetSshKey.java b/java/com/google/gerrit/server/restapi/account/GetSshKey.java
index dc726630c2..58b5d12c14 100644
--- a/java/com/google/gerrit/server/restapi/account/GetSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/GetSshKey.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.common.SshKeyInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountResource.SshKey;
@@ -24,7 +25,7 @@ import com.google.inject.Singleton;
public class GetSshKey implements RestReadView<AccountResource.SshKey> {
@Override
- public SshKeyInfo apply(SshKey rsrc) {
- return GetSshKeys.newSshKeyInfo(rsrc.getSshKey());
+ public Response<SshKeyInfo> apply(SshKey rsrc) {
+ return Response.ok(GetSshKeys.newSshKeyInfo(rsrc.getSshKey()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetSshKeys.java b/java/com/google/gerrit/server/restapi/account/GetSshKeys.java
index 408aa5fd48..0ca9b9e4cf 100644
--- a/java/com/google/gerrit/server/restapi/account/GetSshKeys.java
+++ b/java/com/google/gerrit/server/restapi/account/GetSshKeys.java
@@ -18,6 +18,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -53,13 +54,13 @@ public class GetSshKeys implements RestReadView<AccountResource> {
}
@Override
- public List<SshKeyInfo> apply(AccountResource rsrc)
+ public Response<List<SshKeyInfo>> apply(AccountResource rsrc)
throws AuthException, RepositoryNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
}
- return apply(rsrc.getUser());
+ return Response.ok(apply(rsrc.getUser()));
}
public List<SshKeyInfo> apply(IdentifiedUser user)
diff --git a/java/com/google/gerrit/server/restapi/account/GetStatus.java b/java/com/google/gerrit/server/restapi/account/GetStatus.java
index bc7094f9ea..447ad76f9f 100644
--- a/java/com/google/gerrit/server/restapi/account/GetStatus.java
+++ b/java/com/google/gerrit/server/restapi/account/GetStatus.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetStatus implements RestReadView<AccountResource> {
@Override
- public String apply(AccountResource rsrc) {
- return Strings.nullToEmpty(rsrc.getUser().getAccount().getStatus());
+ public Response<String> apply(AccountResource rsrc) {
+ return Response.ok(Strings.nullToEmpty(rsrc.getUser().getAccount().status()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetUsername.java b/java/com/google/gerrit/server/restapi/account/GetUsername.java
index 01185c3f71..7e58f945cf 100644
--- a/java/com/google/gerrit/server/restapi/account/GetUsername.java
+++ b/java/com/google/gerrit/server/restapi/account/GetUsername.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountResource;
import com.google.inject.Inject;
@@ -27,7 +28,8 @@ public class GetUsername implements RestReadView<AccountResource> {
public GetUsername() {}
@Override
- public String apply(AccountResource rsrc) throws AuthException, ResourceNotFoundException {
- return rsrc.getUser().getUserName().orElseThrow(ResourceNotFoundException::new);
+ public Response<String> apply(AccountResource rsrc)
+ throws AuthException, ResourceNotFoundException {
+ return Response.ok(rsrc.getUser().getUserName().orElseThrow(ResourceNotFoundException::new));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
index fce324e481..353e3f6457 100644
--- a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
@@ -19,11 +19,12 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountState;
@@ -55,7 +56,7 @@ public class GetWatchedProjects implements RestReadView<AccountResource> {
}
@Override
- public List<ProjectWatchInfo> apply(AccountResource rsrc)
+ public Response<List<ProjectWatchInfo>> apply(AccountResource rsrc)
throws AuthException, IOException, ConfigInvalidException, PermissionBackendException,
ResourceNotFoundException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
@@ -64,12 +65,13 @@ public class GetWatchedProjects implements RestReadView<AccountResource> {
Account.Id accountId = rsrc.getUser().getAccountId();
AccountState account = accounts.get(accountId).orElseThrow(ResourceNotFoundException::new);
- return account.getProjectWatches().entrySet().stream()
- .map(e -> toProjectWatchInfo(e.getKey(), e.getValue()))
- .sorted(
- comparing((ProjectWatchInfo pwi) -> pwi.project)
- .thenComparing(pwi -> Strings.nullToEmpty(pwi.filter)))
- .collect(toList());
+ return Response.ok(
+ account.projectWatches().entrySet().stream()
+ .map(e -> toProjectWatchInfo(e.getKey(), e.getValue()))
+ .sorted(
+ comparing((ProjectWatchInfo pwi) -> pwi.project)
+ .thenComparing(pwi -> Strings.nullToEmpty(pwi.filter)))
+ .collect(toList()));
}
private static ProjectWatchInfo toProjectWatchInfo(
diff --git a/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
index 14bd492d99..52361748d0 100644
--- a/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.IdentifiedUser;
@@ -64,7 +65,7 @@ public class PostWatchedProjects
}
@Override
- public List<ProjectWatchInfo> apply(AccountResource rsrc, List<ProjectWatchInfo> input)
+ public Response<List<ProjectWatchInfo>> apply(AccountResource rsrc, List<ProjectWatchInfo> input)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
diff --git a/java/com/google/gerrit/server/restapi/account/PutAgreement.java b/java/com/google/gerrit/server/restapi/account/PutAgreement.java
index 147eec8554..b4b3314a10 100644
--- a/java/com/google/gerrit/server/restapi/account/PutAgreement.java
+++ b/java/com/google/gerrit/server/restapi/account/PutAgreement.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.ContributorAgreement;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.api.accounts.AgreementInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -27,7 +28,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountState;
@@ -93,7 +93,7 @@ public class PutAgreement implements RestModifyView<AccountResource, AgreementIn
AccountState accountState = self.get().state();
try {
- addMembers.addMembers(uuid, ImmutableSet.of(accountState.getAccount().getId()));
+ addMembers.addMembers(uuid, ImmutableSet.of(accountState.account().id()));
} catch (NoSuchGroupException e) {
throw new ResourceConflictException("autoverify group not found", e);
}
diff --git a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
index f029ef94b1..3e7753fb32 100644
--- a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
+++ b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
@@ -58,7 +58,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, HttpPass
try {
rng = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("Cannot create RNG for password generator", e);
+ throw new IllegalStateException("Cannot create RNG for password generator", e);
}
}
@@ -133,7 +133,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, HttpPass
.send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
- "Cannot send HttpPassword update message to %s", user.getAccount().getPreferredEmail());
+ "Cannot send HttpPassword update message to %s", user.getAccount().preferredEmail());
}
return Strings.isNullOrEmpty(newPassword) ? Response.none() : Response.ok(newPassword);
diff --git a/java/com/google/gerrit/server/restapi/account/PutName.java b/java/com/google/gerrit/server/restapi/account/PutName.java
index 9f9dd073ec..0389014666 100644
--- a/java/com/google/gerrit/server/restapi/account/PutName.java
+++ b/java/com/google/gerrit/server/restapi/account/PutName.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -22,7 +23,6 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
@@ -91,8 +91,8 @@ public class PutName implements RestModifyView<AccountResource, NameInput> {
.get()
.update("Set Full Name via API", accountId, u -> u.setFullName(newName))
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
- return Strings.isNullOrEmpty(accountState.getAccount().getFullName())
+ return Strings.isNullOrEmpty(accountState.account().fullName())
? Response.none()
- : Response.ok(accountState.getAccount().getFullName());
+ : Response.ok(accountState.account().fullName());
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/PutPreferred.java b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
index b8edec3f6e..2ddea2f05d 100644
--- a/java/com/google/gerrit/server/restapi/account/PutPreferred.java
+++ b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
@@ -85,13 +85,13 @@ public class PutPreferred implements RestModifyView<AccountResource.Email, Input
"Set Preferred Email via API",
user.getAccountId(),
(a, u) -> {
- if (preferredEmail.equals(a.getAccount().getPreferredEmail())) {
+ if (preferredEmail.equals(a.account().preferredEmail())) {
alreadyPreferred.set(true);
} else {
// check if the user has a matching email
String matchingEmail = null;
for (String email :
- a.getExternalIds().stream()
+ a.externalIds().stream()
.map(ExternalId::email)
.filter(Objects::nonNull)
.collect(toSet())) {
@@ -128,7 +128,7 @@ public class PutPreferred implements RestModifyView<AccountResource.Email, Input
}
// claim the email now
- u.addExternalId(ExternalId.createEmail(a.getAccount().getId(), preferredEmail));
+ u.addExternalId(ExternalId.createEmail(a.account().id(), preferredEmail));
matchingEmail = preferredEmail;
} else {
// Realm says that the email doesn't belong to the user. This can only happen as
diff --git a/java/com/google/gerrit/server/restapi/account/PutStatus.java b/java/com/google/gerrit/server/restapi/account/PutStatus.java
index 4f1128d5da..7e274897be 100644
--- a/java/com/google/gerrit/server/restapi/account/PutStatus.java
+++ b/java/com/google/gerrit/server/restapi/account/PutStatus.java
@@ -73,8 +73,8 @@ public class PutStatus implements RestModifyView<AccountResource, StatusInput> {
.get()
.update("Set Status via API", user.getAccountId(), u -> u.setStatus(newStatus))
.orElseThrow(() -> new ResourceNotFoundException("account not found"));
- return Strings.isNullOrEmpty(accountState.getAccount().getStatus())
+ return Strings.isNullOrEmpty(accountState.account().status())
? Response.none()
- : Response.ok(accountState.getAccount().getStatus());
+ : Response.ok(accountState.account().status());
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/PutUsername.java b/java/com/google/gerrit/server/restapi/account/PutUsername.java
index 8d949b8dad..d5897e582b 100644
--- a/java/com/google/gerrit/server/restapi/account/PutUsername.java
+++ b/java/com/google/gerrit/server/restapi/account/PutUsername.java
@@ -17,15 +17,17 @@ package com.google.gerrit.server.restapi.account;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.extensions.api.accounts.UsernameInput;
import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountResource;
@@ -70,18 +72,12 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu
}
@Override
- public String apply(AccountResource rsrc, UsernameInput input)
- throws AuthException, MethodNotAllowedException, UnprocessableEntityException,
- ResourceConflictException, IOException, ConfigInvalidException,
- PermissionBackendException {
+ public Response<String> apply(AccountResource rsrc, UsernameInput input)
+ throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
}
- if (input == null) {
- input = new UsernameInput();
- }
-
Account.Id accountId = rsrc.getUser().getAccountId();
if (!externalIds.byAccount(accountId, SCHEME_USERNAME).isEmpty()) {
throw new MethodNotAllowedException("Username cannot be changed.");
@@ -92,8 +88,8 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu
throw new MethodNotAllowedException("realm does not allow editing username");
}
- if (Strings.isNullOrEmpty(input.username)) {
- return input.username;
+ if (input == null || Strings.isNullOrEmpty(input.username)) {
+ throw new BadRequestException("input required");
}
if (!ExternalId.isValidUsername(input.username)) {
@@ -112,7 +108,7 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu
// If we are using this identity, don't report the exception.
Optional<ExternalId> other = externalIds.get(key);
if (other.isPresent() && other.get().accountId().equals(accountId)) {
- return input.username;
+ return Response.ok(input.username);
}
// Otherwise, someone else has this identity.
@@ -120,6 +116,6 @@ public class PutUsername implements RestModifyView<AccountResource, UsernameInpu
}
sshKeyCache.evict(input.username);
- return input.username;
+ return Response.ok(input.username);
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
index 522301d0a6..f9e753c3b9 100644
--- a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
+++ b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.ListAccountsOption;
import com.google.gerrit.extensions.client.ListOption;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -23,13 +24,13 @@ import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountInfoComparator;
import com.google.gerrit.server.account.AccountLoader;
@@ -149,14 +150,14 @@ public class QueryAccounts implements RestReadView<TopLevelResource> {
}
@Override
- public List<AccountInfo> apply(TopLevelResource rsrc)
+ public Response<List<AccountInfo>> apply(TopLevelResource rsrc)
throws RestApiException, PermissionBackendException {
if (Strings.isNullOrEmpty(query)) {
throw new BadRequestException("missing query field");
}
if (suggest && (!suggestConfig || query.length() < suggestFrom)) {
- return Collections.emptyList();
+ return Response.ok(Collections.emptyList());
}
Set<FillOptions> fillOptions = EnumSet.of(FillOptions.ID);
@@ -216,7 +217,7 @@ public class QueryAccounts implements RestReadView<TopLevelResource> {
}
QueryResult<AccountState> result = queryProcessor.query(queryPred);
for (AccountState accountState : result.entities()) {
- Account.Id id = accountState.getAccount().getId();
+ Account.Id id = accountState.account().id();
matches.put(id, accountLoader.get(id));
}
@@ -227,10 +228,10 @@ public class QueryAccounts implements RestReadView<TopLevelResource> {
if (!sorted.isEmpty() && result.more()) {
sorted.get(sorted.size() - 1)._moreAccounts = true;
}
- return sorted;
+ return Response.ok(sorted);
} catch (QueryParseException e) {
if (suggest) {
- return ImmutableList.of();
+ return Response.ok(ImmutableList.of());
}
throw new BadRequestException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/account/SetDiffPreferences.java b/java/com/google/gerrit/server/restapi/account/SetDiffPreferences.java
index ee72ab7f2a..cf56965426 100644
--- a/java/com/google/gerrit/server/restapi/account/SetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/SetDiffPreferences.java
@@ -14,13 +14,14 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResource;
@@ -53,7 +54,7 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, DiffP
}
@Override
- public DiffPreferencesInfo apply(AccountResource rsrc, DiffPreferencesInfo input)
+ public Response<DiffPreferencesInfo> apply(AccountResource rsrc, DiffPreferencesInfo input)
throws RestApiException, ConfigInvalidException, RepositoryNotFoundException, IOException,
PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
@@ -65,10 +66,11 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, DiffP
}
Account.Id id = rsrc.getUser().getAccountId();
- return accountsUpdateProvider
- .get()
- .update("Set Diff Preferences via API", id, u -> u.setDiffPreferences(input))
- .map(AccountState::getDiffPreferences)
- .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
+ return Response.ok(
+ accountsUpdateProvider
+ .get()
+ .update("Set Diff Preferences via API", id, u -> u.setDiffPreferences(input))
+ .map(AccountState::diffPreferences)
+ .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString()))));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/SetEditPreferences.java b/java/com/google/gerrit/server/restapi/account/SetEditPreferences.java
index 27d32f2c34..085adaae45 100644
--- a/java/com/google/gerrit/server/restapi/account/SetEditPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/SetEditPreferences.java
@@ -14,13 +14,14 @@
package com.google.gerrit.server.restapi.account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResource;
@@ -54,7 +55,7 @@ public class SetEditPreferences implements RestModifyView<AccountResource, EditP
}
@Override
- public EditPreferencesInfo apply(AccountResource rsrc, EditPreferencesInfo input)
+ public Response<EditPreferencesInfo> apply(AccountResource rsrc, EditPreferencesInfo input)
throws RestApiException, RepositoryNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
@@ -66,10 +67,11 @@ public class SetEditPreferences implements RestModifyView<AccountResource, EditP
}
Account.Id id = rsrc.getUser().getAccountId();
- return accountsUpdateProvider
- .get()
- .update("Set Edit Preferences via API", id, u -> u.setEditPreferences(input))
- .map(AccountState::getEditPreferences)
- .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
+ return Response.ok(
+ accountsUpdateProvider
+ .get()
+ .update("Set Edit Preferences via API", id, u -> u.setEditPreferences(input))
+ .map(AccountState::editPreferences)
+ .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString()))));
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/SetPreferences.java b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
index c6623dbf7b..3f2211ef06 100644
--- a/java/com/google/gerrit/server/restapi/account/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.account;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -22,15 +23,15 @@ import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AccountsUpdate;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -60,21 +61,22 @@ public class SetPreferences implements RestModifyView<AccountResource, GeneralPr
}
@Override
- public GeneralPreferencesInfo apply(AccountResource rsrc, GeneralPreferencesInfo input)
+ public Response<GeneralPreferencesInfo> apply(AccountResource rsrc, GeneralPreferencesInfo input)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
}
checkDownloadScheme(input.downloadScheme);
- Preferences.validateMy(input.my);
+ StoredPreferences.validateMy(input.my);
Account.Id id = rsrc.getUser().getAccountId();
- return accountsUpdateProvider
- .get()
- .update("Set General Preferences via API", id, u -> u.setGeneralPreferences(input))
- .map(AccountState::getGeneralPreferences)
- .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString())));
+ return Response.ok(
+ accountsUpdateProvider
+ .get()
+ .update("Set General Preferences via API", id, u -> u.setGeneralPreferences(input))
+ .map(AccountState::generalPreferences)
+ .orElseThrow(() -> new ResourceNotFoundException(IdString.fromDecoded(id.toString()))));
}
private void checkDownloadScheme(String downloadScheme) throws BadRequestException {
diff --git a/java/com/google/gerrit/server/restapi/account/Stars.java b/java/com/google/gerrit/server/restapi/account/Stars.java
index c610adfd70..cdaa99dac7 100644
--- a/java/com/google/gerrit/server/restapi/account/Stars.java
+++ b/java/com/google/gerrit/server/restapi/account/Stars.java
@@ -21,6 +21,7 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -97,14 +98,23 @@ public class Stars implements ChildCollection<AccountResource, AccountResource.S
@Override
@SuppressWarnings("unchecked")
- public List<ChangeInfo> apply(AccountResource rsrc)
- throws BadRequestException, AuthException, PermissionBackendException {
+ public Response<List<ChangeInfo>> apply(AccountResource rsrc) throws Exception {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
throw new AuthException("not allowed to list stars of another account");
}
+
+ // The type of the value in the response that is returned by QueryChanges depends on the
+ // number of queries that is provided as input. If a single query is provided as input the
+ // value type is {@code List<ChangeInfo>}, if multiple queries are provided as input the value
+ // type is {@code List<List<ChangeInfo>>) (one {@code List<ChangeInfo>} as result to each
+ // query). Since in this case we provide exactly one query ("has:stars") as input we know that
+ // the value always has the type {@code List<ChangeInfo>} and hence we can safely cast the
+ // value to this type.
QueryChanges query = changes.list();
query.addQuery("has:stars");
- return (List<ChangeInfo>) query.apply(TopLevelResource.INSTANCE);
+ Response<?> response = query.apply(TopLevelResource.INSTANCE);
+ List<ChangeInfo> value = (List<ChangeInfo>) response.value();
+ return Response.ok(value);
}
}
@@ -120,11 +130,12 @@ public class Stars implements ChildCollection<AccountResource, AccountResource.S
}
@Override
- public SortedSet<String> apply(AccountResource.Star rsrc) throws AuthException {
+ public Response<SortedSet<String>> apply(AccountResource.Star rsrc) throws AuthException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
throw new AuthException("not allowed to get stars of another account");
}
- return starredChangesUtil.getLabels(self.get().getAccountId(), rsrc.getChange().getId());
+ return Response.ok(
+ starredChangesUtil.getLabels(self.get().getAccountId(), rsrc.getChange().getId()));
}
}
@@ -140,18 +151,19 @@ public class Stars implements ChildCollection<AccountResource, AccountResource.S
}
@Override
- public Collection<String> apply(AccountResource.Star rsrc, StarsInput in)
+ public Response<Collection<String>> apply(AccountResource.Star rsrc, StarsInput in)
throws AuthException, BadRequestException {
if (!self.get().hasSameAccountId(rsrc.getUser())) {
throw new AuthException("not allowed to update stars of another account");
}
try {
- return starredChangesUtil.star(
- self.get().getAccountId(),
- rsrc.getChange().getProject(),
- rsrc.getChange().getId(),
- in.add,
- in.remove);
+ return Response.ok(
+ starredChangesUtil.star(
+ self.get().getAccountId(),
+ rsrc.getChange().getProject(),
+ rsrc.getChange().getId(),
+ in.add,
+ in.remove));
} catch (IllegalLabelException e) {
throw new BadRequestException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/change/Abandon.java b/java/com/google/gerrit/server/restapi/change/Abandon.java
index c3327e4439..df3b58ee0f 100644
--- a/java/com/google/gerrit/server/restapi/change/Abandon.java
+++ b/java/com/google/gerrit/server/restapi/change/Abandon.java
@@ -15,13 +15,14 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountState;
@@ -67,7 +68,7 @@ public class Abandon extends RetryingRestModifyView<ChangeResource, AbandonInput
}
@Override
- protected ChangeInfo applyImpl(
+ protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, AbandonInput input)
throws RestApiException, UpdateException, PermissionBackendException, IOException,
ConfigInvalidException {
@@ -84,7 +85,7 @@ public class Abandon extends RetryingRestModifyView<ChangeResource, AbandonInput
rsrc.getUser(),
input.message,
notifyResolver.resolve(notify, input.notifyDetails));
- return json.noOptions().format(change);
+ return Response.ok(json.noOptions().format(change));
}
private NotifyHandling defaultNotify(Change change) {
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyFix.java b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
index 085f9dee83..74c5bc2f57 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyFix.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -21,8 +23,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.FixResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.edit.ChangeEdit;
@@ -39,7 +39,6 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.List;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@Singleton
@@ -73,12 +72,11 @@ public class ApplyFix implements RestModifyView<FixResource, Void> {
Project.NameKey project = revisionResource.getProject();
ProjectState projectState = projectCache.checkedGet(project);
PatchSet patchSet = revisionResource.getPatchSet();
- ObjectId patchSetCommitId = ObjectId.fromString(patchSet.getRevision().get());
try (Repository repository = gitRepositoryManager.openRepository(project)) {
List<TreeModification> treeModifications =
fixReplacementInterpreter.toTreeModifications(
- repository, projectState, patchSetCommitId, fixResource.getFixReplacements());
+ repository, projectState, patchSet.commitId(), fixResource.getFixReplacements());
ChangeEdit changeEdit =
changeEditModifier.combineWithModifiedPatchSetTree(
repository, revisionResource.getNotes(), patchSet, treeModifications);
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index 53e89b3f85..8449f8a90a 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -17,7 +17,12 @@ package com.google.gerrit.server.restapi.change;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.Input;
@@ -38,10 +43,6 @@ import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.change.ChangeEditResource;
import com.google.gerrit.server.change.ChangeResource;
@@ -64,7 +65,6 @@ import com.google.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -379,16 +379,14 @@ public class ChangeEdits implements ChildCollection<ChangeResource, ChangeEditRe
public Response<BinaryResult> apply(ChangeEditResource rsrc) throws AuthException, IOException {
try {
if (Patch.COMMIT_MSG.equals(rsrc.getPath())) {
- return Response.ok(getMessage.apply(rsrc.getChangeResource()));
+ return getMessage.apply(rsrc.getChangeResource());
}
ChangeEdit edit = rsrc.getChangeEdit();
return Response.ok(
fileContentUtil.getContent(
projectCache.checkedGet(rsrc.getChangeResource().getProject()),
- base
- ? ObjectId.fromString(edit.getBasePatchSet().getRevision().get())
- : edit.getEditCommit(),
+ base ? edit.getBasePatchSet().commitId() : edit.getEditCommit(),
rsrc.getPath(),
null));
} catch (ResourceNotFoundException | BadRequestException e) {
@@ -407,22 +405,22 @@ public class ChangeEdits implements ChildCollection<ChangeResource, ChangeEditRe
}
@Override
- public FileInfo apply(ChangeEditResource rsrc) {
+ public Response<FileInfo> apply(ChangeEditResource rsrc) {
FileInfo r = new FileInfo();
ChangeEdit edit = rsrc.getChangeEdit();
Change change = edit.getChange();
- List<DiffWebLinkInfo> links =
+ ImmutableList<DiffWebLinkInfo> links =
webLinks.getDiffLinks(
change.getProject().get(),
change.getChangeId(),
- edit.getBasePatchSet().getPatchSetId(),
- edit.getBasePatchSet().getRefName(),
+ edit.getBasePatchSet().number(),
+ edit.getBasePatchSet().refName(),
rsrc.getPath(),
0,
edit.getRefName(),
rsrc.getPath());
r.webLinks = links.isEmpty() ? null : links;
- return r;
+ return Response.ok(r);
}
public static class FileInfo {
@@ -446,7 +444,7 @@ public class ChangeEdits implements ChildCollection<ChangeResource, ChangeEditRe
}
@Override
- public Response<?> apply(ChangeResource rsrc, Input input)
+ public Response<Object> apply(ChangeResource rsrc, Input input)
throws AuthException, IOException, BadRequestException, ResourceConflictException,
PermissionBackendException {
if (input == null || Strings.isNullOrEmpty(input.message)) {
@@ -481,7 +479,7 @@ public class ChangeEdits implements ChildCollection<ChangeResource, ChangeEditRe
}
@Override
- public BinaryResult apply(ChangeResource rsrc)
+ public Response<BinaryResult> apply(ChangeResource rsrc)
throws AuthException, IOException, ResourceNotFoundException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getNotes(), rsrc.getUser());
String msg;
@@ -489,18 +487,17 @@ public class ChangeEdits implements ChildCollection<ChangeResource, ChangeEditRe
if (base) {
try (Repository repo = repoManager.openRepository(rsrc.getProject());
RevWalk rw = new RevWalk(repo)) {
- RevCommit commit =
- rw.parseCommit(
- ObjectId.fromString(edit.get().getBasePatchSet().getRevision().get()));
+ RevCommit commit = rw.parseCommit(edit.get().getBasePatchSet().commitId());
msg = commit.getFullMessage();
}
} else {
msg = edit.get().getEditCommit().getFullMessage();
}
- return BinaryResult.create(msg)
- .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
- .base64();
+ return Response.ok(
+ BinaryResult.create(msg)
+ .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+ .base64());
}
throw new ResourceNotFoundException();
}
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
index 6ec4fdbc53..517fbdf9a8 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
@@ -14,13 +14,15 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -37,8 +39,9 @@ public class ChangeIncludedIn implements RestReadView<ChangeResource> {
}
@Override
- public IncludedInInfo apply(ChangeResource rsrc) throws RestApiException, IOException {
+ public Response<IncludedInInfo> apply(ChangeResource rsrc)
+ throws RestApiException, IOException, PermissionBackendException {
PatchSet ps = psUtil.current(rsrc.getNotes());
- return includedIn.apply(rsrc.getProject(), ps.getRevision().get());
+ return Response.ok(includedIn.apply(rsrc.getProject(), ps.commitId().name()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
index 96c517fe25..fae91803a8 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
@@ -22,7 +22,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.change.ChangeMessageResource;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
@@ -50,11 +49,10 @@ public class ChangeMessages implements ChildCollection<ChangeResource, ChangeMes
}
@Override
- public ChangeMessageResource parse(ChangeResource parent, IdString id)
- throws ResourceNotFoundException, PermissionBackendException {
+ public ChangeMessageResource parse(ChangeResource parent, IdString id) throws Exception {
String uuid = id.get();
- List<ChangeMessageInfo> changeMessages = listChangeMessages.apply(parent);
+ List<ChangeMessageInfo> changeMessages = listChangeMessages.apply(parent).value();
int index = -1;
for (int i = 0; i < changeMessages.size(); ++i) {
ChangeMessageInfo changeMessage = changeMessages.get(i);
diff --git a/java/com/google/gerrit/server/restapi/change/ChangesCollection.java b/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
index 028eb52a5e..3b0321d5f1 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -23,8 +25,6 @@ import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeResource;
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPick.java b/java/com/google/gerrit/server/restapi/change/CherryPick.java
index 72781c3bea..1a89935757 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPick.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPick.java
@@ -17,14 +17,15 @@ package com.google.gerrit.server.restapi.change;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.CherryPickChangeInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -75,14 +76,12 @@ public class CherryPick
}
@Override
- public CherryPickChangeInfo applyImpl(
+ public Response<CherryPickChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, CherryPickInput input)
throws IOException, UpdateException, RestApiException, PermissionBackendException,
ConfigInvalidException, NoSuchProjectException {
input.parent = input.parent == null ? 1 : input.parent;
- if (input.message == null || input.message.trim().isEmpty()) {
- throw new BadRequestException("message must be non-empty");
- } else if (input.destination == null || input.destination.trim().isEmpty()) {
+ if (input.destination == null || input.destination.trim().isEmpty()) {
throw new BadRequestException("destination must be non-empty");
}
@@ -103,13 +102,13 @@ public class CherryPick
rsrc.getChange(),
rsrc.getPatchSet(),
input,
- new Branch.NameKey(rsrc.getProject(), refName));
+ BranchNameKey.create(rsrc.getProject(), refName));
CherryPickChangeInfo changeInfo =
json.noOptions()
.format(rsrc.getProject(), cherryPickResult.changeId(), CherryPickChangeInfo::new);
changeInfo.containsGitConflicts =
!cherryPickResult.filesWithGitConflicts().isEmpty() ? true : null;
- return changeInfo;
+ return Response.ok(changeInfo);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
} catch (IntegrationException | NoSuchChangeException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 132310d048..407c5606a1 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -21,6 +21,11 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -28,11 +33,6 @@ import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
@@ -136,30 +136,116 @@ public class CherryPickChange {
this.notifyResolver = notifyResolver;
}
+ /**
+ * This function is used for cherry picking a change.
+ *
+ * @param batchUpdateFactory Used for applying changes to the database.
+ * @param change Change to cherry pick.
+ * @param patch The patch of that change.
+ * @param input Input object for different configurations of cherry pick.
+ * @param dest Destination branch for the cherry pick.
+ * @return Result object that describes the cherry pick.
+ * @throws IOException Unable to open repository or read from the database.
+ * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
+ * key exist in the branch.
+ * @throws IntegrationException Merge conflict or trees are identical after cherry pick.
+ * @throws UpdateException Problem updating the database using batchUpdateFactory.
+ * @throws RestApiException Error such as invalid SHA1
+ * @throws ConfigInvalidException Can't find account to notify.
+ * @throws NoSuchProjectException Can't find project state.
+ */
public Result cherryPick(
BatchUpdate.Factory batchUpdateFactory,
Change change,
PatchSet patch,
CherryPickInput input,
- Branch.NameKey dest)
+ BranchNameKey dest)
throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException,
RestApiException, ConfigInvalidException, NoSuchProjectException {
return cherryPick(
batchUpdateFactory,
change,
change.getProject(),
- ObjectId.fromString(patch.getRevision().get()),
+ patch.commitId(),
input,
- dest);
+ dest,
+ null,
+ null,
+ null);
}
+ /**
+ * This function is called directly to cherry pick a commit. Also, it is used to cherry pick a
+ * change as well as long as sourceChange is not null.
+ *
+ * @param batchUpdateFactory Used for applying changes to the database.
+ * @param sourceChange Change to cherry pick. Can be null, and then the function will only cherry
+ * pick a commit.
+ * @param project Project name
+ * @param sourceCommit Id of the commit to be cherry picked.
+ * @param input Input object for different configurations of cherry pick.
+ * @param dest Destination branch for the cherry pick.
+ * @return Result object that describes the cherry pick.
+ * @throws IOException Unable to open repository or read from the database.
+ * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
+ * key exist in the branch.
+ * @throws IntegrationException Merge conflict or trees are identical after cherry pick.
+ * @throws UpdateException Problem updating the database using batchUpdateFactory.
+ * @throws RestApiException Error such as invalid SHA1
+ * @throws ConfigInvalidException Can't find account to notify.
+ * @throws NoSuchProjectException Can't find project state.
+ */
public Result cherryPick(
BatchUpdate.Factory batchUpdateFactory,
@Nullable Change sourceChange,
Project.NameKey project,
ObjectId sourceCommit,
CherryPickInput input,
- Branch.NameKey dest)
+ BranchNameKey dest)
+ throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException,
+ RestApiException, ConfigInvalidException, NoSuchProjectException {
+ return cherryPick(
+ batchUpdateFactory, sourceChange, project, sourceCommit, input, dest, null, null, null);
+ }
+
+ /**
+ * This function can be called directly to cherry-pick a change (or commit if sourceChange is
+ * null) with a few other parameters that are especially useful for cherry-picking a commit that
+ * is the revert-of another change.
+ *
+ * @param batchUpdateFactory Used for applying changes to the database.
+ * @param sourceChange Change to cherry pick. Can be null, and then the function will only cherry
+ * pick a commit.
+ * @param project Project name
+ * @param sourceCommit Id of the commit to be cherry picked.
+ * @param input Input object for different configurations of cherry pick.
+ * @param dest Destination branch for the cherry pick.
+ * @param topic Topic name for the change created.
+ * @param revertedChange The id of the change that is reverted. This is used for the "revertOf"
+ * field to mark the created cherry pick change as "revertOf" the original change that was
+ * reverted.
+ * @param changeIdForNewChange The Change-Id that the new change that of the cherry pick will
+ * have.
+ * @return Result object that describes the cherry pick.
+ * @throws IOException Unable to open repository or read from the database.
+ * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
+ * key exist in the branch.
+ * @throws IntegrationException Merge conflict or trees are identical after cherry pick.
+ * @throws UpdateException Problem updating the database using batchUpdateFactory.
+ * @throws RestApiException Error such as invalid SHA1
+ * @throws ConfigInvalidException Can't find account to notify.
+ * @throws NoSuchProjectException Can't find project state.
+ */
+ public Result cherryPick(
+ BatchUpdate.Factory batchUpdateFactory,
+ @Nullable Change sourceChange,
+ Project.NameKey project,
+ ObjectId sourceCommit,
+ CherryPickInput input,
+ BranchNameKey dest,
+ @Nullable String topic,
+ @Nullable Change.Id revertedChange,
+ @Nullable ObjectId changeIdForNewChange)
throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException,
RestApiException, ConfigInvalidException, NoSuchProjectException {
@@ -171,10 +257,10 @@ public class CherryPickChange {
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(reader)) {
- Ref destRef = git.getRefDatabase().exactRef(dest.get());
+ Ref destRef = git.getRefDatabase().exactRef(dest.branch());
if (destRef == null) {
throw new InvalidChangeOperationException(
- String.format("Branch %s does not exist.", dest.get()));
+ String.format("Branch %s does not exist.", dest.branch()));
}
RevCommit baseCommit = getBaseCommit(destRef, project.get(), revWalk, input.base);
@@ -189,16 +275,22 @@ public class CherryPickChange {
input.parent, commitToCherryPick.getParentCount()));
}
+ String message = Strings.nullToEmpty(input.message).trim();
+ message = message.isEmpty() ? commitToCherryPick.getFullMessage() : message;
+
Timestamp now = TimeUtil.nowTs();
PersonIdent committerIdent = identifiedUser.newCommitterIdent(now, serverTimeZone);
- final ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
- String commitMessage = ChangeIdUtil.insertId(input.message, generatedChangeId).trim() + '\n';
+ final ObjectId generatedChangeId =
+ changeIdForNewChange != null
+ ? changeIdForNewChange
+ : CommitMessageUtil.generateChangeId();
+ String commitMessage = ChangeIdUtil.insertId(message, generatedChangeId).trim() + '\n';
CodeReviewCommit cherryPickCommit;
- ProjectState projectState = projectCache.checkedGet(dest.getParentKey());
+ ProjectState projectState = projectCache.checkedGet(dest.project());
if (projectState == null) {
- throw new NoSuchProjectException(dest.getParentKey());
+ throw new NoSuchProjectException(dest.project());
}
try {
MergeUtil mergeUtil;
@@ -226,12 +318,12 @@ public class CherryPickChange {
final List<String> idList = cherryPickCommit.getFooterLines(FooterConstants.CHANGE_ID);
if (!idList.isEmpty()) {
final String idStr = idList.get(idList.size() - 1).trim();
- changeKey = new Change.Key(idStr);
+ changeKey = Change.key(idStr);
} else {
- changeKey = new Change.Key("I" + generatedChangeId.name());
+ changeKey = Change.key("I" + generatedChangeId.name());
}
- Branch.NameKey newDest = new Branch.NameKey(project, destRef.getName());
+ BranchNameKey newDest = BranchNameKey.create(project, destRef.getName());
List<ChangeData> destChanges =
queryProvider.get().setLimit(2).byBranchKey(newDest, changeKey);
if (destChanges.size() > 1) {
@@ -252,13 +344,22 @@ public class CherryPickChange {
} else {
// Change key not found on destination branch. We can create a new
// change.
- String newTopic = null;
- if (sourceChange != null && !Strings.isNullOrEmpty(sourceChange.getTopic())) {
- newTopic = sourceChange.getTopic() + "-" + newDest.getShortName();
+ String newTopic = topic;
+ if (topic == null
+ && sourceChange != null
+ && !Strings.isNullOrEmpty(sourceChange.getTopic())) {
+ newTopic = sourceChange.getTopic() + "-" + newDest.shortName();
}
changeId =
createNewChange(
- bu, cherryPickCommit, dest.get(), newTopic, sourceChange, sourceCommit, input);
+ bu,
+ cherryPickCommit,
+ dest.branch(),
+ newTopic,
+ sourceChange,
+ sourceCommit,
+ input,
+ revertedChange);
}
bu.execute();
return Result.create(changeId, cherryPickCommit.getFilesWithGitConflicts());
@@ -337,15 +438,20 @@ public class CherryPickChange {
String refName,
String topic,
@Nullable Change sourceChange,
- ObjectId sourceCommit,
- CherryPickInput input)
+ @Nullable ObjectId sourceCommit,
+ CherryPickInput input,
+ @Nullable Change.Id revertOf)
throws IOException {
- Change.Id changeId = new Change.Id(seq.nextChangeId());
+ Change.Id changeId = Change.id(seq.nextChangeId());
ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
- Branch.NameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
+ ins.setRevertOf(revertOf);
+ BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
ins.setMessage(
- messageForDestinationChange(
- ins.getPatchSetId(), sourceBranch, sourceCommit, cherryPickCommit))
+ revertOf == null
+ ? messageForDestinationChange(
+ ins.getPatchSetId(), sourceBranch, sourceCommit, cherryPickCommit)
+ : "Uploaded patch set 1.") // For revert commits, the message should not include
+ // cherry-pick information.
.setTopic(topic)
.setWorkInProgress(
(sourceChange != null && sourceChange.isWorkInProgress())
@@ -373,12 +479,12 @@ public class CherryPickChange {
private String messageForDestinationChange(
PatchSet.Id patchSetId,
- Branch.NameKey sourceBranch,
+ BranchNameKey sourceBranch,
ObjectId sourceCommit,
CodeReviewCommit cherryPickCommit) {
StringBuilder stringBuilder = new StringBuilder("Patch Set ").append(patchSetId.get());
if (sourceBranch != null) {
- stringBuilder.append(": Cherry Picked from branch ").append(sourceBranch.getShortName());
+ stringBuilder.append(": Cherry Picked from branch ").append(sourceBranch.shortName());
} else {
stringBuilder.append(": Cherry Picked from commit ").append(sourceCommit.getName());
}
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java b/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
index f34f1787fd..a3c8a97cbc 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
@@ -15,14 +15,15 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.CherryPickChangeInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -42,7 +43,6 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.revwalk.RevCommit;
@Singleton
public class CherryPickCommit
@@ -70,13 +70,10 @@ public class CherryPickCommit
}
@Override
- public CherryPickChangeInfo applyImpl(
+ public Response<CherryPickChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, CommitResource rsrc, CherryPickInput input)
throws IOException, UpdateException, RestApiException, PermissionBackendException,
ConfigInvalidException, NoSuchProjectException {
- RevCommit commit = rsrc.getCommit();
- String message = Strings.nullToEmpty(input.message).trim();
- input.message = message.isEmpty() ? commit.getFullMessage() : message;
String destination = Strings.nullToEmpty(input.destination).trim();
input.parent = input.parent == null ? 1 : input.parent;
Project.NameKey projectName = rsrc.getProjectState().getNameKey();
@@ -100,15 +97,15 @@ public class CherryPickCommit
updateFactory,
null,
projectName,
- commit,
+ rsrc.getCommit(),
input,
- new Branch.NameKey(rsrc.getProjectState().getNameKey(), refName));
+ BranchNameKey.create(rsrc.getProjectState().getNameKey(), refName));
CherryPickChangeInfo changeInfo =
json.noOptions()
.format(projectName, cherryPickResult.changeId(), CherryPickChangeInfo::new);
changeInfo.containsGitConflicts =
!cherryPickResult.filesWithGitConflicts().isEmpty() ? true : null;
- return changeInfo;
+ return Response.ok(changeInfo);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
} catch (IntegrationException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index 7112bbfb44..03898b1c99 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -22,6 +22,10 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.entities.FixSuggestion;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
@@ -29,10 +33,6 @@ import com.google.gerrit.extensions.common.FixReplacementInfo;
import com.google.gerrit.extensions.common.FixSuggestionInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.FixReplacement;
-import com.google.gerrit.reviewdb.client.FixSuggestion;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/Comments.java b/java/com/google/gerrit/server/restapi/change/Comments.java
index d9a0d57201..078c2394e9 100644
--- a/java/com/google/gerrit/server/restapi/change/Comments.java
+++ b/java/com/google/gerrit/server/restapi/change/Comments.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.CommentResource;
import com.google.gerrit.server.change.RevisionResource;
@@ -58,7 +58,7 @@ public class Comments implements ChildCollection<RevisionResource, CommentResour
String uuid = id.get();
ChangeNotes notes = rev.getNotes();
- for (Comment c : commentsUtil.publishedByPatchSet(notes, rev.getPatchSet().getId())) {
+ for (Comment c : commentsUtil.publishedByPatchSet(notes, rev.getPatchSet().id())) {
if (uuid.equals(c.key.uuid)) {
return new CommentResource(rev, c);
}
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index f626db2e68..9d65940694 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -21,6 +21,13 @@ import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.exceptions.InvalidMergeStrategyException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
@@ -36,11 +43,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
@@ -64,6 +66,7 @@ import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.restapi.project.CommitsCollection;
import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.google.gerrit.server.update.BatchUpdate;
@@ -79,6 +82,7 @@ import java.io.IOException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.TimeZone;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.InvalidObjectIdException;
@@ -87,6 +91,7 @@ import org.eclipse.jgit.errors.NoMergeBaseException;
import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@@ -101,7 +106,7 @@ import org.eclipse.jgit.util.ChangeIdUtil;
@Singleton
public class CreateChange
extends RetryingRestCollectionModifyView<
- TopLevelResource, ChangeResource, ChangeInput, Response<ChangeInfo>> {
+ TopLevelResource, ChangeResource, ChangeInput, ChangeInfo> {
private final String anonymousCowardName;
private final GitRepositoryManager gitManager;
private final Sequences seq;
@@ -113,6 +118,7 @@ public class CreateChange
private final ChangeInserter.Factory changeInserterFactory;
private final ChangeJson.Factory jsonFactory;
private final ChangeFinder changeFinder;
+ private final Provider<InternalChangeQuery> queryProvider;
private final PatchSetUtil psUtil;
private final MergeUtil.Factory mergeUtilFactory;
private final SubmitType submitType;
@@ -133,6 +139,7 @@ public class CreateChange
ChangeInserter.Factory changeInserterFactory,
ChangeJson.Factory json,
ChangeFinder changeFinder,
+ Provider<InternalChangeQuery> queryProvider,
RetryHelper retryHelper,
PatchSetUtil psUtil,
@GerritServerConfig Config config,
@@ -151,6 +158,7 @@ public class CreateChange
this.changeInserterFactory = changeInserterFactory;
this.jsonFactory = json;
this.changeFinder = changeFinder;
+ this.queryProvider = queryProvider;
this.psUtil = psUtil;
this.submitType = config.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
this.disablePrivateChanges = config.getBoolean("change", null, "disablePrivateChanges", false);
@@ -164,6 +172,9 @@ public class CreateChange
BatchUpdate.Factory updateFactory, TopLevelResource parent, ChangeInput input)
throws IOException, InvalidChangeOperationException, RestApiException, UpdateException,
PermissionBackendException, ConfigInvalidException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
IdentifiedUser me = user.get().asIdentifiedUser();
checkAndSanitizeChangeInput(input, me);
@@ -209,6 +220,20 @@ public class CreateChange
}
input.subject = subject;
+ Optional<String> changeId = getChangeIdFromMessage(input.subject);
+ if (changeId.isPresent()) {
+ if (!queryProvider
+ .get()
+ .setLimit(1)
+ .byBranchKey(
+ BranchNameKey.create(input.project, input.branch), Change.key(changeId.get()))
+ .isEmpty()) {
+ throw new ResourceConflictException(
+ String.format(
+ "A change with Change-Id %s already exists for this branch.", changeId.get()));
+ }
+ }
+
if (input.topic != null) {
input.topic = Strings.emptyToNull(input.topic.trim());
}
@@ -238,7 +263,7 @@ public class CreateChange
input.workInProgress = true;
} else {
input.workInProgress =
- firstNonNull(me.state().getGeneralPreferences().workInProgressByDefault, false);
+ firstNonNull(me.state().generalPreferences().workInProgressByDefault, false);
}
}
@@ -281,7 +306,7 @@ public class CreateChange
if (input.baseChange != null) {
ChangeNotes baseChange = getBaseChange(input.baseChange);
basePatchSet = psUtil.current(baseChange);
- groups = basePatchSet.getGroups();
+ groups = basePatchSet.groups();
}
ObjectId parentCommit =
getParentCommit(
@@ -302,7 +327,7 @@ public class CreateChange
c = newCommit(oi, rw, author, mergeTip, commitMessage);
}
- Change.Id changeId = new Change.Id(seq.nextChangeId());
+ Change.Id changeId = Change.id(seq.nextChangeId());
ChangeInserter ins = changeInserterFactory.create(changeId, c, input.branch);
ins.setMessage(String.format("Uploaded patch set %s.", ins.getPatchSetId().get()));
ins.setTopic(input.topic);
@@ -318,7 +343,7 @@ public class CreateChange
bu.execute();
}
return ins.getChange();
- } catch (IllegalArgumentException e) {
+ } catch (InvalidMergeStrategyException e) {
throw new BadRequestException(e.getMessage());
}
}
@@ -351,7 +376,7 @@ public class CreateChange
throws BadRequestException, IOException, UnprocessableEntityException,
ResourceConflictException {
if (basePatchSet != null) {
- return ObjectId.fromString(basePatchSet.getRevision().get());
+ return basePatchSet.commitId();
}
Ref destRef = repo.getRefDatabase().exactRef(inputBranch);
@@ -403,6 +428,17 @@ public class CreateChange
return parentCommit;
}
+ private Optional<String> getChangeIdFromMessage(String subject) {
+ int indexOfChangeId = ChangeIdUtil.indexOfChangeId(subject, "\n");
+ if (indexOfChangeId == -1) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ subject.substring(
+ indexOfChangeId + 11 /* "Change-Id: "*/,
+ indexOfChangeId + 12 /* "Change-Id: I" */ + Constants.OBJECT_ID_STRING_LENGTH));
+ }
+
private String getCommitMessage(String subject, IdentifiedUser me) {
// Add a Change-Id line if there isn't already one
String commitMessage = subject;
@@ -411,15 +447,14 @@ public class CreateChange
commitMessage = ChangeIdUtil.insertId(commitMessage, id);
}
- if (Boolean.TRUE.equals(me.state().getGeneralPreferences().signedOffBy)) {
+ if (Boolean.TRUE.equals(me.state().generalPreferences().signedOffBy)) {
commitMessage =
Joiner.on("\n")
.join(
commitMessage.trim(),
String.format(
"%s%s",
- SIGNED_OFF_BY_TAG,
- me.state().getAccount().getNameEmail(anonymousCowardName)));
+ SIGNED_OFF_BY_TAG, me.state().account().getNameEmail(anonymousCowardName)));
}
return commitMessage;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
index b6e7628731..f434e31bee 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
@@ -14,9 +14,11 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -25,9 +27,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RevisionResource;
@@ -48,7 +47,7 @@ import java.util.Collections;
@Singleton
public class CreateDraftComment
- extends RetryingRestModifyView<RevisionResource, DraftInput, Response<CommentInfo>> {
+ extends RetryingRestModifyView<RevisionResource, DraftInput, CommentInfo> {
private final Provider<CommentJson> commentJson;
private final CommentsUtil commentsUtil;
private final PatchSetUtil psUtil;
@@ -84,7 +83,7 @@ public class CreateDraftComment
try (BatchUpdate bu =
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
- Op op = new Op(rsrc.getPatchSet().getId(), in);
+ Op op = new Op(rsrc.getPatchSet().id(), in);
bu.addOp(rsrc.getChange().getId(), op);
bu.execute();
return Response.created(
@@ -115,13 +114,14 @@ public class CreateDraftComment
comment =
commentsUtil.newComment(
- ctx, in.path, ps.getId(), in.side(), in.message.trim(), in.unresolved, parentUuid);
+ ctx, in.path, ps.id(), in.side(), in.message.trim(), in.unresolved, parentUuid);
comment.setLineNbrAndRange(in.line, in.range);
comment.tag = in.tag;
- setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
+ setCommentCommitId(comment, patchListCache, ctx.getChange(), ps);
- commentsUtil.putComments(ctx.getUpdate(psId), Status.DRAFT, Collections.singleton(comment));
+ commentsUtil.putComments(
+ ctx.getUpdate(psId), Comment.Status.DRAFT, Collections.singleton(comment));
return true;
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index b7bcaf9852..a1594861ce 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -17,6 +17,11 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.InvalidMergeStrategyException;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.MergeInput;
@@ -28,10 +33,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -76,7 +77,7 @@ import org.eclipse.jgit.util.ChangeIdUtil;
@Singleton
public class CreateMergePatchSet
- extends RetryingRestModifyView<ChangeResource, MergePatchSetInput, Response<ChangeInfo>> {
+ extends RetryingRestModifyView<ChangeResource, MergePatchSetInput, ChangeInfo> {
private final GitRepositoryManager gitManager;
private final CommitsCollection commits;
private final TimeZone serverTimeZone;
@@ -138,7 +139,7 @@ public class CreateMergePatchSet
PatchSet ps = psUtil.current(rsrc.getNotes());
Change change = rsrc.getChange();
Project.NameKey project = change.getProject();
- Branch.NameKey dest = change.getDest();
+ BranchNameKey dest = change.getDest();
try (Repository git = gitManager.openRepository(project);
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
@@ -154,10 +155,10 @@ public class CreateMergePatchSet
List<String> groups = null;
if (!in.inheritParent && !in.baseChange.isEmpty()) {
PatchSet basePS = findBasePatchSet(in.baseChange);
- currentPsCommit = rw.parseCommit(ObjectId.fromString(basePS.getRevision().get()));
- groups = basePS.getGroups();
+ currentPsCommit = rw.parseCommit(basePS.commitId());
+ groups = basePS.groups();
} else {
- currentPsCommit = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ currentPsCommit = rw.parseCommit(ps.commitId());
}
Timestamp now = TimeUtil.nowTs();
@@ -176,7 +177,7 @@ public class CreateMergePatchSet
author,
ObjectId.fromString(change.getKey().get().substring(1)));
- PatchSet.Id nextPsId = ChangeUtil.nextPatchSetId(ps.getId());
+ PatchSet.Id nextPsId = ChangeUtil.nextPatchSetId(ps.id());
PatchSetInserter psInserter =
patchSetInserterFactory.create(rsrc.getNotes(), nextPsId, newCommit);
try (BatchUpdate bu = updateFactory.create(project, me, now)) {
@@ -194,6 +195,8 @@ public class CreateMergePatchSet
ChangeJson json = jsonFactory.create(ListChangesOption.CURRENT_REVISION);
return Response.ok(json.format(psInserter.getChange()));
+ } catch (InvalidMergeStrategyException e) {
+ throw new BadRequestException(e.getMessage());
}
}
@@ -215,7 +218,7 @@ public class CreateMergePatchSet
private RevCommit createMergeCommit(
MergePatchSetInput in,
ProjectState projectState,
- Branch.NameKey dest,
+ BranchNameKey dest,
Repository git,
ObjectInserter oi,
RevWalk rw,
@@ -234,7 +237,7 @@ public class CreateMergePatchSet
parentCommit = currentPsCommit.getId();
} else {
// get the current branch tip of destination branch
- Ref destRef = git.getRefDatabase().exactRef(dest.get());
+ Ref destRef = git.getRefDatabase().exactRef(dest.branch());
if (destRef != null) {
parentCommit = destRef.getObjectId();
} else {
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
index 02387be556..834782ffc0 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountLoader;
@@ -42,8 +42,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-public class DeleteAssignee
- extends RetryingRestModifyView<ChangeResource, Input, Response<AccountInfo>> {
+public class DeleteAssignee extends RetryingRestModifyView<ChangeResource, Input, AccountInfo> {
private final ChangeMessagesUtil cmUtil;
private final AssigneeChanged assigneeChanged;
@@ -105,7 +104,7 @@ public class DeleteAssignee
}
public Account.Id getDeletedAssignee() {
- return deletedAssignee != null ? deletedAssignee.getAccount().getId() : null;
+ return deletedAssignee != null ? deletedAssignee.account().id() : null;
}
private void addMessage(
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChange.java b/java/com/google/gerrit/server/restapi/change/DeleteChange.java
index 3021d8142b..aa4dcf0598 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChange.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChange.java
@@ -16,12 +16,12 @@ package com.google.gerrit.server.restapi.change;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.DeleteChangeOp;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -36,7 +36,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input, Response<?>>
+public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input, Object>
implements UiAction<ChangeResource> {
private final DeleteChangeOp.Factory opFactory;
@@ -48,7 +48,7 @@ public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input,
}
@Override
- protected Response<?> applyImpl(
+ protected Response<Object> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
throws RestApiException, UpdateException, PermissionBackendException {
if (!isChangeDeletable(rsrc)) {
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
index 0fb8e18403..30cfad6b65 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
@@ -20,14 +20,14 @@ import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.DeleteChangeMessageInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountLoader;
@@ -53,7 +53,7 @@ import java.util.List;
@Singleton
public class DeleteChangeMessage
extends RetryingRestModifyView<
- ChangeMessageResource, DeleteChangeMessageInput, Response<ChangeMessageInfo>> {
+ ChangeMessageResource, DeleteChangeMessageInput, ChangeMessageInfo> {
private final Provider<CurrentUser> userProvider;
private final PermissionBackend permissionBackend;
@@ -146,7 +146,7 @@ public class DeleteChangeMessage
@Singleton
public static class DefaultDeleteChangeMessage
- extends RetryingRestModifyView<ChangeMessageResource, Input, Response<ChangeMessageInfo>> {
+ extends RetryingRestModifyView<ChangeMessageResource, Input, ChangeMessageInfo> {
private final DeleteChangeMessage deleteChangeMessage;
@Inject
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteComment.java b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
index 30a8efd713..95479a63a7 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
@@ -15,13 +15,14 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.CommentResource;
@@ -71,7 +72,7 @@ public class DeleteComment
}
@Override
- public CommentInfo applyImpl(
+ public Response<CommentInfo> applyImpl(
BatchUpdate.Factory batchUpdateFactory, CommentResource rsrc, DeleteCommentInput input)
throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException,
UpdateException {
@@ -100,7 +101,7 @@ public class DeleteComment
throw new ResourceNotFoundException("comment not found: " + rsrc.getComment().key);
}
- return commentJson.get().newCommentFormatter().format(updatedComment.get());
+ return Response.ok(commentJson.get().newCommentFormatter().format(updatedComment.get()));
}
private static String getCommentNewMessage(String name, String reason) {
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
index de04d36184..9296988a6a 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
@@ -14,15 +14,15 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.DraftCommentResource;
@@ -42,7 +42,7 @@ import java.util.Optional;
@Singleton
public class DeleteDraftComment
- extends RetryingRestModifyView<DraftCommentResource, Input, Response<CommentInfo>> {
+ extends RetryingRestModifyView<DraftCommentResource, Input, CommentInfo> {
private final CommentsUtil commentsUtil;
private final PatchSetUtil psUtil;
@@ -88,13 +88,13 @@ public class DeleteDraftComment
if (!maybeComment.isPresent()) {
return false; // Nothing to do.
}
- PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), key.patchSetId);
+ PatchSet.Id psId = PatchSet.id(ctx.getChange().getId(), key.patchSetId);
PatchSet ps = psUtil.get(ctx.getNotes(), psId);
if (ps == null) {
throw new ResourceNotFoundException("patch set not found: " + psId);
}
Comment c = maybeComment.get();
- setCommentRevId(c, patchListCache, ctx.getChange(), ps);
+ setCommentCommitId(c, patchListCache, ctx.getChange(), ps);
commentsUtil.deleteComments(ctx.getUpdate(psId), Collections.singleton(c));
return true;
}
diff --git a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
index 8601e683b4..de7a683ca2 100644
--- a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
@@ -36,7 +36,7 @@ import com.google.inject.Singleton;
@Singleton
public class DeletePrivate
- extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, Response<String>> {
+ extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, String> {
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java b/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
index 12dbcdd6d6..b98bb3bdfe 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.DeleteReviewerByEmailOp;
import com.google.gerrit.server.change.DeleteReviewerOp;
import com.google.gerrit.server.change.NotifyResolver;
@@ -34,7 +34,7 @@ import com.google.inject.Singleton;
@Singleton
public class DeleteReviewer
- extends RetryingRestModifyView<ReviewerResource, DeleteReviewerInput, Response<?>> {
+ extends RetryingRestModifyView<ReviewerResource, DeleteReviewerInput, Object> {
private final DeleteReviewerOp.Factory deleteReviewerOpFactory;
private final DeleteReviewerByEmailOp.Factory deleteReviewerByEmailOpFactory;
@@ -50,7 +50,7 @@ public class DeleteReviewer
}
@Override
- protected Response<?> applyImpl(
+ protected Response<Object> applyImpl(
BatchUpdate.Factory updateFactory, ReviewerResource rsrc, DeleteReviewerInput input)
throws RestApiException, UpdateException {
if (input == null) {
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index 77894fb246..1193ad6974 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -19,6 +19,11 @@ import static java.util.Objects.requireNonNull;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -27,11 +32,6 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -64,7 +64,7 @@ import java.util.Map;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
-public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteInput, Response<?>> {
+public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteInput, Object> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ApprovalsUtil approvalsUtil;
@@ -102,7 +102,7 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
}
@Override
- protected Response<?> applyImpl(
+ protected Response<Object> applyImpl(
BatchUpdate.Factory updateFactory, VoteResource rsrc, DeleteVoteInput input)
throws RestApiException, UpdateException, IOException, ConfigInvalidException {
if (input == null) {
@@ -170,16 +170,16 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
boolean found = false;
LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
- Account.Id accountId = accountState.getAccount().getId();
+ Account.Id accountId = accountState.account().id();
for (PatchSetApproval a :
approvalsUtil.byPatchSetUser(
ctx.getNotes(), psId, accountId, ctx.getRevWalk(), ctx.getRepoView().getConfig())) {
- if (labelTypes.byLabel(a.getLabelId()) == null) {
+ if (labelTypes.byLabel(a.labelId()) == null) {
continue; // Ignore undefined labels.
- } else if (!a.getLabel().equals(label)) {
+ } else if (!a.label().equals(label)) {
// Populate map for non-matching labels, needed by VoteDeleted.
- newApprovals.put(a.getLabel(), a.getValue());
+ newApprovals.put(a.label(), a.value());
continue;
} else {
try {
@@ -189,11 +189,11 @@ public class DeleteVote extends RetryingRestModifyView<VoteResource, DeleteVoteI
}
}
// Set the approval to 0 if vote is being removed.
- newApprovals.put(a.getLabel(), (short) 0);
+ newApprovals.put(a.label(), (short) 0);
found = true;
// Set old value, as required by VoteDeleted.
- oldApprovals.put(a.getLabel(), a.getValue());
+ oldApprovals.put(a.label(), a.value());
break;
}
if (!found) {
diff --git a/java/com/google/gerrit/server/restapi/change/DownloadContent.java b/java/com/google/gerrit/server/restapi/change/DownloadContent.java
index 1022cad965..4a4a6808f4 100644
--- a/java/com/google/gerrit/server/restapi/change/DownloadContent.java
+++ b/java/com/google/gerrit/server/restapi/change/DownloadContent.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.change.FileResource;
@@ -24,7 +25,6 @@ import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import java.io.IOException;
-import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.args4j.Option;
public class DownloadContent implements RestReadView<FileResource> {
@@ -41,12 +41,12 @@ public class DownloadContent implements RestReadView<FileResource> {
}
@Override
- public BinaryResult apply(FileResource rsrc)
+ public Response<BinaryResult> apply(FileResource rsrc)
throws ResourceNotFoundException, IOException, NoSuchChangeException {
- String path = rsrc.getPatchKey().get();
+ String path = rsrc.getPatchKey().fileName();
RevisionResource rev = rsrc.getRevision();
- ObjectId revstr = ObjectId.fromString(rev.getPatchSet().getRevision().get());
- return fileContentUtil.downloadContent(
- projectCache.checkedGet(rev.getProject()), revstr, path, parent);
+ return Response.ok(
+ fileContentUtil.downloadContent(
+ projectCache.checkedGet(rev.getProject()), rev.getPatchSet().commitId(), path, parent));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/DraftComments.java b/java/com/google/gerrit/server/restapi/change/DraftComments.java
index dd61ca0855..bab1ac9d9f 100644
--- a/java/com/google/gerrit/server/restapi/change/DraftComments.java
+++ b/java/com/google/gerrit/server/restapi/change/DraftComments.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.DraftCommentResource;
@@ -66,7 +66,7 @@ public class DraftComments implements ChildCollection<RevisionResource, DraftCom
String uuid = id.get();
for (Comment c :
commentsUtil.draftByPatchSetAuthor(
- rev.getPatchSet().getId(), rev.getAccountId(), rev.getNotes())) {
+ rev.getPatchSet().id(), rev.getAccountId(), rev.getNotes())) {
if (uuid.equals(c.key.uuid)) {
return new DraftCommentResource(rev, c);
}
diff --git a/java/com/google/gerrit/server/restapi/change/Files.java b/java/com/google/gerrit/server/restapi/change/Files.java
index d8928105a2..392aef7dc2 100644
--- a/java/com/google/gerrit/server/restapi/change/Files.java
+++ b/java/com/google/gerrit/server/restapi/change/Files.java
@@ -19,6 +19,10 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -31,10 +35,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.AccountPatchReviewStore;
@@ -63,7 +63,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -166,13 +165,13 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
Response.ok(
fileInfoJson.toFileInfoMap(
resource.getChange(),
- resource.getPatchSet().getRevision(),
+ resource.getPatchSet().commitId(),
baseResource.getPatchSet()));
} else if (parentNum != 0) {
int parents =
gApi.changes()
.id(resource.getChange().getChangeId())
- .revision(resource.getPatchSet().getId().get())
+ .revision(resource.getPatchSet().id().get())
.commit(false)
.parents
.size();
@@ -182,7 +181,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
r =
Response.ok(
fileInfoJson.toFileInfoMap(
- resource.getChange(), resource.getPatchSet().getRevision(), parentNum - 1));
+ resource.getChange(), resource.getPatchSet().commitId(), parentNum - 1));
} else {
r = Response.ok(fileInfoJson.toFileInfoMap(resource.getChange(), resource.getPatchSet()));
}
@@ -219,8 +218,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
ObjectReader or = git.newObjectReader();
RevWalk rw = new RevWalk(or);
TreeWalk tw = new TreeWalk(or)) {
- RevCommit c =
- rw.parseCommit(ObjectId.fromString(resource.getPatchSet().getRevision().get()));
+ RevCommit c = rw.parseCommit(resource.getPatchSet().commitId());
tw.addTree(c.getTree());
tw.setRecursive(true);
@@ -244,11 +242,11 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
Account.Id userId = user.getAccountId();
PatchSet patchSetId = resource.getPatchSet();
Optional<PatchSetWithReviewedFiles> o;
- o = accountPatchReviewStore.call(s -> s.findReviewed(patchSetId.getId(), userId));
+ o = accountPatchReviewStore.call(s -> s.findReviewed(patchSetId.id(), userId));
if (o.isPresent()) {
PatchSetWithReviewedFiles res = o.get();
- if (res.patchSetId().equals(patchSetId.getId())) {
+ if (res.patchSetId().equals(patchSetId.id())) {
return res.files();
}
@@ -327,7 +325,7 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
}
accountPatchReviewStore.run(
- s -> s.markReviewed(resource.getPatchSet().getId(), userId, pathList));
+ s -> s.markReviewed(resource.getPatchSet().id(), userId, pathList));
return pathList;
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Fixes.java b/java/com/google/gerrit/server/restapi/change/Fixes.java
index 855d1f48cb..38240e3a98 100644
--- a/java/com/google/gerrit/server/restapi/change/Fixes.java
+++ b/java/com/google/gerrit/server/restapi/change/Fixes.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.FixSuggestion;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.FixSuggestion;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.FixResource;
import com.google.gerrit.server.change.RevisionResource;
@@ -54,7 +54,7 @@ public class Fixes implements ChildCollection<RevisionResource, FixResource> {
ChangeNotes changeNotes = revisionResource.getNotes();
List<RobotComment> robotComments =
- commentsUtil.robotCommentsByPatchSet(changeNotes, revisionResource.getPatchSet().getId());
+ commentsUtil.robotCommentsByPatchSet(changeNotes, revisionResource.getPatchSet().id());
for (RobotComment robotComment : robotComments) {
for (FixSuggestion fixSuggestion : robotComment.fixSuggestions) {
if (Objects.equals(fixId, fixSuggestion.fixId)) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetArchive.java b/java/com/google/gerrit/server/restapi/change/GetArchive.java
index 1bd1bce31e..4ebcbdda2f 100644
--- a/java/com/google/gerrit/server/restapi/change/GetArchive.java
+++ b/java/com/google/gerrit/server/restapi/change/GetArchive.java
@@ -14,10 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
+
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.gerrit.server.change.RevisionResource;
@@ -27,7 +30,6 @@ import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jgit.api.ArchiveCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -47,7 +49,7 @@ public class GetArchive implements RestReadView<RevisionResource> {
}
@Override
- public BinaryResult apply(RevisionResource rsrc)
+ public Response<BinaryResult> apply(RevisionResource rsrc)
throws BadRequestException, IOException, MethodNotAllowedException {
if (Strings.isNullOrEmpty(format)) {
throw new BadRequestException("format is not specified");
@@ -65,7 +67,7 @@ public class GetArchive implements RestReadView<RevisionResource> {
final RevCommit commit;
String name;
try (RevWalk rw = new RevWalk(repo)) {
- commit = rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet().getRevision().get()));
+ commit = rw.parseCommit(rsrc.getPatchSet().commitId());
name = name(f, rw, commit);
}
@@ -93,7 +95,7 @@ public class GetArchive implements RestReadView<RevisionResource> {
bin.disableGzip().setContentType(f.getMimeType()).setAttachmentName(name);
close = false;
- return bin;
+ return Response.ok(bin);
} finally {
if (close) {
repo.close();
@@ -104,6 +106,6 @@ public class GetArchive implements RestReadView<RevisionResource> {
private static String name(ArchiveFormat format, RevWalk rw, RevCommit commit)
throws IOException {
return String.format(
- "%s%s", rw.getObjectReader().abbreviate(commit, 7).name(), format.getDefaultSuffix());
+ "%s%s", abbreviateName(commit, rw.getObjectReader()), format.getDefaultSuffix());
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetAssignee.java b/java/com/google/gerrit/server/restapi/change/GetAssignee.java
index f89fe1b0ea..a5820bfa8e 100644
--- a/java/com/google/gerrit/server/restapi/change/GetAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/GetAssignee.java
@@ -14,10 +14,10 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/restapi/change/GetBlame.java b/java/com/google/gerrit/server/restapi/change/GetBlame.java
index 98a3a8aa5a..12b4d440f3 100644
--- a/java/com/google/gerrit/server/restapi/change/GetBlame.java
+++ b/java/com/google/gerrit/server/restapi/change/GetBlame.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.BlameInfo;
import com.google.gerrit.extensions.common.RangeInfo;
import com.google.gerrit.extensions.restapi.CacheControl;
@@ -23,7 +24,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.FileResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -92,7 +92,7 @@ public class GetBlame implements RestReadView<FileResource> {
String refName =
resource.getRevision().getEdit().isPresent()
? resource.getRevision().getEdit().get().getRefName()
- : resource.getRevision().getPatchSet().getRefName();
+ : resource.getRevision().getPatchSet().refName();
Ref ref = repository.findRef(refName);
if (ref == null) {
@@ -102,7 +102,7 @@ public class GetBlame implements RestReadView<FileResource> {
RevCommit revCommit = revWalk.parseCommit(objectId);
RevCommit[] parents = revCommit.getParents();
- String path = resource.getPatchKey().getFileName();
+ String path = resource.getPatchKey().fileName();
List<BlameInfo> result;
if (!base) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java b/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java
index f55785d161..9e0e0e3b7b 100644
--- a/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.ChangeMessageResource;
import com.google.inject.Singleton;
@@ -23,7 +24,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetChangeMessage implements RestReadView<ChangeMessageResource> {
@Override
- public ChangeMessageInfo apply(ChangeMessageResource resource) {
- return resource.getChangeMessage();
+ public Response<ChangeMessageInfo> apply(ChangeMessageResource resource) {
+ return Response.ok(resource.getChangeMessage());
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetComment.java b/java/com/google/gerrit/server/restapi/change/GetComment.java
index 0109c9501f..5103325557 100644
--- a/java/com/google/gerrit/server/restapi/change/GetComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetComment.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.CommentResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +34,7 @@ public class GetComment implements RestReadView<CommentResource> {
}
@Override
- public CommentInfo apply(CommentResource rsrc) throws PermissionBackendException {
- return commentJson.get().newCommentFormatter().format(rsrc.getComment());
+ public Response<CommentInfo> apply(CommentResource rsrc) throws PermissionBackendException {
+ return Response.ok(commentJson.get().newCommentFormatter().format(rsrc.getComment()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetCommit.java b/java/com/google/gerrit/server/restapi/change/GetCommit.java
index 29286cbabf..21a08dcd23 100644
--- a/java/com/google/gerrit/server/restapi/change/GetCommit.java
+++ b/java/com/google/gerrit/server/restapi/change/GetCommit.java
@@ -15,18 +15,17 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.RevisionJson;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -55,8 +54,7 @@ public class GetCommit implements RestReadView<RevisionResource> {
Project.NameKey p = rsrc.getChange().getProject();
try (Repository repo = repoManager.openRepository(p);
RevWalk rw = new RevWalk(repo)) {
- String rev = rsrc.getPatchSet().getRevision().get();
- RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
+ RevCommit commit = rw.parseCommit(rsrc.getPatchSet().commitId());
rw.parseBody(commit);
CommitInfo info =
json.create(ImmutableSet.of())
diff --git a/java/com/google/gerrit/server/restapi/change/GetContent.java b/java/com/google/gerrit/server/restapi/change/GetContent.java
index 1d35ab5915..bf7c51f652 100644
--- a/java/com/google/gerrit/server/restapi/change/GetContent.java
+++ b/java/com/google/gerrit/server/restapi/change/GetContent.java
@@ -14,13 +14,14 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.change.FileResource;
@@ -33,7 +34,6 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import java.io.IOException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -61,25 +61,28 @@ public class GetContent implements RestReadView<FileResource> {
}
@Override
- public BinaryResult apply(FileResource rsrc)
+ public Response<BinaryResult> apply(FileResource rsrc)
throws ResourceNotFoundException, IOException, BadRequestException {
- String path = rsrc.getPatchKey().get();
+ String path = rsrc.getPatchKey().fileName();
if (Patch.COMMIT_MSG.equals(path)) {
String msg = getMessage(rsrc.getRevision().getChangeResource().getNotes());
- return BinaryResult.create(msg)
- .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
- .base64();
+ return Response.ok(
+ BinaryResult.create(msg)
+ .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+ .base64());
} else if (Patch.MERGE_LIST.equals(path)) {
byte[] mergeList = getMergeList(rsrc.getRevision().getChangeResource().getNotes());
- return BinaryResult.create(mergeList)
- .setContentType(FileContentUtil.TEXT_X_GERRIT_MERGE_LIST)
- .base64();
+ return Response.ok(
+ BinaryResult.create(mergeList)
+ .setContentType(FileContentUtil.TEXT_X_GERRIT_MERGE_LIST)
+ .base64());
}
- return fileContentUtil.getContent(
- projectCache.checkedGet(rsrc.getRevision().getProject()),
- ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
- path,
- parent);
+ return Response.ok(
+ fileContentUtil.getContent(
+ projectCache.checkedGet(rsrc.getRevision().getProject()),
+ rsrc.getRevision().getPatchSet().commitId(),
+ path,
+ parent));
}
private String getMessage(ChangeNotes notes) throws IOException {
@@ -91,7 +94,7 @@ public class GetContent implements RestReadView<FileResource> {
try (Repository git = gitManager.openRepository(notes.getProjectName());
RevWalk revWalk = new RevWalk(git)) {
- RevCommit commit = revWalk.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ RevCommit commit = revWalk.parseCommit(ps.commitId());
return commit.getFullMessage();
} catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(changeId, e);
@@ -108,9 +111,7 @@ public class GetContent implements RestReadView<FileResource> {
try (Repository git = gitManager.openRepository(notes.getProjectName());
RevWalk revWalk = new RevWalk(git)) {
return Text.forMergeList(
- ComparisonType.againstAutoMerge(),
- revWalk.getObjectReader(),
- ObjectId.fromString(ps.getRevision().get()))
+ ComparisonType.againstAutoMerge(), revWalk.getObjectReader(), ps.commitId())
.getContent();
} catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(changeId, e);
diff --git a/java/com/google/gerrit/server/restapi/change/GetDescription.java b/java/com/google/gerrit/server/restapi/change/GetDescription.java
index 1a7ec63c8c..6794d81e85 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDescription.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDescription.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.change;
-import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.inject.Singleton;
@@ -22,7 +22,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetDescription implements RestReadView<RevisionResource> {
@Override
- public String apply(RevisionResource rsrc) {
- return Strings.nullToEmpty(rsrc.getPatchSet().getDescription());
+ public Response<String> apply(RevisionResource rsrc) {
+ return Response.ok(rsrc.getPatchSet().description().orElse(""));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 6a7f1faa84..39c5a3bef3 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -25,6 +25,9 @@ import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.common.ChangeType;
@@ -43,9 +46,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.jgit.diff.ReplaceEdit;
import com.google.gerrit.prettify.common.SparseFileContent;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.change.FileResource;
@@ -140,14 +140,14 @@ public class GetDiff implements RestReadView<FileResource> {
PatchScriptFactory psf;
PatchSet basePatchSet = null;
- PatchSet.Id pId = resource.getPatchKey().getParentKey();
- String fileName = resource.getPatchKey().getFileName();
+ PatchSet.Id pId = resource.getPatchKey().patchSetId();
+ String fileName = resource.getPatchKey().fileName();
ChangeNotes notes = resource.getRevision().getNotes();
if (base != null) {
RevisionResource baseResource =
revisions.parse(resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
- psf = patchScriptFactoryFactory.create(notes, fileName, basePatchSet.getId(), pId, prefs);
+ psf = patchScriptFactoryFactory.create(notes, fileName, basePatchSet.id(), pId, prefs);
} else if (parentNum > 0) {
psf = patchScriptFactoryFactory.create(notes, fileName, parentNum - 1, pId, prefs);
} else {
@@ -195,20 +195,20 @@ public class GetDiff implements RestReadView<FileResource> {
ProjectState state = projectCache.get(resource.getRevision().getChange().getProject());
DiffInfo result = new DiffInfo();
- String revA = basePatchSet != null ? basePatchSet.getRefName() : content.commitIdA;
+ String revA = basePatchSet != null ? basePatchSet.refName() : content.commitIdA;
String revB =
resource.getRevision().getEdit().isPresent()
? resource.getRevision().getEdit().get().getRefName()
- : resource.getRevision().getPatchSet().getRefName();
+ : resource.getRevision().getPatchSet().refName();
- List<DiffWebLinkInfo> links =
+ ImmutableList<DiffWebLinkInfo> links =
webLinks.getDiffLinks(
state.getName(),
- resource.getPatchKey().getParentKey().getParentKey().get(),
- basePatchSet != null ? basePatchSet.getId().get() : null,
+ resource.getPatchKey().patchSetId().changeId().get(),
+ basePatchSet != null ? basePatchSet.id().get() : null,
revA,
MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
- resource.getPatchKey().getParentKey().get(),
+ resource.getPatchKey().patchSetId().get(),
revB,
ps.getNewName());
result.webLinks = links.isEmpty() ? null : links;
@@ -271,7 +271,7 @@ public class GetDiff implements RestReadView<FileResource> {
}
private List<WebLinkInfo> getFileWebLinks(Project project, String rev, String file) {
- List<WebLinkInfo> links = webLinks.getFileLinks(project.getName(), rev, file);
+ ImmutableList<WebLinkInfo> links = webLinks.getFileLinks(project.getName(), rev, file);
return links.isEmpty() ? null : links;
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetDraftComment.java b/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
index ca5b56f16d..797dc9edc5 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.DraftCommentResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +34,7 @@ public class GetDraftComment implements RestReadView<DraftCommentResource> {
}
@Override
- public CommentInfo apply(DraftCommentResource rsrc) throws PermissionBackendException {
- return commentJson.get().newCommentFormatter().format(rsrc.getComment());
+ public Response<CommentInfo> apply(DraftCommentResource rsrc) throws PermissionBackendException {
+ return Response.ok(commentJson.get().newCommentFormatter().format(rsrc.getComment()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetMergeList.java b/java/com/google/gerrit/server/restapi/change/GetMergeList.java
index 0c18a8f04e..0c67fd639c 100644
--- a/java/com/google/gerrit/server/restapi/change/GetMergeList.java
+++ b/java/com/google/gerrit/server/restapi/change/GetMergeList.java
@@ -16,12 +16,12 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.RevisionJson;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -31,7 +31,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -67,8 +66,7 @@ public class GetMergeList implements RestReadView<RevisionResource> {
Project.NameKey p = rsrc.getChange().getProject();
try (Repository repo = repoManager.openRepository(p);
RevWalk rw = new RevWalk(repo)) {
- String rev = rsrc.getPatchSet().getRevision().get();
- RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
+ RevCommit commit = rw.parseCommit(rsrc.getPatchSet().commitId());
rw.parseBody(commit);
if (uninterestingParent < 1 || uninterestingParent > commit.getParentCount()) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java b/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
index 1d56669ac6..c1c9a34950 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
@@ -16,10 +16,10 @@ package com.google.gerrit.server.restapi.change;
import static java.util.stream.Collectors.toList;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/restapi/change/GetPatch.java b/java/com/google/gerrit/server/restapi/change/GetPatch.java
index b205ece4ba..187ebcee19 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPatch.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPatch.java
@@ -14,11 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -31,8 +33,6 @@ import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -60,15 +60,15 @@ public class GetPatch implements RestReadView<RevisionResource> {
}
@Override
- public BinaryResult apply(RevisionResource rsrc)
+ public Response<BinaryResult> apply(RevisionResource rsrc)
throws ResourceConflictException, IOException, ResourceNotFoundException {
final Repository repo = repoManager.openRepository(rsrc.getProject());
boolean close = true;
try {
final RevWalk rw = new RevWalk(repo);
+ BinaryResult bin = null;
try {
- final RevCommit commit =
- rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet().getRevision().get()));
+ final RevCommit commit = rw.parseCommit(rsrc.getPatchSet().commitId());
RevCommit[] parents = commit.getParents();
if (parents.length > 1) {
throw new ResourceConflictException("Revision has more than 1 parent.");
@@ -78,7 +78,7 @@ public class GetPatch implements RestReadView<RevisionResource> {
final RevCommit base = parents[0];
rw.parseBody(base);
- BinaryResult bin =
+ bin =
new BinaryResult() {
@Override
public void writeTo(OutputStream out) throws IOException {
@@ -132,10 +132,13 @@ public class GetPatch implements RestReadView<RevisionResource> {
}
close = false;
- return bin;
+ return Response.ok(bin);
} finally {
if (close) {
rw.close();
+ if (bin != null) {
+ bin.close();
+ }
}
}
} finally {
@@ -189,7 +192,6 @@ public class GetPatch implements RestReadView<RevisionResource> {
}
private static String fileName(RevWalk rw, RevCommit commit) throws IOException {
- AbbreviatedObjectId id = rw.getObjectReader().abbreviate(commit, 7);
- return id.name() + ".diff";
+ return abbreviateName(commit, rw.getObjectReader()) + ".diff";
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetPureRevert.java b/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
index fa5cc36f5e..765be5f51e 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPureRevert.java
@@ -19,6 +19,7 @@ import com.google.gerrit.extensions.common.PureRevertInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.PureRevert;
@@ -46,9 +47,9 @@ public class GetPureRevert implements RestReadView<ChangeResource> {
}
@Override
- public PureRevertInfo apply(ChangeResource rsrc)
+ public Response<PureRevertInfo> apply(ChangeResource rsrc)
throws ResourceConflictException, IOException, BadRequestException, AuthException {
boolean isPureRevert = pureRevert.get(rsrc.getNotes(), Optional.ofNullable(claimedOriginal));
- return new PureRevertInfo(isPureRevert);
+ return Response.ok(new PureRevertInfo(isPureRevert));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetRelated.java b/java/com/google/gerrit/server/restapi/change/GetRelated.java
index 332cc4de08..a846d50f1c 100644
--- a/java/com/google/gerrit/server/restapi/change/GetRelated.java
+++ b/java/com/google/gerrit/server/restapi/change/GetRelated.java
@@ -19,14 +19,15 @@ import static java.util.stream.Collectors.toSet;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
import com.google.gerrit.extensions.api.changes.RelatedChangesInfo;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.index.IndexConfig;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.PatchSetUtil;
@@ -68,12 +69,12 @@ public class GetRelated implements RestReadView<RevisionResource> {
}
@Override
- public RelatedChangesInfo apply(RevisionResource rsrc)
+ public Response<RelatedChangesInfo> apply(RevisionResource rsrc)
throws RepositoryNotFoundException, IOException, NoSuchProjectException,
PermissionBackendException {
RelatedChangesInfo relatedChangesInfo = new RelatedChangesInfo();
relatedChangesInfo.changes = getRelated(rsrc);
- return relatedChangesInfo;
+ return Response.ok(relatedChangesInfo);
}
private List<RelatedChangeAndCommitInfo> getRelated(RevisionResource rsrc)
@@ -102,7 +103,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
for (RelatedChangesSorter.PatchSetData d : sorter.sort(cds, basePs)) {
PatchSet ps = d.patchSet();
RevCommit commit;
- if (isEdit && ps.getId().equals(basePs.getId())) {
+ if (isEdit && ps.id().equals(basePs.id())) {
// Replace base of an edit with the edit itself.
ps = rsrc.getPatchSet();
commit = rsrc.getEdit().get().getEditCommit();
@@ -114,7 +115,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
if (result.size() == 1) {
RelatedChangeAndCommitInfo r = result.get(0);
- if (r.commit != null && r.commit.commit.equals(rsrc.getPatchSet().getRevision().get())) {
+ if (r.commit != null && r.commit.commit.equals(rsrc.getPatchSet().commitId().name())) {
return Collections.emptyList();
}
}
@@ -123,13 +124,13 @@ public class GetRelated implements RestReadView<RevisionResource> {
@VisibleForTesting
public static Set<String> getAllGroups(ChangeNotes notes, PatchSetUtil psUtil) {
- return psUtil.byChange(notes).stream().flatMap(ps -> ps.getGroups().stream()).collect(toSet());
+ return psUtil.byChange(notes).stream().flatMap(ps -> ps.groups().stream()).collect(toSet());
}
private void reloadChangeIfStale(List<ChangeData> cds, PatchSet wantedPs) {
for (ChangeData cd : cds) {
- if (cd.getId().equals(wantedPs.getId().getParentKey())) {
- if (cd.patchSet(wantedPs.getId()) == null) {
+ if (cd.getId().equals(wantedPs.id().changeId())) {
+ if (cd.patchSet(wantedPs.id()) == null) {
cd.reloadChange();
}
}
@@ -144,7 +145,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
if (change != null) {
info.changeId = change.getKey().get();
info._changeNumber = change.getChangeId();
- info._revisionNumber = ps != null ? ps.getPatchSetId() : null;
+ info._revisionNumber = ps != null ? ps.number() : null;
PatchSet.Id curr = change.currentPatchSetId();
info._currentRevisionNumber = curr != null ? curr.get() : null;
info.status = ChangeUtil.status(change).toUpperCase(Locale.US);
diff --git a/java/com/google/gerrit/server/restapi/change/GetReviewer.java b/java/com/google/gerrit/server/restapi/change/GetReviewer.java
index 73760dab96..a672b176f8 100644
--- a/java/com/google/gerrit/server/restapi/change/GetReviewer.java
+++ b/java/com/google/gerrit/server/restapi/change/GetReviewer.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.ReviewerJson;
import com.google.gerrit.server.change.ReviewerResource;
@@ -33,7 +34,8 @@ public class GetReviewer implements RestReadView<ReviewerResource> {
}
@Override
- public List<ReviewerInfo> apply(ReviewerResource rsrc) throws PermissionBackendException {
- return json.format(rsrc);
+ public Response<List<ReviewerInfo>> apply(ReviewerResource rsrc)
+ throws PermissionBackendException {
+ return Response.ok(json.format(rsrc));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetRobotComment.java b/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
index 75d994d1ce..4ff9942e2f 100644
--- a/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.common.RobotCommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.RobotCommentResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +34,8 @@ public class GetRobotComment implements RestReadView<RobotCommentResource> {
}
@Override
- public RobotCommentInfo apply(RobotCommentResource rsrc) throws PermissionBackendException {
- return commentJson.get().newRobotCommentFormatter().format(rsrc.getComment());
+ public Response<RobotCommentInfo> apply(RobotCommentResource rsrc)
+ throws PermissionBackendException {
+ return Response.ok(commentJson.get().newRobotCommentFormatter().format(rsrc.getComment()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetTopic.java b/java/com/google/gerrit/server/restapi/change/GetTopic.java
index 7ab1cb1289..6951fa5e4c 100644
--- a/java/com/google/gerrit/server/restapi/change/GetTopic.java
+++ b/java/com/google/gerrit/server/restapi/change/GetTopic.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetTopic implements RestReadView<ChangeResource> {
@Override
- public String apply(ChangeResource rsrc) {
- return Strings.nullToEmpty(rsrc.getChange().getTopic());
+ public Response<String> apply(ChangeResource rsrc) {
+ return Response.ok(Strings.nullToEmpty(rsrc.getChange().getTopic()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Index.java b/java/com/google/gerrit/server/restapi/change/Index.java
index 90dad9800b..5a17c07d6f 100644
--- a/java/com/google/gerrit/server/restapi/change/Index.java
+++ b/java/com/google/gerrit/server/restapi/change/Index.java
@@ -30,7 +30,7 @@ import com.google.inject.Singleton;
import java.io.IOException;
@Singleton
-public class Index extends RetryingRestModifyView<ChangeResource, Input, Response<?>> {
+public class Index extends RetryingRestModifyView<ChangeResource, Input, Object> {
private final PermissionBackend permissionBackend;
private final ChangeIndexer indexer;
@@ -42,7 +42,7 @@ public class Index extends RetryingRestModifyView<ChangeResource, Input, Respons
}
@Override
- protected Response<?> applyImpl(
+ protected Response<Object> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
throws IOException, AuthException, PermissionBackendException {
permissionBackend.currentUser().check(GlobalPermission.MAINTAIN_SERVER);
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
index 403922db99..edd6201f69 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.change;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.query.change.ChangeData;
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
index f8fda731c9..cc35a5e477 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -59,12 +60,12 @@ public class ListChangeDrafts implements RestReadView<ChangeResource> {
}
@Override
- public Map<String, List<CommentInfo>> apply(ChangeResource rsrc)
+ public Response<Map<String, List<CommentInfo>>> apply(ChangeResource rsrc)
throws AuthException, PermissionBackendException {
if (requireAuthentication() && !rsrc.getUser().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- return getCommentFormatter().format(listComments(rsrc));
+ return Response.ok(getCommentFormatter().format(listComments(rsrc)));
}
public List<CommentInfo> getComments(ChangeResource rsrc)
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
index a2e3d4b589..bfc9f1268e 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
@@ -16,9 +16,10 @@ package com.google.gerrit.server.restapi.change;
import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.ChangeResource;
@@ -41,13 +42,14 @@ public class ListChangeMessages implements RestReadView<ChangeResource> {
}
@Override
- public List<ChangeMessageInfo> apply(ChangeResource resource) throws PermissionBackendException {
+ public Response<List<ChangeMessageInfo>> apply(ChangeResource resource)
+ throws PermissionBackendException {
List<ChangeMessage> messages = changeMessagesUtil.byChange(resource.getNotes());
List<ChangeMessageInfo> messageInfos =
messages.stream()
.map(m -> createChangeMessageInfo(m, accountLoader))
.collect(Collectors.toList());
accountLoader.fill();
- return messageInfos;
+ return Response.ok(messageInfos);
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
index e5840fdfad..719a4779db 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.change;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.ChangeResource;
@@ -42,14 +43,15 @@ public class ListChangeRobotComments implements RestReadView<ChangeResource> {
}
@Override
- public Map<String, List<RobotCommentInfo>> apply(ChangeResource rsrc)
+ public Response<Map<String, List<RobotCommentInfo>>> apply(ChangeResource rsrc)
throws AuthException, PermissionBackendException {
ChangeData cd = changeDataFactory.create(rsrc.getNotes());
- return commentJson
- .get()
- .setFillAccounts(true)
- .setFillPatchSet(true)
- .newRobotCommentFormatter()
- .format(commentsUtil.robotCommentsByChange(cd.notes()));
+ return Response.ok(
+ commentJson
+ .get()
+ .setFillAccounts(true)
+ .setFillPatchSet(true)
+ .newRobotCommentFormatter()
+ .format(commentsUtil.robotCommentsByChange(cd.notes())));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListReviewers.java b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
index 12732ffed9..25ef480e00 100644
--- a/java/com/google/gerrit/server/restapi/change/ListReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ReviewerJson;
@@ -44,7 +45,7 @@ public class ListReviewers implements RestReadView<ChangeResource> {
}
@Override
- public List<ReviewerInfo> apply(ChangeResource rsrc) throws PermissionBackendException {
+ public Response<List<ReviewerInfo>> apply(ChangeResource rsrc) throws PermissionBackendException {
Map<String, ReviewerResource> reviewers = new LinkedHashMap<>();
for (Account.Id accountId : approvalsUtil.getReviewers(rsrc.getNotes()).all()) {
if (!reviewers.containsKey(accountId.toString())) {
@@ -56,6 +57,6 @@ public class ListReviewers implements RestReadView<ChangeResource> {
reviewers.put(adr.toString(), new ReviewerResource(rsrc, adr));
}
}
- return json.format(reviewers.values());
+ return Response.ok(json.format(reviewers.values()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java b/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
index b39ba63ed7..de05d2aeea 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.change;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -37,6 +37,6 @@ public class ListRevisionComments extends ListRevisionDrafts {
@Override
protected Iterable<Comment> listComments(RevisionResource rsrc) {
ChangeNotes notes = rsrc.getNotes();
- return commentsUtil.publishedByPatchSet(notes, rsrc.getPatchSet().getId());
+ return commentsUtil.publishedByPatchSet(notes, rsrc.getPatchSet().id());
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
index a46bd6c802..199a752285 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
@@ -15,9 +15,10 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Comment;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -40,7 +41,7 @@ public class ListRevisionDrafts implements RestReadView<RevisionResource> {
protected Iterable<Comment> listComments(RevisionResource rsrc) {
return commentsUtil.draftByPatchSetAuthor(
- rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes());
+ rsrc.getPatchSet().id(), rsrc.getAccountId(), rsrc.getNotes());
}
protected boolean includeAuthorInfo() {
@@ -48,13 +49,14 @@ public class ListRevisionDrafts implements RestReadView<RevisionResource> {
}
@Override
- public Map<String, List<CommentInfo>> apply(RevisionResource rsrc)
+ public Response<Map<String, List<CommentInfo>>> apply(RevisionResource rsrc)
throws PermissionBackendException {
- return commentJson
- .get()
- .setFillAccounts(includeAuthorInfo())
- .newCommentFormatter()
- .format(listComments(rsrc));
+ return Response.ok(
+ commentJson
+ .get()
+ .setFillAccounts(includeAuthorInfo())
+ .newCommentFormatter()
+ .format(listComments(rsrc)));
}
public ImmutableList<CommentInfo> getComments(RevisionResource rsrc)
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
index 920cde9ea1..73b1f59b79 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ReviewerJson;
import com.google.gerrit.server.change.ReviewerResource;
@@ -45,7 +46,7 @@ class ListRevisionReviewers implements RestReadView<RevisionResource> {
}
@Override
- public List<ReviewerInfo> apply(RevisionResource rsrc)
+ public Response<List<ReviewerInfo>> apply(RevisionResource rsrc)
throws MethodNotAllowedException, PermissionBackendException {
if (!rsrc.isCurrent()) {
throw new MethodNotAllowedException("Cannot list reviewers on non-current patch set");
@@ -62,6 +63,6 @@ class ListRevisionReviewers implements RestReadView<RevisionResource> {
reviewers.put(address.toString(), new ReviewerResource(rsrc, address));
}
}
- return json.format(reviewers.values());
+ return Response.ok(json.format(reviewers.values()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
index bbf46a3f70..bbbe12dd95 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
@@ -15,9 +15,10 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.common.RobotCommentInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -39,13 +40,14 @@ public class ListRobotComments implements RestReadView<RevisionResource> {
}
@Override
- public Map<String, List<RobotCommentInfo>> apply(RevisionResource rsrc)
+ public Response<Map<String, List<RobotCommentInfo>>> apply(RevisionResource rsrc)
throws PermissionBackendException {
- return commentJson
- .get()
- .setFillAccounts(true)
- .newRobotCommentFormatter()
- .format(listComments(rsrc));
+ return Response.ok(
+ commentJson
+ .get()
+ .setFillAccounts(true)
+ .newRobotCommentFormatter()
+ .format(listComments(rsrc)));
}
public ImmutableList<RobotCommentInfo> getComments(RevisionResource rsrc)
@@ -58,6 +60,6 @@ public class ListRobotComments implements RestReadView<RevisionResource> {
}
private Iterable<RobotComment> listComments(RevisionResource rsrc) {
- return commentsUtil.robotCommentsByPatchSet(rsrc.getNotes(), rsrc.getPatchSet().getId());
+ return commentsUtil.robotCommentsByPatchSet(rsrc.getNotes(), rsrc.getPatchSet().id());
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Mergeable.java b/java/com/google/gerrit/server/restapi/change/Mergeable.java
index ece8938379..9b17ed6264 100644
--- a/java/com/google/gerrit/server/restapi/change/Mergeable.java
+++ b/java/com/google/gerrit/server/restapi/change/Mergeable.java
@@ -14,17 +14,17 @@
package com.google.gerrit.server.restapi.change;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.change.RevisionResource;
@@ -50,8 +50,6 @@ import org.eclipse.jgit.lib.Repository;
import org.kohsuke.args4j.Option;
public class Mergeable implements RestReadView<RevisionResource> {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
@Option(
name = "--other-branches",
aliases = {"-o"},
@@ -89,7 +87,7 @@ public class Mergeable implements RestReadView<RevisionResource> {
}
@Override
- public MergeableInfo apply(RevisionResource resource)
+ public Response<MergeableInfo> apply(RevisionResource resource)
throws AuthException, ResourceConflictException, BadRequestException, IOException {
Change change = resource.getChange();
PatchSet ps = resource.getPatchSet();
@@ -97,17 +95,17 @@ public class Mergeable implements RestReadView<RevisionResource> {
if (!change.isNew()) {
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
- } else if (!ps.getId().equals(change.currentPatchSetId())) {
+ } else if (!ps.id().equals(change.currentPatchSetId())) {
// Only the current revision is mergeable. Others always fail.
- return result;
+ return Response.ok(result);
}
ChangeData cd = changeDataFactory.create(resource.getNotes());
result.submitType = getSubmitType(cd);
try (Repository git = gitManager.openRepository(change.getProject())) {
- ObjectId commit = toId(ps);
- Ref ref = git.getRefDatabase().exactRef(change.getDest().get());
+ ObjectId commit = ps.commitId();
+ Ref ref = git.getRefDatabase().exactRef(change.getDest().branch());
ProjectState projectState = projectCache.get(change.getProject());
String strategy = mergeUtilFactory.create(projectState).mergeStrategyName();
result.strategy = strategy;
@@ -132,7 +130,7 @@ public class Mergeable implements RestReadView<RevisionResource> {
}
}
}
- return result;
+ return Response.ok(result);
}
private SubmitType getSubmitType(ChangeData cd) {
@@ -161,15 +159,6 @@ public class Mergeable implements RestReadView<RevisionResource> {
return refresh(change, commit, ref, submitType, strategy, git, old);
}
- private static ObjectId toId(PatchSet ps) {
- try {
- return ObjectId.fromString(ps.getRevision().get());
- } catch (IllegalArgumentException e) {
- logger.atSevere().log("Invalid revision on patch set %s", ps);
- return null;
- }
- }
-
private boolean refresh(
final Change change,
ObjectId commit,
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index f2335b155d..7d4c4d19f4 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -23,6 +23,14 @@ import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.MoveInput;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -30,16 +38,9 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
@@ -114,7 +115,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
}
@Override
- protected ChangeInfo applyImpl(
+ protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, MoveInput input)
throws RestApiException, UpdateException, PermissionBackendException, IOException {
if (!moveEnabled) {
@@ -135,7 +136,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
throw new ResourceConflictException("Change is " + ChangeUtil.status(change));
}
- Branch.NameKey newDest = new Branch.NameKey(project, input.destinationBranch);
+ BranchNameKey newDest = BranchNameKey.create(project, input.destinationBranch);
if (change.getDest().equals(newDest)) {
throw new ResourceConflictException("Change is already destined for the specified branch");
}
@@ -157,14 +158,14 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
u.addOp(change.getId(), op);
u.execute();
}
- return json.noOptions().format(op.getChange());
+ return Response.ok(json.noOptions().format(op.getChange()));
}
private class Op implements BatchUpdateOp {
private final MoveInput input;
private Change change;
- private Branch.NameKey newDestKey;
+ private BranchNameKey newDestKey;
Op(MoveInput input) {
this.input = input;
@@ -183,8 +184,8 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
}
Project.NameKey projectKey = change.getProject();
- newDestKey = new Branch.NameKey(projectKey, input.destinationBranch);
- Branch.NameKey changePrevDest = change.getDest();
+ newDestKey = BranchNameKey.create(projectKey, input.destinationBranch);
+ BranchNameKey changePrevDest = change.getDest();
if (changePrevDest.equals(newDestKey)) {
throw new ResourceConflictException("Change is already destined for the specified branch");
}
@@ -193,8 +194,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
try (Repository repo = repoManager.openRepository(projectKey);
RevWalk revWalk = new RevWalk(repo)) {
RevCommit currPatchsetRevCommit =
- revWalk.parseCommit(
- ObjectId.fromString(psUtil.current(ctx.getNotes()).getRevision().get()));
+ revWalk.parseCommit(psUtil.current(ctx.getNotes()).commitId());
if (currPatchsetRevCommit.getParentCount() > 1) {
throw new ResourceConflictException("Merge commit cannot be moved");
}
@@ -216,7 +216,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
if (!asChanges(queryProvider.get().byBranchKey(newDestKey, changeKey)).isEmpty()) {
throw new ResourceConflictException(
"Destination "
- + newDestKey.getShortName()
+ + newDestKey.shortName()
+ " has a different change with same change key "
+ changeKey);
}
@@ -227,16 +227,16 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
PatchSet.Id psId = change.currentPatchSetId();
ChangeUpdate update = ctx.getUpdate(psId);
- update.setBranch(newDestKey.get());
+ update.setBranch(newDestKey.branch());
change.setDest(newDestKey);
updateApprovals(ctx, update, psId, projectKey);
StringBuilder msgBuf = new StringBuilder();
msgBuf.append("Change destination moved from ");
- msgBuf.append(changePrevDest.getShortName());
+ msgBuf.append(changePrevDest.shortName());
msgBuf.append(" to ");
- msgBuf.append(newDestKey.getShortName());
+ msgBuf.append(newDestKey.shortName());
if (!Strings.isNullOrEmpty(input.message)) {
msgBuf.append("\n\n");
msgBuf.append(input.message);
@@ -262,7 +262,7 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
approvalsUtil.byPatchSet(
ctx.getNotes(), psId, ctx.getRevWalk(), ctx.getRepoView().getConfig())) {
ProjectState projectState = projectCache.checkedGet(project);
- LabelType type = projectState.getLabelTypes(ctx.getNotes()).byLabel(psa.getLabelId());
+ LabelType type = projectState.getLabelTypes(ctx.getNotes()).byLabel(psa.labelId());
// Only keep veto votes, defined as votes where:
// 1- the label function allows minimum values to block submission.
// 2- the vote holds the minimum value.
@@ -271,12 +271,13 @@ public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, Chan
}
// Remove votes from NoteDb.
- update.removeApprovalFor(psa.getAccountId(), psa.getLabel());
+ update.removeApprovalFor(psa.accountId(), psa.label());
approvals.add(
- new PatchSetApproval(
- new PatchSetApproval.Key(psId, psa.getAccountId(), new LabelId(psa.getLabel())),
- (short) 0,
- ctx.getWhen()));
+ PatchSetApproval.builder()
+ .key(PatchSetApproval.key(psId, psa.accountId(), LabelId.create(psa.label())))
+ .value(0)
+ .granted(ctx.getWhen())
+ .build());
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/PostHashtags.java b/java/com/google/gerrit/server/restapi/change/PostHashtags.java
index da499a91ba..516dead9f5 100644
--- a/java/com/google/gerrit/server/restapi/change/PostHashtags.java
+++ b/java/com/google/gerrit/server/restapi/change/PostHashtags.java
@@ -33,8 +33,7 @@ import com.google.inject.Singleton;
@Singleton
public class PostHashtags
- extends RetryingRestModifyView<
- ChangeResource, HashtagsInput, Response<ImmutableSortedSet<String>>>
+ extends RetryingRestModifyView<ChangeResource, HashtagsInput, ImmutableSortedSet<String>>
implements UiAction<ChangeResource> {
private final SetHashtagsOp.Factory hashtagsFactory;
diff --git a/java/com/google/gerrit/server/restapi/change/PostPrivate.java b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
index 5aa2ecc07d..f008df3589 100644
--- a/java/com/google/gerrit/server/restapi/change/PostPrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.restapi.change;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.SetPrivateOp;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -39,8 +39,7 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
@Singleton
-public class PostPrivate
- extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, Response<String>>
+public class PostPrivate extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, String>
implements UiAction<ChangeResource> {
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 8c94ba322f..e0d0a04640 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -16,7 +16,8 @@ package com.google.gerrit.server.restapi.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static com.google.gerrit.server.permissions.LabelPermission.ForUser.ON_BEHALF_OF;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -29,15 +30,28 @@ import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
+import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.entities.FixSuggestion;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -61,20 +75,11 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.FixReplacement;
-import com.google.gerrit.reviewdb.client.FixSuggestion;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
@@ -107,12 +112,14 @@ import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.LabelPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.update.CommentsRejectedException;
import com.google.gerrit.server.update.Context;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -134,18 +141,21 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@Singleton
public class PostReview
- extends RetryingRestModifyView<RevisionResource, ReviewInput, Response<ReviewResult>> {
+ extends RetryingRestModifyView<RevisionResource, ReviewInput, ReviewResult> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- public static final String ERROR_ADDING_REVIEWER = "error adding reviewer";
+ private static final String ERROR_ADDING_REVIEWER = "error adding reviewer";
public static final String ERROR_WIP_READY_MUTUALLY_EXCLUSIVE =
"work_in_progress and ready are mutually exclusive";
@@ -172,6 +182,7 @@ public class PostReview
private final WorkInProgressOp.Factory workInProgressOpFactory;
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
+ private final PluginSetContext<CommentValidator> commentValidators;
private final boolean strictLabels;
@Inject
@@ -194,7 +205,8 @@ public class PostReview
@GerritServerConfig Config gerritConfig,
WorkInProgressOp.Factory workInProgressOpFactory,
ProjectCache projectCache,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ PluginSetContext<CommentValidator> commentValidators) {
super(retryHelper);
this.changeResourceFactory = changeResourceFactory;
this.changeDataFactory = changeDataFactory;
@@ -214,6 +226,7 @@ public class PostReview
this.workInProgressOpFactory = workInProgressOpFactory;
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
+ this.commentValidators = commentValidators;
this.strictLabels = gerritConfig.getBoolean("change", "strictLabels", false);
}
@@ -236,7 +249,10 @@ public class PostReview
}
ProjectState projectState = projectCache.checkedGet(revision.getProject());
LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());
+
input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
+ logger.atFine().log("draft handling = %s", input.drafts);
+
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, labelTypes, input);
}
@@ -244,10 +260,11 @@ public class PostReview
checkLabels(revision, labelTypes, input.labels);
}
if (input.comments != null) {
- cleanUpComments(input.comments);
+ input.comments = cleanUpComments(input.comments);
checkComments(revision, input.comments);
}
if (input.robotComments != null) {
+ input.robotComments = cleanUpComments(input.robotComments);
checkRobotComments(revision, input.robotComments);
}
@@ -357,8 +374,7 @@ public class PostReview
// Add the review op.
bu.addOp(
- revision.getChange().getId(),
- new Op(projectState, revision.getPatchSet().getId(), input));
+ revision.getChange().getId(), new Op(projectState, revision.getPatchSet().id(), input));
// Notify based on ReviewInput, ignoring the notify settings from any AddReviewerInputs.
NotifyResolver.Result notify =
@@ -538,37 +554,30 @@ public class PostReview
}
}
- private static <T extends CommentInput> void cleanUpComments(
+ private static <T extends CommentInput> Map<String, List<T>> cleanUpComments(
Map<String, List<T>> commentsPerPath) {
- Iterator<List<T>> mapValueIterator = commentsPerPath.values().iterator();
- while (mapValueIterator.hasNext()) {
- List<T> comments = mapValueIterator.next();
+ Map<String, List<T>> cleanedUpCommentMap = new HashMap<>();
+ for (Map.Entry<String, List<T>> e : commentsPerPath.entrySet()) {
+ String path = e.getKey();
+ List<T> comments = e.getValue();
+
if (comments == null) {
- mapValueIterator.remove();
continue;
}
- cleanUpComments(comments);
- if (comments.isEmpty()) {
- mapValueIterator.remove();
+ List<T> cleanedUpComments = cleanUpComments(comments);
+ if (!cleanedUpComments.isEmpty()) {
+ cleanedUpCommentMap.put(path, cleanedUpComments);
}
}
+ return cleanedUpCommentMap;
}
- private static <T extends CommentInput> void cleanUpComments(List<T> comments) {
- Iterator<T> commentsIterator = comments.iterator();
- while (commentsIterator.hasNext()) {
- T comment = commentsIterator.next();
- if (comment == null) {
- commentsIterator.remove();
- continue;
- }
-
- comment.message = Strings.nullToEmpty(comment.message).trim();
- if (comment.message.isEmpty()) {
- commentsIterator.remove();
- }
- }
+ private static <T extends CommentInput> List<T> cleanUpComments(List<T> comments) {
+ return comments.stream()
+ .filter(Objects::nonNull)
+ .filter(comment -> !Strings.nullToEmpty(comment.message).trim().isEmpty())
+ .collect(toList());
}
private <T extends CommentInput> void checkComments(
@@ -577,7 +586,7 @@ public class PostReview
Set<String> revisionFilePaths = getAffectedFilePaths(revision);
for (Map.Entry<String, List<T>> entry : commentsPerPath.entrySet()) {
String path = entry.getKey();
- PatchSet.Id patchSetId = revision.getPatchSet().getId();
+ PatchSet.Id patchSetId = revision.getPatchSet().id();
ensurePathRefersToAvailableOrMagicFile(path, revisionFilePaths, patchSetId);
List<T> comments = entry.getValue();
@@ -591,7 +600,7 @@ public class PostReview
private Set<String> getAffectedFilePaths(RevisionResource revision)
throws PatchListNotAvailableException {
- ObjectId newId = ObjectId.fromString(revision.getPatchSet().getRevision().get());
+ ObjectId newId = revision.getPatchSet().commitId();
DiffSummaryKey key =
DiffSummaryKey.fromPatchListKey(
PatchListKey.againstDefaultBase(newId, Whitespace.IGNORE_NONE));
@@ -626,7 +635,6 @@ public class PostReview
private void checkRobotComments(
RevisionResource revision, Map<String, List<RobotCommentInput>> in)
throws BadRequestException, PatchListNotAvailableException {
- cleanUpComments(in);
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
String commentPath = e.getKey();
for (RobotCommentInput c : e.getValue()) {
@@ -799,7 +807,10 @@ public class PostReview
}
}
- /** Used to compare Comments with CommentInput comments. */
+ /**
+ * Used to compare existing {@link Comment}-s with {@link CommentInput} comments by copying only
+ * the fields to compare.
+ */
@AutoValue
abstract static class CommentSetEntry {
private static CommentSetEntry create(
@@ -861,12 +872,11 @@ public class PostReview
@Override
public boolean updateChange(ChangeContext ctx)
throws ResourceConflictException, UnprocessableEntityException, IOException,
- PatchListNotAvailableException {
+ PatchListNotAvailableException, CommentsRejectedException {
user = ctx.getIdentifiedUser();
notes = ctx.getNotes();
ps = psUtil.get(ctx.getNotes(), psId);
- boolean dirty = false;
- dirty |= insertComments(ctx);
+ boolean dirty = insertComments(ctx);
dirty |= insertRobotComments(ctx);
dirty |= updateLabels(projectState, ctx);
dirty |= insertMessage(ctx);
@@ -895,14 +905,19 @@ public class PostReview
}
private boolean insertComments(ChangeContext ctx)
- throws UnprocessableEntityException, PatchListNotAvailableException {
- Map<String, List<CommentInput>> map = in.comments;
- if (map == null) {
- map = Collections.emptyMap();
+ throws UnprocessableEntityException, PatchListNotAvailableException,
+ CommentsRejectedException {
+ Map<String, List<CommentInput>> inputComments = in.comments;
+ if (inputComments == null) {
+ inputComments = Collections.emptyMap();
}
- Map<String, Comment> drafts = Collections.emptyMap();
- if (!map.isEmpty() || in.drafts != DraftHandling.KEEP) {
+ // HashMap instead of Collections.emptyMap() avoids warning about remove() on immutable
+ // object.
+ Map<String, Comment> drafts = new HashMap<>();
+ // If there are inputComments we need the deduplication loop below, so we have to read (and
+ // publish) drafts here.
+ if (!inputComments.isEmpty() || in.drafts != DraftHandling.KEEP) {
if (in.drafts == DraftHandling.PUBLISH_ALL_REVISIONS) {
drafts = changeDrafts(ctx);
} else {
@@ -910,51 +925,84 @@ public class PostReview
}
}
+ // This will be populated with Comment-s created from inputComments.
List<Comment> toPublish = new ArrayList<>();
- Set<CommentSetEntry> existingIds =
+ Set<CommentSetEntry> existingComments =
in.omitDuplicateComments ? readExistingComments(ctx) : Collections.emptySet();
- for (Map.Entry<String, List<CommentInput>> ent : map.entrySet()) {
- String path = ent.getKey();
- for (CommentInput c : ent.getValue()) {
- String parent = Url.decode(c.inReplyTo);
- Comment e = drafts.remove(Url.decode(c.id));
- if (e == null) {
- e = commentsUtil.newComment(ctx, path, psId, c.side(), c.message, c.unresolved, parent);
+ // Deduplication:
+ // - Ignore drafts with the same ID as an inputComment here. These are deleted later.
+ // - Swallow comments that already exist.
+ for (Map.Entry<String, List<CommentInput>> entry : inputComments.entrySet()) {
+ String path = entry.getKey();
+ for (CommentInput inputComment : entry.getValue()) {
+ Comment comment = drafts.remove(Url.decode(inputComment.id));
+ if (comment == null) {
+ String parent = Url.decode(inputComment.inReplyTo);
+ comment =
+ commentsUtil.newComment(
+ ctx,
+ path,
+ psId,
+ inputComment.side(),
+ inputComment.message,
+ inputComment.unresolved,
+ parent);
} else {
- e.writtenOn = ctx.getWhen();
- e.side = c.side();
- e.message = c.message;
+ // In ChangeUpdate#putComment() the draft with the same ID will be deleted.
+ comment.writtenOn = ctx.getWhen();
+ comment.side = inputComment.side();
+ comment.message = inputComment.message;
}
- setCommentRevId(e, patchListCache, ctx.getChange(), ps);
- e.setLineNbrAndRange(c.line, c.range);
- e.tag = in.tag;
+ setCommentCommitId(comment, patchListCache, ctx.getChange(), ps);
+ comment.setLineNbrAndRange(inputComment.line, inputComment.range);
+ comment.tag = in.tag;
- if (existingIds.contains(CommentSetEntry.create(e))) {
+ if (existingComments.contains(CommentSetEntry.create(comment))) {
continue;
}
- toPublish.add(e);
+ toPublish.add(comment);
}
}
switch (in.drafts) {
case PUBLISH:
case PUBLISH_ALL_REVISIONS:
+ validateComments(Streams.concat(drafts.values().stream(), toPublish.stream()));
publishCommentUtil.publish(ctx, psId, drafts.values(), in.tag);
comments.addAll(drafts.values());
break;
case KEEP:
default:
+ validateComments(toPublish.stream());
break;
}
- ChangeUpdate u = ctx.getUpdate(psId);
- commentsUtil.putComments(u, Status.PUBLISHED, toPublish);
+ ChangeUpdate changeUpdate = ctx.getUpdate(psId);
+ commentsUtil.putComments(changeUpdate, Comment.Status.PUBLISHED, toPublish);
comments.addAll(toPublish);
return !toPublish.isEmpty();
}
+ private void validateComments(Stream<Comment> comments) throws CommentsRejectedException {
+ ImmutableList<CommentForValidation> draftsForValidation =
+ comments
+ .map(
+ comment ->
+ CommentForValidation.create(
+ comment.lineNbr > 0
+ ? CommentForValidation.CommentType.INLINE_COMMENT
+ : CommentForValidation.CommentType.FILE_COMMENT,
+ comment.message))
+ .collect(toImmutableList());
+ ImmutableList<CommentValidationFailure> draftValidationFailures =
+ PublishCommentUtil.findInvalidComments(commentValidators, draftsForValidation);
+ if (!draftValidationFailures.isEmpty()) {
+ throw new CommentsRejectedException(draftValidationFailures);
+ }
+ }
+
private boolean insertRobotComments(ChangeContext ctx) throws PatchListNotAvailableException {
if (in.robotComments == null) {
return false;
@@ -1003,7 +1051,7 @@ public class PostReview
robotComment.properties = robotCommentInput.properties;
robotComment.setLineNbrAndRange(robotCommentInput.line, robotCommentInput.range);
robotComment.tag = in.tag;
- setCommentRevId(robotComment, patchListCache, ctx.getChange(), ps);
+ setCommentCommitId(robotComment, patchListCache, ctx.getChange(), ps);
robotComment.fixSuggestions = createFixSuggestionsFromInput(robotCommentInput.fixSuggestions);
return robotComment;
}
@@ -1049,27 +1097,25 @@ public class PostReview
}
private Map<String, Comment> changeDrafts(ChangeContext ctx) {
- Map<String, Comment> drafts = new HashMap<>();
- for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getNotes(), user.getAccountId())) {
- c.tag = in.tag;
- drafts.put(c.key.uuid, c);
- }
- return drafts;
+ return commentsUtil.draftByChangeAuthor(ctx.getNotes(), user.getAccountId()).stream()
+ .collect(
+ Collectors.toMap(
+ c -> c.key.uuid,
+ c -> {
+ c.tag = in.tag;
+ return c;
+ }));
}
private Map<String, Comment> patchSetDrafts(ChangeContext ctx) {
- Map<String, Comment> drafts = new HashMap<>();
- for (Comment c :
- commentsUtil.draftByPatchSetAuthor(psId, user.getAccountId(), ctx.getNotes())) {
- drafts.put(c.key.uuid, c);
- }
- return drafts;
+ return commentsUtil.draftByPatchSetAuthor(psId, user.getAccountId(), ctx.getNotes()).stream()
+ .collect(Collectors.toMap(c -> c.key.uuid, c -> c));
}
private Map<String, Short> approvalsByKey(Collection<PatchSetApproval> patchsetApprovals) {
Map<String, Short> labels = new HashMap<>();
for (PatchSetApproval psa : patchsetApprovals) {
- labels.put(psa.getLabel(), psa.getValue());
+ labels.put(psa.label(), psa.value());
}
return labels;
}
@@ -1106,15 +1152,9 @@ public class PostReview
}
private boolean isReviewer(ChangeContext ctx) {
- if (ctx.getAccountId().equals(ctx.getChange().getOwner())) {
- return true;
- }
ChangeData cd = changeDataFactory.create(ctx.getNotes());
ReviewerSet reviewers = cd.reviewers();
- if (reviewers.byState(REVIEWER).contains(ctx.getAccountId())) {
- return true;
- }
- return false;
+ return reviewers.byState(REVIEWER).contains(ctx.getAccountId());
}
private boolean updateLabels(ProjectState projectState, ChangeContext ctx)
@@ -1149,35 +1189,40 @@ public class PostReview
// User requested delete of this label.
oldApprovals.put(normName, null);
if (c != null) {
- if (c.getValue() != 0) {
+ if (c.value() != 0) {
addLabelDelta(normName, (short) 0);
oldApprovals.put(normName, previous.get(normName));
}
del.add(c);
update.putApproval(normName, (short) 0);
}
- } else if (c != null && c.getValue() != ent.getValue()) {
- c.setValue(ent.getValue());
- c.setGranted(ctx.getWhen());
- c.setTag(in.tag);
- ctx.getUser().updateRealAccountId(c::setRealAccountId);
+ } else if (c != null && c.value() != ent.getValue()) {
+ PatchSetApproval.Builder b =
+ c.toBuilder()
+ .value(ent.getValue())
+ .granted(ctx.getWhen())
+ .tag(Optional.ofNullable(in.tag));
+ ctx.getUser().updateRealAccountId(b::realAccountId);
+ c = b.build();
ups.add(c);
- addLabelDelta(normName, c.getValue());
+ addLabelDelta(normName, c.value());
oldApprovals.put(normName, previous.get(normName));
- approvals.put(normName, c.getValue());
+ approvals.put(normName, c.value());
update.putApproval(normName, ent.getValue());
- } else if (c != null && c.getValue() == ent.getValue()) {
+ } else if (c != null && c.value() == ent.getValue()) {
current.put(normName, c);
oldApprovals.put(normName, null);
- approvals.put(normName, c.getValue());
+ approvals.put(normName, c.value());
} else if (c == null) {
- c = ApprovalsUtil.newApproval(psId, user, lt.getLabelId(), ent.getValue(), ctx.getWhen());
- c.setTag(in.tag);
- c.setGranted(ctx.getWhen());
+ c =
+ ApprovalsUtil.newApproval(psId, user, lt.getLabelId(), ent.getValue(), ctx.getWhen())
+ .tag(Optional.ofNullable(in.tag))
+ .granted(ctx.getWhen())
+ .build();
ups.add(c);
- addLabelDelta(normName, c.getValue());
+ addLabelDelta(normName, c.value());
oldApprovals.put(normName, previous.get(normName));
- approvals.put(normName, c.getValue());
+ approvals.put(normName, c.value());
update.putReviewer(user.getAccountId(), REVIEWER);
update.putApproval(normName, ent.getValue());
}
@@ -1219,7 +1264,7 @@ public class PostReview
List<String> disallowed = new ArrayList<>(labelTypes.getLabelTypes().size());
for (PatchSetApproval psa : del) {
- LabelType lt = requireNonNull(labelTypes.byLabel(psa.getLabel()));
+ LabelType lt = requireNonNull(labelTypes.byLabel(psa.label()));
String normName = lt.getName();
if (!lt.allowPostSubmit()) {
disallowed.add(normName);
@@ -1231,7 +1276,7 @@ public class PostReview
}
for (PatchSetApproval psa : ups) {
- LabelType lt = requireNonNull(labelTypes.byLabel(psa.getLabel()));
+ LabelType lt = requireNonNull(labelTypes.byLabel(psa.label()));
String normName = lt.getName();
if (!lt.allowPostSubmit()) {
disallowed.add(normName);
@@ -1240,8 +1285,8 @@ public class PostReview
if (prev == null) {
continue;
}
- checkState(prev != psa.getValue()); // Should be filtered out above.
- if (prev > psa.getValue()) {
+ checkState(prev != psa.value()); // Should be filtered out above.
+ if (prev > psa.value()) {
reduced.add(psa);
}
// No need to set postSubmit bit, which is set automatically when parsing from NoteDb.
@@ -1256,7 +1301,7 @@ public class PostReview
throw new ResourceConflictException(
"Cannot reduce vote on labels for closed change: "
+ reduced.stream()
- .map(PatchSetApproval::getLabel)
+ .map(PatchSetApproval::label)
.distinct()
.sorted()
.collect(joining(", ")));
@@ -1283,18 +1328,16 @@ public class PostReview
}
LabelId labelId = labelTypes.get(0).getLabelId();
- PatchSetApproval c = ApprovalsUtil.newApproval(psId, user, labelId, 0, ctx.getWhen());
- c.setTag(in.tag);
- c.setGranted(ctx.getWhen());
- ups.add(c);
+ ups.add(
+ ApprovalsUtil.newApproval(psId, user, labelId, 0, ctx.getWhen())
+ .tag(Optional.ofNullable(in.tag))
+ .granted(ctx.getWhen())
+ .build());
} else {
// Pick a random label that is about to be deleted and keep it.
Iterator<PatchSetApproval> i = del.iterator();
- PatchSetApproval c = i.next();
- c.setValue((short) 0);
- c.setGranted(ctx.getWhen());
+ ups.add(i.next().toBuilder().value(0).granted(ctx.getWhen()).build());
i.remove();
- ups.add(c);
}
}
ctx.getUpdate(ctx.getChange().currentPatchSetId()).putReviewer(user.getAccountId(), REVIEWER);
@@ -1317,7 +1360,7 @@ public class PostReview
continue;
}
- LabelType lt = labelTypes.byLabel(a.getLabelId());
+ LabelType lt = labelTypes.byLabel(a.labelId());
if (lt != null) {
current.put(lt.getName(), a);
} else {
@@ -1327,7 +1370,7 @@ public class PostReview
return current;
}
- private boolean insertMessage(ChangeContext ctx) {
+ private boolean insertMessage(ChangeContext ctx) throws CommentsRejectedException {
String msg = Strings.nullToEmpty(in.message).trim();
StringBuilder buf = new StringBuilder();
@@ -1340,6 +1383,15 @@ public class PostReview
buf.append(String.format("\n\n(%d comments)", comments.size()));
}
if (!msg.isEmpty()) {
+ ImmutableList<CommentValidationFailure> messageValidationFailure =
+ PublishCommentUtil.findInvalidComments(
+ commentValidators,
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.CHANGE_MESSAGE, msg)));
+ if (!messageValidationFailure.isEmpty()) {
+ throw new CommentsRejectedException(messageValidationFailure);
+ }
buf.append("\n\n").append(msg);
} else if (in.ready) {
buf.append("\n\n" + START_REVIEW_MESSAGE);
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewers.java b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
index 8abd96402f..f74643ca6c 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.ReviewerAdder;
@@ -59,7 +60,7 @@ public class PostReviewers
}
@Override
- protected AddReviewerResult applyImpl(
+ protected Response<AddReviewerResult> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, AddReviewerInput input)
throws IOException, RestApiException, UpdateException, PermissionBackendException,
ConfigInvalidException {
@@ -69,7 +70,7 @@ public class PostReviewers
ReviewerAddition addition = reviewerAdder.prepare(rsrc.getNotes(), rsrc.getUser(), input, true);
if (addition.op == null) {
- return addition.result;
+ return Response.ok(addition.result);
}
try (BatchUpdate bu =
updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
@@ -81,7 +82,7 @@ public class PostReviewers
// Re-read change to take into account results of the update.
addition.gatherResults(changeDataFactory.create(rsrc.getProject(), rsrc.getId()));
- return addition.result;
+ return Response.ok(addition.result);
}
private NotifyResolver.Result resolveNotify(ChangeResource rsrc, AddReviewerInput input)
diff --git a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
index c3cee0e2c4..e6a60d517d 100644
--- a/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
+++ b/java/com/google/gerrit/server/restapi/change/PreviewSubmit.java
@@ -15,17 +15,18 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.PreconditionFailedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ArchiveFormat;
@@ -80,7 +81,7 @@ public class PreviewSubmit implements RestReadView<RevisionResource> {
}
@Override
- public BinaryResult apply(RevisionResource rsrc)
+ public Response<BinaryResult> apply(RevisionResource rsrc)
throws RestApiException, UpdateException, IOException, ConfigInvalidException,
PermissionBackendException {
if (Strings.isNullOrEmpty(format)) {
@@ -105,7 +106,7 @@ public class PreviewSubmit implements RestReadView<RevisionResource> {
throw new MethodNotAllowedException("Anonymous users cannot submit");
}
- return getBundles(rsrc, f);
+ return Response.ok(getBundles(rsrc, f));
}
private BinaryResult getBundles(RevisionResource rsrc, ArchiveFormat f)
diff --git a/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java b/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
index a47037ca78..44f35a0722 100644
--- a/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
+++ b/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
@@ -39,7 +39,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
public class PublishChangeEdit
- extends RetryingRestModifyView<ChangeResource, PublishChangeEditInput, Response<?>> {
+ extends RetryingRestModifyView<ChangeResource, PublishChangeEditInput, Object> {
private final ChangeEditUtil editUtil;
private final NotifyResolver notifyResolver;
private final ContributorAgreementsChecker contributorAgreementsChecker;
@@ -57,7 +57,7 @@ public class PublishChangeEdit
}
@Override
- protected Response<?> applyImpl(
+ protected Response<Object> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, PublishChangeEditInput in)
throws IOException, RestApiException, UpdateException, ConfigInvalidException,
NoSuchProjectException {
diff --git a/java/com/google/gerrit/server/restapi/change/PutAssignee.java b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
index a9a6f12623..d4a14d47d3 100644
--- a/java/com/google/gerrit/server/restapi/change/PutAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
@@ -22,9 +22,12 @@ import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeResource;
@@ -53,6 +56,7 @@ public class PutAssignee extends RetryingRestModifyView<ChangeResource, Assignee
private final ReviewerAdder reviewerAdder;
private final AccountLoader.Factory accountLoaderFactory;
private final PermissionBackend permissionBackend;
+ private final ApprovalsUtil approvalsUtil;
@Inject
PutAssignee(
@@ -61,17 +65,19 @@ public class PutAssignee extends RetryingRestModifyView<ChangeResource, Assignee
RetryHelper retryHelper,
ReviewerAdder reviewerAdder,
AccountLoader.Factory accountLoaderFactory,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ ApprovalsUtil approvalsUtil) {
super(retryHelper);
this.accountResolver = accountResolver;
this.assigneeFactory = assigneeFactory;
this.reviewerAdder = reviewerAdder;
this.accountLoaderFactory = accountLoaderFactory;
this.permissionBackend = permissionBackend;
+ this.approvalsUtil = approvalsUtil;
}
@Override
- protected AccountInfo applyImpl(
+ protected Response<AccountInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, AssigneeInput input)
throws RestApiException, UpdateException, IOException, PermissionBackendException,
ConfigInvalidException {
@@ -97,12 +103,15 @@ public class PutAssignee extends RetryingRestModifyView<ChangeResource, Assignee
SetAssigneeOp op = assigneeFactory.create(assignee);
bu.addOp(rsrc.getId(), op);
- ReviewerAddition reviewersAddition = addAssigneeAsCC(rsrc, input.assignee);
- reviewersAddition.op.suppressEmail();
- bu.addOp(rsrc.getId(), reviewersAddition.op);
+ ReviewerSet currentReviewers = approvalsUtil.getReviewers(rsrc.getNotes());
+ if (!currentReviewers.all().contains(assignee.getAccountId())) {
+ ReviewerAddition reviewersAddition = addAssigneeAsCC(rsrc, input.assignee);
+ reviewersAddition.op.suppressEmail();
+ bu.addOp(rsrc.getId(), reviewersAddition.op);
+ }
bu.execute();
- return accountLoaderFactory.create(true).fillOne(assignee.getAccountId());
+ return Response.ok(accountLoaderFactory.create(true).fillOne(assignee.getAccountId()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/PutDescription.java b/java/com/google/gerrit/server/restapi/change/PutDescription.java
index 0ec38e15f0..451d010a6b 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDescription.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RevisionResource;
@@ -39,7 +39,7 @@ import com.google.inject.Singleton;
@Singleton
public class PutDescription
- extends RetryingRestModifyView<RevisionResource, DescriptionInput, Response<String>>
+ extends RetryingRestModifyView<RevisionResource, DescriptionInput, String>
implements UiAction<RevisionResource> {
private final ChangeMessagesUtil cmUtil;
private final PatchSetUtil psUtil;
@@ -57,7 +57,7 @@ public class PutDescription
throws UpdateException, RestApiException, PermissionBackendException {
rsrc.permissions().check(ChangePermission.EDIT_DESCRIPTION);
- Op op = new Op(input != null ? input : new DescriptionInput(), rsrc.getPatchSet().getId());
+ Op op = new Op(input != null ? input : new DescriptionInput(), rsrc.getPatchSet().id());
try (BatchUpdate u =
updateFactory.create(rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
u.addOp(rsrc.getChange().getId(), op);
@@ -84,7 +84,7 @@ public class PutDescription
public boolean updateChange(ChangeContext ctx) {
ChangeUpdate update = ctx.getUpdate(psId);
newDescription = Strings.nullToEmpty(input.description);
- oldDescription = Strings.nullToEmpty(psUtil.get(ctx.getNotes(), psId).getDescription());
+ oldDescription = psUtil.get(ctx.getNotes(), psId).description().orElse("");
if (oldDescription.equals(newDescription)) {
return false;
}
diff --git a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
index f6c9abe65d..5696fcbf6e 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
@@ -14,8 +14,10 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
+import static com.google.gerrit.server.CommentsUtil.setCommentCommitId;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -23,9 +25,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.DraftCommentResource;
@@ -49,7 +48,7 @@ import java.util.Optional;
@Singleton
public class PutDraftComment
- extends RetryingRestModifyView<DraftCommentResource, DraftInput, Response<CommentInfo>> {
+ extends RetryingRestModifyView<DraftCommentResource, DraftInput, CommentInfo> {
private final DeleteDraftComment delete;
private final CommentsUtil commentsUtil;
@@ -124,7 +123,7 @@ public class PutDraftComment
// user.
ctx.getUser().updateRealAccountId(comment::setRealAuthor);
- PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), origComment.key.patchSetId);
+ PatchSet.Id psId = PatchSet.id(ctx.getChange().getId(), origComment.key.patchSetId);
ChangeUpdate update = ctx.getUpdate(psId);
PatchSet ps = psUtil.get(ctx.getNotes(), psId);
@@ -138,9 +137,9 @@ public class PutDraftComment
commentsUtil.deleteComments(update, Collections.singleton(origComment));
comment.key.filename = in.path;
}
- setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
+ setCommentCommitId(comment, patchListCache, ctx.getChange(), ps);
commentsUtil.putComments(
- update, Status.DRAFT, Collections.singleton(update(comment, in, ctx.getWhen())));
+ update, Comment.Status.DRAFT, Collections.singleton(update(comment, in, ctx.getWhen())));
return true;
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index 3c7259d467..cb73747822 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.CommitMessageInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -21,8 +23,6 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -58,8 +58,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@Singleton
-public class PutMessage
- extends RetryingRestModifyView<ChangeResource, CommitMessageInput, Response<?>> {
+public class PutMessage extends RetryingRestModifyView<ChangeResource, CommitMessageInput, String> {
private final GitRepositoryManager repositoryManager;
private final Provider<CurrentUser> userProvider;
@@ -116,7 +115,7 @@ public class PutMessage
try (Repository repository = repositoryManager.openRepository(resource.getProject());
RevWalk revWalk = new RevWalk(repository);
ObjectInserter objectInserter = repository.newObjectInserter()) {
- RevCommit patchSetCommit = revWalk.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ RevCommit patchSetCommit = revWalk.parseCommit(ps.commitId());
String currentCommitMessage = patchSetCommit.getFullMessage();
if (input.message.equals(currentCommitMessage)) {
@@ -129,7 +128,7 @@ public class PutMessage
// Ensure that BatchUpdate will update the same repo
bu.setRepository(repository, new RevWalk(objectInserter.newReader()), objectInserter);
- PatchSet.Id psId = ChangeUtil.nextPatchSetId(repository, ps.getId());
+ PatchSet.Id psId = ChangeUtil.nextPatchSetId(repository, ps.id());
ObjectId newCommit =
createCommit(objectInserter, patchSetCommit, sanitizedCommitMessage, ts);
PatchSetInserter inserter = psInserterFactory.create(resource.getNotes(), psId, newCommit);
diff --git a/java/com/google/gerrit/server/restapi/change/PutTopic.java b/java/com/google/gerrit/server/restapi/change/PutTopic.java
index abfa49ba92..cfeb8845ff 100644
--- a/java/com/google/gerrit/server/restapi/change/PutTopic.java
+++ b/java/com/google/gerrit/server/restapi/change/PutTopic.java
@@ -15,13 +15,13 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.extensions.api.changes.TopicInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.ChangeResource;
@@ -41,7 +41,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-public class PutTopic extends RetryingRestModifyView<ChangeResource, TopicInput, Response<String>>
+public class PutTopic extends RetryingRestModifyView<ChangeResource, TopicInput, String>
implements UiAction<ChangeResource> {
private final ChangeMessagesUtil cmUtil;
private final TopicEdited topicEdited;
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index e1ed8d64fa..bf4d197147 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -21,18 +21,23 @@ import com.google.gerrit.extensions.client.ListOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryRequiresAuthException;
import com.google.gerrit.index.query.QueryResult;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.DynamicOptions;
import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
@@ -45,6 +50,8 @@ public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOpti
private final ChangeJson.Factory json;
private final ChangeQueryBuilder qb;
private final ChangeQueryProcessor imp;
+ private final Provider<CurrentUser> userProvider;
+ private final PermissionBackend permissionBackend;
private EnumSet<ListChangesOption> options;
@Option(
@@ -87,16 +94,32 @@ public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOpti
imp.setNoLimit(on);
}
+ @Option(name = "--skip-visibility", usage = "Skip visibility check, only for administrators")
+ public void skipVisibility(boolean on) throws AuthException, PermissionBackendException {
+ if (on) {
+ CurrentUser user = userProvider.get();
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ imp.enforceVisibility(false);
+ }
+ }
+
@Override
public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
imp.setDynamicBean(plugin, dynamicBean);
}
@Inject
- QueryChanges(ChangeJson.Factory json, ChangeQueryBuilder qb, ChangeQueryProcessor qp) {
+ QueryChanges(
+ ChangeJson.Factory json,
+ ChangeQueryBuilder qb,
+ ChangeQueryProcessor qp,
+ Provider<CurrentUser> userProvider,
+ PermissionBackend permissionBackend) {
this.json = json;
this.qb = qb;
this.imp = qp;
+ this.userProvider = userProvider;
+ this.permissionBackend = permissionBackend;
options = EnumSet.noneOf(ListChangesOption.class);
}
@@ -113,7 +136,7 @@ public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOpti
}
@Override
- public List<?> apply(TopLevelResource rsrc)
+ public Response<List<?>> apply(TopLevelResource rsrc)
throws BadRequestException, AuthException, PermissionBackendException {
List<List<ChangeInfo>> out;
try {
@@ -124,7 +147,7 @@ public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOpti
logger.atFine().withCause(e).log("Reject change query with 400 Bad Request: %s", queries);
throw new BadRequestException(e.getMessage(), e);
}
- return out.size() == 1 ? out.get(0) : out;
+ return Response.ok(out.size() == 1 ? out.get(0) : out);
}
private List<List<ChangeInfo>> query() throws QueryParseException, PermissionBackendException {
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 782b91aa08..af8f971b0d 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -17,19 +17,20 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeJson;
@@ -98,7 +99,7 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
}
@Override
- protected ChangeInfo applyImpl(
+ protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, RebaseInput input)
throws UpdateException, RestApiException, IOException, PermissionBackendException {
// Not allowed to rebase if the current patch set is locked.
@@ -131,14 +132,14 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
.setFireRevisionCreated(true));
bu.execute();
}
- return json.create(OPTIONS).format(change.getProject(), change.getId());
+ return Response.ok(json.create(OPTIONS).format(change.getProject(), change.getId()));
}
private ObjectId findBaseRev(
Repository repo, RevWalk rw, RevisionResource rsrc, RebaseInput input)
throws RestApiException, IOException, NoSuchChangeException, AuthException,
PermissionBackendException {
- Branch.NameKey destRefKey = rsrc.getChange().getDest();
+ BranchNameKey destRefKey = rsrc.getChange().getDest();
if (input == null || input.base == null) {
return rebaseUtil.findBaseRevision(rsrc.getPatchSet(), destRefKey, repo, rw);
}
@@ -147,10 +148,10 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
String str = input.base.trim();
if (str.equals("")) {
// Remove existing dependency to other patch set.
- Ref destRef = repo.exactRef(destRefKey.get());
+ Ref destRef = repo.exactRef(destRefKey.branch());
if (destRef == null) {
throw new ResourceConflictException(
- "can't rebase onto tip of branch " + destRefKey.get() + "; branch doesn't exist");
+ "can't rebase onto tip of branch " + destRefKey.branch() + "; branch doesn't exist");
}
return destRef.getObjectId();
}
@@ -167,8 +168,8 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
String.format("Base change not found: %s", input.base), e);
}
- PatchSet.Id baseId = base.patchSet().getId();
- if (change.getId().equals(baseId.getParentKey())) {
+ PatchSet.Id baseId = base.patchSet().id();
+ if (change.getId().equals(baseId.changeId())) {
throw new ResourceConflictException("cannot rebase change onto itself");
}
@@ -189,18 +190,18 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
+ baseChange.getKey()
+ " is a descendant of the current change - recursion not allowed");
}
- return ObjectId.fromString(base.patchSet().getRevision().get());
+ return base.patchSet().commitId();
}
private boolean isMergedInto(RevWalk rw, PatchSet base, PatchSet tip) throws IOException {
- ObjectId baseId = ObjectId.fromString(base.getRevision().get());
- ObjectId tipId = ObjectId.fromString(tip.getRevision().get());
+ ObjectId baseId = base.commitId();
+ ObjectId tipId = tip.commitId();
return rw.isMergedInto(rw.parseCommit(baseId), rw.parseCommit(tipId));
}
private boolean hasOneParent(RevWalk rw, PatchSet ps) throws IOException {
// Prevent rebase of exotic changes (merge commit, no ancestor).
- RevCommit c = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ RevCommit c = rw.parseCommit(ps.commitId());
return c.getParentCount() == 1;
}
@@ -238,7 +239,7 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
}
boolean enabled = false;
- try (Repository repo = repoManager.openRepository(change.getDest().getParentKey());
+ try (Repository repo = repoManager.openRepository(change.getDest().project());
RevWalk rw = new RevWalk(repo)) {
if (hasOneParent(rw, rsrc.getPatchSet())) {
enabled = rebaseUtil.canRebase(rsrc.getPatchSet(), change.getDest(), repo, rw);
@@ -272,14 +273,15 @@ public class Rebase extends RetryingRestModifyView<RevisionResource, RebaseInput
}
@Override
- protected ChangeInfo applyImpl(
+ protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, RebaseInput input)
- throws UpdateException, RestApiException, IOException, PermissionBackendException {
+ throws Exception {
PatchSet ps = psUtil.current(rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
}
- return rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input);
+ return Response.ok(
+ rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input).value());
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java b/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
index 81294edf5c..7be8765b90 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -33,7 +33,7 @@ import java.io.IOException;
import org.eclipse.jgit.lib.Repository;
@Singleton
-public class RebaseChangeEdit extends RetryingRestModifyView<ChangeResource, Input, Response<?>> {
+public class RebaseChangeEdit extends RetryingRestModifyView<ChangeResource, Input, Object> {
private final GitRepositoryManager repositoryManager;
private final ChangeEditModifier editModifier;
@@ -48,7 +48,8 @@ public class RebaseChangeEdit extends RetryingRestModifyView<ChangeResource, Inp
}
@Override
- protected Response<?> applyImpl(BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input in)
+ protected Response<Object> applyImpl(
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input in)
throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
Project.NameKey project = rsrc.getProject();
try (Repository repository = repositoryManager.openRepository(project)) {
diff --git a/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java b/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
index 1421ab6774..8040847e72 100644
--- a/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
+++ b/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
@@ -24,10 +24,10 @@ import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -74,8 +74,8 @@ class RelatedChangesSorter {
throws IOException, PermissionBackendException {
checkArgument(!in.isEmpty(), "Input may not be empty");
// Map of all patch sets, keyed by commit SHA-1.
- Map<String, PatchSetData> byId = collectById(in);
- PatchSetData start = byId.get(startPs.getRevision().get());
+ Map<ObjectId, PatchSetData> byId = collectById(in);
+ PatchSetData start = byId.get(startPs.commitId());
checkArgument(start != null, "%s not found in %s", startPs, in);
// Map of patch set -> immediate parent.
@@ -89,12 +89,12 @@ class RelatedChangesSorter {
for (ChangeData cd : in) {
for (PatchSet ps : cd.patchSets()) {
- PatchSetData thisPsd = requireNonNull(byId.get(ps.getRevision().get()));
- if (cd.getId().equals(start.id()) && !ps.getId().equals(start.psId())) {
+ PatchSetData thisPsd = requireNonNull(byId.get(ps.commitId()));
+ if (cd.getId().equals(start.id()) && !ps.id().equals(start.psId())) {
otherPatchSetsOfStart.add(thisPsd);
}
for (RevCommit p : thisPsd.commit().getParents()) {
- PatchSetData parentPsd = byId.get(p.name());
+ PatchSetData parentPsd = byId.get(p);
if (parentPsd != null) {
parents.put(thisPsd, parentPsd);
children.put(parentPsd, thisPsd);
@@ -112,9 +112,9 @@ class RelatedChangesSorter {
return result;
}
- private Map<String, PatchSetData> collectById(List<ChangeData> in) throws IOException {
+ private Map<ObjectId, PatchSetData> collectById(List<ChangeData> in) throws IOException {
Project.NameKey project = in.get(0).change().getProject();
- Map<String, PatchSetData> result = Maps.newHashMapWithExpectedSize(in.size() * 3);
+ Map<ObjectId, PatchSetData> result = Maps.newHashMapWithExpectedSize(in.size() * 3);
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
rw.setRetainBody(true);
@@ -126,10 +126,9 @@ class RelatedChangesSorter {
project,
cd.change().getProject());
for (PatchSet ps : cd.patchSets()) {
- String id = ps.getRevision().get();
- RevCommit c = rw.parseCommit(ObjectId.fromString(id));
+ RevCommit c = rw.parseCommit(ps.commitId());
PatchSetData psd = PatchSetData.create(cd, ps, c);
- result.put(id, psd);
+ result.put(ps.commitId(), psd);
}
}
}
@@ -252,16 +251,16 @@ class RelatedChangesSorter {
abstract RevCommit commit();
PatchSet.Id psId() {
- return patchSet().getId();
+ return patchSet().id();
}
Change.Id id() {
- return psId().getParentKey();
+ return psId().changeId();
}
@Override
public final int hashCode() {
- return Objects.hash(patchSet().getId(), commit());
+ return Objects.hash(patchSet().id(), commit());
}
@Override
@@ -270,7 +269,7 @@ class RelatedChangesSorter {
return false;
}
PatchSetData o = (PatchSetData) obj;
- return Objects.equals(patchSet().getId(), o.patchSet().getId())
+ return Objects.equals(patchSet().id(), o.patchSet().id())
&& Objects.equals(commit(), o.commit());
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index 5f56cdb1d4..679d4f840c 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -16,16 +16,17 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Change.Status;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.RestoreInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
@@ -81,7 +82,7 @@ public class Restore extends RetryingRestModifyView<ChangeResource, RestoreInput
}
@Override
- protected ChangeInfo applyImpl(
+ protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, RestoreInput input)
throws RestApiException, UpdateException, PermissionBackendException, IOException {
// Not allowed to restore if the current patch set is locked.
@@ -95,7 +96,7 @@ public class Restore extends RetryingRestModifyView<ChangeResource, RestoreInput
updateFactory.create(rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
u.addOp(rsrc.getId(), op).execute();
}
- return json.noOptions().format(op.change);
+ return Response.ok(json.noOptions().format(op.change));
}
private class Op implements BatchUpdateOp {
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index dd51e7f28b..e196abced6 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -18,33 +18,32 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
-import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeJson;
-import com.google.gerrit.server.change.ChangeMessages;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.extensions.events.ChangeReverted;
+import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mail.send.RevertedSender;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -66,24 +65,19 @@ import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.CommitMessageUtil;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.sql.Timestamp;
-import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.ChangeIdUtil;
@Singleton
public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput, ChangeInfo>
@@ -98,12 +92,12 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
private final PatchSetUtil psUtil;
private final RevertedSender.Factory revertedSenderFactory;
private final ChangeJson.Factory json;
- private final Provider<PersonIdent> serverIdent;
private final ApprovalsUtil approvalsUtil;
private final ChangeReverted changeReverted;
private final ContributorAgreementsChecker contributorAgreements;
private final ProjectCache projectCache;
private final NotifyResolver notifyResolver;
+ private final CommitUtil commitUtil;
@Inject
Revert(
@@ -116,12 +110,12 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
PatchSetUtil psUtil,
RevertedSender.Factory revertedSenderFactory,
ChangeJson.Factory json,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
ApprovalsUtil approvalsUtil,
ChangeReverted changeReverted,
ContributorAgreementsChecker contributorAgreements,
ProjectCache projectCache,
- NotifyResolver notifyResolver) {
+ NotifyResolver notifyResolver,
+ CommitUtil commitUtil) {
super(retryHelper);
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
@@ -131,16 +125,16 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
this.psUtil = psUtil;
this.revertedSenderFactory = revertedSenderFactory;
this.json = json;
- this.serverIdent = serverIdent;
this.approvalsUtil = approvalsUtil;
this.changeReverted = changeReverted;
this.contributorAgreements = contributorAgreements;
this.projectCache = projectCache;
this.notifyResolver = notifyResolver;
+ this.commitUtil = commitUtil;
}
@Override
- public ChangeInfo applyImpl(
+ public Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, RevertInput input)
throws IOException, RestApiException, UpdateException, NoSuchChangeException,
PermissionBackendException, NoSuchProjectException, ConfigInvalidException {
@@ -154,13 +148,12 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
Change.Id revertId = revert(updateFactory, rsrc.getNotes(), rsrc.getUser(), input);
- return json.noOptions().format(rsrc.getProject(), revertId);
+ return Response.ok(json.noOptions().format(rsrc.getProject(), revertId));
}
private Change.Id revert(
BatchUpdate.Factory updateFactory, ChangeNotes notes, CurrentUser user, RevertInput input)
throws IOException, RestApiException, UpdateException, ConfigInvalidException {
- String message = Strings.emptyToNull(input.message);
Change.Id changeIdToRevert = notes.getChangeId();
PatchSet.Id patchSetId = notes.getChange().currentPatchSetId();
PatchSet patch = psUtil.get(notes, patchSetId);
@@ -173,50 +166,25 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
RevWalk revWalk = new RevWalk(reader)) {
- RevCommit commitToRevert =
- revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
- if (commitToRevert.getParentCount() == 0) {
- throw new ResourceConflictException("Cannot revert initial commit");
- }
Timestamp now = TimeUtil.nowTs();
- PersonIdent committerIdent = serverIdent.get();
- PersonIdent authorIdent =
- user.asIdentifiedUser().newCommitterIdent(now, committerIdent.getTimeZone());
-
- RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
- revWalk.parseHeaders(parentToCommitToRevert);
-
- CommitBuilder revertCommitBuilder = new CommitBuilder();
- revertCommitBuilder.addParentId(commitToRevert);
- revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
- revertCommitBuilder.setAuthor(authorIdent);
- revertCommitBuilder.setCommitter(authorIdent);
-
- Change changeToRevert = notes.getChange();
- if (message == null) {
- message =
- MessageFormat.format(
- ChangeMessages.get().revertChangeDefaultMessage,
- changeToRevert.getSubject(),
- patch.getRevision().get());
- }
-
ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
- revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, generatedChangeId, true));
+ Change changeToRevert = notes.getChange();
+ ObjectId revertCommitId =
+ commitUtil.createRevertCommit(
+ input.message, notes, user, generatedChangeId, now, oi, revWalk);
- Change.Id changeId = new Change.Id(seq.nextChangeId());
- ObjectId id = oi.insert(revertCommitBuilder);
- RevCommit revertCommit = revWalk.parseCommit(id);
+ RevCommit revertCommit = revWalk.parseCommit(revertCommitId);
+ Change.Id changeId = Change.id(seq.nextChangeId());
NotifyResolver.Result notify =
notifyResolver.resolve(
firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails);
ChangeInserter ins =
changeInserterFactory
- .create(changeId, revertCommit, notes.getChange().getDest().get())
- .setTopic(changeToRevert.getTopic());
+ .create(changeId, revertCommit, notes.getChange().getDest().branch())
+ .setTopic(input.topic == null ? changeToRevert.getTopic() : input.topic.trim());
ins.setMessage("Uploaded patch set 1.");
ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes);
diff --git a/java/com/google/gerrit/server/restapi/change/Reviewed.java b/java/com/google/gerrit/server/restapi/change/Reviewed.java
index 4594503520..7152799f61 100644
--- a/java/com/google/gerrit/server/restapi/change/Reviewed.java
+++ b/java/com/google/gerrit/server/restapi/change/Reviewed.java
@@ -40,9 +40,9 @@ public class Reviewed {
accountPatchReviewStore.call(
s ->
s.markReviewed(
- resource.getPatchKey().getParentKey(),
+ resource.getPatchKey().patchSetId(),
resource.getAccountId(),
- resource.getPatchKey().getFileName()));
+ resource.getPatchKey().fileName()));
return reviewFlagUpdated ? Response.created("") : Response.ok("");
}
}
@@ -61,9 +61,9 @@ public class Reviewed {
accountPatchReviewStore.run(
s ->
s.clearReviewed(
- resource.getPatchKey().getParentKey(),
+ resource.getPatchKey().patchSetId(),
resource.getAccountId(),
- resource.getPatchKey().getFileName()));
+ resource.getPatchKey().fileName()));
return Response.none();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
index 92f7185696..f07d8153a3 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Strings;
@@ -22,18 +21,20 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.FanOutExecutor;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.change.ReviewerSuggestion;
import com.google.gerrit.server.change.SuggestedReviewer;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.plugincontext.PluginMapContext;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
@@ -44,11 +45,11 @@ import com.google.inject.Provider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -63,13 +64,6 @@ import org.eclipse.jgit.lib.Config;
public class ReviewerRecommender {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private static final double BASE_REVIEWER_WEIGHT = 10;
- private static final double BASE_OWNER_WEIGHT = 1;
- private static final double BASE_COMMENT_WEIGHT = 0.5;
- private static final double[] WEIGHTS =
- new double[] {
- BASE_REVIEWER_WEIGHT, BASE_OWNER_WEIGHT, BASE_COMMENT_WEIGHT,
- };
private static final long PLUGIN_QUERY_TIMEOUT = 500; // ms
private final ChangeQueryBuilder changeQueryBuilder;
@@ -78,6 +72,7 @@ public class ReviewerRecommender {
private final Provider<InternalChangeQuery> queryProvider;
private final ExecutorService executor;
private final ApprovalsUtil approvalsUtil;
+ private final AccountCache accountCache;
@Inject
ReviewerRecommender(
@@ -86,16 +81,19 @@ public class ReviewerRecommender {
Provider<InternalChangeQuery> queryProvider,
@FanOutExecutor ExecutorService executor,
ApprovalsUtil approvalsUtil,
- @GerritServerConfig Config config) {
+ @GerritServerConfig Config config,
+ AccountCache accountCache) {
this.changeQueryBuilder = changeQueryBuilder;
this.config = config;
this.queryProvider = queryProvider;
this.reviewerSuggestionPluginMap = reviewerSuggestionPluginMap;
this.executor = executor;
this.approvalsUtil = approvalsUtil;
+ this.accountCache = accountCache;
}
public List<Account.Id> suggestReviewers(
+ ReviewerState reviewerState,
@Nullable ChangeNotes changeNotes,
SuggestReviewers suggestReviewers,
ProjectState projectState,
@@ -109,12 +107,7 @@ public class ReviewerRecommender {
double baseWeight = config.getInt("addReviewer", "baseWeight", 1);
logger.atFine().log("base weight: %s", baseWeight);
- Map<Account.Id, MutableDouble> reviewerScores;
- if (Strings.isNullOrEmpty(query)) {
- reviewerScores = baseRankingForEmptyQuery(baseWeight);
- } else {
- reviewerScores = baseRankingForCandidateList(candidateList, projectState, baseWeight);
- }
+ Map<Account.Id, MutableDouble> reviewerScores = baseRanking(baseWeight, query, candidateList);
logger.atFine().log("Base reviewer scores: %s", reviewerScores);
// Send the query along with a candidate list to all plugins and merge the
@@ -178,7 +171,7 @@ public class ReviewerRecommender {
// Remove existing reviewers
approvalsUtil
.getReviewers(changeNotes)
- .byState(REVIEWER)
+ .byState(ReviewerStateInternal.fromReviewerState(reviewerState))
.forEach(
r -> {
if (reviewerScores.remove(r) != null) {
@@ -196,7 +189,18 @@ public class ReviewerRecommender {
return sortedSuggestions;
}
- private Map<Account.Id, MutableDouble> baseRankingForEmptyQuery(double baseWeight)
+ /**
+ * @param baseWeight The weight applied to the ordering of the reviewers.
+ * @param query Query to match. For example, it can try to match all users that start with "Ab".
+ * @param candidateList The list of candidates based on the query. If query is empty, this list is
+ * also empty.
+ * @return Map of account ids that match the query and their appropriate ranking (the better the
+ * ranking, the better it is to suggest them as reviewers).
+ * @throws IOException Can't find owner="self" account.
+ * @throws ConfigInvalidException Can't find owner="self" account.
+ */
+ private Map<Account.Id, MutableDouble> baseRanking(
+ double baseWeight, String query, List<Account.Id> candidateList)
throws IOException, ConfigInvalidException {
// Get the user's last 25 changes, check approvals
try {
@@ -206,14 +210,15 @@ public class ReviewerRecommender {
.setLimit(25)
.setRequestedFields(ChangeField.APPROVAL)
.query(changeQueryBuilder.owner("self"));
- Map<Account.Id, MutableDouble> suggestions = new HashMap<>();
+ Map<Account.Id, MutableDouble> suggestions = new LinkedHashMap<>();
+ // Put those candidates at the bottom of the list
+ candidateList.stream().forEach(id -> suggestions.put(id, new MutableDouble(0)));
+
for (ChangeData cd : result) {
for (PatchSetApproval approval : cd.currentApprovals()) {
- Account.Id id = approval.getAccountId();
- if (suggestions.containsKey(id)) {
- suggestions.get(id).add(baseWeight);
- } else {
- suggestions.put(id, new MutableDouble(baseWeight));
+ Account.Id id = approval.accountId();
+ if (Strings.isNullOrEmpty(query) || accountMatchesQuery(id, query)) {
+ suggestions.computeIfAbsent(id, (ignored) -> new MutableDouble(0)).add(baseWeight);
}
}
}
@@ -225,63 +230,15 @@ public class ReviewerRecommender {
}
}
- private Map<Account.Id, MutableDouble> baseRankingForCandidateList(
- List<Account.Id> candidates, ProjectState projectState, double baseWeight)
- throws IOException, ConfigInvalidException {
- // Get each reviewer's activity based on number of applied labels
- // (weighted 10d), number of comments (weighted 0.5d) and number of owned
- // changes (weighted 1d).
- Map<Account.Id, MutableDouble> reviewers = new LinkedHashMap<>();
- if (candidates.isEmpty()) {
- return reviewers;
- }
- List<Predicate<ChangeData>> predicates = new ArrayList<>();
- for (Account.Id id : candidates) {
- try {
- Predicate<ChangeData> projectQuery = changeQueryBuilder.project(projectState.getName());
-
- // Get all labels for this project and create a compound OR query to
- // fetch all changes where users have applied one of these labels
- List<LabelType> labelTypes = projectState.getLabelTypes().getLabelTypes();
- List<Predicate<ChangeData>> labelPredicates = new ArrayList<>(labelTypes.size());
- for (LabelType type : labelTypes) {
- labelPredicates.add(changeQueryBuilder.label(type.getName() + ",user=" + id));
- }
- Predicate<ChangeData> reviewerQuery =
- Predicate.and(projectQuery, Predicate.or(labelPredicates));
-
- Predicate<ChangeData> ownerQuery =
- Predicate.and(projectQuery, changeQueryBuilder.owner(id.toString()));
- Predicate<ChangeData> commentedByQuery =
- Predicate.and(projectQuery, changeQueryBuilder.commentby(id.toString()));
-
- predicates.add(reviewerQuery);
- predicates.add(ownerQuery);
- predicates.add(commentedByQuery);
- reviewers.put(id, new MutableDouble());
- } catch (QueryParseException e) {
- // Unhandled: If an exception is thrown, we won't increase the
- // candidates's score
- logger.atSevere().withCause(e).log("Exception while suggesting reviewers");
+ private boolean accountMatchesQuery(Account.Id id, String query) {
+ Optional<Account> account = accountCache.get(id).map(AccountState::account);
+ if (account.isPresent() && account.get().isActive()) {
+ if ((account.get().fullName() != null && account.get().fullName().startsWith(query))
+ || (account.get().preferredEmail() != null
+ && account.get().preferredEmail().startsWith(query))) {
+ return true;
}
}
-
- List<List<ChangeData>> result = queryProvider.get().setLimit(25).noFields().query(predicates);
-
- Iterator<List<ChangeData>> queryResultIterator = result.iterator();
- Iterator<Account.Id> reviewersIterator = reviewers.keySet().iterator();
-
- int i = 0;
- Account.Id currentId = null;
- while (queryResultIterator.hasNext()) {
- List<ChangeData> currentResult = queryResultIterator.next();
- if (i % WEIGHTS.length == 0) {
- currentId = reviewersIterator.next();
- }
-
- reviewers.get(currentId).add(WEIGHTS[i % WEIGHTS.length] * baseWeight * currentResult.size());
- i++;
- }
- return reviewers;
+ return false;
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Reviewers.java b/java/com/google/gerrit/server/restapi/change/Reviewers.java
index 546ca0168d..b2714da7c9 100644
--- a/java/com/google/gerrit/server/restapi/change/Reviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/Reviewers.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -22,7 +23,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ReviewerResource;
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
index f5907e76e6..676cc07c1f 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
@@ -24,21 +24,26 @@ import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.GroupBaseInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
+import com.google.gerrit.index.query.TooManyTermsInQueryException;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
@@ -49,6 +54,7 @@ import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.change.ReviewerAdder;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndexCollection;
+import com.google.gerrit.server.index.account.AccountIndexRewriter;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -113,12 +119,9 @@ public class ReviewersUtil {
}
}
- // Generate a candidate list at 3x the size of what the user wants to see to
- // give the ranking algorithm a good set of candidates it can work with
- private static final int CANDIDATE_LIST_MULTIPLIER = 3;
-
private final AccountLoader.Factory accountLoaderFactory;
private final AccountQueryBuilder accountQueryBuilder;
+ private final AccountIndexRewriter accountIndexRewriter;
private final GroupBackend groupBackend;
private final GroupMembers groupMembers;
private final ReviewerRecommender reviewerRecommender;
@@ -132,6 +135,7 @@ public class ReviewersUtil {
ReviewersUtil(
AccountLoader.Factory accountLoaderFactory,
AccountQueryBuilder accountQueryBuilder,
+ AccountIndexRewriter accountIndexRewriter,
GroupBackend groupBackend,
GroupMembers groupMembers,
ReviewerRecommender reviewerRecommender,
@@ -142,6 +146,7 @@ public class ReviewersUtil {
Provider<CurrentUser> self) {
this.accountLoaderFactory = accountLoaderFactory;
this.accountQueryBuilder = accountQueryBuilder;
+ this.accountIndexRewriter = accountIndexRewriter;
this.groupBackend = groupBackend;
this.groupMembers = groupMembers;
this.reviewerRecommender = reviewerRecommender;
@@ -157,12 +162,13 @@ public class ReviewersUtil {
}
public List<SuggestedReviewerInfo> suggestReviewers(
+ ReviewerState reviewerState,
@Nullable ChangeNotes changeNotes,
SuggestReviewers suggestReviewers,
ProjectState projectState,
VisibilityControl visibilityControl,
boolean excludeGroups)
- throws IOException, ConfigInvalidException, PermissionBackendException {
+ throws IOException, ConfigInvalidException, PermissionBackendException, BadRequestException {
CurrentUser currentUser = self.get();
if (changeNotes != null) {
logger.atFine().log(
@@ -190,7 +196,8 @@ public class ReviewersUtil {
}
List<Account.Id> sortedRecommendations =
- recommendAccounts(changeNotes, suggestReviewers, projectState, candidateList);
+ recommendAccounts(
+ reviewerState, changeNotes, suggestReviewers, projectState, candidateList);
logger.atFine().log("Sorted recommendations: %s", sortedRecommendations);
// Filter accounts by visibility and enforce limit
@@ -221,37 +228,59 @@ public class ReviewersUtil {
return suggestedReviewers;
}
- private List<Account.Id> suggestAccounts(SuggestReviewers suggestReviewers) {
+ private static Account.Id fromIdField(FieldBundle f, boolean useLegacyNumericFields) {
+ if (useLegacyNumericFields) {
+ return Account.id(f.getValue(AccountField.ID).intValue());
+ }
+ return Account.id(Integer.valueOf(f.getValue(AccountField.ID_STR)));
+ }
+
+ private List<Account.Id> suggestAccounts(SuggestReviewers suggestReviewers)
+ throws BadRequestException {
try (Timer0.Context ctx = metrics.queryAccountsLatency.start()) {
- try {
- // For performance reasons we don't use AccountQueryProvider as it would always load the
- // complete account from the cache (or worse, from NoteDb) even though we only need the ID
- // which we can directly get from the returned results.
- Predicate<AccountState> pred =
- Predicate.and(
- AccountPredicates.isActive(),
- accountQueryBuilder.defaultQuery(suggestReviewers.getQuery()));
- logger.atFine().log("accounts index query: %s", pred);
- ResultSet<FieldBundle> result =
- accountIndexes
- .getSearchIndex()
- .getSource(
- pred,
- QueryOptions.create(
- indexConfig,
- 0,
- suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER,
- ImmutableSet.of(AccountField.ID.getName())))
- .readRaw();
- List<Account.Id> matches =
- result.toList().stream()
- .map(f -> new Account.Id(f.getValue(AccountField.ID).intValue()))
- .collect(toList());
- logger.atFine().log("Matches: %s", matches);
- return matches;
- } catch (QueryParseException e) {
+ // For performance reasons we don't use AccountQueryProvider as it would always load the
+ // complete account from the cache (or worse, from NoteDb) even though we only need the ID
+ // which we can directly get from the returned results.
+ Predicate<AccountState> pred =
+ Predicate.and(
+ AccountPredicates.isActive(),
+ accountQueryBuilder.defaultQuery(suggestReviewers.getQuery()));
+ logger.atFine().log("accounts index query: %s", pred);
+ accountIndexRewriter.validateMaxTermsInQuery(pred);
+ boolean useLegacyNumericFields =
+ accountIndexes.getSearchIndex().getSchema().useLegacyNumericFields();
+ FieldDef<AccountState, ?> idField =
+ useLegacyNumericFields ? AccountField.ID : AccountField.ID_STR;
+ ResultSet<FieldBundle> result =
+ accountIndexes
+ .getSearchIndex()
+ .getSource(
+ pred,
+ QueryOptions.create(
+ indexConfig,
+ 0,
+ suggestReviewers.getLimit(),
+ ImmutableSet.of(idField.getName())))
+ .readRaw();
+ List<Account.Id> matches =
+ result.toList().stream()
+ .map(f -> fromIdField(f, useLegacyNumericFields))
+ .collect(toList());
+ logger.atFine().log("Matches: %s", matches);
+ return matches;
+ } catch (TooManyTermsInQueryException e) {
+ throw new BadRequestException(e.getMessage());
+ } catch (QueryParseException e) {
+ logger.atWarning().withCause(e).log("Suggesting accounts failed, return empty result.");
+ return ImmutableList.of();
+ } catch (StorageException e) {
+ if (e.getCause() instanceof TooManyTermsInQueryException) {
+ throw new BadRequestException(e.getMessage());
+ }
+ if (e.getCause() instanceof QueryParseException) {
return ImmutableList.of();
}
+ throw e;
}
}
@@ -286,6 +315,7 @@ public class ReviewersUtil {
}
private List<Account.Id> recommendAccounts(
+ ReviewerState reviewerState,
@Nullable ChangeNotes changeNotes,
SuggestReviewers suggestReviewers,
ProjectState projectState,
@@ -293,7 +323,7 @@ public class ReviewersUtil {
throws IOException, ConfigInvalidException {
try (Timer0.Context ctx = metrics.recommendAccountsLatency.start()) {
return reviewerRecommender.suggestReviewers(
- changeNotes, suggestReviewers, projectState, candidateList);
+ reviewerState, changeNotes, suggestReviewers, projectState, candidateList);
}
}
@@ -405,7 +435,7 @@ public class ReviewersUtil {
// require that at least one member in the group can see the change
for (Account account : members) {
- if (visibilityControl.isVisibleTo(account.getId())) {
+ if (visibilityControl.isVisibleTo(account.id())) {
if (needsConfirmation) {
result.allowedWithConfirmation = true;
} else {
diff --git a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
index a41143cdc6..ac0945d602 100644
--- a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -23,7 +24,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.change.RevisionResource;
diff --git a/java/com/google/gerrit/server/restapi/change/Revisions.java b/java/com/google/gerrit/server/restapi/change/Revisions.java
index c5cce4f3e5..7ca989c742 100644
--- a/java/com/google/gerrit/server/restapi/change/Revisions.java
+++ b/java/com/google/gerrit/server/restapi/change/Revisions.java
@@ -16,14 +16,15 @@ package com.google.gerrit.server.restapi.change;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
@@ -36,11 +37,13 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
@Singleton
public class Revisions implements ChildCollection<ChangeResource, RevisionResource> {
@@ -121,42 +124,46 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
} else if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) {
// Legacy patch set number syntax.
return byLegacyPatchSetId(change, id);
- } else if (id.length() < 4 || id.length() > RevId.LEN) {
+ } else if (id.length() < 4 || id.length() > ObjectIds.STR_LEN) {
// Require a minimum of 4 digits.
// Impossibly long identifier will never match.
return Collections.emptyList();
} else {
List<RevisionResource> out = new ArrayList<>();
for (PatchSet ps : psUtil.byChange(change.getNotes())) {
- if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
+ if (ObjectIds.matchesAbbreviation(ps.commitId(), id)) {
out.add(new RevisionResource(change, ps));
}
}
// Not an existing patch set on a change, but might be an edit.
- if (out.isEmpty() && id.length() == RevId.LEN) {
- return loadEdit(change, new RevId(id));
+ if (out.isEmpty() && ObjectId.isId(id)) {
+ return loadEdit(change, ObjectId.fromString(id));
}
return out;
}
}
private List<RevisionResource> byLegacyPatchSetId(ChangeResource change, String id) {
- PatchSet ps =
- psUtil.get(change.getNotes(), new PatchSet.Id(change.getId(), Integer.parseInt(id)));
+ PatchSet ps = psUtil.get(change.getNotes(), PatchSet.id(change.getId(), Integer.parseInt(id)));
if (ps != null) {
return Collections.singletonList(new RevisionResource(change, ps));
}
return Collections.emptyList();
}
- private List<RevisionResource> loadEdit(ChangeResource change, RevId revid)
+ private List<RevisionResource> loadEdit(ChangeResource change, @Nullable ObjectId commitId)
throws AuthException, IOException {
Optional<ChangeEdit> edit = editUtil.byChange(change.getNotes(), change.getUser());
if (edit.isPresent()) {
- PatchSet ps = new PatchSet(new PatchSet.Id(change.getId(), 0));
- RevId editRevId = new RevId(ObjectId.toString(edit.get().getEditCommit()));
- ps.setRevision(editRevId);
- if (revid == null || editRevId.equals(revid)) {
+ RevCommit editCommit = edit.get().getEditCommit();
+ PatchSet ps =
+ PatchSet.builder()
+ .id(PatchSet.id(change.getId(), 0))
+ .commitId(editCommit)
+ .uploader(change.getUser().getAccountId())
+ .createdOn(new Timestamp(editCommit.getCommitterIdent().getWhen().getTime()))
+ .build();
+ if (commitId == null || editCommit.equals(commitId)) {
return Collections.singletonList(new RevisionResource(change, ps, edit));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/RobotComments.java b/java/com/google/gerrit/server/restapi/change/RobotComments.java
index 1aa8c2ac92..9f81d0ab5a 100644
--- a/java/com/google/gerrit/server/restapi/change/RobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/RobotComments.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.RobotCommentResource;
@@ -59,7 +59,7 @@ public class RobotComments implements ChildCollection<RevisionResource, RobotCom
String uuid = id.get();
ChangeNotes notes = rev.getNotes();
- for (RobotComment c : commentsUtil.robotCommentsByPatchSet(notes, rev.getPatchSet().getId())) {
+ for (RobotComment c : commentsUtil.robotCommentsByPatchSet(notes, rev.getPatchSet().id())) {
if (uuid.equals(c.key.uuid)) {
return new RobotCommentResource(rev, c);
}
diff --git a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
index aacf58b2d6..288806cb1c 100644
--- a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
+++ b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
@@ -17,12 +17,12 @@ package com.google.gerrit.server.restapi.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyResolver;
@@ -39,7 +39,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-public class SetReadyForReview extends RetryingRestModifyView<ChangeResource, Input, Response<?>>
+public class SetReadyForReview extends RetryingRestModifyView<ChangeResource, Input, String>
implements UiAction<ChangeResource> {
private final WorkInProgressOp.Factory opFactory;
@@ -50,7 +50,7 @@ public class SetReadyForReview extends RetryingRestModifyView<ChangeResource, In
}
@Override
- protected Response<?> applyImpl(
+ protected Response<String> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
throws RestApiException, UpdateException, PermissionBackendException {
rsrc.permissions().check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);
diff --git a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
index 852813e96d..3fb0295099 100644
--- a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
+++ b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
@@ -17,12 +17,12 @@ package com.google.gerrit.server.restapi.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyResolver;
@@ -39,7 +39,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
-public class SetWorkInProgress extends RetryingRestModifyView<ChangeResource, Input, Response<?>>
+public class SetWorkInProgress extends RetryingRestModifyView<ChangeResource, Input, String>
implements UiAction<ChangeResource> {
private final WorkInProgressOp.Factory opFactory;
@@ -50,7 +50,7 @@ public class SetWorkInProgress extends RetryingRestModifyView<ChangeResource, In
}
@Override
- protected Response<?> applyImpl(
+ protected Response<String> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
throws RestApiException, UpdateException, PermissionBackendException {
rsrc.permissions().check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 596769b57e..137eb3219f 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -14,29 +14,32 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static java.util.stream.Collectors.joining;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -50,11 +53,9 @@ import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -109,7 +110,6 @@ public class Submit
private final GitRepositoryManager repoManager;
private final PermissionBackend permissionBackend;
private final ChangeData.Factory changeDataFactory;
- private final ChangeNotes.Factory changeNotesFactory;
private final Provider<MergeOp> mergeOpProvider;
private final Provider<MergeSuperSet> mergeSuperSet;
private final AccountResolver accountResolver;
@@ -129,7 +129,6 @@ public class Submit
GitRepositoryManager repoManager,
PermissionBackend permissionBackend,
ChangeData.Factory changeDataFactory,
- ChangeNotes.Factory changeNotesFactory,
Provider<MergeOp> mergeOpProvider,
Provider<MergeSuperSet> mergeSuperSet,
AccountResolver accountResolver,
@@ -140,7 +139,6 @@ public class Submit
this.repoManager = repoManager;
this.permissionBackend = permissionBackend;
this.changeDataFactory = changeDataFactory;
- this.changeNotesFactory = changeNotesFactory;
this.mergeOpProvider = mergeOpProvider;
this.mergeSuperSet = mergeSuperSet;
this.accountResolver = accountResolver;
@@ -175,7 +173,7 @@ public class Submit
}
@Override
- public Output apply(RevisionResource rsrc, SubmitInput input)
+ public Response<Output> apply(RevisionResource rsrc, SubmitInput input)
throws RestApiException, RepositoryNotFoundException, IOException, PermissionBackendException,
UpdateException, ConfigInvalidException {
input.onBehalfOf = Strings.emptyToNull(input.onBehalfOf);
@@ -188,44 +186,48 @@ public class Submit
}
projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
- return new Output(mergeChange(rsrc, submitter, input));
+ return mergeChange(rsrc, submitter, input);
}
- public Change mergeChange(RevisionResource rsrc, IdentifiedUser submitter, SubmitInput input)
- throws RestApiException, IOException, UpdateException, ConfigInvalidException,
- PermissionBackendException {
+ @UsedAt(UsedAt.Project.GOOGLE)
+ public Response<Output> mergeChange(
+ RevisionResource rsrc, IdentifiedUser submitter, SubmitInput input)
+ throws RestApiException, IOException {
Change change = rsrc.getChange();
if (!change.isNew()) {
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
} else if (!ProjectUtil.branchExists(repoManager, change.getDest())) {
throw new ResourceConflictException(
- String.format("destination branch \"%s\" not found.", change.getDest().get()));
- } else if (!rsrc.getPatchSet().getId().equals(change.currentPatchSetId())) {
+ String.format("destination branch \"%s\" not found.", change.getDest().branch()));
+ } else if (!rsrc.getPatchSet().id().equals(change.currentPatchSetId())) {
// TODO Allow submitting non-current revision by changing the current.
throw new ResourceConflictException(
String.format(
- "revision %s is not current revision", rsrc.getPatchSet().getRevision().get()));
+ "revision %s is not current revision", rsrc.getPatchSet().commitId().name()));
}
try (MergeOp op = mergeOpProvider.get()) {
- op.merge(change, submitter, true, input, false);
- }
+ Change updatedChange;
+
+ try (ReadonlyRequestWindow readonlyRequestWindow =
+ PerThreadCache.openReadonlyRequestWindow()) {
+ updatedChange = op.merge(change, submitter, true, input, false);
+ } catch (Exception e) {
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+ return Response.<Output>internalServerError(e).traceId(op.getTraceId().orElse(null));
+ }
- // Read the ChangeNotes only after MergeOp is fully done (including MergeOp#close) to be sure
- // to have the correct state of the repo.
- try (ReadonlyRequestWindow readonlyRequestWindow = PerThreadCache.openReadonlyRequestWindow()) {
- change = changeNotesFactory.createChecked(change.getProject(), change.getId()).getChange();
- } catch (NoSuchChangeException e) {
- throw new ResourceConflictException("change is deleted", e);
- }
+ if (updatedChange.isMerged()) {
+ return Response.ok(new Output(updatedChange));
+ }
- if (change.isMerged()) {
- return change;
- }
- if (change.isNew()) {
- throw new RestApiException("change unexpectedly had status NEW after submit attempt");
+ String msg =
+ String.format(
+ "change %s of project %s unexpectedly had status %s after submit attempt",
+ updatedChange.getId(), updatedChange.getProject(), updatedChange.getStatus());
+ logger.atWarning().log(msg);
+ throw new RestApiException(msg);
}
- throw new ResourceConflictException("change is " + ChangeUtil.status(change));
}
/**
@@ -358,12 +360,11 @@ public class Submit
.setVisible(true)
.setEnabled(Boolean.TRUE.equals(enabled));
}
- RevId revId = resource.getPatchSet().getRevision();
Map<String, String> params =
ImmutableMap.of(
- "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
- "branch", change.getDest().getShortName(),
- "commit", ObjectId.fromString(revId.get()).abbreviate(7).name(),
+ "patchSet", String.valueOf(resource.getPatchSet().number()),
+ "branch", change.getDest().shortName(),
+ "commit", abbreviateName(resource.getPatchSet().commitId()),
"submitSize", String.valueOf(cs.size()));
ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors : titlePattern;
return new UiAction.Description()
@@ -379,10 +380,10 @@ public class Submit
mergeabilityMap.add(change);
}
- ListMultimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
- for (Branch.NameKey branch : cbb.keySet()) {
+ ListMultimap<BranchNameKey, ChangeData> cbb = cs.changesByBranch();
+ for (BranchNameKey branch : cbb.keySet()) {
Collection<ChangeData> targetBranch = cbb.get(branch);
- HashMap<Change.Id, RevCommit> commits = findCommits(targetBranch, branch.getParentKey());
+ HashMap<Change.Id, RevCommit> commits = findCommits(targetBranch, branch.project());
Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
for (RevCommit commit : commits.values()) {
@@ -427,9 +428,7 @@ public class Submit
try (Repository repo = repoManager.openRepository(project);
RevWalk walk = new RevWalk(repo)) {
for (ChangeData change : changes) {
- RevCommit commit =
- walk.parseCommit(
- ObjectId.fromString(psUtil.current(change.notes()).getRevision().get()));
+ RevCommit commit = walk.parseCommit(psUtil.current(change.notes()).commitId());
commits.put(change.getId(), commit);
}
}
@@ -468,16 +467,20 @@ public class Submit
}
@Override
- public ChangeInfo apply(ChangeResource rsrc, SubmitInput input)
- throws RestApiException, RepositoryNotFoundException, IOException,
- PermissionBackendException, UpdateException, ConfigInvalidException {
+ public Response<ChangeInfo> apply(ChangeResource rsrc, SubmitInput input) throws Exception {
PatchSet ps = psUtil.current(rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
}
- Output out = submit.apply(new RevisionResource(rsrc, ps), input);
- return json.noOptions().format(out.change);
+ Response<Output> response = submit.apply(new RevisionResource(rsrc, ps), input);
+ if (response instanceof Response.InternalServerError) {
+ Response.InternalServerError<?> ise = (Response.InternalServerError<?>) response;
+ return Response.<ChangeInfo>internalServerError(ise.cause())
+ .traceId(ise.traceId().orElse(null));
+ }
+
+ return Response.ok(json.noOptions().format(response.value().change));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/SubmittedTogether.java b/java/com/google/gerrit/server/restapi/change/SubmittedTogether.java
index cefbfdb748..214a0015a1 100644
--- a/java/com/google/gerrit/server/restapi/change/SubmittedTogether.java
+++ b/java/com/google/gerrit/server/restapi/change/SubmittedTogether.java
@@ -19,6 +19,7 @@ import static java.util.Collections.reverseOrder;
import static java.util.stream.Collectors.toList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
@@ -26,8 +27,8 @@ import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.WalkSorter;
@@ -58,7 +59,8 @@ public class SubmittedTogether implements RestReadView<ChangeResource> {
EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.SUBMITTABLE);
private static final Comparator<ChangeData> COMPARATOR =
- Comparator.comparing(ChangeData::project).thenComparing(cd -> cd.getId().id, reverseOrder());
+ Comparator.comparing(ChangeData::project)
+ .thenComparing(cd -> cd.getId().get(), reverseOrder());
private final ChangeJson.Factory json;
private final Provider<InternalChangeQuery> queryProvider;
@@ -107,14 +109,14 @@ public class SubmittedTogether implements RestReadView<ChangeResource> {
}
@Override
- public Object apply(ChangeResource resource)
+ public Response<Object> apply(ChangeResource resource)
throws AuthException, BadRequestException, ResourceConflictException, IOException,
PermissionBackendException {
SubmittedTogetherInfo info = applyInfo(resource);
if (options.isEmpty()) {
- return info.changes;
+ return Response.ok(info.changes);
}
- return info;
+ return Response.ok(info);
}
public SubmittedTogetherInfo applyInfo(ChangeResource resource)
diff --git a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
index f5a27516bc..d247b5b1d9 100644
--- a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
@@ -14,10 +14,12 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeResource;
@@ -38,15 +40,31 @@ import org.kohsuke.args4j.Option;
public class SuggestChangeReviewers extends SuggestReviewers
implements RestReadView<ChangeResource> {
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> self;
+ private final ProjectCache projectCache;
+
+ private boolean excludeGroups;
+ private ReviewerState reviewerState = ReviewerState.REVIEWER;
+
@Option(
name = "--exclude-groups",
aliases = {"-e"},
usage = "exclude groups from query")
- boolean excludeGroups;
+ public SuggestChangeReviewers setExcludeGroups(boolean excludeGroups) {
+ this.excludeGroups = excludeGroups;
+ return this;
+ }
- private final PermissionBackend permissionBackend;
- private final Provider<CurrentUser> self;
- private final ProjectCache projectCache;
+ @Option(
+ name = "--reviewer-state",
+ usage =
+ "The type of reviewers that should be suggested"
+ + " (can be 'REVIEWER' or 'CC', default is 'REVIEWER')")
+ public SuggestChangeReviewers setReviewerState(ReviewerState reviewerState) {
+ this.reviewerState = reviewerState;
+ return this;
+ }
@Inject
SuggestChangeReviewers(
@@ -63,18 +81,24 @@ public class SuggestChangeReviewers extends SuggestReviewers
}
@Override
- public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
+ public Response<List<SuggestedReviewerInfo>> apply(ChangeResource rsrc)
throws AuthException, BadRequestException, IOException, ConfigInvalidException,
PermissionBackendException {
if (!self.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
- return reviewersUtil.suggestReviewers(
- rsrc.getNotes(),
- this,
- projectCache.checkedGet(rsrc.getProject()),
- getVisibility(rsrc),
- excludeGroups);
+ if (reviewerState.equals(ReviewerState.REMOVED)) {
+ throw new BadRequestException(
+ String.format("Unsupported reviewer state: %s", ReviewerState.REMOVED));
+ }
+ return Response.ok(
+ reviewersUtil.suggestReviewers(
+ reviewerState,
+ rsrc.getNotes(),
+ this,
+ projectCache.checkedGet(rsrc.getProject()),
+ getVisibility(rsrc),
+ excludeGroups));
}
private VisibilityControl getVisibility(ChangeResource rsrc) {
diff --git a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
index 370bdb25e7..bae2e52e59 100644
--- a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
+++ b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
@@ -15,9 +15,6 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.TestSubmitRuleInfo;
@@ -25,30 +22,26 @@ import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.common.TestSubmitRuleInput.Filters;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.rules.DefaultSubmitRule;
+import com.google.gerrit.server.rules.PrologOptions;
import com.google.gerrit.server.rules.PrologRule;
import com.google.gerrit.server.rules.RulesCache;
import com.google.inject.Inject;
import java.util.LinkedHashMap;
-import java.util.List;
import org.kohsuke.args4j.Option;
public class TestSubmitRule implements RestModifyView<RevisionResource, TestSubmitRuleInput> {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
private final ChangeData.Factory changeDataFactory;
private final RulesCache rules;
private final AccountLoader.Factory accountInfoFactory;
private final ProjectCache projectCache;
- private final DefaultSubmitRule defaultSubmitRule;
private final PrologRule prologRule;
@Option(name = "--filters", usage = "impact of filters in parent projects")
@@ -60,56 +53,41 @@ public class TestSubmitRule implements RestModifyView<RevisionResource, TestSubm
RulesCache rules,
AccountLoader.Factory infoFactory,
ProjectCache projectCache,
- DefaultSubmitRule defaultSubmitRule,
PrologRule prologRule) {
this.changeDataFactory = changeDataFactory;
this.rules = rules;
this.accountInfoFactory = infoFactory;
this.projectCache = projectCache;
- this.defaultSubmitRule = defaultSubmitRule;
this.prologRule = prologRule;
}
@Override
- public List<TestSubmitRuleInfo> apply(RevisionResource rsrc, TestSubmitRuleInput input)
+ public Response<TestSubmitRuleInfo> apply(RevisionResource rsrc, TestSubmitRuleInput input)
throws AuthException, PermissionBackendException, BadRequestException {
if (input == null) {
input = new TestSubmitRuleInput();
}
- if (input.rule != null && !rules.isProjectRulesEnabled()) {
+ if (input.rule == null) {
+ throw new BadRequestException("rule is required");
+ }
+ if (!rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
}
input.filters = MoreObjects.firstNonNull(input.filters, filters);
- SubmitRuleOptions opts =
- SubmitRuleOptions.builder()
- .skipFilters(input.filters == Filters.SKIP)
- .rule(input.rule)
- .logErrors(logger.atFine().isEnabled())
- .build();
-
ProjectState projectState = projectCache.get(rsrc.getProject());
if (projectState == null) {
throw new BadRequestException("project not found");
}
ChangeData cd = changeDataFactory.create(rsrc.getNotes());
- List<SubmitRecord> records;
- if (projectState.hasPrologRules() || input.rule != null) {
- records = ImmutableList.copyOf(prologRule.evaluate(cd, opts));
- } else {
- // No rules were provided as input and we have no rules.pl. This means we are supposed to run
- // the default rules. Nowadays, the default rules are implemented in Java, not Prolog.
- // Therefore, we call the DefaultRuleEvaluator instead.
- records = ImmutableList.copyOf(defaultSubmitRule.evaluate(cd, opts));
- }
+ SubmitRecord record =
+ prologRule.evaluate(
+ cd, PrologOptions.dryRunOptions(input.rule, input.filters == Filters.SKIP));
- List<TestSubmitRuleInfo> out = Lists.newArrayListWithCapacity(records.size());
AccountLoader accounts = accountInfoFactory.create(true);
- for (SubmitRecord r : records) {
- out.add(newSubmitRuleInfo(r, accounts));
- }
+ TestSubmitRuleInfo out = newSubmitRuleInfo(record, accounts);
accounts.fill();
- return out;
+ return Response.ok(out);
}
private static TestSubmitRuleInfo newSubmitRuleInfo(SubmitRecord r, AccountLoader accounts) {
diff --git a/java/com/google/gerrit/server/restapi/change/TestSubmitType.java b/java/com/google/gerrit/server/restapi/change/TestSubmitType.java
index 4c7fad2011..cb52fcba3c 100644
--- a/java/com/google/gerrit/server/restapi/change/TestSubmitType.java
+++ b/java/com/google/gerrit/server/restapi/change/TestSubmitType.java
@@ -15,83 +15,92 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.MoreObjects;
-import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.common.TestSubmitRuleInput.Filters;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.rules.PrologOptions;
+import com.google.gerrit.server.rules.PrologRule;
import com.google.gerrit.server.rules.RulesCache;
import com.google.inject.Inject;
import org.kohsuke.args4j.Option;
public class TestSubmitType implements RestModifyView<RevisionResource, TestSubmitRuleInput> {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
private final ChangeData.Factory changeDataFactory;
private final RulesCache rules;
- private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
+ private final PrologRule prologRule;
@Option(name = "--filters", usage = "impact of filters in parent projects")
private Filters filters = Filters.RUN;
@Inject
- TestSubmitType(
- ChangeData.Factory changeDataFactory,
- RulesCache rules,
- SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
+ TestSubmitType(ChangeData.Factory changeDataFactory, RulesCache rules, PrologRule prologRule) {
this.changeDataFactory = changeDataFactory;
this.rules = rules;
- this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
+ this.prologRule = prologRule;
}
@Override
- public SubmitType apply(RevisionResource rsrc, TestSubmitRuleInput input)
+ public Response<SubmitType> apply(RevisionResource rsrc, TestSubmitRuleInput input)
throws AuthException, BadRequestException {
if (input == null) {
input = new TestSubmitRuleInput();
}
- if (input.rule != null && !rules.isProjectRulesEnabled()) {
+ if (input.rule == null) {
+ throw new BadRequestException("rule is required");
+ }
+ if (!rules.isProjectRulesEnabled()) {
throw new AuthException("project rules are disabled");
}
input.filters = MoreObjects.firstNonNull(input.filters, filters);
- SubmitRuleOptions opts =
- SubmitRuleOptions.builder()
- .logErrors(logger.atFine().isEnabled())
- .skipFilters(input.filters == Filters.SKIP)
- .rule(input.rule)
- .build();
-
- SubmitRuleEvaluator evaluator = submitRuleEvaluatorFactory.create(opts);
ChangeData cd = changeDataFactory.create(rsrc.getNotes());
- SubmitTypeRecord rec = evaluator.getSubmitType(cd);
+ SubmitTypeRecord rec =
+ prologRule.getSubmitType(
+ cd, PrologOptions.dryRunOptions(input.rule, input.filters == Filters.SKIP));
if (rec.status != SubmitTypeRecord.Status.OK) {
throw new BadRequestException(String.format("rule produced invalid result: %s", rec));
}
- return rec.type;
+ return Response.ok(rec.type);
}
public static class Get implements RestReadView<RevisionResource> {
- private final TestSubmitType test;
+ private final ChangeData.Factory changeDataFactory;
+ private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
@Inject
- Get(TestSubmitType test) {
- this.test = test;
+ Get(
+ ChangeData.Factory changeDataFactory,
+ SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
+ this.changeDataFactory = changeDataFactory;
+ this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
}
@Override
- public SubmitType apply(RevisionResource resource) throws AuthException, BadRequestException {
- return test.apply(resource, null);
+ public Response<SubmitType> apply(RevisionResource resource)
+ throws AuthException, ResourceConflictException {
+ SubmitRuleEvaluator evaluator =
+ submitRuleEvaluatorFactory.create(SubmitRuleOptions.defaults());
+ ChangeData cd = changeDataFactory.create(resource.getNotes());
+ SubmitTypeRecord rec = evaluator.getSubmitType(cd);
+
+ if (rec.status != SubmitTypeRecord.Status.OK) {
+ throw new ResourceConflictException(String.format("rule produced invalid result: %s", rec));
+ }
+
+ return Response.ok(rec.type);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Votes.java b/java/com/google/gerrit/server/restapi/change/Votes.java
index 31efe545fd..d8990024b6 100644
--- a/java/com/google/gerrit/server/restapi/change/Votes.java
+++ b/java/com/google/gerrit/server/restapi/change/Votes.java
@@ -14,15 +14,16 @@
package com.google.gerrit.server.restapi.change;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.change.VoteResource;
@@ -71,7 +72,8 @@ public class Votes implements ChildCollection<ReviewerResource, VoteResource> {
}
@Override
- public Map<String, Short> apply(ReviewerResource rsrc) throws MethodNotAllowedException {
+ public Response<Map<String, Short>> apply(ReviewerResource rsrc)
+ throws MethodNotAllowedException {
if (rsrc.getRevisionResource() != null && !rsrc.getRevisionResource().isCurrent()) {
throw new MethodNotAllowedException("Cannot list votes on non-current patch set");
}
@@ -85,9 +87,9 @@ public class Votes implements ChildCollection<ReviewerResource, VoteResource> {
null,
null);
for (PatchSetApproval psa : byPatchSetUser) {
- votes.put(psa.getLabel(), psa.getValue());
+ votes.put(psa.label(), psa.value());
}
- return votes;
+ return Response.ok(votes);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/CheckConsistency.java b/java/com/google/gerrit/server/restapi/config/CheckConsistency.java
index 61d5c796b2..50e774aeb1 100644
--- a/java/com/google/gerrit/server/restapi/config/CheckConsistency.java
+++ b/java/com/google/gerrit/server/restapi/config/CheckConsistency.java
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckAccount
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckGroupsResultInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountsConsistencyChecker;
@@ -54,7 +55,7 @@ public class CheckConsistency implements RestModifyView<ConfigResource, Consiste
}
@Override
- public ConsistencyCheckInfo apply(ConfigResource resource, ConsistencyCheckInput input)
+ public Response<ConsistencyCheckInfo> apply(ConfigResource resource, ConsistencyCheckInput input)
throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
@@ -80,6 +81,6 @@ public class CheckConsistency implements RestModifyView<ConfigResource, Consiste
new CheckGroupsResultInfo(groupsConsistencyChecker.check());
}
- return consistencyCheckInfo;
+ return Response.ok(consistencyCheckInfo);
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
index b6dcd35cfb..b56f1b88c8 100644
--- a/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
+++ b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.restapi.config;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
diff --git a/java/com/google/gerrit/server/restapi/config/GetCache.java b/java/com/google/gerrit/server/restapi/config/GetCache.java
index 5abaf1e397..93600ea582 100644
--- a/java/com/google/gerrit/server/restapi/config/GetCache.java
+++ b/java/com/google/gerrit/server/restapi/config/GetCache.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.config;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.CacheResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
public class GetCache implements RestReadView<CacheResource> {
@Override
- public ListCaches.CacheInfo apply(CacheResource rsrc) {
- return new ListCaches.CacheInfo(rsrc.getName(), rsrc.getCache());
+ public Response<ListCaches.CacheInfo> apply(CacheResource rsrc) {
+ return Response.ok(new ListCaches.CacheInfo(rsrc.getName(), rsrc.getCache()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
index 13c28189ed..44c71b374e 100644
--- a/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
@@ -17,8 +17,9 @@ package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -41,10 +42,10 @@ public class GetDiffPreferences implements RestReadView<ConfigResource> {
}
@Override
- public DiffPreferencesInfo apply(ConfigResource configResource)
+ public Response<DiffPreferencesInfo> apply(ConfigResource configResource)
throws BadRequestException, ResourceConflictException, IOException, ConfigInvalidException {
try (Repository git = gitManager.openRepository(allUsersName)) {
- return Preferences.readDefaultDiffPreferences(allUsersName, git);
+ return Response.ok(StoredPreferences.readDefaultDiffPreferences(allUsersName, git));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java b/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
index 2ec547b1e1..a5ab967c36 100644
--- a/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetEditPreferences.java
@@ -17,8 +17,9 @@ package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -40,10 +41,10 @@ public class GetEditPreferences implements RestReadView<ConfigResource> {
}
@Override
- public EditPreferencesInfo apply(ConfigResource configResource)
+ public Response<EditPreferencesInfo> apply(ConfigResource configResource)
throws BadRequestException, ResourceConflictException, IOException, ConfigInvalidException {
try (Repository git = gitManager.openRepository(allUsersName)) {
- return Preferences.readDefaultEditPreferences(allUsersName, git);
+ return Response.ok(StoredPreferences.readDefaultEditPreferences(allUsersName, git));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetPreferences.java b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
index 4dbbc8cf37..8da9134a0d 100644
--- a/java/com/google/gerrit/server/restapi/config/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
@@ -15,8 +15,9 @@
package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -38,10 +39,10 @@ public class GetPreferences implements RestReadView<ConfigResource> {
}
@Override
- public GeneralPreferencesInfo apply(ConfigResource rsrc)
+ public Response<GeneralPreferencesInfo> apply(ConfigResource rsrc)
throws IOException, ConfigInvalidException {
try (Repository git = gitMgr.openRepository(allUsersName)) {
- return Preferences.readDefaultGeneralPreferences(allUsersName, git);
+ return Response.ok(StoredPreferences.readDefaultGeneralPreferences(allUsersName, git));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index aa0e350481..2d504c7347 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -35,6 +35,7 @@ import com.google.gerrit.extensions.common.UserConfigInfo;
import com.google.gerrit.extensions.config.CloneCommand;
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.server.EnableSignedPush;
@@ -133,7 +134,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
}
@Override
- public ServerInfo apply(ConfigResource rsrc) throws PermissionBackendException {
+ public Response<ServerInfo> apply(ConfigResource rsrc) throws PermissionBackendException {
ServerInfo info = new ServerInfo();
info.accounts = getAccountsInfo();
info.auth = getAuthInfo();
@@ -148,7 +149,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
info.user = getUserInfo();
info.receive = getReceiveInfo();
- return info;
+ return Response.ok(info);
}
private AccountsInfo getAccountsInfo() {
@@ -225,7 +226,9 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
+ "\u2026";
info.updateDelay =
(int) ConfigUtil.getTimeUnit(config, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
- info.submitWholeTopic = MergeSuperSet.wholeTopicEnabled(config);
+ info.submitWholeTopic = toBoolean(MergeSuperSet.wholeTopicEnabled(config));
+ info.excludeMergeableInChangeInfo =
+ toBoolean(this.config.getBoolean("change", "api", "excludeMergeableInChangeInfo", false));
info.disablePrivateChanges =
toBoolean(this.config.getBoolean("change", null, "disablePrivateChanges", false));
return info;
diff --git a/java/com/google/gerrit/server/restapi/config/GetSummary.java b/java/com/google/gerrit/server/restapi/config/GetSummary.java
index 1d8da63b3d..d0a1498bfb 100644
--- a/java/com/google/gerrit/server/restapi/config/GetSummary.java
+++ b/java/com/google/gerrit/server/restapi/config/GetSummary.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.config;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.SitePath;
@@ -69,7 +70,7 @@ public class GetSummary implements RestReadView<ConfigResource> {
}
@Override
- public SummaryInfo apply(ConfigResource rsrc) {
+ public Response<SummaryInfo> apply(ConfigResource rsrc) {
if (gc) {
System.gc();
System.runFinalization();
@@ -83,7 +84,7 @@ public class GetSummary implements RestReadView<ConfigResource> {
if (jvm) {
summary.jvmSummary = getJvmSummary();
}
- return summary;
+ return Response.ok(summary);
}
private TaskSummaryInfo getTaskSummary() {
diff --git a/java/com/google/gerrit/server/restapi/config/GetTask.java b/java/com/google/gerrit/server/restapi/config/GetTask.java
index a32f3bacb5..513c99a016 100644
--- a/java/com/google/gerrit/server/restapi/config/GetTask.java
+++ b/java/com/google/gerrit/server/restapi/config/GetTask.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.config;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.TaskResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
public class GetTask implements RestReadView<TaskResource> {
@Override
- public ListTasks.TaskInfo apply(TaskResource rsrc) {
- return new ListTasks.TaskInfo(rsrc.getTask());
+ public Response<ListTasks.TaskInfo> apply(TaskResource rsrc) {
+ return Response.ok(new ListTasks.TaskInfo(rsrc.getTask()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/IndexChanges.java b/java/com/google/gerrit/server/restapi/config/IndexChanges.java
index 6ba93e8436..904c44f809 100644
--- a/java/com/google/gerrit/server/restapi/config/IndexChanges.java
+++ b/java/com/google/gerrit/server/restapi/config/IndexChanges.java
@@ -51,7 +51,7 @@ public class IndexChanges implements RestModifyView<ConfigResource, Input> {
}
@Override
- public Object apply(ConfigResource resource, Input input) {
+ public Response<String> apply(ConfigResource resource, Input input) {
if (input == null || input.changes == null) {
return Response.ok("Nothing to index");
}
diff --git a/java/com/google/gerrit/server/restapi/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java
index f310ed7e71..ccafbe89b1 100644
--- a/java/com/google/gerrit/server/restapi/config/ListCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -28,6 +28,7 @@ import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.cache.PersistentCache;
import com.google.gerrit.server.config.ConfigResource;
@@ -69,21 +70,22 @@ public class ListCaches implements RestReadView<ConfigResource> {
}
@Override
- public Object apply(ConfigResource rsrc) {
+ public Response<Object> apply(ConfigResource rsrc) {
if (format == null) {
- return getCacheInfos();
+ return Response.ok(getCacheInfos());
}
Stream<String> cacheNames =
Streams.stream(cacheMap)
.map(e -> cacheNameOf(e.getPluginName(), e.getExportName()))
.sorted();
if (OutputFormat.TEXT_LIST.equals(format)) {
- return BinaryResult.create(cacheNames.collect(joining("\n")))
- .base64()
- .setContentType("text/plain")
- .setCharacterEncoding(UTF_8);
+ return Response.ok(
+ BinaryResult.create(cacheNames.collect(joining("\n")))
+ .base64()
+ .setContentType("text/plain")
+ .setCharacterEncoding(UTF_8));
}
- return cacheNames.collect(toImmutableList());
+ return Response.ok(cacheNames.collect(toImmutableList()));
}
public enum CacheType {
diff --git a/java/com/google/gerrit/server/restapi/config/ListCapabilities.java b/java/com/google/gerrit/server/restapi/config/ListCapabilities.java
index cacbbf590a..6c8bf7421c 100644
--- a/java/com/google/gerrit/server/restapi/config/ListCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCapabilities.java
@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.CapabilityConstants;
import com.google.gerrit.server.config.ConfigResource;
@@ -43,13 +44,14 @@ public class ListCapabilities implements RestReadView<ConfigResource> {
}
@Override
- public Map<String, CapabilityInfo> apply(ConfigResource resource)
+ public Response<Map<String, CapabilityInfo>> apply(ConfigResource resource)
throws ResourceNotFoundException, IllegalAccessException, NoSuchFieldException {
permissionBackend.checkUsesDefaultCapabilities();
- return ImmutableMap.<String, CapabilityInfo>builder()
- .putAll(collectCoreCapabilities())
- .putAll(collectPluginCapabilities())
- .build();
+ return Response.ok(
+ ImmutableMap.<String, CapabilityInfo>builder()
+ .putAll(collectCoreCapabilities())
+ .putAll(collectPluginCapabilities())
+ .build());
}
public Map<String, CapabilityInfo> collectPluginCapabilities() {
diff --git a/java/com/google/gerrit/server/restapi/config/ListTasks.java b/java/com/google/gerrit/server/restapi/config/ListTasks.java
index 7402c15b21..6a3ca421fa 100644
--- a/java/com/google/gerrit/server/restapi/config/ListTasks.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTasks.java
@@ -17,9 +17,10 @@ package com.google.gerrit.server.restapi.config;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.WorkQueue;
@@ -62,7 +63,7 @@ public class ListTasks implements RestReadView<ConfigResource> {
}
@Override
- public List<TaskInfo> apply(ConfigResource resource)
+ public Response<List<TaskInfo>> apply(ConfigResource resource)
throws AuthException, PermissionBackendException {
CurrentUser user = self.get();
if (!user.isIdentifiedUser()) {
@@ -72,7 +73,7 @@ public class ListTasks implements RestReadView<ConfigResource> {
List<TaskInfo> allTasks = getTasks();
try {
permissionBackend.user(user).check(GlobalPermission.VIEW_QUEUE);
- return allTasks;
+ return Response.ok(allTasks);
} catch (AuthException e) {
// Fall through to filter tasks.
}
@@ -83,7 +84,7 @@ public class ListTasks implements RestReadView<ConfigResource> {
if (task.projectName != null) {
Boolean visible = visibilityCache.get(task.projectName);
if (visible == null) {
- Project.NameKey nameKey = new Project.NameKey(task.projectName);
+ Project.NameKey nameKey = Project.nameKey(task.projectName);
ProjectState state = projectCache.get(nameKey);
if (state == null || !state.statePermitsRead()) {
visible = false;
@@ -102,7 +103,7 @@ public class ListTasks implements RestReadView<ConfigResource> {
}
}
}
- return visibleTasks;
+ return Response.ok(visibleTasks);
}
private List<TaskInfo> getTasks() {
diff --git a/java/com/google/gerrit/server/restapi/config/ReloadConfig.java b/java/com/google/gerrit/server/restapi/config/ReloadConfig.java
index 0685a5862e..9ce7ffd8b0 100644
--- a/java/com/google/gerrit/server/restapi/config/ReloadConfig.java
+++ b/java/com/google/gerrit/server/restapi/config/ReloadConfig.java
@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.Multimap;
import com.google.gerrit.extensions.api.config.ConfigUpdateEntryInfo;
import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.config.ConfigResource;
@@ -47,17 +48,18 @@ public class ReloadConfig implements RestModifyView<ConfigResource, Input> {
}
@Override
- public Map<String, List<ConfigUpdateEntryInfo>> apply(ConfigResource resource, Input input)
- throws RestApiException, PermissionBackendException {
+ public Response<Map<String, List<ConfigUpdateEntryInfo>>> apply(
+ ConfigResource resource, Input input) throws RestApiException, PermissionBackendException {
permissions.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
Multimap<UpdateResult, ConfigUpdateEntry> updates = config.reloadConfig();
if (updates.isEmpty()) {
- return Collections.emptyMap();
+ return Response.ok(Collections.emptyMap());
}
- return updates.asMap().entrySet().stream()
- .collect(
- Collectors.toMap(
- e -> e.getKey().name().toLowerCase(), e -> toEntryInfos(e.getValue())));
+ return Response.ok(
+ updates.asMap().entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ e -> e.getKey().name().toLowerCase(), e -> toEntryInfos(e.getValue()))));
}
private static List<ConfigUpdateEntryInfo> toEntryInfos(
diff --git a/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java b/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
index 068f3326c5..96654a9c94 100644
--- a/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
@@ -21,9 +21,10 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -54,7 +55,8 @@ public class SetDiffPreferences implements RestModifyView<ConfigResource, DiffPr
}
@Override
- public DiffPreferencesInfo apply(ConfigResource configResource, DiffPreferencesInfo input)
+ public Response<DiffPreferencesInfo> apply(
+ ConfigResource configResource, DiffPreferencesInfo input)
throws BadRequestException, IOException, ConfigInvalidException {
if (input == null) {
throw new BadRequestException("input must be provided");
@@ -64,9 +66,9 @@ public class SetDiffPreferences implements RestModifyView<ConfigResource, DiffPr
}
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
- DiffPreferencesInfo updatedPrefs = Preferences.updateDefaultDiffPreferences(md, input);
+ DiffPreferencesInfo updatedPrefs = StoredPreferences.updateDefaultDiffPreferences(md, input);
accountCache.evictAll();
- return updatedPrefs;
+ return Response.ok(updatedPrefs);
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/SetEditPreferences.java b/java/com/google/gerrit/server/restapi/config/SetEditPreferences.java
index daca734a58..4bb420b570 100644
--- a/java/com/google/gerrit/server/restapi/config/SetEditPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetEditPreferences.java
@@ -21,9 +21,10 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -54,7 +55,8 @@ public class SetEditPreferences implements RestModifyView<ConfigResource, EditPr
}
@Override
- public EditPreferencesInfo apply(ConfigResource configResource, EditPreferencesInfo input)
+ public Response<EditPreferencesInfo> apply(
+ ConfigResource configResource, EditPreferencesInfo input)
throws BadRequestException, IOException, ConfigInvalidException {
if (input == null) {
throw new BadRequestException("input must be provided");
@@ -64,9 +66,9 @@ public class SetEditPreferences implements RestModifyView<ConfigResource, EditPr
}
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
- EditPreferencesInfo updatedPrefs = Preferences.updateDefaultEditPreferences(md, input);
+ EditPreferencesInfo updatedPrefs = StoredPreferences.updateDefaultEditPreferences(md, input);
accountCache.evictAll();
- return updatedPrefs;
+ return Response.ok(updatedPrefs);
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/SetPreferences.java b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
index 6a0c22b531..c88c11197d 100644
--- a/java/com/google/gerrit/server/restapi/config/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
@@ -21,9 +21,10 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.Preferences;
+import com.google.gerrit.server.account.StoredPreferences;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -54,16 +55,17 @@ public class SetPreferences implements RestModifyView<ConfigResource, GeneralPre
}
@Override
- public GeneralPreferencesInfo apply(ConfigResource rsrc, GeneralPreferencesInfo input)
+ public Response<GeneralPreferencesInfo> apply(ConfigResource rsrc, GeneralPreferencesInfo input)
throws BadRequestException, IOException, ConfigInvalidException {
if (!hasSetFields(input)) {
throw new BadRequestException("unsupported option");
}
- Preferences.validateMy(input.my);
+ StoredPreferences.validateMy(input.my);
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
- GeneralPreferencesInfo updatedPrefs = Preferences.updateDefaultGeneralPreferences(md, input);
+ GeneralPreferencesInfo updatedPrefs =
+ StoredPreferences.updateDefaultGeneralPreferences(md, input);
accountCache.evictAll();
- return updatedPrefs;
+ return Response.ok(updatedPrefs);
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/TasksCollection.java b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
index 9a7aa3a3dd..837d07167c 100644
--- a/java/com/google/gerrit/server/restapi/config/TasksCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.config;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -21,7 +22,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.TaskResource;
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index b2b14a1a5b..caff2065ca 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -18,19 +18,19 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
@@ -111,7 +111,7 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
}
@Override
- public List<AccountInfo> apply(GroupResource resource, Input input)
+ public Response<List<AccountInfo>> apply(GroupResource resource, Input input)
throws AuthException, NotInternalGroupException, UnprocessableEntityException, IOException,
ConfigInvalidException, ResourceNotFoundException, PermissionBackendException {
GroupDescription.Internal internalGroup =
@@ -130,7 +130,7 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
throw new UnprocessableEntityException(
String.format("Account Inactive: %s", nameOrEmailOrId));
}
- newMemberIds.add(a.getId());
+ newMemberIds.add(a.id());
}
AccountGroup.UUID groupUuid = internalGroup.getGroupUUID();
@@ -139,14 +139,14 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
} catch (NoSuchGroupException e) {
throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
- return toAccountInfoList(newMemberIds);
+ return Response.ok(toAccountInfoList(newMemberIds));
}
Account findAccount(String nameOrEmailOrId)
throws UnprocessableEntityException, IOException, ConfigInvalidException {
AccountResolver.Result result = accountResolver.resolve(nameOrEmailOrId);
try {
- return result.asUnique().getAccount();
+ return result.asUnique().account();
} catch (UnresolvableAccountException e) {
switch (authType) {
case HTTP_LDAP:
@@ -193,7 +193,7 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
req.setSkipAuthentication(true);
return accountCache
.get(accountManager.authenticate(req).getAccountId())
- .map(AccountState::getAccount);
+ .map(AccountState::account);
} catch (AccountException e) {
return Optional.empty();
}
@@ -221,15 +221,14 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
}
@Override
- public AccountInfo apply(GroupResource resource, IdString id, Input input)
- throws AuthException, MethodNotAllowedException, ResourceNotFoundException, IOException,
- ConfigInvalidException, PermissionBackendException {
+ public Response<AccountInfo> apply(GroupResource resource, IdString id, Input input)
+ throws Exception {
AddMembers.Input in = new AddMembers.Input();
in._oneMember = id.get();
try {
- List<AccountInfo> list = put.apply(resource, in);
+ List<AccountInfo> list = put.apply(resource, in).value();
if (list.size() == 1) {
- return list.get(0);
+ return Response.created(list.get(0));
}
throw new IllegalStateException();
} catch (UnprocessableEntityException e) {
@@ -248,7 +247,7 @@ public class AddMembers implements RestModifyView<GroupResource, Input> {
}
@Override
- public AccountInfo apply(MemberResource resource, Input input)
+ public Response<AccountInfo> apply(MemberResource resource, Input input)
throws PermissionBackendException {
// Do nothing, the user is already a member.
return get.apply(resource);
diff --git a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
index 5c879e721d..3fd3f2948c 100644
--- a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
@@ -19,17 +19,17 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupResolver;
@@ -91,7 +91,7 @@ public class AddSubgroups implements RestModifyView<GroupResource, Input> {
}
@Override
- public List<GroupInfo> apply(GroupResource resource, Input input)
+ public Response<List<GroupInfo>> apply(GroupResource resource, Input input)
throws NotInternalGroupException, AuthException, UnprocessableEntityException,
ResourceNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
@@ -118,7 +118,7 @@ public class AddSubgroups implements RestModifyView<GroupResource, Input> {
} catch (NoSuchGroupException e) {
throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
- return result;
+ return Response.ok(result);
}
private void addSubgroups(
@@ -142,15 +142,14 @@ public class AddSubgroups implements RestModifyView<GroupResource, Input> {
}
@Override
- public GroupInfo apply(GroupResource resource, IdString id, Input input)
- throws AuthException, MethodNotAllowedException, ResourceNotFoundException, IOException,
- ConfigInvalidException, PermissionBackendException {
+ public Response<GroupInfo> apply(GroupResource resource, IdString id, Input input)
+ throws Exception {
AddSubgroups.Input in = new AddSubgroups.Input();
in.groups = ImmutableList.of(id.get());
try {
- List<GroupInfo> list = addSubgroups.apply(resource, in);
+ List<GroupInfo> list = addSubgroups.apply(resource, in).value();
if (list.size() == 1) {
- return list.get(0);
+ return Response.created(list.get(0));
}
throw new IllegalStateException();
} catch (UnprocessableEntityException e) {
@@ -169,7 +168,7 @@ public class AddSubgroups implements RestModifyView<GroupResource, Input> {
}
@Override
- public GroupInfo apply(SubgroupResource resource, Input input)
+ public Response<GroupInfo> apply(SubgroupResource resource, Input input)
throws PermissionBackendException {
// Do nothing, the group is already included.
return get.get().apply(resource);
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index eaedcd7a18..7c5e7ed38b 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -19,6 +19,8 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -29,12 +31,11 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.UserInitiated;
@@ -122,7 +123,7 @@ public class CreateGroup
}
@Override
- public GroupInfo apply(TopLevelResource resource, IdString id, GroupInput input)
+ public Response<GroupInfo> apply(TopLevelResource resource, IdString id, GroupInput input)
throws AuthException, BadRequestException, UnprocessableEntityException,
ResourceConflictException, IOException, ConfigInvalidException, ResourceNotFoundException,
PermissionBackendException {
@@ -137,6 +138,16 @@ public class CreateGroup
AccountGroup.UUID ownerUuid = owner(input);
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(name);
+ args.uuid = Strings.isNullOrEmpty(input.uuid) ? null : AccountGroup.UUID.parse(input.uuid);
+ if (args.uuid != null) {
+ if (!AccountGroup.isInternalGroup(args.uuid)) {
+ throw new BadRequestException(String.format("invalid group UUID '%s'", args.uuid.get()));
+ }
+ if (groupCache.get(args.uuid).isPresent()) {
+ throw new ResourceConflictException(
+ String.format("group with UUID '%s' already exists", args.uuid.get()));
+ }
+ }
args.groupDescription = Strings.emptyToNull(input.description);
args.visibleToAll = MoreObjects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
args.ownerGroupUuid = ownerUuid;
@@ -148,7 +159,7 @@ public class CreateGroup
throw new UnprocessableEntityException(
String.format("Account Inactive: %s", nameOrEmailOrId));
}
- members.add(a.getId());
+ members.add(a.id());
}
args.initialMembers = members;
} else {
@@ -165,7 +176,7 @@ public class CreateGroup
throw new ResourceConflictException(e.getMessage(), e);
}
- return json.format(new InternalGroupDescription(createGroup(args)));
+ return Response.created(json.format(new InternalGroupDescription(createGroup(args))));
}
private AccountGroup.UUID owner(GroupInput input) throws UnprocessableEntityException {
@@ -193,11 +204,13 @@ public class CreateGroup
}
}
- AccountGroup.Id groupId = new AccountGroup.Id(sequences.nextGroupId());
+ AccountGroup.Id groupId = AccountGroup.id(sequences.nextGroupId());
AccountGroup.UUID uuid =
- GroupUUID.make(
- createGroupArgs.getGroupName(),
- self.get().newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone()));
+ MoreObjects.firstNonNull(
+ createGroupArgs.uuid,
+ GroupUUID.make(
+ createGroupArgs.getGroupName(),
+ self.get().newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone())));
InternalGroupCreation groupCreation =
InternalGroupCreation.builder()
.setGroupUUID(uuid)
diff --git a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
index b5771a34c3..b9fd00004c 100644
--- a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
@@ -16,6 +16,8 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -23,8 +25,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupControl;
@@ -68,7 +68,7 @@ public class DeleteMembers implements RestModifyView<GroupResource, Input> {
Set<Account.Id> membersToRemove = new HashSet<>();
for (String nameOrEmail : input.members) {
- membersToRemove.add(accountResolver.resolve(nameOrEmail).asUnique().getAccount().getId());
+ membersToRemove.add(accountResolver.resolve(nameOrEmail).asUnique().account().id());
}
AccountGroup.UUID groupUuid = internalGroup.getGroupUUID();
try {
diff --git a/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java b/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
index 9ecfa1f696..b9d6ca80a9 100644
--- a/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/DeleteSubgroups.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -24,7 +25,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupResolver;
diff --git a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
index 1a781d989d..508547d45e 100644
--- a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
+++ b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
@@ -17,15 +17,16 @@ package com.google.gerrit.server.restapi.group;
import static java.util.Comparator.comparing;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.GroupAuditEventInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache;
@@ -74,7 +75,7 @@ public class GetAuditLog implements RestReadView<GroupResource> {
}
@Override
- public List<? extends GroupAuditEventInfo> apply(GroupResource rsrc)
+ public Response<List<? extends GroupAuditEventInfo>> apply(GroupResource rsrc)
throws AuthException, NotInternalGroupException, IOException, ConfigInvalidException,
PermissionBackendException {
GroupDescription.Internal group =
@@ -90,22 +91,24 @@ public class GetAuditLog implements RestReadView<GroupResource> {
try (Repository allUsersRepo = repoManager.openRepository(allUsers)) {
for (AccountGroupMemberAudit auditEvent :
groups.getMembersAudit(allUsersRepo, group.getGroupUUID())) {
- AccountInfo member = accountLoader.get(auditEvent.getMemberId());
+ AccountInfo member = accountLoader.get(auditEvent.memberId());
auditEvents.add(
GroupAuditEventInfo.createAddUserEvent(
- accountLoader.get(auditEvent.getAddedBy()), auditEvent.getAddedOn(), member));
+ accountLoader.get(auditEvent.addedBy()), auditEvent.addedOn(), member));
if (!auditEvent.isActive()) {
auditEvents.add(
GroupAuditEventInfo.createRemoveUserEvent(
- accountLoader.get(auditEvent.getRemovedBy()), auditEvent.getRemovedOn(), member));
+ accountLoader.get(auditEvent.removedBy().orElse(null)),
+ auditEvent.removedOn(),
+ member));
}
}
- for (AccountGroupByIdAud auditEvent :
+ for (AccountGroupByIdAudit auditEvent :
groups.getSubgroupsAudit(allUsersRepo, group.getGroupUUID())) {
- AccountGroup.UUID includedGroupUUID = auditEvent.getIncludeUUID();
+ AccountGroup.UUID includedGroupUUID = auditEvent.includeUuid();
Optional<InternalGroup> includedGroup = groupCache.get(includedGroupUUID);
GroupInfo member;
if (includedGroup.isPresent()) {
@@ -121,14 +124,14 @@ public class GetAuditLog implements RestReadView<GroupResource> {
auditEvents.add(
GroupAuditEventInfo.createAddGroupEvent(
- accountLoader.get(auditEvent.getAddedBy()),
- auditEvent.getKey().getAddedOn(),
- member));
+ accountLoader.get(auditEvent.addedBy()), auditEvent.addedOn(), member));
if (!auditEvent.isActive()) {
auditEvents.add(
GroupAuditEventInfo.createRemoveGroupEvent(
- accountLoader.get(auditEvent.getRemovedBy()), auditEvent.getRemovedOn(), member));
+ accountLoader.get(auditEvent.removedBy().orElse(null)),
+ auditEvent.removedOn(),
+ member));
}
}
}
@@ -137,6 +140,6 @@ public class GetAuditLog implements RestReadView<GroupResource> {
// sort by date and then reverse so that the newest audit event comes first
auditEvents.sort(comparing((GroupAuditEventInfo a) -> a.date).reversed());
- return auditEvents;
+ return Response.ok(auditEvents);
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetDescription.java b/java/com/google/gerrit/server/restapi/group/GetDescription.java
index c34fda77f9..b77028118d 100644
--- a/java/com/google/gerrit/server/restapi/group/GetDescription.java
+++ b/java/com/google/gerrit/server/restapi/group/GetDescription.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupResource;
import com.google.inject.Singleton;
@@ -23,9 +24,9 @@ import com.google.inject.Singleton;
@Singleton
public class GetDescription implements RestReadView<GroupResource> {
@Override
- public String apply(GroupResource resource) throws NotInternalGroupException {
+ public Response<String> apply(GroupResource resource) throws NotInternalGroupException {
GroupDescription.Internal group =
resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
- return Strings.nullToEmpty(group.getDescription());
+ return Response.ok(Strings.nullToEmpty(group.getDescription()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetDetail.java b/java/com/google/gerrit/server/restapi/group/GetDetail.java
index c7573836a2..f6b89304f7 100644
--- a/java/com/google/gerrit/server/restapi/group/GetDetail.java
+++ b/java/com/google/gerrit/server/restapi/group/GetDetail.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.group;
import com.google.gerrit.extensions.client.ListGroupsOption;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -32,7 +33,7 @@ public class GetDetail implements RestReadView<GroupResource> {
}
@Override
- public GroupInfo apply(GroupResource rsrc) throws PermissionBackendException {
- return json.format(rsrc);
+ public Response<GroupInfo> apply(GroupResource rsrc) throws PermissionBackendException {
+ return Response.ok(json.format(rsrc));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetGroup.java b/java/com/google/gerrit/server/restapi/group/GetGroup.java
index 3ae447bf6e..4785d25f54 100644
--- a/java/com/google/gerrit/server/restapi/group/GetGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/GetGroup.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.group;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -31,7 +32,7 @@ public class GetGroup implements RestReadView<GroupResource> {
}
@Override
- public GroupInfo apply(GroupResource resource) throws PermissionBackendException {
- return json.format(resource.getGroup());
+ public Response<GroupInfo> apply(GroupResource resource) throws PermissionBackendException {
+ return Response.ok(json.format(resource.getGroup()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetMember.java b/java/com/google/gerrit/server/restapi/group/GetMember.java
index 63a8a1bba3..8dbcd27d5c 100644
--- a/java/com/google/gerrit/server/restapi/group/GetMember.java
+++ b/java/com/google/gerrit/server/restapi/group/GetMember.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.group;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.group.MemberResource;
@@ -32,10 +33,10 @@ public class GetMember implements RestReadView<MemberResource> {
}
@Override
- public AccountInfo apply(MemberResource rsrc) throws PermissionBackendException {
+ public Response<AccountInfo> apply(MemberResource rsrc) throws PermissionBackendException {
AccountLoader loader = infoFactory.create(true);
AccountInfo info = loader.get(rsrc.getMember().getAccountId());
loader.fill();
- return info;
+ return Response.ok(info);
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetName.java b/java/com/google/gerrit/server/restapi/group/GetName.java
index 8cc1fe0c2f..131dbe41c9 100644
--- a/java/com/google/gerrit/server/restapi/group/GetName.java
+++ b/java/com/google/gerrit/server/restapi/group/GetName.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.group;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
public class GetName implements RestReadView<GroupResource> {
@Override
- public String apply(GroupResource resource) {
- return resource.getName();
+ public Response<String> apply(GroupResource resource) {
+ return Response.ok(resource.getName());
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetOptions.java b/java/com/google/gerrit/server/restapi/group/GetOptions.java
index e5bfe30100..5d8ba0251c 100644
--- a/java/com/google/gerrit/server/restapi/group/GetOptions.java
+++ b/java/com/google/gerrit/server/restapi/group/GetOptions.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.group;
import com.google.gerrit.extensions.common.GroupOptionsInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupResource;
import com.google.inject.Singleton;
@@ -23,7 +24,7 @@ import com.google.inject.Singleton;
public class GetOptions implements RestReadView<GroupResource> {
@Override
- public GroupOptionsInfo apply(GroupResource resource) {
- return GroupJson.createOptions(resource.getGroup());
+ public Response<GroupOptionsInfo> apply(GroupResource resource) {
+ return Response.ok(GroupJson.createOptions(resource.getGroup()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetOwner.java b/java/com/google/gerrit/server/restapi/group/GetOwner.java
index 10e1b23dc1..e8bdfaae2b 100644
--- a/java/com/google/gerrit/server/restapi/group/GetOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/GetOwner.java
@@ -18,6 +18,7 @@ import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupResource;
@@ -38,13 +39,13 @@ public class GetOwner implements RestReadView<GroupResource> {
}
@Override
- public GroupInfo apply(GroupResource resource)
+ public Response<GroupInfo> apply(GroupResource resource)
throws NotInternalGroupException, ResourceNotFoundException, PermissionBackendException {
GroupDescription.Internal group =
resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
try {
GroupControl c = controlFactory.validateFor(group.getOwnerGroupUUID());
- return json.format(c.getGroup());
+ return Response.ok(json.format(c.getGroup()));
} catch (NoSuchGroupException e) {
throw new ResourceNotFoundException(group.getOwnerGroupUUID().get(), e);
}
diff --git a/java/com/google/gerrit/server/restapi/group/GetSubgroup.java b/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
index 446618009c..c209511f6c 100644
--- a/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
+++ b/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.group;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.SubgroupResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -31,7 +32,7 @@ public class GetSubgroup implements RestReadView<SubgroupResource> {
}
@Override
- public GroupInfo apply(SubgroupResource rsrc) throws PermissionBackendException {
- return json.format(rsrc.getMemberDescription());
+ public Response<GroupInfo> apply(SubgroupResource rsrc) throws PermissionBackendException {
+ return Response.ok(json.format(rsrc.getMemberDescription()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/GroupJson.java b/java/com/google/gerrit/server/restapi/group/GroupJson.java
index 12b9d613a7..99c9df7152 100644
--- a/java/com/google/gerrit/server/restapi/group/GroupJson.java
+++ b/java/com/google/gerrit/server/restapi/group/GroupJson.java
@@ -20,11 +20,11 @@ import static com.google.gerrit.extensions.client.ListGroupsOption.MEMBERS;
import com.google.common.base.Strings;
import com.google.common.base.Suppliers;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.client.ListGroupsOption;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.GroupOptionsInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupResource;
diff --git a/java/com/google/gerrit/server/restapi/group/Index.java b/java/com/google/gerrit/server/restapi/group/Index.java
index 1267f7a836..d64669f3c5 100644
--- a/java/com/google/gerrit/server/restapi/group/Index.java
+++ b/java/com/google/gerrit/server/restapi/group/Index.java
@@ -14,12 +14,12 @@
package com.google.gerrit.server.restapi.group;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/group/ListGroups.java b/java/com/google/gerrit/server/restapi/group/ListGroups.java
index 7c52300e12..1802ea636f 100644
--- a/java/com/google/gerrit/server/restapi/group/ListGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListGroups.java
@@ -23,17 +23,18 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.client.ListGroupsOption;
import com.google.gerrit.extensions.client.ListOption;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
@@ -250,18 +251,16 @@ public class ListGroups implements RestReadView<TopLevelResource> {
}
@Override
- public SortedMap<String, GroupInfo> apply(TopLevelResource resource)
- throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
+ public Response<SortedMap<String, GroupInfo>> apply(TopLevelResource resource) throws Exception {
SortedMap<String, GroupInfo> output = new TreeMap<>();
for (GroupInfo info : get()) {
output.put(MoreObjects.firstNonNull(info.name, "Group " + Url.decode(info.id)), info);
info.name = null;
}
- return output;
+ return Response.ok(output);
}
- public List<GroupInfo> get()
- throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
+ public List<GroupInfo> get() throws Exception {
if (!Strings.isNullOrEmpty(suggest)) {
return suggestGroups();
}
@@ -279,7 +278,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
}
if (user != null) {
- return accountGetGroups.apply(new AccountResource(userFactory.create(user)));
+ return accountGetGroups.apply(new AccountResource(userFactory.create(user))).value();
}
return getAllGroups();
diff --git a/java/com/google/gerrit/server/restapi/group/ListMembers.java b/java/com/google/gerrit/server/restapi/group/ListMembers.java
index 75be44cbee..23f0aa7ace 100644
--- a/java/com/google/gerrit/server/restapi/group/ListMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/ListMembers.java
@@ -21,10 +21,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountInfoComparator;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GroupCache;
@@ -65,14 +66,14 @@ public class ListMembers implements RestReadView<GroupResource> {
}
@Override
- public List<AccountInfo> apply(GroupResource resource)
+ public Response<List<AccountInfo>> apply(GroupResource resource)
throws NotInternalGroupException, PermissionBackendException {
GroupDescription.Internal group =
resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
if (recursive) {
- return getTransitiveMembers(group, resource.getControl());
+ return Response.ok(getTransitiveMembers(group, resource.getControl()));
}
- return getDirectMembers(group, resource.getControl());
+ return Response.ok(getDirectMembers(group, resource.getControl()));
}
public List<AccountInfo> getTransitiveMembers(AccountGroup.UUID groupUuid)
diff --git a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
index bb72a10d0b..540718ff7b 100644
--- a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
@@ -19,10 +19,11 @@ import static java.util.Comparator.comparing;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -45,12 +46,12 @@ public class ListSubgroups implements RestReadView<GroupResource> {
}
@Override
- public List<GroupInfo> apply(GroupResource rsrc)
+ public Response<List<GroupInfo>> apply(GroupResource rsrc)
throws NotInternalGroupException, PermissionBackendException {
GroupDescription.Internal group =
rsrc.asInternalGroup().orElseThrow(NotInternalGroupException::new);
- return getDirectSubgroups(group, rsrc.getControl());
+ return Response.ok(getDirectSubgroups(group, rsrc.getControl()));
}
public List<GroupInfo> getDirectSubgroups(
diff --git a/java/com/google/gerrit/server/restapi/group/PutDescription.java b/java/com/google/gerrit/server/restapi/group/PutDescription.java
index c4e6f09cfa..8fe4b20585 100644
--- a/java/com/google/gerrit/server/restapi/group/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/group/PutDescription.java
@@ -16,13 +16,13 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.db.GroupsUpdate;
diff --git a/java/com/google/gerrit/server/restapi/group/PutName.java b/java/com/google/gerrit/server/restapi/group/PutName.java
index adc20d523f..9a3c87d5d7 100644
--- a/java/com/google/gerrit/server/restapi/group/PutName.java
+++ b/java/com/google/gerrit/server/restapi/group/PutName.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.NameInput;
@@ -23,8 +24,8 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.db.GroupsUpdate;
@@ -45,7 +46,7 @@ public class PutName implements RestModifyView<GroupResource, NameInput> {
}
@Override
- public String apply(GroupResource rsrc, NameInput input)
+ public Response<String> apply(GroupResource rsrc, NameInput input)
throws NotInternalGroupException, AuthException, BadRequestException,
ResourceConflictException, ResourceNotFoundException, IOException,
ConfigInvalidException {
@@ -62,11 +63,11 @@ public class PutName implements RestModifyView<GroupResource, NameInput> {
}
if (internalGroup.getName().equals(newName)) {
- return newName;
+ return Response.ok(newName);
}
renameGroup(internalGroup, newName);
- return newName;
+ return Response.ok(newName);
}
private void renameGroup(GroupDescription.Internal group, String newName)
@@ -74,7 +75,7 @@ public class PutName implements RestModifyView<GroupResource, NameInput> {
ConfigInvalidException {
AccountGroup.UUID groupUuid = group.getGroupUUID();
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey(newName)).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey(newName)).build();
try {
groupsUpdateProvider.get().updateGroup(groupUuid, groupUpdate);
} catch (NoSuchGroupException e) {
diff --git a/java/com/google/gerrit/server/restapi/group/PutOptions.java b/java/com/google/gerrit/server/restapi/group/PutOptions.java
index 747d899a9b..53bf5715d8 100644
--- a/java/com/google/gerrit/server/restapi/group/PutOptions.java
+++ b/java/com/google/gerrit/server/restapi/group/PutOptions.java
@@ -15,13 +15,14 @@
package com.google.gerrit.server.restapi.group;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.common.GroupOptionsInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.group.db.GroupsUpdate;
@@ -42,7 +43,7 @@ public class PutOptions implements RestModifyView<GroupResource, GroupOptionsInf
}
@Override
- public GroupOptionsInfo apply(GroupResource resource, GroupOptionsInfo input)
+ public Response<GroupOptionsInfo> apply(GroupResource resource, GroupOptionsInfo input)
throws NotInternalGroupException, AuthException, BadRequestException,
ResourceNotFoundException, IOException, ConfigInvalidException {
GroupDescription.Internal internalGroup =
@@ -73,6 +74,6 @@ public class PutOptions implements RestModifyView<GroupResource, GroupOptionsInf
if (input.visibleToAll) {
options.visibleToAll = true;
}
- return options;
+ return Response.ok(options);
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/PutOwner.java b/java/com/google/gerrit/server/restapi/group/PutOwner.java
index f766a84c1d..04129af65f 100644
--- a/java/com/google/gerrit/server/restapi/group/PutOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/PutOwner.java
@@ -16,15 +16,16 @@ package com.google.gerrit.server.restapi.group;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.api.groups.OwnerInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.group.GroupResolver;
import com.google.gerrit.server.group.GroupResource;
@@ -54,7 +55,7 @@ public class PutOwner implements RestModifyView<GroupResource, OwnerInput> {
}
@Override
- public GroupInfo apply(GroupResource resource, OwnerInput input)
+ public Response<GroupInfo> apply(GroupResource resource, OwnerInput input)
throws ResourceNotFoundException, NotInternalGroupException, AuthException,
BadRequestException, UnprocessableEntityException, IOException, ConfigInvalidException,
PermissionBackendException {
@@ -79,6 +80,6 @@ public class PutOwner implements RestModifyView<GroupResource, OwnerInput> {
throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
}
}
- return json.format(owner);
+ return Response.ok(json.format(owner));
}
}
diff --git a/java/com/google/gerrit/server/restapi/group/QueryGroups.java b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
index b8dd28d7f3..a233111963 100644
--- a/java/com/google/gerrit/server/restapi/group/QueryGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
@@ -21,6 +21,7 @@ import com.google.gerrit.extensions.client.ListOption;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.QueryParseException;
@@ -97,7 +98,7 @@ public class QueryGroups implements RestReadView<TopLevelResource> {
}
@Override
- public List<GroupInfo> apply(TopLevelResource resource)
+ public Response<List<GroupInfo>> apply(TopLevelResource resource)
throws BadRequestException, MethodNotAllowedException, PermissionBackendException {
if (Strings.isNullOrEmpty(query)) {
throw new BadRequestException("missing query field");
@@ -129,7 +130,7 @@ public class QueryGroups implements RestReadView<TopLevelResource> {
if (!groupInfos.isEmpty() && result.more()) {
groupInfos.get(groupInfos.size() - 1)._moreGroups = true;
}
- return groupInfos;
+ return Response.ok(groupInfos);
} catch (QueryParseException e) {
throw new BadRequestException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/project/BanCommit.java b/java/com/google/gerrit/server/restapi/project/BanCommit.java
index 3d101b2484..64e38b0176 100644
--- a/java/com/google/gerrit/server/restapi/project/BanCommit.java
+++ b/java/com/google/gerrit/server/restapi/project/BanCommit.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.api.projects.BanCommitInput;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.git.BanCommitResult;
@@ -45,7 +46,7 @@ public class BanCommit
}
@Override
- protected BanResultInfo applyImpl(
+ protected Response<BanResultInfo> applyImpl(
BatchUpdate.Factory updateFactory, ProjectResource rsrc, BanCommitInput input)
throws RestApiException, UpdateException, IOException, PermissionBackendException {
BanResultInfo r = new BanResultInfo();
@@ -65,7 +66,7 @@ public class BanCommit
r.alreadyBanned = transformCommits(result.getAlreadyBannedCommits());
r.ignored = transformCommits(result.getIgnoredObjectIds());
}
- return r;
+ return Response.ok(r);
}
private static List<String> transformCommits(List<ObjectId> commits) {
diff --git a/java/com/google/gerrit/server/restapi/project/BranchesCollection.java b/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
index 5fbb4f8b35..2d78bb05a5 100644
--- a/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/BranchesCollection.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.project;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -21,8 +23,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/restapi/project/Check.java b/java/com/google/gerrit/server/restapi/project/Check.java
index a6fd7646ae..66a2df4bb6 100644
--- a/java/com/google/gerrit/server/restapi/project/Check.java
+++ b/java/com/google/gerrit/server/restapi/project/Check.java
@@ -19,6 +19,7 @@ import com.google.gerrit.extensions.api.projects.CheckProjectResultInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -40,9 +41,9 @@ public class Check implements RestModifyView<ProjectResource, CheckProjectInput>
}
@Override
- public CheckProjectResultInfo apply(ProjectResource rsrc, CheckProjectInput input)
+ public Response<CheckProjectResultInfo> apply(ProjectResource rsrc, CheckProjectInput input)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
permissionBackend.user(rsrc.getUser()).check(GlobalPermission.ADMINISTRATE_SERVER);
- return projectsConsistencyChecker.check(rsrc.getNameKey(), input);
+ return Response.ok(projectsConsistencyChecker.check(rsrc.getNameKey(), input));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CheckAccess.java b/java/com/google/gerrit/server/restapi/project/CheckAccess.java
index 67b68b578f..037a95396c 100644
--- a/java/com/google/gerrit/server/restapi/project/CheckAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/CheckAccess.java
@@ -14,17 +14,18 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_HEADS;
+import static com.google.gerrit.entities.RefNames.REFS_HEADS;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
import com.google.gerrit.extensions.api.config.AccessCheckInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.DefaultPermissionMappings;
@@ -59,7 +60,7 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
}
@Override
- public AccessCheckInfo apply(ProjectResource rsrc, AccessCheckInput input)
+ public Response<AccessCheckInfo> apply(ProjectResource rsrc, AccessCheckInput input)
throws PermissionBackendException, RestApiException, IOException, ConfigInvalidException {
permissionBackend.user(rsrc.getUser()).check(GlobalPermission.VIEW_ACCESS);
@@ -72,7 +73,7 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
throw new BadRequestException("input requires 'account'");
}
- Account.Id match = accountResolver.resolve(input.account).asUnique().getAccount().getId();
+ Account.Id match = accountResolver.resolve(input.account).asUnique().account().id();
AccessCheckInfo info = new AccessCheckInfo();
try {
@@ -83,7 +84,7 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
} catch (AuthException e) {
info.message = String.format("user %s cannot see project %s", match, rsrc.getName());
info.status = HttpServletResponse.SC_FORBIDDEN;
- return info;
+ return Response.ok(info);
}
RefPermission refPerm;
@@ -106,7 +107,7 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
try {
permissionBackend
.absentUser(match)
- .ref(new Branch.NameKey(rsrc.getNameKey(), input.ref))
+ .ref(BranchNameKey.create(rsrc.getNameKey(), input.ref))
.check(refPerm);
} catch (AuthException e) {
info.status = HttpServletResponse.SC_FORBIDDEN;
@@ -114,7 +115,7 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
String.format(
"user %s lacks permission %s for %s in project %s",
match, input.permission, input.ref, rsrc.getName());
- return info;
+ return Response.ok(info);
}
} else {
// We say access is okay if there are no refs, but this warrants a warning,
@@ -126,6 +127,6 @@ public class CheckAccess implements RestModifyView<ProjectResource, AccessCheckI
}
}
info.status = HttpServletResponse.SC_OK;
- return info;
+ return Response.ok(info);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CheckAccessReadView.java b/java/com/google/gerrit/server/restapi/project/CheckAccessReadView.java
index 770e8c3e97..6aaa6783fe 100644
--- a/java/com/google/gerrit/server/restapi/project/CheckAccessReadView.java
+++ b/java/com/google/gerrit/server/restapi/project/CheckAccessReadView.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
import com.google.gerrit.extensions.api.config.AccessCheckInput;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -48,7 +49,7 @@ public class CheckAccessReadView implements RestReadView<ProjectResource> {
}
@Override
- public AccessCheckInfo apply(ProjectResource rsrc)
+ public Response<AccessCheckInfo> apply(ProjectResource rsrc)
throws PermissionBackendException, RestApiException, IOException, ConfigInvalidException {
AccessCheckInput input = new AccessCheckInput();
diff --git a/java/com/google/gerrit/server/restapi/project/CheckMergeability.java b/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
index de2ac6458d..4864fdef28 100644
--- a/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
+++ b/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
@@ -14,10 +14,12 @@
package com.google.gerrit.server.restapi.project;
+import com.google.gerrit.exceptions.InvalidMergeStrategyException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -75,7 +77,7 @@ public class CheckMergeability implements RestReadView<BranchResource> {
}
@Override
- public MergeableInfo apply(BranchResource resource)
+ public Response<MergeableInfo> apply(BranchResource resource)
throws IOException, BadRequestException, ResourceNotFoundException {
if (!(submitType.equals(SubmitType.MERGE_ALWAYS)
|| submitType.equals(SubmitType.MERGE_IF_NECESSARY))) {
@@ -106,7 +108,7 @@ public class CheckMergeability implements RestReadView<BranchResource> {
result.mergeable = true;
result.commitMerged = true;
result.contentMerged = true;
- return result;
+ return Response.ok(result);
}
if (m.merge(false, targetCommit, sourceCommit)) {
@@ -119,9 +121,9 @@ public class CheckMergeability implements RestReadView<BranchResource> {
result.conflicts = ((ResolveMerger) m).getUnmergedPaths();
}
}
- } catch (IllegalArgumentException e) {
+ } catch (InvalidMergeStrategyException e) {
throw new BadRequestException(e.getMessage());
}
- return result;
+ return Response.ok(result);
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
index 8cc8298a02..e566858782 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
@@ -14,11 +14,13 @@
package com.google.gerrit.server.restapi.project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.CommitResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -35,9 +37,10 @@ public class CommitIncludedIn implements RestReadView<CommitResource> {
}
@Override
- public IncludedInInfo apply(CommitResource rsrc) throws RestApiException, IOException {
+ public Response<IncludedInInfo> apply(CommitResource rsrc)
+ throws RestApiException, IOException, PermissionBackendException {
RevCommit commit = rsrc.getCommit();
Project.NameKey project = rsrc.getProjectState().getNameKey();
- return includedIn.apply(project, commit.getId().getName());
+ return Response.ok(includedIn.apply(project, commit.getId().getName()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index d6c44691cc..d5380c67ae 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -16,14 +16,16 @@ package com.google.gerrit.server.restapi.project;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import com.google.common.base.Throwables;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.project.CommitResource;
@@ -32,6 +34,9 @@ import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.Reachable;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryHelper.Action;
+import com.google.gerrit.server.update.RetryHelper.ActionType;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -49,6 +54,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
public class CommitsCollection implements ChildCollection<ProjectResource, CommitResource> {
private final DynamicMap<RestView<CommitResource>> views;
private final GitRepositoryManager repoManager;
+ private final RetryHelper retryHelper;
private final ChangeIndexCollection indexes;
private final Provider<InternalChangeQuery> queryProvider;
private final Reachable reachable;
@@ -57,11 +63,13 @@ public class CommitsCollection implements ChildCollection<ProjectResource, Commi
public CommitsCollection(
DynamicMap<RestView<CommitResource>> views,
GitRepositoryManager repoManager,
+ RetryHelper retryHelper,
ChangeIndexCollection indexes,
Provider<InternalChangeQuery> queryProvider,
Reachable reachable) {
this.views = views;
this.repoManager = repoManager;
+ this.retryHelper = retryHelper;
this.indexes = indexes;
this.queryProvider = queryProvider;
this.reachable = reachable;
@@ -115,7 +123,13 @@ public class CommitsCollection implements ChildCollection<ProjectResource, Commi
// Check first if any change references the commit in question. This is much cheaper than ref
// visibility filtering and reachability computation.
List<ChangeData> changes =
- queryProvider.get().enforceVisibility(true).setLimit(1).byProjectCommit(project, commit);
+ executeIndexQuery(
+ () ->
+ queryProvider
+ .get()
+ .enforceVisibility(true)
+ .setLimit(1)
+ .byProjectCommit(project, commit));
if (!changes.isEmpty()) {
return true;
}
@@ -128,4 +142,14 @@ public class CommitsCollection implements ChildCollection<ProjectResource, Commi
.collect(toImmutableList());
return reachable.fromRefs(project, repo, commit, refs);
}
+
+ private <T> T executeIndexQuery(Action<T> action) {
+ try {
+ return retryHelper.execute(
+ ActionType.INDEX_QUERY, action, StorageException.class::isInstance);
+ } catch (Exception e) {
+ Throwables.throwIfUnchecked(e);
+ throw new StorageException(e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 37bc265dee..5deace9ba4 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
@@ -25,8 +27,6 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index 0741377759..fe48301825 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -16,6 +16,10 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -24,10 +28,6 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeJson;
@@ -112,7 +112,7 @@ public class CreateAccessChange implements RestModifyView<ProjectResource, Proje
List<AccessSection> additions = setAccess.getAccessSections(input.add);
Project.NameKey newParentProjectName =
- input.parent == null ? null : new Project.NameKey(input.parent);
+ input.parent == null ? null : Project.nameKey(input.parent);
try (MetaDataUpdate md = metaDataUpdateUser.create(rsrc.getNameKey())) {
ProjectConfig config = projectConfigFactory.read(md);
@@ -134,11 +134,10 @@ public class CreateAccessChange implements RestModifyView<ProjectResource, Proje
md.setMessage("Review access change");
md.setInsertChangeId(true);
- Change.Id changeId = new Change.Id(seq.nextChangeId());
+ Change.Id changeId = Change.id(seq.nextChangeId());
RevCommit commit =
- config.commitToNewRef(
- md, new PatchSet.Id(changeId, Change.INITIAL_PATCH_SET_ID).toRefName());
+ config.commitToNewRef(md, PatchSet.id(changeId, Change.INITIAL_PATCH_SET_ID).toRefName());
if (commit.name().equals(oldCommitSha1)) {
throw new BadRequestException("no change");
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index 01e0cdfc41..fd6e024732 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -14,19 +14,20 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -82,7 +83,7 @@ public class CreateBranch
}
@Override
- public BranchInfo apply(ProjectResource rsrc, IdString id, BranchInput input)
+ public Response<BranchInfo> apply(ProjectResource rsrc, IdString id, BranchInput input)
throws BadRequestException, AuthException, ResourceConflictException, IOException,
PermissionBackendException, NoSuchProjectException {
String ref = id.get();
@@ -112,7 +113,7 @@ public class CreateBranch
+ "\"");
}
- final Branch.NameKey name = new Branch.NameKey(rsrc.getNameKey(), ref);
+ final BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
ObjectId revid = RefUtil.parseBaseRevision(repo, rsrc.getNameKey(), input.revision);
RevWalk rw = RefUtil.verifyConnected(repo, revid);
@@ -144,7 +145,7 @@ public class CreateBranch
case NEW:
case NO_CHANGE:
referenceUpdated.fire(
- name.getParentKey(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
+ name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
break;
case LOCK_FAILURE:
if (repo.getRefDatabase().exactRef(ref) != null) {
@@ -182,7 +183,7 @@ public class CreateBranch
info.ref = ref;
info.revision = revid.getName();
- if (isConfigRef(name.get())) {
+ if (isConfigRef(name.branch())) {
// Never allow to delete the meta config branch.
info.canDelete = null;
} else {
@@ -192,7 +193,7 @@ public class CreateBranch
? true
: null;
}
- return info;
+ return Response.created(info);
} catch (IOException err) {
logger.atSevere().withCause(err).log("Cannot create branch \"%s\"", name);
throw err;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateDashboard.java b/java/com/google/gerrit/server/restapi/project/CreateDashboard.java
index e8b6236e89..314df73238 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateDashboard.java
@@ -19,15 +19,12 @@ import com.google.gerrit.extensions.api.projects.SetDashboardInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.DashboardResource;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import org.kohsuke.args4j.Option;
@Singleton
@@ -45,14 +42,16 @@ public class CreateDashboard
@Override
public Response<DashboardInfo> apply(ProjectResource parent, IdString id, SetDashboardInput input)
- throws RestApiException, IOException, PermissionBackendException {
+ throws Exception {
parent.getProjectState().checkStatePermitsWrite();
if (!DashboardsCollection.isDefaultDashboard(id)) {
throw new ResourceNotFoundException(id);
}
SetDefaultDashboard set = setDefault.get();
set.inherited = inherited;
- return set.apply(
- DashboardResource.projectDefault(parent.getProjectState(), parent.getUser()), input);
+ return Response.created(
+ set.apply(
+ DashboardResource.projectDefault(parent.getProjectState(), parent.getUser()), input)
+ .value());
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/CreateProject.java b/java/com/google/gerrit/server/restapi/project/CreateProject.java
index 8b81f10286..a5a00346bd 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateProject.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateProject.java
@@ -20,6 +20,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -33,7 +34,6 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index 6a04e0dac3..5cfb118d28 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -25,6 +25,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.server.WebLinks;
@@ -78,7 +79,7 @@ public class CreateTag implements RestCollectionCreateView<ProjectResource, TagR
}
@Override
- public TagInfo apply(ProjectResource resource, IdString id, TagInput input)
+ public Response<TagInfo> apply(ProjectResource resource, IdString id, TagInput input)
throws RestApiException, IOException, PermissionBackendException, NoSuchProjectException {
String ref = id.get();
if (input == null) {
@@ -146,7 +147,8 @@ public class CreateTag implements RestCollectionCreateView<ProjectResource, TagR
result.getObjectId(),
resource.getUser().asIdentifiedUser().state());
try (RevWalk w = new RevWalk(repo)) {
- return ListTags.createTagInfo(perm, result, w, resource.getProjectState(), links);
+ return Response.created(
+ ListTags.createTagInfo(perm, result, w, resource.getProjectState(), links));
}
}
} catch (InvalidRevisionException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
index 4dc83e87c1..ca48109065 100644
--- a/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/DashboardsCollection.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
+import static com.google.gerrit.entities.RefNames.REFS_DASHBOARDS;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
@@ -22,6 +22,7 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -33,7 +34,6 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.UrlEncoded;
import com.google.gerrit.server.git.GitRepositoryManager;
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
index b94fb51bf7..6248a61cd9 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
@@ -14,16 +14,16 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.BranchResource;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -47,12 +47,12 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input> {
@Override
public Response<?> apply(BranchResource rsrc, Input input)
throws RestApiException, IOException, PermissionBackendException {
- if (RefNames.HEAD.equals(rsrc.getBranchKey().get())) {
+ if (RefNames.HEAD.equals(rsrc.getBranchKey().branch())) {
throw new MethodNotAllowedException("not allowed to delete HEAD");
- } else if (isConfigRef(rsrc.getBranchKey().get())) {
+ } else if (isConfigRef(rsrc.getBranchKey().branch())) {
// Never allow to delete the meta config branch.
throw new MethodNotAllowedException(
- "not allowed to delete branch " + rsrc.getBranchKey().get());
+ "not allowed to delete branch " + rsrc.getBranchKey().branch());
}
if (!queryProvider.get().setLimit(1).byBranchOpen(rsrc.getBranchKey()).isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteBranches.java b/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
index ba25e3394e..ca5962e1b5 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.restapi.project;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteDashboard.java b/java/com/google/gerrit/server/restapi/project/DeleteDashboard.java
index 2702d58803..9d9e5f5f39 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteDashboard.java
@@ -18,14 +18,11 @@ import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.SetDashboardInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.DashboardResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
@Singleton
public class DeleteDashboard implements RestModifyView<DashboardResource, SetDashboardInput> {
@@ -38,7 +35,7 @@ public class DeleteDashboard implements RestModifyView<DashboardResource, SetDas
@Override
public Response<DashboardInfo> apply(DashboardResource resource, SetDashboardInput input)
- throws RestApiException, IOException, PermissionBackendException {
+ throws Exception {
if (resource.isProjectDefault()) {
SetDashboardInput in = new SetDashboardInput();
in.commitMessage = input != null ? input.commitMessage : null;
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteRef.java b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
index dae759ae79..1979d611ed 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteRef.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.restapi.project;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import static java.lang.String.format;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -25,11 +25,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -260,7 +260,7 @@ public class DeleteRef {
}
if (!refName.startsWith(R_TAGS)) {
- Branch.NameKey branchKey = new Branch.NameKey(projectState.getNameKey(), ref.getName());
+ BranchNameKey branchKey = BranchNameKey.create(projectState.getNameKey(), ref.getName());
if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
}
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteTag.java b/java/com/google/gerrit/server/restapi/project/DeleteTag.java
index 33955eec04..545b75270f 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteTag.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteTag.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
diff --git a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
index 09f973b50f..0ee82795ea 100644
--- a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
@@ -14,14 +14,16 @@
package com.google.gerrit.server.restapi.project;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.change.FileInfoJson;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListKey;
@@ -32,6 +34,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.util.Map;
import org.eclipse.jgit.revwalk.RevCommit;
import org.kohsuke.args4j.Option;
@@ -82,7 +85,8 @@ public class FilesInCommitCollection implements ChildCollection<CommitResource,
}
@Override
- public Object apply(CommitResource resource) throws PatchListNotAvailableException {
+ public Response<Map<String, FileInfo>> apply(CommitResource resource)
+ throws PatchListNotAvailableException {
RevCommit commit = resource.getCommit();
PatchListKey key;
@@ -94,7 +98,7 @@ public class FilesInCommitCollection implements ChildCollection<CommitResource,
key = PatchListKey.againstCommit(null, commit, DiffPreferencesInfo.Whitespace.IGNORE_NONE);
}
- return fileInfoJson.toFileInfoMap(resource.getProjectState().getNameKey(), key);
+ return Response.ok(fileInfoJson.toFileInfoMap(resource.getProjectState().getNameKey(), key));
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GarbageCollect.java b/java/com/google/gerrit/server/restapi/project/GarbageCollect.java
index 800e75e29c..537d45ab27 100644
--- a/java/com/google/gerrit/server/restapi/project/GarbageCollect.java
+++ b/java/com/google/gerrit/server/restapi/project/GarbageCollect.java
@@ -19,13 +19,13 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.common.data.GarbageCollectionResult;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.UrlFormatter;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -70,12 +70,12 @@ public class GarbageCollect
}
@Override
- public Object apply(ProjectResource rsrc, Input input) {
+ public Response<?> apply(ProjectResource rsrc, Input input) {
Project.NameKey project = rsrc.getNameKey();
if (input.async) {
return applyAsync(project, input);
}
- return applySync(project, input);
+ return Response.ok(applySync(project, input));
}
private Response.Accepted applyAsync(Project.NameKey project, Input input) {
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 171c2e5f6d..079df502cd 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -29,6 +29,9 @@ import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
@@ -37,10 +40,8 @@ import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.GroupBackend;
@@ -116,18 +117,16 @@ public class GetAccess implements RestReadView<ProjectResource> {
this.projectConfigFactory = projectConfigFactory;
}
- public ProjectAccessInfo apply(Project.NameKey nameKey)
- throws ResourceNotFoundException, ResourceConflictException, IOException,
- PermissionBackendException {
+ public ProjectAccessInfo apply(Project.NameKey nameKey) throws Exception {
ProjectState state = projectCache.checkedGet(nameKey);
if (state == null) {
throw new ResourceNotFoundException(nameKey.get());
}
- return apply(new ProjectResource(state, user.get()));
+ return apply(new ProjectResource(state, user.get())).value();
}
@Override
- public ProjectAccessInfo apply(ProjectResource rsrc)
+ public Response<ProjectAccessInfo> apply(ProjectResource rsrc)
throws ResourceNotFoundException, ResourceConflictException, IOException,
PermissionBackendException {
// Load the current configuration from the repository, ensuring it's the most
@@ -275,7 +274,7 @@ public class GetAccess implements RestReadView<ProjectResource> {
.filter(e -> e.getValue() != null)
.collect(toMap(e -> e.getKey().get(), Map.Entry::getValue));
- return info;
+ return Response.ok(info);
}
private void loadGroup(Map<AccountGroup.UUID, GroupInfo> groups, AccountGroup.UUID id) {
diff --git a/java/com/google/gerrit/server/restapi/project/GetBranch.java b/java/com/google/gerrit/server/restapi/project/GetBranch.java
index 7d32f3da86..52a47a4493 100644
--- a/java/com/google/gerrit/server/restapi/project/GetBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/GetBranch.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.BranchResource;
@@ -34,8 +35,8 @@ public class GetBranch implements RestReadView<BranchResource> {
}
@Override
- public BranchInfo apply(BranchResource rsrc)
+ public Response<BranchInfo> apply(BranchResource rsrc)
throws ResourceNotFoundException, IOException, PermissionBackendException {
- return list.get().toBranchInfo(rsrc);
+ return Response.ok(list.get().toBranchInfo(rsrc));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetChildProject.java b/java/com/google/gerrit/server/restapi/project/GetChildProject.java
index e69907eaa5..b90f6ee9d1 100644
--- a/java/com/google/gerrit/server/restapi/project/GetChildProject.java
+++ b/java/com/google/gerrit/server/restapi/project/GetChildProject.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.project.ChildProjectResource;
import com.google.gerrit.server.project.ProjectJson;
@@ -37,9 +38,9 @@ public class GetChildProject implements RestReadView<ChildProjectResource> {
}
@Override
- public ProjectInfo apply(ChildProjectResource rsrc) throws ResourceNotFoundException {
+ public Response<ProjectInfo> apply(ChildProjectResource rsrc) throws ResourceNotFoundException {
if (recursive || rsrc.isDirectChild()) {
- return json.format(rsrc.getChild().getProject());
+ return Response.ok(json.format(rsrc.getChild().getProject()));
}
throw new ResourceNotFoundException(rsrc.getChild().getName());
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetCommit.java b/java/com/google/gerrit/server/restapi/project/GetCommit.java
index 1c1ae907cd..cca6a1a5de 100644
--- a/java/com/google/gerrit/server/restapi/project/GetCommit.java
+++ b/java/com/google/gerrit/server/restapi/project/GetCommit.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.project.CommitResource;
@@ -25,7 +26,7 @@ import java.io.IOException;
public class GetCommit implements RestReadView<CommitResource> {
@Override
- public CommitInfo apply(CommitResource rsrc) throws IOException {
- return CommitUtil.toCommitInfo(rsrc.getCommit());
+ public Response<CommitInfo> apply(CommitResource rsrc) throws IOException {
+ return Response.ok(CommitUtil.toCommitInfo(rsrc.getCommit()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetConfig.java b/java/com/google/gerrit/server/restapi/project/GetConfig.java
index b3ad96233a..ce45e7de8d 100644
--- a/java/com/google/gerrit/server/restapi/project/GetConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/GetConfig.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.EnableSignedPush;
@@ -53,15 +54,16 @@ public class GetConfig implements RestReadView<ProjectResource> {
}
@Override
- public ConfigInfo apply(ProjectResource resource) {
- return new ConfigInfoImpl(
- serverEnableSignedPush,
- resource.getProjectState(),
- resource.getUser(),
- pluginConfigEntries,
- cfgFactory,
- allProjects,
- uiActions,
- views);
+ public Response<ConfigInfo> apply(ProjectResource resource) {
+ return Response.ok(
+ new ConfigInfoImpl(
+ serverEnableSignedPush,
+ resource.getProjectState(),
+ resource.getUser(),
+ pluginConfigEntries,
+ cfgFactory,
+ allProjects,
+ uiActions,
+ views));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetContent.java b/java/com/google/gerrit/server/restapi/project/GetContent.java
index 132b644b4c..4e3fc8eeeb 100644
--- a/java/com/google/gerrit/server/restapi/project/GetContent.java
+++ b/java/com/google/gerrit/server/restapi/project/GetContent.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.project.FileResource;
@@ -34,8 +35,9 @@ public class GetContent implements RestReadView<FileResource> {
}
@Override
- public BinaryResult apply(FileResource rsrc)
+ public Response<BinaryResult> apply(FileResource rsrc)
throws ResourceNotFoundException, BadRequestException, IOException {
- return fileContentUtil.getContent(rsrc.getProjectState(), rsrc.getRev(), rsrc.getPath(), null);
+ return Response.ok(
+ fileContentUtil.getContent(rsrc.getProjectState(), rsrc.getRev(), rsrc.getPath(), null));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetDashboard.java b/java/com/google/gerrit/server/restapi/project/GetDashboard.java
index 2ec67e72a7..928a36fc09 100644
--- a/java/com/google/gerrit/server/restapi/project/GetDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/GetDashboard.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
+import static com.google.gerrit.entities.RefNames.REFS_DASHBOARDS;
import static com.google.gerrit.server.restapi.project.DashboardsCollection.isDefaultDashboard;
import com.google.common.base.Splitter;
@@ -25,6 +25,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.Url;
@@ -56,7 +57,7 @@ public class GetDashboard implements RestReadView<DashboardResource> {
}
@Override
- public DashboardInfo apply(DashboardResource rsrc)
+ public Response<DashboardInfo> apply(DashboardResource rsrc)
throws RestApiException, IOException, PermissionBackendException {
if (inherited && !rsrc.isProjectDefault()) {
throw new BadRequestException("inherited flag can only be used with default");
@@ -71,13 +72,14 @@ public class GetDashboard implements RestReadView<DashboardResource> {
}
}
- return DashboardsCollection.parse(
- rsrc.getProjectState().getProject(),
- rsrc.getRefName().substring(REFS_DASHBOARDS.length()),
- rsrc.getPathName(),
- rsrc.getConfig(),
- rsrc.getProjectState().getName(),
- true);
+ return Response.ok(
+ DashboardsCollection.parse(
+ rsrc.getProjectState().getProject(),
+ rsrc.getRefName().substring(REFS_DASHBOARDS.length()),
+ rsrc.getPathName(),
+ rsrc.getConfig(),
+ rsrc.getProjectState().getName(),
+ true));
}
private DashboardResource defaultOf(ProjectState projectState, CurrentUser user)
diff --git a/java/com/google/gerrit/server/restapi/project/GetDescription.java b/java/com/google/gerrit/server/restapi/project/GetDescription.java
index d387ff1980..2561b916a0 100644
--- a/java/com/google/gerrit/server/restapi/project/GetDescription.java
+++ b/java/com/google/gerrit/server/restapi/project/GetDescription.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@ import com.google.inject.Singleton;
@Singleton
public class GetDescription implements RestReadView<ProjectResource> {
@Override
- public String apply(ProjectResource rsrc) {
- return Strings.nullToEmpty(rsrc.getProjectState().getProject().getDescription());
+ public Response<String> apply(ProjectResource rsrc) {
+ return Response.ok(Strings.nullToEmpty(rsrc.getProjectState().getProject().getDescription()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetHead.java b/java/com/google/gerrit/server/restapi/project/GetHead.java
index 043991fc59..4e0a144799 100644
--- a/java/com/google/gerrit/server/restapi/project/GetHead.java
+++ b/java/com/google/gerrit/server/restapi/project/GetHead.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -52,7 +53,7 @@ public class GetHead implements RestReadView<ProjectResource> {
}
@Override
- public String apply(ProjectResource rsrc)
+ public Response<String> apply(ProjectResource rsrc)
throws AuthException, ResourceNotFoundException, IOException, PermissionBackendException {
rsrc.getProjectState().statePermitsRead();
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
@@ -66,12 +67,12 @@ public class GetHead implements RestReadView<ProjectResource> {
.project(rsrc.getNameKey())
.ref(n)
.check(RefPermission.READ);
- return n;
+ return Response.ok(n);
} else if (head.getObjectId() != null) {
try (RevWalk rw = new RevWalk(repo)) {
RevCommit commit = rw.parseCommit(head.getObjectId());
if (commits.canRead(rsrc.getProjectState(), repo, commit)) {
- return head.getObjectId().name();
+ return Response.ok(head.getObjectId().name());
}
throw new AuthException("not allowed to see HEAD");
} catch (MissingObjectException | IncorrectObjectTypeException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/GetParent.java b/java/com/google/gerrit/server/restapi/project/GetParent.java
index a4942e3e70..9b93d5bb3b 100644
--- a/java/com/google/gerrit/server/restapi/project/GetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/GetParent.java
@@ -14,8 +14,9 @@
package com.google.gerrit.server.restapi.project;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
@@ -31,9 +32,9 @@ public class GetParent implements RestReadView<ProjectResource> {
}
@Override
- public String apply(ProjectResource resource) {
+ public Response<String> apply(ProjectResource resource) {
Project project = resource.getProjectState().getProject();
Project.NameKey parentName = project.getParent(allProjectsName);
- return parentName != null ? parentName.get() : "";
+ return Response.ok(parentName != null ? parentName.get() : "");
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetProject.java b/java/com/google/gerrit/server/restapi/project/GetProject.java
index 26159e428e..2f7d370950 100644
--- a/java/com/google/gerrit/server/restapi/project/GetProject.java
+++ b/java/com/google/gerrit/server/restapi/project/GetProject.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.project.ProjectJson;
import com.google.gerrit.server.project.ProjectResource;
@@ -32,7 +33,7 @@ public class GetProject implements RestReadView<ProjectResource> {
}
@Override
- public ProjectInfo apply(ProjectResource rsrc) {
- return json.format(rsrc.getProjectState());
+ public Response<ProjectInfo> apply(ProjectResource rsrc) {
+ return Response.ok(json.format(rsrc.getProjectState()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetReflog.java b/java/com/google/gerrit/server/restapi/project/GetReflog.java
index a690042186..f9c6fd9bb6 100644
--- a/java/com/google/gerrit/server/restapi/project/GetReflog.java
+++ b/java/com/google/gerrit/server/restapi/project/GetReflog.java
@@ -19,6 +19,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CommonConverters;
@@ -89,7 +90,7 @@ public class GetReflog implements RestReadView<BranchResource> {
}
@Override
- public List<ReflogEntryInfo> apply(BranchResource rsrc)
+ public Response<List<ReflogEntryInfo>> apply(BranchResource rsrc)
throws RestApiException, IOException, PermissionBackendException {
permissionBackend
.user(rsrc.getUser())
@@ -123,7 +124,7 @@ public class GetReflog implements RestReadView<BranchResource> {
}
}
}
- return Lists.transform(entries, this::newReflogEntryInfo);
+ return Response.ok(Lists.transform(entries, this::newReflogEntryInfo));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetStatistics.java b/java/com/google/gerrit/server/restapi/project/GetStatistics.java
index a40806227e..db97855f52 100644
--- a/java/com/google/gerrit/server/restapi/project/GetStatistics.java
+++ b/java/com/google/gerrit/server/restapi/project/GetStatistics.java
@@ -18,6 +18,7 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectResource;
@@ -42,11 +43,11 @@ public class GetStatistics implements RestReadView<ProjectResource> {
}
@Override
- public RepositoryStatistics apply(ProjectResource rsrc)
+ public Response<RepositoryStatistics> apply(ProjectResource rsrc)
throws ResourceNotFoundException, ResourceConflictException {
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
GarbageCollectCommand gc = Git.wrap(repo).gc();
- return new RepositoryStatistics(gc.getStatistics());
+ return Response.ok(new RepositoryStatistics(gc.getStatistics()));
} catch (GitAPIException | JGitInternalException e) {
throw new ResourceConflictException(e.getMessage());
} catch (IOException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/GetTag.java b/java/com/google/gerrit/server/restapi/project/GetTag.java
index 6d5a510ba9..6ab2f8bbc4 100644
--- a/java/com/google/gerrit/server/restapi/project/GetTag.java
+++ b/java/com/google/gerrit/server/restapi/project/GetTag.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.project.TagResource;
import com.google.inject.Singleton;
@@ -23,7 +24,7 @@ import com.google.inject.Singleton;
public class GetTag implements RestReadView<TagResource> {
@Override
- public TagInfo apply(TagResource resource) {
- return resource.getTagInfo();
+ public Response<TagInfo> apply(TagResource resource) {
+ return Response.ok(resource.getTagInfo());
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/Index.java b/java/com/google/gerrit/server/restapi/project/Index.java
index bc58b23db0..b14380aca6 100644
--- a/java/com/google/gerrit/server/restapi/project/Index.java
+++ b/java/com/google/gerrit/server/restapi/project/Index.java
@@ -18,21 +18,18 @@ import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.projects.IndexProjectInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.index.project.ProjectIndexer;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import java.util.concurrent.Future;
@RequiresCapability(GlobalCapability.MAINTAIN_SERVER)
@@ -53,14 +50,14 @@ public class Index implements RestModifyView<ProjectResource, IndexProjectInput>
}
@Override
- public Response.Accepted apply(ProjectResource rsrc, IndexProjectInput input)
- throws IOException, PermissionBackendException, RestApiException {
+ public Response.Accepted apply(ProjectResource rsrc, IndexProjectInput input) throws Exception {
String response = "Project " + rsrc.getName() + " submitted for reindexing";
reindex(rsrc.getNameKey(), input.async);
if (Boolean.TRUE.equals(input.indexChildren)) {
- for (ProjectInfo child : listChildProjectsProvider.get().withRecursive(true).apply(rsrc)) {
- reindex(new Project.NameKey(child.name), input.async);
+ for (ProjectInfo child :
+ listChildProjectsProvider.get().withRecursive(true).apply(rsrc).value()) {
+ reindex(Project.nameKey(child.name), input.async);
}
response += " (indexing children recursively)";
diff --git a/java/com/google/gerrit/server/restapi/project/IndexChanges.java b/java/com/google/gerrit/server/restapi/project/IndexChanges.java
index b6b3d6b101..45a6616111 100644
--- a/java/com/google/gerrit/server/restapi/project/IndexChanges.java
+++ b/java/com/google/gerrit/server/restapi/project/IndexChanges.java
@@ -19,11 +19,11 @@ import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
import com.google.gerrit.server.index.IndexExecutor;
diff --git a/java/com/google/gerrit/server/restapi/project/ListBranches.java b/java/com/google/gerrit/server/restapi/project/ListBranches.java
index ae9ef28d2b..fecdc8e255 100644
--- a/java/com/google/gerrit/server/restapi/project/ListBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/ListBranches.java
@@ -14,11 +14,12 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.common.ActionInfo;
@@ -26,11 +27,11 @@ import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.extensions.webui.UiActions;
@@ -127,15 +128,16 @@ public class ListBranches implements RestReadView<ProjectResource> {
}
@Override
- public List<BranchInfo> apply(ProjectResource rsrc)
+ public Response<ImmutableList<BranchInfo>> apply(ProjectResource rsrc)
throws RestApiException, IOException, PermissionBackendException {
rsrc.getProjectState().checkStatePermitsRead();
- return new RefFilter<BranchInfo>(Constants.R_HEADS)
- .subString(matchSubstring)
- .regex(matchRegex)
- .start(start)
- .limit(limit)
- .filter(allBranches(rsrc));
+ return Response.ok(
+ new RefFilter<BranchInfo>(Constants.R_HEADS)
+ .subString(matchSubstring)
+ .regex(matchRegex)
+ .start(start)
+ .limit(limit)
+ .filter(allBranches(rsrc)));
}
BranchInfo toBranchInfo(BranchResource rsrc)
@@ -278,7 +280,8 @@ public class ListBranches implements RestReadView<ProjectResource> {
info.actions.put(d.getId(), new ActionInfo(d));
}
- List<WebLinkInfo> links = webLinks.getBranchLinks(projectState.getName(), ref.getName());
+ ImmutableList<WebLinkInfo> links =
+ webLinks.getBranchLinks(projectState.getName(), ref.getName());
info.webLinks = links.isEmpty() ? null : links;
return info;
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListChildProjects.java b/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
index 387972032e..0bd053e27e 100644
--- a/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
@@ -16,12 +16,13 @@ package com.google.gerrit.server.restapi.project;
import static java.util.stream.Collectors.toList;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
@@ -65,7 +66,7 @@ public class ListChildProjects implements RestReadView<ProjectResource> {
}
@Override
- public List<ProjectInfo> apply(ProjectResource rsrc)
+ public Response<List<ProjectInfo>> apply(ProjectResource rsrc)
throws PermissionBackendException, RestApiException {
if (limit < 0) {
throw new BadRequestException("limit must be a positive number");
@@ -75,20 +76,17 @@ public class ListChildProjects implements RestReadView<ProjectResource> {
}
rsrc.getProjectState().checkStatePermitsRead();
if (recursive) {
- return childProjects.list(rsrc.getNameKey());
+ return Response.ok(childProjects.list(rsrc.getNameKey()));
}
- return directChildProjects(rsrc.getNameKey());
+ return Response.ok(directChildProjects(rsrc.getNameKey()));
}
private List<ProjectInfo> directChildProjects(Project.NameKey parent) throws RestApiException {
PermissionBackend.WithUser currentUser = permissionBackend.currentUser();
return queryProvider.get().withQuery("parent:" + parent.get()).withLimit(limit).apply().stream()
.filter(
- p ->
- currentUser
- .project(new Project.NameKey(p.name))
- .testOrFalse(ProjectPermission.ACCESS))
+ p -> currentUser.project(Project.nameKey(p.name)).testOrFalse(ProjectPermission.ACCESS))
.collect(toList());
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListDashboards.java b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
index fd8668ad10..440671934b 100644
--- a/java/com/google/gerrit/server/restapi/project/ListDashboards.java
+++ b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
@@ -14,15 +14,16 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
+import static com.google.gerrit.entities.RefNames.REFS_DASHBOARDS;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -63,11 +64,11 @@ public class ListDashboards implements RestReadView<ProjectResource> {
}
@Override
- public List<?> apply(ProjectResource rsrc)
+ public Response<List<?>> apply(ProjectResource rsrc)
throws ResourceNotFoundException, IOException, PermissionBackendException {
String project = rsrc.getName();
if (!inherited) {
- return scan(rsrc.getProjectState(), project, true);
+ return Response.ok(scan(rsrc.getProjectState(), project, true));
}
List<List<DashboardInfo>> all = new ArrayList<>();
@@ -83,7 +84,7 @@ public class ListDashboards implements RestReadView<ProjectResource> {
all.add(list);
}
}
- return all;
+ return Response.ok(all);
}
private Collection<ProjectState> tree(ProjectResource rsrc) throws PermissionBackendException {
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index c583923e87..63842827f3 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -27,6 +27,9 @@ import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.common.ProjectInfo;
@@ -35,13 +38,11 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.GroupControl;
@@ -303,16 +304,17 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
@Override
- public Object apply(TopLevelResource resource)
+ public Response<Object> apply(TopLevelResource resource)
throws BadRequestException, PermissionBackendException {
if (format == OutputFormat.TEXT) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
displayToStream(buf);
- return BinaryResult.create(buf.toByteArray())
- .setContentType("text/plain")
- .setCharacterEncoding(UTF_8);
+ return Response.ok(
+ BinaryResult.create(buf.toByteArray())
+ .setContentType("text/plain")
+ .setCharacterEncoding(UTF_8));
}
- return apply();
+ return Response.ok(apply());
}
public SortedMap<String, ProjectInfo> apply()
@@ -505,7 +507,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
continue;
}
- List<WebLinkInfo> links = webLinks.getProjectLinks(projectName.get());
+ ImmutableList<WebLinkInfo> links = webLinks.getProjectLinks(projectName.get());
info.webLinks = links.isEmpty() ? null : links;
if (stdout == null || format.isJson()) {
diff --git a/java/com/google/gerrit/server/restapi/project/ListTags.java b/java/com/google/gerrit/server/restapi/project/ListTags.java
index 08b9b84a5e..ff6d30e533 100644
--- a/java/com/google/gerrit/server/restapi/project/ListTags.java
+++ b/java/com/google/gerrit/server/restapi/project/ListTags.java
@@ -14,18 +14,19 @@
package com.google.gerrit.server.restapi.project;
-import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
+import static com.google.gerrit.entities.RefNames.isConfigRef;
import static java.util.Comparator.comparing;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -116,7 +117,7 @@ public class ListTags implements RestReadView<ProjectResource> {
}
@Override
- public List<TagInfo> apply(ProjectResource resource)
+ public Response<ImmutableList<TagInfo>> apply(ProjectResource resource)
throws IOException, ResourceNotFoundException, RestApiException, PermissionBackendException {
resource.getProjectState().checkStatePermitsRead();
@@ -137,12 +138,13 @@ public class ListTags implements RestReadView<ProjectResource> {
tags.sort(comparing(t -> t.ref));
- return new RefFilter<TagInfo>(Constants.R_TAGS)
- .start(start)
- .limit(limit)
- .subString(matchSubstring)
- .regex(matchRegex)
- .filter(tags);
+ return Response.ok(
+ new RefFilter<TagInfo>(Constants.R_TAGS)
+ .start(start)
+ .limit(limit)
+ .subString(matchSubstring)
+ .regex(matchRegex)
+ .filter(tags));
}
public TagInfo get(ProjectResource resource, IdString id)
@@ -182,7 +184,7 @@ public class ListTags implements RestReadView<ProjectResource> {
perm.testOrFalse(RefPermission.DELETE) && projectState.statePermitsWrite() ? true : null;
}
- List<WebLinkInfo> webLinks = links.getTagLinks(projectState.getName(), ref.getName());
+ ImmutableList<WebLinkInfo> webLinks = links.getTagLinks(projectState.getName(), ref.getName());
if (object instanceof RevTag) {
// Annotated or signed tag
RevTag tag = (RevTag) object;
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectNode.java b/java/com/google/gerrit/server/restapi/project/ProjectNode.java
index c83e473361..1e6200c728 100644
--- a/java/com/google/gerrit/server/restapi/project/ProjectNode.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectNode.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.restapi.project;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.util.TreeFormatter.TreeNode;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
index 31c90e5afa..7d6f7ef67a 100644
--- a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.collect.ListMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -30,7 +31,6 @@ import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -138,7 +138,7 @@ public class ProjectsCollection
throws IOException, PermissionBackendException, ResourceConflictException {
id = ProjectUtil.sanitizeProjectName(id);
- Project.NameKey nameKey = new Project.NameKey(id);
+ Project.NameKey nameKey = Project.nameKey(id);
ProjectState state = projectCache.checkedGet(nameKey);
if (state == null) {
return null;
diff --git a/java/com/google/gerrit/server/restapi/project/PutBranch.java b/java/com/google/gerrit/server/restapi/project/PutBranch.java
index fec8abf4e9..02fc6689c1 100644
--- a/java/com/google/gerrit/server/restapi/project/PutBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/PutBranch.java
@@ -17,6 +17,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.project.BranchResource;
import com.google.inject.Singleton;
@@ -25,7 +26,8 @@ import com.google.inject.Singleton;
public class PutBranch implements RestModifyView<BranchResource, BranchInput> {
@Override
- public BranchInfo apply(BranchResource rsrc, BranchInput input) throws ResourceConflictException {
+ public Response<BranchInfo> apply(BranchResource rsrc, BranchInput input)
+ throws ResourceConflictException {
throw new ResourceConflictException("Branch \"" + rsrc.getRef() + "\" already exists");
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index 56ddbb4a99..4aecb4aa46 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -17,6 +17,8 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -26,11 +28,10 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.EnableSignedPush;
import com.google.gerrit.server.config.AllProjectsName;
@@ -108,13 +109,13 @@ public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
}
@Override
- public ConfigInfo apply(ProjectResource rsrc, ConfigInput input)
+ public Response<ConfigInfo> apply(ProjectResource rsrc, ConfigInput input)
throws RestApiException, PermissionBackendException {
permissionBackend
.currentUser()
.project(rsrc.getNameKey())
.check(ProjectPermission.WRITE_CONFIG);
- return apply(rsrc.getProjectState(), input);
+ return Response.ok(apply(rsrc.getProjectState(), input));
}
public ConfigInfo apply(ProjectState projectState, ConfigInput input)
diff --git a/java/com/google/gerrit/server/restapi/project/PutDescription.java b/java/com/google/gerrit/server/restapi/project/PutDescription.java
index c3a063de68..a0b9feb908 100644
--- a/java/com/google/gerrit/server/restapi/project/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/project/PutDescription.java
@@ -16,13 +16,13 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.DescriptionInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/restapi/project/PutTag.java b/java/com/google/gerrit/server/restapi/project/PutTag.java
index 06c5157368..b6f3f24b92 100644
--- a/java/com/google/gerrit/server/restapi/project/PutTag.java
+++ b/java/com/google/gerrit/server/restapi/project/PutTag.java
@@ -17,13 +17,15 @@ package com.google.gerrit.server.restapi.project;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.project.TagResource;
public class PutTag implements RestModifyView<TagResource, TagInput> {
@Override
- public TagInfo apply(TagResource resource, TagInput input) throws ResourceConflictException {
+ public Response<TagInfo> apply(TagResource resource, TagInput input)
+ throws ResourceConflictException {
throw new ResourceConflictException("Tag \"" + resource.getTagInfo().ref + "\" already exists");
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/QueryProjects.java b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
index 8727df33e3..7066d9a0a5 100644
--- a/java/com/google/gerrit/server/restapi/project/QueryProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
@@ -19,6 +19,7 @@ import com.google.common.collect.Lists;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.project.ProjectData;
@@ -86,9 +87,9 @@ public class QueryProjects implements RestReadView<TopLevelResource> {
}
@Override
- public List<ProjectInfo> apply(TopLevelResource resource)
+ public Response<List<ProjectInfo>> apply(TopLevelResource resource)
throws BadRequestException, MethodNotAllowedException {
- return apply();
+ return Response.ok(apply());
}
public List<ProjectInfo> apply() throws BadRequestException, MethodNotAllowedException {
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccess.java b/java/com/google/gerrit/server/restapi/project/SetAccess.java
index 95bc75f7f0..02c1b545fe 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccess.java
@@ -17,23 +17,20 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CreateGroupPermissionSyncer;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
@@ -41,7 +38,6 @@ import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -80,9 +76,8 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
}
@Override
- public ProjectAccessInfo apply(ProjectResource rsrc, ProjectAccessInput input)
- throws ResourceNotFoundException, ResourceConflictException, IOException, AuthException,
- BadRequestException, UnprocessableEntityException, PermissionBackendException {
+ public Response<ProjectAccessInfo> apply(ProjectResource rsrc, ProjectAccessInput input)
+ throws Exception {
MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
ProjectConfig config;
@@ -117,7 +112,7 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
identifiedUser.get(),
config,
rsrc.getNameKey(),
- input.parent == null ? null : new Project.NameKey(input.parent),
+ input.parent == null ? null : Project.nameKey(input.parent),
!checkedAdmin);
if (!Strings.isNullOrEmpty(input.message)) {
@@ -138,6 +133,6 @@ public class SetAccess implements RestModifyView<ProjectResource, ProjectAccessI
throw new ResourceConflictException(rsrc.getName(), e);
}
- return getAccess.apply(rsrc.getNameKey());
+ return Response.ok(getAccess.apply(rsrc.getNameKey()));
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
index e206319191..ea29fb3008 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
@@ -21,6 +21,7 @@ import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
@@ -29,7 +30,6 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.group.GroupResolver;
diff --git a/java/com/google/gerrit/server/restapi/project/SetDashboard.java b/java/com/google/gerrit/server/restapi/project/SetDashboard.java
index 2804b7c858..e8e0c0d01a 100644
--- a/java/com/google/gerrit/server/restapi/project/SetDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/SetDashboard.java
@@ -18,14 +18,11 @@ import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.SetDashboardInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.DashboardResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
@Singleton
public class SetDashboard implements RestModifyView<DashboardResource, SetDashboardInput> {
@@ -38,7 +35,7 @@ public class SetDashboard implements RestModifyView<DashboardResource, SetDashbo
@Override
public Response<DashboardInfo> apply(DashboardResource resource, SetDashboardInput input)
- throws RestApiException, IOException, PermissionBackendException {
+ throws Exception {
if (resource.isProjectDefault()) {
return defaultSetter.get().apply(resource, input);
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
index 3917bee62b..49b5cab537 100644
--- a/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
+++ b/java/com/google/gerrit/server/restapi/project/SetDefaultDashboard.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.api.projects.SetDashboardInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -23,12 +24,9 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.DashboardResource;
import com.google.gerrit.server.project.ProjectCache;
@@ -36,7 +34,6 @@ import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.kohsuke.args4j.Option;
@@ -70,7 +67,7 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, SetDashbo
@Override
public Response<DashboardInfo> apply(DashboardResource rsrc, SetDashboardInput input)
- throws RestApiException, IOException, PermissionBackendException {
+ throws Exception {
if (input == null) {
input = new SetDashboardInput(); // Delete would set input to null.
}
@@ -119,9 +116,9 @@ class SetDefaultDashboard implements RestModifyView<DashboardResource, SetDashbo
cache.evict(rsrc.getProjectState().getProject());
if (target != null) {
- DashboardInfo info = get.get().apply(target);
- info.isDefault = true;
- return Response.ok(info);
+ Response<DashboardInfo> response = get.get().apply(target);
+ response.value().isDefault = true;
+ return response;
}
return Response.none();
} catch (RepositoryNotFoundException notFound) {
diff --git a/java/com/google/gerrit/server/restapi/project/SetHead.java b/java/com/google/gerrit/server/restapi/project/SetHead.java
index 430d709888..0afea5c86f 100644
--- a/java/com/google/gerrit/server/restapi/project/SetHead.java
+++ b/java/com/google/gerrit/server/restapi/project/SetHead.java
@@ -15,15 +15,16 @@
package com.google.gerrit.server.restapi.project;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.HeadInput;
import com.google.gerrit.extensions.events.HeadUpdatedListener;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -63,7 +64,7 @@ public class SetHead implements RestModifyView<ProjectResource, HeadInput> {
}
@Override
- public String apply(ProjectResource rsrc, HeadInput input)
+ public Response<String> apply(ProjectResource rsrc, HeadInput input)
throws AuthException, ResourceNotFoundException, BadRequestException,
UnprocessableEntityException, IOException, PermissionBackendException {
if (input == null || Strings.isNullOrEmpty(input.ref)) {
@@ -109,7 +110,7 @@ public class SetHead implements RestModifyView<ProjectResource, HeadInput> {
fire(rsrc.getNameKey(), oldHead, newHead);
}
- return ref;
+ return Response.ok(ref);
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(rsrc.getName(), e);
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index 8e0363fbb0..a610dd42b3 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -20,14 +20,15 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
@@ -85,11 +86,11 @@ public class SetParent
}
@Override
- public String apply(ProjectResource rsrc, ParentInput input)
+ public Response<String> apply(ProjectResource rsrc, ParentInput input)
throws AuthException, ResourceConflictException, ResourceNotFoundException,
UnprocessableEntityException, IOException, PermissionBackendException,
BadRequestException {
- return apply(rsrc, input, true);
+ return Response.ok(apply(rsrc, input, true));
}
public String apply(ProjectResource rsrc, ParentInput input, boolean checkIfAdmin)
@@ -155,7 +156,7 @@ public class SetParent
newParent = Strings.emptyToNull(newParent);
if (newParent != null) {
- ProjectState parent = cache.get(new Project.NameKey(newParent));
+ ProjectState parent = cache.get(Project.nameKey(newParent));
if (parent == null) {
throw new UnprocessableEntityException("parent project " + newParent + " not found");
}
diff --git a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
index 2be6c19b47..32aec59b76 100644
--- a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
+++ b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
@@ -20,20 +20,19 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
+import java.util.Optional;
/**
* Java implementation of Gerrit's default pre-submit rules behavior: check if the labels have the
@@ -63,13 +62,13 @@ public final class DefaultSubmitRule implements SubmitRule {
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd, SubmitRuleOptions options) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
ProjectState projectState = projectCache.get(cd.project());
// In case at least one project has a rules.pl file, we let Prolog handle it.
// The Prolog rules engine will also handle the labels for us.
if (projectState == null || projectState.hasPrologRules()) {
- return Collections.emptyList();
+ return Optional.empty();
}
SubmitRecord submitRecord = new SubmitRecord();
@@ -86,7 +85,7 @@ public final class DefaultSubmitRule implements SubmitRule {
submitRecord.errorMessage = "Unable to fetch labels and approvals for the change";
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
submitRecord.labels = new ArrayList<>(labelTypes.size());
@@ -99,7 +98,7 @@ public final class DefaultSubmitRule implements SubmitRule {
submitRecord.errorMessage = "Unable to find the LabelFunction for label " + t.getName();
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
Collection<PatchSetApproval> approvalsForLabel = getApprovalsForLabel(approvals, t);
@@ -119,13 +118,13 @@ public final class DefaultSubmitRule implements SubmitRule {
}
}
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
private static List<PatchSetApproval> getApprovalsForLabel(
List<PatchSetApproval> approvals, LabelType t) {
return approvals.stream()
- .filter(input -> input.getLabel().equals(t.getLabelId().get()))
+ .filter(input -> input.label().equals(t.getLabelId().get()))
.collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
index 759be39f44..54915fb519 100644
--- a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
+++ b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
@@ -17,23 +17,22 @@ package com.google.gerrit.server.rules;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.Exports;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
/**
* Rule to require an approval from a user that did not upload the current patch set or block
@@ -56,7 +55,7 @@ public class IgnoreSelfApprovalRule implements SubmitRule {
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd, SubmitRuleOptions options) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
List<LabelType> labelTypes;
List<PatchSetApproval> approvals;
try {
@@ -64,21 +63,21 @@ public class IgnoreSelfApprovalRule implements SubmitRule {
approvals = cd.currentApprovals();
} catch (StorageException e) {
logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_LABELS);
- return singletonRuleError(E_UNABLE_TO_FETCH_LABELS);
+ return ruleError(E_UNABLE_TO_FETCH_LABELS);
}
boolean shouldIgnoreSelfApproval = labelTypes.stream().anyMatch(LabelType::ignoreSelfApproval);
if (!shouldIgnoreSelfApproval) {
// Shortcut to avoid further processing if no label should ignore uploader approvals
- return ImmutableList.of();
+ return Optional.empty();
}
Account.Id uploader;
try {
- uploader = cd.currentPatchSet().getUploader();
+ uploader = cd.currentPatchSet().uploader();
} catch (StorageException e) {
logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_UPLOADER);
- return singletonRuleError(E_UNABLE_TO_FETCH_UPLOADER);
+ return ruleError(E_UNABLE_TO_FETCH_UPLOADER);
}
SubmitRecord submitRecord = new SubmitRecord();
@@ -120,10 +119,10 @@ public class IgnoreSelfApprovalRule implements SubmitRule {
}
if (submitRecord.labels.isEmpty()) {
- return ImmutableList.of();
+ return Optional.empty();
}
- return ImmutableList.of(submitRecord);
+ return Optional.of(submitRecord);
}
private static boolean labelCheckPassed(SubmitRecord.Label label) {
@@ -140,18 +139,18 @@ public class IgnoreSelfApprovalRule implements SubmitRule {
return false;
}
- private static Collection<SubmitRecord> singletonRuleError(String reason) {
+ private static Optional<SubmitRecord> ruleError(String reason) {
SubmitRecord submitRecord = new SubmitRecord();
submitRecord.errorMessage = reason;
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return ImmutableList.of(submitRecord);
+ return Optional.of(submitRecord);
}
@VisibleForTesting
static Collection<PatchSetApproval> filterOutPositiveApprovalsOfUser(
Collection<PatchSetApproval> approvals, Account.Id user) {
return approvals.stream()
- .filter(input -> input.getValue() < 0 || !input.getAccountId().equals(user))
+ .filter(input -> input.value() < 0 || !input.accountId().equals(user))
.collect(toImmutableList());
}
@@ -159,7 +158,7 @@ public class IgnoreSelfApprovalRule implements SubmitRule {
static Collection<PatchSetApproval> filterApprovalsByLabel(
Collection<PatchSetApproval> approvals, LabelType t) {
return approvals.stream()
- .filter(input -> input.getLabelId().get().equals(t.getLabelId().get()))
+ .filter(input -> input.labelId().get().equals(t.getLabelId().get()))
.collect(toImmutableList());
}
}
diff --git a/java/com/google/gerrit/server/rules/PrologOptions.java b/java/com/google/gerrit/server/rules/PrologOptions.java
new file mode 100644
index 0000000000..a176f0429e
--- /dev/null
+++ b/java/com/google/gerrit/server/rules/PrologOptions.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.rules;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import java.util.Optional;
+
+@AutoValue
+public abstract class PrologOptions {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ public static PrologOptions defaultOptions() {
+ return new AutoValue_PrologOptions.Builder().logErrors(true).skipFilters(false).build();
+ }
+
+ public static PrologOptions dryRunOptions(String ruleToTest, boolean skipFilters) {
+ return new AutoValue_PrologOptions.Builder()
+ .logErrors(logger.atFine().isEnabled())
+ .skipFilters(skipFilters)
+ .rule(ruleToTest)
+ .build();
+ }
+
+ /** Whether errors should be logged. */
+ abstract boolean logErrors();
+
+ /** Whether Prolog filters from parent projects should be skipped. */
+ abstract boolean skipFilters();
+
+ /**
+ * Prolog rule that should be run. If not given, the Prolog rule that is configured for the
+ * project is used (the rule from rules.pl in refs/meta/config).
+ */
+ abstract Optional<String> rule();
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract PrologOptions.Builder logErrors(boolean logErrors);
+
+ abstract PrologOptions.Builder skipFilters(boolean skipFilters);
+
+ abstract PrologOptions.Builder rule(@Nullable String rule);
+
+ abstract PrologOptions build();
+ }
+}
diff --git a/java/com/google/gerrit/server/rules/PrologRule.java b/java/com/google/gerrit/server/rules/PrologRule.java
index 0c54f40b28..bf1d545834 100644
--- a/java/com/google/gerrit/server/rules/PrologRule.java
+++ b/java/com/google/gerrit/server/rules/PrologRule.java
@@ -18,12 +18,10 @@ import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.Optional;
@Singleton
public class PrologRule implements SubmitRule {
@@ -37,21 +35,29 @@ public class PrologRule implements SubmitRule {
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd, SubmitRuleOptions opts) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
ProjectState projectState = projectCache.get(cd.project());
// We only want to run the Prolog engine if we have at least one rules.pl file to use.
- if ((projectState == null || !projectState.hasPrologRules()) && opts.rule() == null) {
- return Collections.emptyList();
+ if ((projectState == null || !projectState.hasPrologRules())) {
+ return Optional.empty();
}
+ return Optional.of(evaluate(cd, PrologOptions.defaultOptions()));
+ }
+
+ public SubmitRecord evaluate(ChangeData cd, PrologOptions opts) {
return getEvaluator(cd, opts).evaluate();
}
- private PrologRuleEvaluator getEvaluator(ChangeData cd, SubmitRuleOptions opts) {
- return factory.create(cd, opts);
+ public SubmitTypeRecord getSubmitType(ChangeData cd) {
+ return getSubmitType(cd, PrologOptions.defaultOptions());
}
- public SubmitTypeRecord getSubmitType(ChangeData cd, SubmitRuleOptions opts) {
+ public SubmitTypeRecord getSubmitType(ChangeData cd, PrologOptions opts) {
return getEvaluator(cd, opts).getSubmitType();
}
+
+ private PrologRuleEvaluator getEvaluator(ChangeData cd, PrologOptions opts) {
+ return factory.create(cd, opts);
+ }
}
diff --git a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
index 9cde54c9a9..72dc46a310 100644
--- a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
+++ b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.rules;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.createRuleError;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.defaultRuleError;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.defaultTypeError;
@@ -24,10 +25,10 @@ import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.Emails;
@@ -35,7 +36,6 @@ import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RuleEvalException;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -51,7 +51,6 @@ import com.googlecode.prolog_cafe.lang.Term;
import com.googlecode.prolog_cafe.lang.VariableTerm;
import java.io.StringReader;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -73,7 +72,7 @@ public class PrologRuleEvaluator {
public interface Factory {
/** Returns a new {@link PrologRuleEvaluator} with the specified options */
- PrologRuleEvaluator create(ChangeData cd, SubmitRuleOptions options);
+ PrologRuleEvaluator create(ChangeData cd, PrologOptions options);
}
/**
@@ -95,7 +94,7 @@ public class PrologRuleEvaluator {
private final PrologEnvironment.Factory envFactory;
private final ChangeData cd;
private final ProjectState projectState;
- private final SubmitRuleOptions opts;
+ private final PrologOptions opts;
private Term submitRule;
@AssistedInject
@@ -107,7 +106,7 @@ public class PrologRuleEvaluator {
PrologEnvironment.Factory envFactory,
ProjectCache projectCache,
@Assisted ChangeData cd,
- @Assisted SubmitRuleOptions options) {
+ @Assisted PrologOptions options) {
this.accountCache = accountCache;
this.accounts = accounts;
this.emails = emails;
@@ -141,10 +140,9 @@ public class PrologRuleEvaluator {
/**
* Evaluate the submit rules.
*
- * @return List of {@link SubmitRecord} objects returned from the evaluated rules, including any
- * errors.
+ * @return {@link SubmitRecord} returned from the evaluated rules. Can include errors.
*/
- public Collection<SubmitRecord> evaluate() {
+ public SubmitRecord evaluate() {
Change change;
try {
change = cd.change();
@@ -159,12 +157,6 @@ public class PrologRuleEvaluator {
return ruleError("Error looking up change " + cd.getId(), e);
}
- if (!opts.allowClosed() && change.isClosed()) {
- SubmitRecord rec = new SubmitRecord();
- rec.status = SubmitRecord.Status.CLOSED;
- return Collections.singletonList(rec);
- }
-
List<Term> results;
try {
results =
@@ -200,28 +192,32 @@ public class PrologRuleEvaluator {
* output. Later after the loop the out collection is reversed to restore it to the original
* ordering.
*/
- public List<SubmitRecord> resultsToSubmitRecord(Term submitRule, List<Term> results) {
- boolean foundOk = false;
- List<SubmitRecord> out = new ArrayList<>(results.size());
+ public SubmitRecord resultsToSubmitRecord(Term submitRule, List<Term> results) {
+ checkState(!results.isEmpty(), "the list of Prolog terms must not be empty");
+
+ SubmitRecord resultSubmitRecord = new SubmitRecord();
+ resultSubmitRecord.labels = new ArrayList<>();
for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
Term submitRecord = results.get(resultIdx);
- SubmitRecord rec = new SubmitRecord();
- out.add(rec);
if (!(submitRecord instanceof StructureTerm) || 1 != submitRecord.arity()) {
return invalidResult(submitRule, submitRecord);
}
- if ("ok".equals(submitRecord.name())) {
- rec.status = SubmitRecord.Status.OK;
-
- } else if ("not_ready".equals(submitRecord.name())) {
- rec.status = SubmitRecord.Status.NOT_READY;
-
- } else {
+ if (!"ok".equals(submitRecord.name()) && !"not_ready".equals(submitRecord.name())) {
return invalidResult(submitRule, submitRecord);
}
+ // This transformation is required to adapt Prolog's behavior to the way Gerrit handles
+ // SubmitRecords, as defined in the SubmitRecord#allRecordsOK method.
+ // When several rules are defined in Prolog, they are all matched to a SubmitRecord. We want
+ // the change to be submittable when at least one result is OK.
+ if ("ok".equals(submitRecord.name())) {
+ resultSubmitRecord.status = SubmitRecord.Status.OK;
+ } else if ("not_ready".equals(submitRecord.name()) && resultSubmitRecord.status == null) {
+ resultSubmitRecord.status = SubmitRecord.Status.NOT_READY;
+ }
+
// Unpack the one argument. This should also be a structure with one
// argument per label that needs to be reported on to the caller.
//
@@ -231,8 +227,6 @@ public class PrologRuleEvaluator {
return invalidResult(submitRule, submitRecord);
}
- rec.labels = new ArrayList<>(submitRecord.arity());
-
for (Term state : ((StructureTerm) submitRecord).args()) {
if (!(state instanceof StructureTerm)
|| 2 != state.arity()
@@ -241,7 +235,7 @@ public class PrologRuleEvaluator {
}
SubmitRecord.Label lbl = new SubmitRecord.Label();
- rec.labels.add(lbl);
+ resultSubmitRecord.labels.add(lbl);
lbl.label = checkLabelName(state.arg(0).name());
Term status = state.arg(1);
@@ -272,24 +266,12 @@ public class PrologRuleEvaluator {
}
}
- if (rec.status == SubmitRecord.Status.OK) {
- foundOk = true;
+ if (resultSubmitRecord.status == SubmitRecord.Status.OK) {
break;
}
}
- Collections.reverse(out);
-
- // This transformation is required to adapt Prolog's behavior to the way Gerrit handles
- // SubmitRecords, as defined in the SubmitRecord#allRecordsOK method.
- // When several rules are defined in Prolog, they are all matched to a SubmitRecord. We want
- // the change to be submittable when at least one result is OK.
- if (foundOk) {
- for (SubmitRecord record : out) {
- record.status = SubmitRecord.Status.OK;
- }
- }
-
- return out;
+ Collections.reverse(resultSubmitRecord.labels);
+ return resultSubmitRecord;
}
@VisibleForTesting
@@ -306,7 +288,7 @@ public class PrologRuleEvaluator {
return VALID_LABEL_MATCHER.retainFrom(name);
}
- private List<SubmitRecord> invalidResult(Term rule, Term record, String reason) {
+ private SubmitRecord invalidResult(Term rule, Term record, String reason) {
return ruleError(
String.format(
"Submit rule %s for change %s of %s output invalid result: %s%s",
@@ -317,15 +299,15 @@ public class PrologRuleEvaluator {
(reason == null ? "" : ". Reason: " + reason)));
}
- private List<SubmitRecord> invalidResult(Term rule, Term record) {
+ private SubmitRecord invalidResult(Term rule, Term record) {
return invalidResult(rule, record, null);
}
- private List<SubmitRecord> ruleError(String err) {
+ private SubmitRecord ruleError(String err) {
return ruleError(err, null);
}
- private List<SubmitRecord> ruleError(String err, Exception e) {
+ private SubmitRecord ruleError(String err, Exception e) {
if (opts.logErrors()) {
logger.atSevere().withCause(e).log(err);
return defaultRuleError();
@@ -465,22 +447,22 @@ public class PrologRuleEvaluator {
PrologEnvironment env;
try {
PrologMachineCopy pmc;
- if (opts.rule() == null) {
+ if (opts.rule().isPresent()) {
+ pmc = rulesCache.loadMachine("stdin", new StringReader(opts.rule().get()));
+ } else {
pmc =
rulesCache.loadMachine(
projectState.getNameKey(), projectState.getConfig().getRulesId());
- } else {
- pmc = rulesCache.loadMachine("stdin", new StringReader(opts.rule()));
}
env = envFactory.create(pmc);
} catch (CompileException err) {
String msg;
- if (opts.rule() == null) {
+ if (opts.rule().isPresent()) {
+ msg = err.getMessage();
+ } else {
msg =
String.format(
"Cannot load rules.pl for %s: %s", projectState.getName(), err.getMessage());
- } else {
- msg = err.getMessage();
}
throw new RuleEvalException(msg, err);
}
@@ -540,7 +522,7 @@ public class PrologRuleEvaluator {
if (status instanceof StructureTerm && status.arity() == 1) {
Term who = status.arg(0);
if (isUser(who)) {
- label.appliedBy = new Account.Id(((IntegerTerm) who.arg(0)).intValue());
+ label.appliedBy = Account.id(((IntegerTerm) who.arg(0)).intValue());
} else {
throw new UserTermExpected(label);
}
diff --git a/java/com/google/gerrit/server/rules/RulesCache.java b/java/com/google/gerrit/server/rules/RulesCache.java
index d4e90f9dc7..0cd21a40f9 100644
--- a/java/com/google/gerrit/server/rules/RulesCache.java
+++ b/java/com/google/gerrit/server/rules/RulesCache.java
@@ -19,8 +19,8 @@ import static com.googlecode.prolog_cafe.lang.PrologMachineCopy.save;
import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
diff --git a/java/com/google/gerrit/server/rules/StoredValues.java b/java/com/google/gerrit/server/rules/StoredValues.java
index 30d329dca1..1e08a240e3 100644
--- a/java/com/google/gerrit/server/rules/StoredValues.java
+++ b/java/com/google/gerrit/server/rules/StoredValues.java
@@ -16,12 +16,12 @@ package com.google.gerrit.server.rules;
import static com.google.gerrit.server.rules.StoredValue.create;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
@@ -42,7 +42,6 @@ import com.googlecode.prolog_cafe.lang.Prolog;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -97,9 +96,8 @@ public final class StoredValues {
PatchListCache plCache = env.getArgs().getPatchListCache();
Change change = getChange(engine);
Project.NameKey project = change.getProject();
- ObjectId b = ObjectId.fromString(ps.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
- PatchListKey plKey = PatchListKey.againstDefaultBase(b, ws);
+ PatchListKey plKey = PatchListKey.againstDefaultBase(ps.commitId(), ws);
PatchList patchList;
try {
patchList = plCache.get(plKey, project);
diff --git a/java/com/google/gerrit/server/rules/SubmitRule.java b/java/com/google/gerrit/server/rules/SubmitRule.java
index 2a68683db5..b221117f86 100644
--- a/java/com/google/gerrit/server/rules/SubmitRule.java
+++ b/java/com/google/gerrit/server/rules/SubmitRule.java
@@ -15,15 +15,14 @@ package com.google.gerrit.server.rules;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
-import java.util.Collection;
+import java.util.Optional;
/**
* Allows plugins to decide whether a change is ready to be submitted or not.
*
- * <p>For a given {@link ChangeData}, each plugin is called and returns a {@link Collection} of
- * {@link SubmitRecord}. This collection can be empty, or contain one or several values.
+ * <p>For a given {@link ChangeData}, each plugin is called and returns a {@link Optional} of {@link
+ * SubmitRecord}.
*
* <p>A Change can only be submitted if all the plugins give their consent.
*
@@ -39,6 +38,9 @@ import java.util.Collection;
*/
@ExtensionPoint
public interface SubmitRule {
- /** Returns a {@link Collection} of {@link SubmitRecord} status for the change. */
- Collection<SubmitRecord> evaluate(ChangeData changeData, SubmitRuleOptions options);
+ /**
+ * Returns a {@link Optional} of {@link SubmitRecord} status for the change. {@code
+ * Optional#empty()} if the SubmitRule was a no-op.
+ */
+ Optional<SubmitRecord> evaluate(ChangeData changeData);
}
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 9446b7c908..c15efbaa00 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.schema;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_SEQUENCES;
+import static com.google.gerrit.entities.RefNames.REFS_SEQUENCES;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -30,8 +30,8 @@ import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
diff --git a/java/com/google/gerrit/server/schema/AllProjectsInput.java b/java/com/google/gerrit/server/schema/AllProjectsInput.java
index 7231b1858a..6e11a5d66a 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsInput.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsInput.java
@@ -21,8 +21,8 @@ import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.server.notedb.Sequences;
import java.util.Optional;
diff --git a/java/com/google/gerrit/server/schema/AllUsersCreator.java b/java/com/google/gerrit/server/schema/AllUsersCreator.java
index d2f5ef183c..4904028409 100644
--- a/java/com/google/gerrit/server/schema/AllUsersCreator.java
+++ b/java/com/google/gerrit/server/schema/AllUsersCreator.java
@@ -26,8 +26,8 @@ import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
diff --git a/java/com/google/gerrit/server/schema/BUILD b/java/com/google/gerrit/server/schema/BUILD
index ee99c67c71..cee086280b 100644
--- a/java/com/google/gerrit/server/schema/BUILD
+++ b/java/com/google/gerrit/server/schema/BUILD
@@ -9,23 +9,23 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/git",
"//java/com/google/gerrit/gpg",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/server/logging",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-archive",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/commons:dbcp",
"//lib/flogger:api",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:jsonevent-layout",
"//lib/log:log4j",
],
diff --git a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
index 0612bb983b..49dcc46aad 100644
--- a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
+++ b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
@@ -21,19 +21,22 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.change.AccountPatchReviewStore;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.config.ThreadSettingsConfig;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -212,14 +215,22 @@ public abstract class JdbcAccountPatchReviewStore
@Override
public boolean markReviewed(PatchSet.Id psId, Account.Id accountId, String path) {
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Mark file as reviewed",
+ Metadata.builder()
+ .patchSetId(psId.get())
+ .accountId(accountId.get())
+ .filePath(path)
+ .build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
"INSERT INTO account_patch_reviews "
+ "(account_id, change_id, patch_set_id, file_name) VALUES "
+ "(?, ?, ?, ?)")) {
stmt.setInt(1, accountId.get());
- stmt.setInt(2, psId.getParentKey().get());
+ stmt.setInt(2, psId.changeId().get());
stmt.setInt(3, psId.get());
stmt.setString(4, path);
stmt.executeUpdate();
@@ -239,7 +250,15 @@ public abstract class JdbcAccountPatchReviewStore
return;
}
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Mark files as reviewed",
+ Metadata.builder()
+ .patchSetId(psId.get())
+ .accountId(accountId.get())
+ .resourceCount(paths.size())
+ .build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
"INSERT INTO account_patch_reviews "
@@ -247,7 +266,7 @@ public abstract class JdbcAccountPatchReviewStore
+ "(?, ?, ?, ?)")) {
for (String path : paths) {
stmt.setInt(1, accountId.get());
- stmt.setInt(2, psId.getParentKey().get());
+ stmt.setInt(2, psId.changeId().get());
stmt.setInt(3, psId.get());
stmt.setString(4, path);
stmt.addBatch();
@@ -264,14 +283,22 @@ public abstract class JdbcAccountPatchReviewStore
@Override
public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path) {
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Clear reviewed flag",
+ Metadata.builder()
+ .patchSetId(psId.get())
+ .accountId(accountId.get())
+ .filePath(path)
+ .build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
"DELETE FROM account_patch_reviews "
+ "WHERE account_id = ? AND change_id = ? AND "
+ "patch_set_id = ? AND file_name = ?")) {
stmt.setInt(1, accountId.get());
- stmt.setInt(2, psId.getParentKey().get());
+ stmt.setInt(2, psId.changeId().get());
stmt.setInt(3, psId.get());
stmt.setString(4, path);
stmt.executeUpdate();
@@ -282,12 +309,16 @@ public abstract class JdbcAccountPatchReviewStore
@Override
public void clearReviewed(PatchSet.Id psId) {
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Clear all reviewed flags of patch set",
+ Metadata.builder().patchSetId(psId.get()).build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
"DELETE FROM account_patch_reviews "
+ "WHERE change_id = ? AND patch_set_id = ?")) {
- stmt.setInt(1, psId.getParentKey().get());
+ stmt.setInt(1, psId.changeId().get());
stmt.setInt(2, psId.get());
stmt.executeUpdate();
} catch (SQLException e) {
@@ -297,7 +328,11 @@ public abstract class JdbcAccountPatchReviewStore
@Override
public void clearReviewed(Change.Id changeId) {
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Clear all reviewed flags of change",
+ Metadata.builder().changeId(changeId.get()).build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement("DELETE FROM account_patch_reviews WHERE change_id = ?")) {
stmt.setInt(1, changeId.get());
@@ -309,7 +344,11 @@ public abstract class JdbcAccountPatchReviewStore
@Override
public Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId) {
- try (Connection con = ds.getConnection();
+ try (TraceTimer ignored =
+ TraceContext.newTimer(
+ "Find reviewed flags",
+ Metadata.builder().patchSetId(psId.get()).accountId(accountId.get()).build());
+ Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
"SELECT patch_set_id, file_name FROM account_patch_reviews APR1 "
@@ -319,11 +358,11 @@ public abstract class JdbcAccountPatchReviewStore
+ "AND APR1.change_id = APR2.change_id "
+ "AND patch_set_id <= ?)")) {
stmt.setInt(1, accountId.get());
- stmt.setInt(2, psId.getParentKey().get());
+ stmt.setInt(2, psId.changeId().get());
stmt.setInt(3, psId.get());
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
- PatchSet.Id id = new PatchSet.Id(psId.getParentKey(), rs.getInt("patch_set_id"));
+ PatchSet.Id id = PatchSet.id(psId.changeId(), rs.getInt("patch_set_id"));
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
do {
builder.add(rs.getString("file_name"));
diff --git a/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java b/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
index b0a3370fe1..dd82be2159 100644
--- a/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
+++ b/java/com/google/gerrit/server/schema/MariaDBAccountPatchReviewStore.java
@@ -22,6 +22,7 @@ import com.google.gerrit.server.config.ThreadSettingsConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.sql.SQLException;
+import java.sql.Statement;
import org.eclipse.jgit.lib.Config;
@Singleton
@@ -50,4 +51,17 @@ public class MariaDBAccountPatchReviewStore extends JdbcAccountPatchReviewStore
return new StorageException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
}
}
+
+ @Override
+ protected void doCreateTable(Statement stmt) throws SQLException {
+ stmt.executeUpdate(
+ "CREATE TABLE IF NOT EXISTS account_patch_reviews ("
+ + "account_id INTEGER DEFAULT 0 NOT NULL, "
+ + "change_id INTEGER DEFAULT 0 NOT NULL, "
+ + "patch_set_id INTEGER DEFAULT 0 NOT NULL, "
+ + "file_name VARCHAR(255) DEFAULT '' NOT NULL, "
+ + "CONSTRAINT primary_key_account_patch_reviews "
+ + "PRIMARY KEY (change_id, patch_set_id, account_id, file_name)"
+ + ")");
+ }
}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
index df95ff7c12..0e22af925f 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
@@ -20,8 +20,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
index 33534fc0a6..0360ec0011 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
@@ -14,15 +14,20 @@
package com.google.gerrit.server.schema;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.ProvisionException;
+import org.eclipse.jgit.lib.Config;
public class NoteDbSchemaVersionCheck implements LifecycleListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public static Module module() {
return new LifecycleModule() {
@Override
@@ -34,11 +39,16 @@ public class NoteDbSchemaVersionCheck implements LifecycleListener {
private final NoteDbSchemaVersionManager versionManager;
private final SitePaths sitePaths;
+ private Config gerritConfig;
@Inject
- NoteDbSchemaVersionCheck(NoteDbSchemaVersionManager versionManager, SitePaths sitePaths) {
+ NoteDbSchemaVersionCheck(
+ NoteDbSchemaVersionManager versionManager,
+ SitePaths sitePaths,
+ @GerritServerConfig Config gerritConfig) {
this.versionManager = versionManager;
this.sitePaths = sitePaths;
+ this.gerritConfig = gerritConfig;
}
@Override
@@ -53,7 +63,18 @@ public class NoteDbSchemaVersionCheck implements LifecycleListener {
sitePaths.site_path.toAbsolutePath()));
}
int expected = NoteDbSchemaVersions.LATEST;
- if (current != expected) {
+
+ if (current > expected
+ && gerritConfig.getBoolean("gerrit", "experimentalRollingUpgrade", false)) {
+ logger.atWarning().log(
+ "Gerrit has detected refs/meta/version %d different than the expected %d."
+ + "Bear in mind that this is supported ONLY for rolling upgrades to immediate next "
+ + "Gerrit version (e.g. v3.1 to v3.2). If this is not expected, remove gerrit.experimentalRollingUpgrade "
+ + "from $GERRIT_SITE/etc/gerrit.config and restart Gerrit."
+ + "Please note that gerrit.experimentalRollingUpgrade is intended to be used "
+ + "for the rolling upgrade phase only and should be disabled afterwards.",
+ current, expected);
+ } else if (current != expected) {
String advice =
current > expected
? "Downgrade is not supported"
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionManager.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionManager.java
index 7ff0ea6c44..803872c441 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionManager.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionManager.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.schema;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+import static com.google.gerrit.entities.RefNames.REFS_VERSION;
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.exceptions.StorageException;
diff --git a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
index 9e12807cae..5e7dbf03ee 100644
--- a/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
+++ b/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
@@ -20,8 +20,8 @@ import static java.util.stream.Collectors.toList;
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
diff --git a/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
index 0a5823a743..26f1990e89 100644
--- a/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
@@ -17,10 +17,10 @@ package com.google.gerrit.server.schema;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.git.RefUpdateUtil;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.config.AllProjectsName;
@@ -218,8 +218,8 @@ public class SchemaCreatorImpl implements SchemaCreator {
private InternalGroupCreation getGroupCreation(Sequences seqs, GroupReference groupReference) {
int next = seqs.nextGroupId();
return InternalGroupCreation.builder()
- .setNameKey(new AccountGroup.NameKey(groupReference.getName()))
- .setId(new AccountGroup.Id(next))
+ .setNameKey(AccountGroup.nameKey(groupReference.getName()))
+ .setId(AccountGroup.id(next))
.setGroupUUID(groupReference.getUUID())
.build();
}
diff --git a/java/com/google/gerrit/server/schema/VersionedAccountPreferences.java b/java/com/google/gerrit/server/schema/VersionedAccountPreferences.java
index 297fdcddb7..468c26b162 100644
--- a/java/com/google/gerrit/server/schema/VersionedAccountPreferences.java
+++ b/java/com/google/gerrit/server/schema/VersionedAccountPreferences.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.schema;
import com.google.common.base.Strings;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
diff --git a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
index 63837b2627..a6424b9683 100644
--- a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
+++ b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
@@ -15,11 +15,12 @@
package com.google.gerrit.server.schema.testing;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import java.io.IOException;
@@ -151,8 +152,8 @@ public class AllProjectsCreatorTestUtil {
Set<String> subsections1 = config1.getSubsections(section);
Set<String> subsections2 = config2.getSubsections(section);
- assertThat(subsections1)
- .named("section \"%s\"", section)
+ assertWithMessage("section \"%s\"", section)
+ .that(subsections1)
.containsExactlyElementsIn(subsections2);
subsections1.forEach(s -> assertSubsectionEquivalent(config1, config2, section, s));
@@ -163,12 +164,12 @@ public class AllProjectsCreatorTestUtil {
Set<String> subsectionNames1 = config1.getNames(section, subsection);
Set<String> subsectionNames2 = config2.getNames(section, subsection);
String name = String.format("subsection \"%s\" of section \"%s\"", subsection, section);
- assertThat(subsectionNames1).named(name).containsExactlyElementsIn(subsectionNames2);
+ assertWithMessage(name).that(subsectionNames1).containsExactlyElementsIn(subsectionNames2);
subsectionNames1.forEach(
n ->
- assertThat(config1.getStringList(section, subsection, n))
- .named(name)
+ assertWithMessage(name)
+ .that(config1.getStringList(section, subsection, n))
.asList()
.containsExactlyElementsIn(config2.getStringList(section, subsection, n)));
}
diff --git a/java/com/google/gerrit/server/schema/testing/BUILD b/java/com/google/gerrit/server/schema/testing/BUILD
index d641c47917..77bb777f0f 100644
--- a/java/com/google/gerrit/server/schema/testing/BUILD
+++ b/java/com/google/gerrit/server/schema/testing/BUILD
@@ -7,10 +7,10 @@ java_library(
testonly = True,
srcs = glob(["*.java"]),
deps = [
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/server/securestore/testing/BUILD b/java/com/google/gerrit/server/securestore/testing/BUILD
index c2582b9a26..9afc44acb2 100644
--- a/java/com/google/gerrit/server/securestore/testing/BUILD
+++ b/java/com/google/gerrit/server/securestore/testing/BUILD
@@ -8,6 +8,6 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/server",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
],
)
diff --git a/java/com/google/gerrit/server/ssh/NoSshKeyCache.java b/java/com/google/gerrit/server/ssh/NoSshKeyCache.java
index 74bb50c912..3ba446c052 100644
--- a/java/com/google/gerrit/server/ssh/NoSshKeyCache.java
+++ b/java/com/google/gerrit/server/ssh/NoSshKeyCache.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.ssh;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.InvalidSshKeyException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
diff --git a/java/com/google/gerrit/server/ssh/SshKeyCreator.java b/java/com/google/gerrit/server/ssh/SshKeyCreator.java
index beaf1bace7..2241d863dd 100644
--- a/java/com/google/gerrit/server/ssh/SshKeyCreator.java
+++ b/java/com/google/gerrit/server/ssh/SshKeyCreator.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.ssh;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.InvalidSshKeyException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountSshKey;
public interface SshKeyCreator {
diff --git a/java/com/google/gerrit/server/submit/ChangeSet.java b/java/com/google/gerrit/server/submit/ChangeSet.java
index e721be41ab..65e0b48227 100644
--- a/java/com/google/gerrit/server/submit/ChangeSet.java
+++ b/java/com/google/gerrit/server/submit/ChangeSet.java
@@ -20,9 +20,9 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.query.change.ChangeData;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -76,8 +76,8 @@ public class ChangeSet {
return changeData;
}
- public ListMultimap<Branch.NameKey, ChangeData> changesByBranch() {
- ListMultimap<Branch.NameKey, ChangeData> ret =
+ public ListMultimap<BranchNameKey, ChangeData> changesByBranch() {
+ ListMultimap<BranchNameKey, ChangeData> ret =
MultimapBuilder.hashKeys().arrayListValues().build();
for (ChangeData cd : changeData.values()) {
ret.put(cd.change().getDest(), cd);
diff --git a/java/com/google/gerrit/server/submit/CherryPick.java b/java/com/google/gerrit/server/submit/CherryPick.java
index 8ff3cc5dbc..8b7b2cd1fb 100644
--- a/java/com/google/gerrit/server/submit/CherryPick.java
+++ b/java/com/google/gerrit/server/submit/CherryPick.java
@@ -19,11 +19,11 @@ import static com.google.gerrit.server.submit.CommitMergeStatus.SKIPPED_IDENTICA
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.MergeTip;
@@ -162,7 +162,7 @@ public class CherryPick extends SubmitStrategy {
ctx.getUpdate(psId),
psId,
newCommit,
- prevPs != null ? prevPs.getGroups() : ImmutableList.of(),
+ prevPs != null ? prevPs.groups() : ImmutableList.of(),
null,
null);
ctx.getChange().setCurrentPatchSet(patchSetInfo);
diff --git a/java/com/google/gerrit/server/submit/CommitMergeStatus.java b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
index 12172dd7b6..bf8b8408d0 100644
--- a/java/com/google/gerrit/server/submit/CommitMergeStatus.java
+++ b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.submit;
import static java.util.stream.Collectors.toSet;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -49,7 +49,8 @@ public enum CommitMergeStatus {
+ "Please rebase the change locally and upload the rebased commit for review."),
SKIPPED_IDENTICAL_TREE(
- "Marking change merged without cherry-picking to branch, as the resulting commit would be empty."),
+ "Marking change merged without cherry-picking to branch, as the resulting commit would be"
+ + " empty."),
MISSING_DEPENDENCY("Depends on change that was not submitted."),
@@ -102,24 +103,22 @@ public enum CommitMergeStatus {
commit, otherCommit, caller != null ? caller.getLoggableName() : "<user-not-available>");
} else if (changes.size() == 1) {
ChangeData cd = changes.get(0);
- if (cd.currentPatchSet().getRevision().get().equals(otherCommit)) {
+ if (cd.currentPatchSet().commitId().name().equals(otherCommit)) {
return String.format(
"Commit %s depends on commit %s of change %d which cannot be merged.",
commit, otherCommit, cd.getId().get());
}
Optional<PatchSet> patchSet =
- cd.patchSets().stream()
- .filter(ps -> ps.getRevision().get().equals(otherCommit))
- .findAny();
+ cd.patchSets().stream().filter(ps -> ps.commitId().name().equals(otherCommit)).findAny();
if (patchSet.isPresent()) {
return String.format(
"Commit %s depends on commit %s, which is outdated patch set %d of change %d."
+ " The latest patch set is %d.",
commit,
otherCommit,
- patchSet.get().getId().get(),
+ patchSet.get().id().get(),
cd.getId().get(),
- cd.currentPatchSet().getId().get());
+ cd.currentPatchSet().id().get());
}
// should not happen, fall-back to default message
return String.format(
diff --git a/java/com/google/gerrit/server/submit/EmailMerge.java b/java/com/google/gerrit/server/submit/EmailMerge.java
index a1f56eb1ac..c94d49e303 100644
--- a/java/com/google/gerrit/server/submit/EmailMerge.java
+++ b/java/com/google/gerrit/server/submit/EmailMerge.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.NotifyResolver;
diff --git a/java/com/google/gerrit/server/submit/FastForwardOp.java b/java/com/google/gerrit/server/submit/FastForwardOp.java
index 08f5abb06d..c83e113365 100644
--- a/java/com/google/gerrit/server/submit/FastForwardOp.java
+++ b/java/com/google/gerrit/server/submit/FastForwardOp.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.submit;
import static com.google.gerrit.server.submit.CommitMergeStatus.EMPTY_COMMIT;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.update.RepoContext;
diff --git a/java/com/google/gerrit/server/submit/GitModules.java b/java/com/google/gerrit/server/submit/GitModules.java
index d49f53f2f9..f8f6bc4e9a 100644
--- a/java/com/google/gerrit/server/submit/GitModules.java
+++ b/java/com/google/gerrit/server/submit/GitModules.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmoduleSubscription;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
@@ -45,7 +45,7 @@ public class GitModules {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
- GitModules create(Branch.NameKey project, MergeOpRepoManager m);
+ GitModules create(BranchNameKey project, MergeOpRepoManager m);
}
private static final String GIT_MODULES = ".gitmodules";
@@ -55,16 +55,16 @@ public class GitModules {
@Inject
GitModules(
@CanonicalWebUrl @Nullable String canonicalWebUrl,
- @Assisted Branch.NameKey branch,
+ @Assisted BranchNameKey branch,
@Assisted MergeOpRepoManager orm)
throws IOException {
- Project.NameKey project = branch.getParentKey();
+ Project.NameKey project = branch.project();
logger.atFine().log("Loading .gitmodules of %s for project %s", branch, project);
try {
OpenRepo or = orm.getRepo(project);
- ObjectId id = or.repo.resolve(branch.get());
+ ObjectId id = or.repo.resolve(branch.branch());
if (id == null) {
- throw new IOException("Cannot open branch " + branch.get());
+ throw new IOException("Cannot open branch " + branch.branch());
}
RevCommit commit = or.rw.parseCommit(id);
@@ -80,7 +80,7 @@ public class GitModules {
config = new BlobBasedConfig(null, or.repo, commit, GIT_MODULES);
} catch (ConfigInvalidException e) {
throw new IOException(
- "Could not read .gitmodules of super project: " + branch.getParentKey(), e);
+ "Could not read .gitmodules of super project: " + branch.project(), e);
}
subscriptions =
new SubmoduleSectionParser(config, canonicalWebUrl, branch).parseAllSections();
@@ -89,7 +89,7 @@ public class GitModules {
}
}
- Collection<SubmoduleSubscription> subscribedTo(Branch.NameKey src) {
+ Collection<SubmoduleSubscription> subscribedTo(BranchNameKey src) {
Collection<SubmoduleSubscription> ret = new ArrayList<>();
for (SubmoduleSubscription s : subscriptions) {
if (s.getSubmodule().equals(src)) {
diff --git a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
index 8e2f44f7d4..d76c94e167 100644
--- a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
+++ b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
@@ -23,12 +23,12 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -55,7 +55,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
@@ -79,12 +78,12 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
@AutoValue
abstract static class QueryKey {
- private static QueryKey create(Branch.NameKey branch, Iterable<String> hashes) {
+ private static QueryKey create(BranchNameKey branch, Iterable<String> hashes) {
return new AutoValue_LocalMergeSuperSetComputation_QueryKey(
branch, ImmutableSet.copyOf(hashes));
}
- abstract Branch.NameKey branch();
+ abstract BranchNameKey branch();
abstract ImmutableSet<String> hashes();
}
@@ -92,7 +91,7 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
private final PermissionBackend permissionBackend;
private final Provider<InternalChangeQuery> queryProvider;
private final Map<QueryKey, ImmutableList<ChangeData>> queryCache;
- private final Map<Branch.NameKey, Optional<RevCommit>> heads;
+ private final Map<BranchNameKey, Optional<RevCommit>> heads;
private final ProjectCache projectCache;
private final ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory;
private final int maxSubmittableChangesAtOnce;
@@ -124,10 +123,10 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
// For each target branch we run a separate rev walk to find open changes
// reachable from changes already in the merge super set.
- ImmutableListMultimap<Branch.NameKey, ChangeData> bc =
+ ImmutableListMultimap<BranchNameKey, ChangeData> bc =
byBranch(Iterables.concat(changeSet.changes(), changeSet.nonVisibleChanges()));
- for (Branch.NameKey b : bc.keySet()) {
- OpenRepo or = getRepo(orm, b.getParentKey());
+ for (BranchNameKey b : bc.keySet()) {
+ OpenRepo or = getRepo(orm, b.project());
List<RevCommit> visibleCommits = new ArrayList<>();
List<RevCommit> nonVisibleCommits = new ArrayList<>();
for (ChangeData cd : bc.get(b)) {
@@ -144,8 +143,7 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
}
// Get the underlying git commit object
- String objIdStr = cd.currentPatchSet().getRevision().get();
- RevCommit commit = or.rw.parseCommit(ObjectId.fromString(objIdStr));
+ RevCommit commit = or.rw.parseCommit(cd.currentPatchSet().commitId());
// Always include the input, even if merged. This allows
// SubmitStrategyOp to correct the situation later, assuming it gets
@@ -172,9 +170,9 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
return new ChangeSet(visibleChanges, nonVisibleChanges);
}
- private static ImmutableListMultimap<Branch.NameKey, ChangeData> byBranch(
+ private static ImmutableListMultimap<BranchNameKey, ChangeData> byBranch(
Iterable<ChangeData> changes) {
- ImmutableListMultimap.Builder<Branch.NameKey, ChangeData> builder =
+ ImmutableListMultimap.Builder<BranchNameKey, ChangeData> builder =
ImmutableListMultimap.builder();
for (ChangeData cd : changes) {
builder.put(cd.change().getDest(), cd);
@@ -224,7 +222,7 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
private ChangeSet byCommitsOnBranchNotMerged(
OpenRepo or,
- Branch.NameKey branch,
+ BranchNameKey branch,
Set<String> visibleHashes,
Set<String> nonVisibleHashes,
CurrentUser user)
@@ -247,7 +245,7 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
}
private ImmutableList<ChangeData> byCommitsOnBranchNotMerged(
- OpenRepo or, Branch.NameKey branch, Set<String> hashes) throws IOException {
+ OpenRepo or, BranchNameKey branch, Set<String> hashes) throws IOException {
if (hashes.isEmpty()) {
return ImmutableList.of();
}
@@ -266,7 +264,7 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
Collection<RevCommit> sourceCommits,
Set<String> ignoreHashes,
OpenRepo or,
- Branch.NameKey b,
+ BranchNameKey b,
int limit)
throws IOException {
Set<String> destHashes = new HashSet<>();
@@ -299,10 +297,10 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
return destHashes;
}
- private void markHeadUninteresting(OpenRepo or, Branch.NameKey b) throws IOException {
+ private void markHeadUninteresting(OpenRepo or, BranchNameKey b) throws IOException {
Optional<RevCommit> head = heads.get(b);
if (head == null) {
- Ref ref = or.repo.getRefDatabase().exactRef(b.get());
+ Ref ref = or.repo.getRefDatabase().exactRef(b.branch());
head = ref != null ? Optional.of(or.rw.parseCommit(ref.getObjectId())) : Optional.empty();
heads.put(b, head);
}
diff --git a/java/com/google/gerrit/server/submit/MergeOneOp.java b/java/com/google/gerrit/server/submit/MergeOneOp.java
index 9806bdfe4b..4555a327d2 100644
--- a/java/com/google/gerrit/server/submit/MergeOneOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOneOp.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.submit;
import static com.google.gerrit.server.submit.CommitMergeStatus.EMPTY_COMMIT;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.update.RepoContext;
import java.io.IOException;
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 63d60a68ef..adb75a402e 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -36,6 +36,11 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -48,11 +53,6 @@ import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -91,6 +91,7 @@ import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -119,7 +120,7 @@ public class MergeOp implements AutoCloseable {
public static class CommitStatus {
private final ImmutableMap<Change.Id, ChangeData> changes;
- private final ImmutableSetMultimap<Branch.NameKey, Change.Id> byBranch;
+ private final ImmutableSetMultimap<BranchNameKey, Change.Id> byBranch;
private final Map<Change.Id, CodeReviewCommit> commits;
private final ListMultimap<Change.Id, String> problems;
private final boolean allowClosed;
@@ -128,7 +129,7 @@ public class MergeOp implements AutoCloseable {
checkArgument(
!cs.furtherHiddenChanges(), "CommitStatus must not be called with hidden changes");
changes = cs.changesById();
- ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> bb = ImmutableSetMultimap.builder();
+ ImmutableSetMultimap.Builder<BranchNameKey, Change.Id> bb = ImmutableSetMultimap.builder();
for (ChangeData cd : cs.changes()) {
bb.put(cd.change().getDest(), cd.getId());
}
@@ -142,7 +143,7 @@ public class MergeOp implements AutoCloseable {
return changes.keySet();
}
- public ImmutableSet<Change.Id> getChangeIds(Branch.NameKey branch) {
+ public ImmutableSet<Change.Id> getChangeIds(BranchNameKey branch) {
return byBranch.get(branch);
}
@@ -233,6 +234,9 @@ public class MergeOp implements AutoCloseable {
private final RetryHelper retryHelper;
private final ChangeData.Factory changeDataFactory;
+ // Changes that were updated by this MergeOp.
+ private final Map<Change.Id, Change> updatedChanges;
+
private Timestamp ts;
private RequestId submissionId;
private IdentifiedUser caller;
@@ -244,6 +248,7 @@ public class MergeOp implements AutoCloseable {
private Set<Project.NameKey> allProjects;
private boolean dryrun;
private TopicMetrics topicMetrics;
+ private String traceId;
@Inject
MergeOp(
@@ -273,6 +278,7 @@ public class MergeOp implements AutoCloseable {
this.retryHelper = retryHelper;
this.topicMetrics = topicMetrics;
this.changeDataFactory = changeDataFactory;
+ this.updatedChanges = new HashMap<>();
}
@Override
@@ -296,7 +302,7 @@ public class MergeOp implements AutoCloseable {
throw new IllegalStateException(
String.format(
"SubmitRuleEvaluator.evaluate for change %s returned empty list for %s in %s",
- cd.getId(), patchSet.getId(), cd.change().getProject().get()));
+ cd.getId(), patchSet.id(), cd.change().getProject().get()));
}
for (SubmitRecord record : results) {
@@ -318,7 +324,7 @@ public class MergeOp implements AutoCloseable {
throw new IllegalStateException(
String.format(
"Unexpected SubmitRecord status %s for %s in %s",
- record.status, patchSet.getId().getId(), cd.change().getProject().get()));
+ record.status, patchSet.id().getId(), cd.change().getProject().get()));
}
}
throw new IllegalStateException();
@@ -428,8 +434,9 @@ public class MergeOp implements AutoCloseable {
* @throws RestApiException if an error occurred.
* @throws PermissionBackendException if permissions can't be checked
* @throws IOException an error occurred reading from NoteDb.
+ * @return the merged change
*/
- public void merge(
+ public Change merge(
Change change,
IdentifiedUser caller,
boolean checkSubmitRules,
@@ -511,11 +518,22 @@ public class MergeOp implements AutoCloseable {
retryHelper
.getDefaultTimeout(ActionType.CHANGE_UPDATE)
.multipliedBy(cs.projects().size()))
+ .caller(getClass())
+ .retryWithTrace(t -> !(t instanceof RestApiException))
+ .onAutoTrace(traceId -> this.traceId = traceId)
.build());
if (projects > 1) {
topicMetrics.topicSubmissionsCompleted.increment();
}
+
+ // It's expected that callers invoke this method only for open changes and that the provided
+ // change either gets updated to merged or that this method fails with an exception. For
+ // safety, fall-back to return the provided change if there was no update for this change
+ // (e.g. caller provided a change that was already merged).
+ return updatedChanges.containsKey(change.getId())
+ ? updatedChanges.get(change.getId())
+ : change;
} catch (IOException e) {
// Anything before the merge attempt is an error
throw new StorageException(e);
@@ -523,6 +541,10 @@ public class MergeOp implements AutoCloseable {
}
}
+ public Optional<String> getTraceId() {
+ return Optional.ofNullable(traceId);
+ }
+
private void openRepoManager() {
if (orm != null) {
orm.close();
@@ -573,18 +595,18 @@ public class MergeOp implements AutoCloseable {
throws IntegrationException, RestApiException, UpdateException {
checkArgument(!cs.furtherHiddenChanges(), "cannot integrate hidden changes into history");
logger.atFine().log("Beginning merge attempt on %s", cs);
- Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>();
+ Map<BranchNameKey, BranchBatch> toSubmit = new HashMap<>();
- ListMultimap<Branch.NameKey, ChangeData> cbb;
+ ListMultimap<BranchNameKey, ChangeData> cbb;
try {
cbb = cs.changesByBranch();
} catch (StorageException e) {
throw new IntegrationException("Error reading changes to submit", e);
}
- Set<Branch.NameKey> branches = cbb.keySet();
+ Set<BranchNameKey> branches = cbb.keySet();
- for (Branch.NameKey branch : branches) {
- OpenRepo or = openRepo(branch.getParentKey());
+ for (BranchNameKey branch : branches) {
+ OpenRepo or = openRepo(branch.project());
if (or != null) {
toSubmit.put(branch, validateChangeList(or, cbb.get(branch)));
}
@@ -597,10 +619,17 @@ public class MergeOp implements AutoCloseable {
SubmoduleOp submoduleOp = subOpFactory.create(branches, orm);
List<SubmitStrategy> strategies = getSubmitStrategies(toSubmit, submoduleOp, dryrun);
this.allProjects = submoduleOp.getProjectsInOrder();
- BatchUpdate.execute(
- orm.batchUpdates(allProjects),
- new SubmitStrategyListener(submitInput, strategies, commitStatus),
- dryrun);
+ try {
+ BatchUpdate.execute(
+ orm.batchUpdates(allProjects),
+ new SubmitStrategyListener(submitInput, strategies, commitStatus),
+ dryrun);
+ } finally {
+ // If the BatchUpdate fails it can be that merging some of the changes was actually
+ // successful. This is why we must to collect the updated changes also when an exception was
+ // thrown.
+ strategies.forEach(s -> updatedChanges.putAll(s.getUpdatedChanges()));
+ }
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(e.getMessage());
} catch (IOException | SubmoduleException e) {
@@ -641,14 +670,14 @@ public class MergeOp implements AutoCloseable {
}
private List<SubmitStrategy> getSubmitStrategies(
- Map<Branch.NameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, boolean dryrun)
+ Map<BranchNameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, boolean dryrun)
throws IntegrationException, NoSuchProjectException, IOException {
List<SubmitStrategy> strategies = new ArrayList<>();
- Set<Branch.NameKey> allBranches = submoduleOp.getBranchesInOrder();
+ Set<BranchNameKey> allBranches = submoduleOp.getBranchesInOrder();
Set<CodeReviewCommit> allCommits =
toSubmit.values().stream().map(BranchBatch::commits).flatMap(Set::stream).collect(toSet());
- for (Branch.NameKey branch : allBranches) {
- OpenRepo or = orm.getRepo(branch.getParentKey());
+ for (BranchNameKey branch : allBranches) {
+ OpenRepo or = orm.getRepo(branch.project());
if (toSubmit.containsKey(branch)) {
BranchBatch submitting = toSubmit.get(branch);
logger.atFine().log("adding ops for branch batch %s", submitting);
@@ -769,55 +798,47 @@ public class MergeOp implements AutoCloseable {
}
PatchSet ps;
- Branch.NameKey destBranch = chg.getDest();
+ BranchNameKey destBranch = chg.getDest();
try {
ps = cd.currentPatchSet();
} catch (StorageException e) {
commitStatus.logProblem(changeId, e);
continue;
}
- if (ps == null || ps.getRevision() == null || ps.getRevision().get() == null) {
- commitStatus.logProblem(changeId, "Missing patch set or revision on change");
- continue;
- }
-
- String idstr = ps.getRevision().get();
- ObjectId id;
- try {
- id = ObjectId.fromString(idstr);
- } catch (IllegalArgumentException e) {
- commitStatus.logProblem(changeId, e);
+ if (ps == null) {
+ commitStatus.logProblem(changeId, "Missing patch set on change");
continue;
}
- if (!revisions.containsEntry(id, ps.getId())) {
- if (revisions.containsValue(ps.getId())) {
+ ObjectId id = ps.commitId();
+ if (!revisions.containsEntry(id, ps.id())) {
+ if (revisions.containsValue(ps.id())) {
// TODO This is actually an error, the patch set ref exists but points to a revision that
// is different from the revision that we have stored for the patch set in the change
// meta data.
commitStatus.logProblem(
changeId,
"Revision "
- + idstr
+ + id.name()
+ " of patch set "
- + ps.getPatchSetId()
+ + ps.number()
+ " does not match the revision of the patch set ref "
- + ps.getId().toRefName());
+ + ps.id().toRefName());
continue;
}
// The patch set ref is not found but we want to merge the change. We can't safely do that
- // if the patch set ref is missing. In a multi-master setup this can indicate a replication
- // lag (e.g. the change meta data was already replicated, but the replication of the patch
- // set ref is still pending).
+ // if the patch set ref is missing. In a cluster setups with multiple primary nodes this can
+ // indicate a replication lag (e.g. the change meta data was already replicated, but the
+ // replication of the patch set ref is still pending).
commitStatus.logProblem(
changeId,
"Patch set ref "
- + ps.getId().toRefName()
+ + ps.id().toRefName()
+ " not found. Expected patch set ref of "
- + ps.getPatchSetId()
+ + ps.number()
+ " to point to revision "
- + idstr);
+ + id.name());
continue;
}
@@ -830,13 +851,12 @@ public class MergeOp implements AutoCloseable {
}
commit.setNotes(notes);
- commit.setPatchsetId(ps.getId());
+ commit.setPatchsetId(ps.id());
commitStatus.put(commit);
MergeValidators mergeValidators = mergeValidatorsFactory.create();
try {
- mergeValidators.validatePreMerge(
- or.repo, commit, or.project, destBranch, ps.getId(), caller);
+ mergeValidators.validatePreMerge(or.repo, commit, or.project, destBranch, ps.id(), caller);
} catch (MergeValidationException mve) {
commitStatus.problem(changeId, mve.getMessage());
continue;
diff --git a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
index 764aca8ce0..c2577e7f16 100644
--- a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
+++ b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
@@ -18,9 +18,9 @@ import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import com.google.common.collect.Maps;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.git.CodeReviewCommit;
@@ -67,7 +67,7 @@ public class MergeOpRepoManager implements AutoCloseable {
BatchUpdate update;
private final ObjectReader reader;
- private final Map<Branch.NameKey, OpenBranch> branches;
+ private final Map<BranchNameKey, OpenBranch> branches;
private OpenRepo(Repository repo, ProjectState project) {
this.repo = repo;
@@ -84,7 +84,7 @@ public class MergeOpRepoManager implements AutoCloseable {
branches = Maps.newHashMapWithExpectedSize(1);
}
- OpenBranch getBranch(Branch.NameKey branch) throws IntegrationException {
+ OpenBranch getBranch(BranchNameKey branch) throws IntegrationException {
OpenBranch ob = branches.get(branch);
if (ob == null) {
ob = new OpenBranch(this, branch);
@@ -134,13 +134,13 @@ public class MergeOpRepoManager implements AutoCloseable {
final CodeReviewCommit oldTip;
MergeTip mergeTip;
- OpenBranch(OpenRepo or, Branch.NameKey name) throws IntegrationException {
+ OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationException {
try {
- update = or.repo.updateRef(name.get());
+ update = or.repo.updateRef(name.branch());
if (update.getOldObjectId() != null) {
oldTip = or.rw.parseCommit(update.getOldObjectId());
- } else if (Objects.equals(or.repo.getFullBranch(), name.get())
- || Objects.equals(RefNames.REFS_CONFIG, name.get())) {
+ } else if (Objects.equals(or.repo.getFullBranch(), name.branch())
+ || Objects.equals(RefNames.REFS_CONFIG, name.branch())) {
oldTip = null;
update.setExpectedOldObjectId(ObjectId.zeroId());
} else {
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index d729833a96..bcebc7f27c 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -19,9 +19,9 @@ import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.logging.TraceContext;
diff --git a/java/com/google/gerrit/server/submit/RebaseSorter.java b/java/com/google/gerrit/server/submit/RebaseSorter.java
index 829ee9c575..47757681fa 100644
--- a/java/com/google/gerrit/server/submit/RebaseSorter.java
+++ b/java/com/google/gerrit/server/submit/RebaseSorter.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
@@ -108,7 +108,7 @@ public class RebaseSorter {
return sorted;
}
- private boolean isAlreadyMerged(CodeReviewCommit commit, Branch.NameKey dest) throws IOException {
+ private boolean isAlreadyMerged(CodeReviewCommit commit, BranchNameKey dest) throws IOException {
try (CodeReviewRevWalk mirw = CodeReviewCommit.newRevWalk(rw.getObjectReader())) {
mirw.reset();
mirw.markStart(commit);
diff --git a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
index b2ad4e253a..65e18ad812 100644
--- a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
@@ -19,12 +19,12 @@ import static com.google.gerrit.server.submit.CommitMergeStatus.EMPTY_COMMIT;
import static com.google.gerrit.server.submit.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.change.RebaseChangeOp;
import com.google.gerrit.server.git.CodeReviewCommit;
@@ -235,7 +235,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
ctx.getUpdate(newPatchSetId),
newPatchSetId,
newCommit,
- prevPs != null ? prevPs.getGroups() : ImmutableList.of(),
+ prevPs != null ? prevPs.groups() : ImmutableList.of(),
null,
null);
}
@@ -310,7 +310,6 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
throws IntegrationException {
// Test for merge instead of cherry pick to avoid false negatives
// on commit chains.
- return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
- && args.mergeUtil.canMerge(args.mergeSorter, repo, mergeTip, toMerge);
+ return args.mergeUtil.canMerge(args.mergeSorter, repo, mergeTip, toMerge);
}
}
diff --git a/java/com/google/gerrit/server/submit/SubmitDryRun.java b/java/com/google/gerrit/server/submit/SubmitDryRun.java
index 391d9562bb..ff1a1f023c 100644
--- a/java/com/google/gerrit/server/submit/SubmitDryRun.java
+++ b/java/com/google/gerrit/server/submit/SubmitDryRun.java
@@ -20,8 +20,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
@@ -112,7 +112,7 @@ public class SubmitDryRun {
SubmitType submitType,
Repository repo,
CodeReviewRevWalk rw,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
ObjectId tip,
ObjectId toMerge,
Set<RevCommit> alreadyAccepted)
@@ -155,10 +155,10 @@ public class SubmitDryRun {
}
}
- private ProjectState getProject(Branch.NameKey branch) throws NoSuchProjectException {
- ProjectState p = projectCache.get(branch.getParentKey());
+ private ProjectState getProject(BranchNameKey branch) throws NoSuchProjectException {
+ ProjectState p = projectCache.get(branch.project());
if (p == null) {
- throw new NoSuchProjectException(branch.getParentKey());
+ throw new NoSuchProjectException(branch.project());
}
return p;
}
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategy.java b/java/com/google/gerrit/server/submit/SubmitStrategy.java
index dc221f87a2..4c68e1b521 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategy.java
@@ -14,14 +14,16 @@
package com.google.gerrit.server.submit;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.GerritPersonIdent;
@@ -55,7 +57,9 @@ import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -82,7 +86,7 @@ public abstract class SubmitStrategy {
interface Factory {
Arguments create(
SubmitType submitType,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
CommitStatus commitStatus,
CodeReviewRevWalk rw,
IdentifiedUser caller,
@@ -114,7 +118,7 @@ public abstract class SubmitStrategy {
final ProjectConfig.Factory projectConfigFactory;
final SetPrivateOp.Factory setPrivateOpFactory;
- final Branch.NameKey destBranch;
+ final BranchNameKey destBranch;
final CodeReviewRevWalk rw;
final CommitStatus commitStatus;
final IdentifiedUser caller;
@@ -152,7 +156,7 @@ public abstract class SubmitStrategy {
Provider<InternalChangeQuery> queryProvider,
ProjectConfig.Factory projectConfigFactory,
SetPrivateOp.Factory setPrivateOpFactory,
- @Assisted Branch.NameKey destBranch,
+ @Assisted BranchNameKey destBranch,
@Assisted CommitStatus commitStatus,
@Assisted CodeReviewRevWalk rw,
@Assisted IdentifiedUser caller,
@@ -197,8 +201,8 @@ public abstract class SubmitStrategy {
this.project =
requireNonNull(
- projectCache.get(destBranch.getParentKey()),
- () -> String.format("project not found: %s", destBranch.getParentKey()));
+ projectCache.get(destBranch.project()),
+ () -> String.format("project not found: %s", destBranch.project()));
this.mergeSorter =
new MergeSorter(caller, rw, alreadyAccepted, canMergeFlag, queryProvider, incoming);
this.rebaseSorter =
@@ -217,8 +221,24 @@ public abstract class SubmitStrategy {
final Arguments args;
+ private final Set<SubmitStrategyOp> submitStrategyOps;
+
SubmitStrategy(Arguments args) {
this.args = requireNonNull(args);
+ this.submitStrategyOps = new HashSet<>();
+ }
+
+ /**
+ * Returns the updated changed after this submit strategy has been executed.
+ *
+ * @return the updated changes after this submit strategy has been executed
+ */
+ public ImmutableMap<Change.Id, Change> getUpdatedChanges() {
+ return submitStrategyOps.stream()
+ .map(SubmitStrategyOp::getUpdatedChange)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(toImmutableMap(c -> c.getId(), c -> c));
}
/**
@@ -249,8 +269,10 @@ public abstract class SubmitStrategy {
for (CodeReviewCommit c : difference) {
Change.Id id = c.change().getId();
bu.addOp(id, args.setPrivateOpFactory.create(false, null));
- bu.addOp(id, new ImplicitIntegrateOp(args, c));
+ ImplicitIntegrateOp implicitIntegrateOp = new ImplicitIntegrateOp(args, c);
+ bu.addOp(id, implicitIntegrateOp);
maybeAddTestHelperOp(bu, id);
+ this.submitStrategyOps.add(implicitIntegrateOp);
}
// Then ops for explicitly merged changes
@@ -258,6 +280,7 @@ public abstract class SubmitStrategy {
bu.addOp(op.getId(), args.setPrivateOpFactory.create(false, null));
bu.addOp(op.getId(), op);
maybeAddTestHelperOp(bu, op.getId());
+ this.submitStrategyOps.add(op);
}
}
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
index 30326f773d..cba572bcb7 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
@@ -48,7 +48,7 @@ public class SubmitStrategyFactory {
RevFlag canMergeFlag,
Set<RevCommit> alreadyAccepted,
Set<CodeReviewCommit> incoming,
- Branch.NameKey destBranch,
+ BranchNameKey destBranch,
IdentifiedUser caller,
MergeTip mergeTip,
CommitStatus commitStatus,
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyListener.java b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
index 782cd7bde6..f8bcfc189b 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.submit;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.TestSubmitInput;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.submit.MergeOp.CommitStatus;
@@ -129,7 +129,7 @@ public class SubmitStrategyListener implements BatchUpdateListener {
case ALREADY_MERGED:
// Already an ancestor of tip.
- alreadyMerged.add(commit.getPatchsetId().getParentKey());
+ alreadyMerged.add(commit.getPatchsetId().changeId());
break;
case PATH_CONFLICT:
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index 14522a2a70..79f062d5f3 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -20,19 +20,18 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
-import com.google.common.base.Function;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
@@ -89,12 +88,12 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
return toMerge;
}
- protected final Branch.NameKey getDest() {
+ protected final BranchNameKey getDest() {
return toMerge.change().getDest();
}
protected final Project.NameKey getProject() {
- return getDest().getParentKey();
+ return getDest().project();
}
@Override
@@ -132,14 +131,15 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// Needed by postUpdate, at which point mergeTip will have advanced further,
// so it's easier to just snapshot the command.
command =
- new ReceiveCommand(firstNonNull(tipBefore, ObjectId.zeroId()), tipAfter, getDest().get());
+ new ReceiveCommand(
+ firstNonNull(tipBefore, ObjectId.zeroId()), tipAfter, getDest().branch());
ctx.addRefUpdate(command);
args.submoduleOp.addBranchTip(getDest(), tipAfter);
}
private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit)
throws IntegrationException {
- String refName = getDest().get();
+ String refName = getDest().branch();
if (RefNames.REFS_CONFIG.equals(refName)) {
logger.atFine().log("Loading new configuration from %s", RefNames.REFS_CONFIG);
try {
@@ -251,7 +251,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
args.psUtil.get(ctx.getNotes(), oldPsId),
() -> String.format("missing old patch set %s", oldPsId));
} else {
- PatchSet.Id n = newPatchSet.getId();
+ PatchSet.Id n = newPatchSet.id();
checkState(
!n.equals(oldPsId) && n.equals(newPsId),
"current patch was %s and is now %s, but updateChangeImpl returned"
@@ -294,6 +294,16 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
return true;
}
+ /**
+ * Returns the updated change after this op has been executed.
+ *
+ * @return the updated change after this op has been executed, {@link Optional#empty()} if the op
+ * was not executed yet, or if the execution has failed
+ */
+ public Optional<Change> getUpdatedChange() {
+ return Optional.ofNullable(updatedChange);
+ }
+
private PatchSet getOrCreateAlreadyMergedPatchSet(ChangeContext ctx) throws IOException {
PatchSet.Id psId = alreadyMergedCommit.getPatchsetId();
logger.atFine().log("Fixing up already-merged patch set %s", psId);
@@ -311,7 +321,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// a patch set ref. Fix up the database. Note that this uses the current
// user as the uploader, which is as good a guess as any.
List<String> groups =
- prevPs != null ? prevPs.getGroups() : GroupCollector.getDefaultGroups(alreadyMergedCommit);
+ prevPs != null ? prevPs.groups() : GroupCollector.getDefaultGroups(alreadyMergedCommit);
return args.psUtil.insert(
ctx.getRevWalk(), ctx.getUpdate(psId), psId, alreadyMergedCommit, groups, null, null);
}
@@ -333,7 +343,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// approvals as well.
if (!newPsId.equals(oldPsId)) {
saveApprovals(normalized, newPsUpdate, true);
- submitter = convertPatchSet(newPsId).apply(submitter);
+ submitter = submitter.copyWithPatchSet(newPsId);
}
}
@@ -344,12 +354,13 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
for (PatchSetApproval psa :
args.approvalsUtil.byPatchSet(
ctx.getNotes(), psId, ctx.getRevWalk(), ctx.getRepoView().getConfig())) {
- byKey.put(psa.getKey(), psa);
+ byKey.put(psa.key(), psa);
}
submitter =
- ApprovalsUtil.newApproval(psId, ctx.getUser(), LabelId.legacySubmit(), 1, ctx.getWhen());
- byKey.put(submitter.getKey(), submitter);
+ ApprovalsUtil.newApproval(psId, ctx.getUser(), LabelId.legacySubmit(), 1, ctx.getWhen())
+ .build();
+ byKey.put(submitter.key(), submitter);
// Flatten out existing approvals for this patch set based upon the current
// permissions. Once the change is closed the approvals are not updated at
@@ -358,7 +369,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// permissions get modified in the future, historical records stay accurate.
LabelNormalizer.Result normalized =
args.labelNormalizer.normalize(ctx.getNotes(), byKey.values());
- update.putApproval(submitter.getLabel(), submitter.getValue());
+ update.putApproval(submitter.label(), submitter.value());
saveApprovals(normalized, update, false);
return normalized;
}
@@ -366,10 +377,10 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
private void saveApprovals(
LabelNormalizer.Result normalized, ChangeUpdate update, boolean includeUnchanged) {
for (PatchSetApproval psa : normalized.updated()) {
- update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
+ update.putApprovalFor(psa.accountId(), psa.label(), psa.value());
}
for (PatchSetApproval psa : normalized.deleted()) {
- update.removeApprovalFor(psa.getAccountId(), psa.getLabel());
+ update.removeApprovalFor(psa.accountId(), psa.label());
}
// TODO(dborowitz): Don't use a label in NoteDb; just check when status
@@ -377,27 +388,17 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
for (PatchSetApproval psa : normalized.unchanged()) {
if (includeUnchanged || psa.isLegacySubmit()) {
logger.atFine().log("Adding submit label %s", psa);
- update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
+ update.putApprovalFor(psa.accountId(), psa.label(), psa.value());
}
}
}
- private static Function<PatchSetApproval, PatchSetApproval> convertPatchSet(
- final PatchSet.Id psId) {
- return psa -> {
- if (psa.getPatchSetId().equals(psId)) {
- return psa;
- }
- return new PatchSetApproval(psId, psa);
- };
- }
-
private String getByAccountName() {
requireNonNull(submitter, "getByAccountName called before submitter populated");
Optional<Account> account =
- args.accountCache.get(submitter.getAccountId()).map(AccountState::getAccount);
- if (account.isPresent() && account.get().getFullName() != null) {
- return " by " + account.get().getFullName();
+ args.accountCache.get(submitter.accountId()).map(AccountState::account);
+ if (account.isPresent() && account.get().fullName() != null) {
+ return " by " + account.get().fullName();
}
return "";
}
@@ -483,7 +484,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
getProject(), command.getRefName(), command.getOldId(), command.getNewId());
// TODO(dborowitz): Move to BatchUpdate? Would also allow us to run once
// per project even if multiple changes to refs/meta/config are submitted.
- if (RefNames.REFS_CONFIG.equals(getDest().get())) {
+ if (RefNames.REFS_CONFIG.equals(getDest().branch())) {
args.projectCache.evict(getProject());
ProjectState p = args.projectCache.get(getProject());
try (Repository git = args.repoManager.openRepository(getProject())) {
@@ -498,7 +499,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// have failed fast in one of the other steps.
try {
args.mergedSenderFactory
- .create(ctx.getProject(), getId(), submitter.getAccountId(), ctx.getNotify(getId()))
+ .create(ctx.getProject(), getId(), submitter.accountId(), ctx.getNotify(getId()))
.sendAsync();
} catch (Exception e) {
logger.atSevere().withCause(e).log("Cannot email merged notification for %s", getId());
@@ -507,7 +508,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
args.changeMerged.fire(
updatedChange,
mergedPatchSet,
- args.accountCache.get(submitter.getAccountId()).orElse(null),
+ args.accountCache.get(submitter.accountId()).orElse(null),
args.mergeTip.getCurrentTip().name(),
ctx.getWhen());
}
diff --git a/java/com/google/gerrit/server/submit/SubmoduleOp.java b/java/com/google/gerrit/server/submit/SubmoduleOp.java
index afcf9c5d76..8ab99ddb01 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleOp.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleOp.java
@@ -23,11 +23,11 @@ import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.SubscribeSection;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.SubmoduleSubscription;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.VerboseSuperprojectUpdate;
@@ -79,9 +79,9 @@ public class SubmoduleOp {
/** Only used for branches without code review changes */
public class GitlinkOp implements RepoOnlyOp {
- private final Branch.NameKey branch;
+ private final BranchNameKey branch;
- GitlinkOp(Branch.NameKey branch) {
+ GitlinkOp(BranchNameKey branch) {
this.branch = branch;
}
@@ -89,7 +89,7 @@ public class SubmoduleOp {
public void updateRepo(RepoContext ctx) throws Exception {
CodeReviewCommit c = composeGitlinksCommit(branch);
if (c != null) {
- ctx.addRefUpdate(c.getParent(0), c, branch.get());
+ ctx.addRefUpdate(c.getParent(0), c, branch.branch());
addBranchTip(branch, c);
}
}
@@ -114,7 +114,7 @@ public class SubmoduleOp {
this.projectCache = projectCache;
}
- public SubmoduleOp create(Set<Branch.NameKey> updatedBranches, MergeOpRepoManager orm)
+ public SubmoduleOp create(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
throws SubmoduleException {
return new SubmoduleOp(
gitmodulesFactory, serverIdent.get(), cfg, projectCache, updatedBranches, orm);
@@ -129,41 +129,41 @@ public class SubmoduleOp {
private final long maxCombinedCommitMessageSize;
private final long maxCommitMessages;
private final MergeOpRepoManager orm;
- private final Map<Branch.NameKey, GitModules> branchGitModules;
+ private final Map<BranchNameKey, GitModules> branchGitModules;
/** Branches updated as part of the enclosing submit or push batch. */
- private final ImmutableSet<Branch.NameKey> updatedBranches;
+ private final ImmutableSet<BranchNameKey> updatedBranches;
/**
* Current branch tips, taking into account commits created during the submit process as well as
* submodule updates produced by this class.
*/
- private final Map<Branch.NameKey, CodeReviewCommit> branchTips;
+ private final Map<BranchNameKey, CodeReviewCommit> branchTips;
/**
* Branches in a superproject that contain submodule subscriptions, plus branches in submodules
* which are subscribed to by some superproject.
*/
- private final Set<Branch.NameKey> affectedBranches;
+ private final Set<BranchNameKey> affectedBranches;
/** Copy of {@link #affectedBranches}, sorted by submodule traversal order. */
- private final ImmutableSet<Branch.NameKey> sortedBranches;
+ private final ImmutableSet<BranchNameKey> sortedBranches;
/** Multimap of superproject branch to submodule subscriptions contained in that branch. */
- private final SetMultimap<Branch.NameKey, SubmoduleSubscription> targets;
+ private final SetMultimap<BranchNameKey, SubmoduleSubscription> targets;
/**
* Multimap of superproject name to all branch names within that superproject which have submodule
* subscriptions.
*/
- private final SetMultimap<Project.NameKey, Branch.NameKey> branchesByProject;
+ private final SetMultimap<Project.NameKey, BranchNameKey> branchesByProject;
private SubmoduleOp(
GitModules.Factory gitmodulesFactory,
PersonIdent myIdent,
Config cfg,
ProjectCache projectCache,
- Set<Branch.NameKey> updatedBranches,
+ Set<BranchNameKey> updatedBranches,
MergeOpRepoManager orm)
throws SubmoduleException {
this.gitmodulesFactory = gitmodulesFactory;
@@ -214,15 +214,15 @@ public class SubmoduleOp {
//
// In addition to improving readability, this approach has the advantage of making (1) and (2)
// testable using small tests.
- private ImmutableSet<Branch.NameKey> calculateSubscriptionMaps() throws SubmoduleException {
+ private ImmutableSet<BranchNameKey> calculateSubscriptionMaps() throws SubmoduleException {
if (!enableSuperProjectSubscriptions) {
logger.atFine().log("Updating superprojects disabled");
return null;
}
logger.atFine().log("Calculating superprojects - submodules map");
- LinkedHashSet<Branch.NameKey> allVisited = new LinkedHashSet<>();
- for (Branch.NameKey updatedBranch : updatedBranches) {
+ LinkedHashSet<BranchNameKey> allVisited = new LinkedHashSet<>();
+ for (BranchNameKey updatedBranch : updatedBranches) {
if (allVisited.contains(updatedBranch)) {
continue;
}
@@ -240,9 +240,9 @@ public class SubmoduleOp {
}
private void searchForSuperprojects(
- Branch.NameKey current,
- LinkedHashSet<Branch.NameKey> currentVisited,
- LinkedHashSet<Branch.NameKey> allVisited)
+ BranchNameKey current,
+ LinkedHashSet<BranchNameKey> currentVisited,
+ LinkedHashSet<BranchNameKey> allVisited)
throws SubmoduleException {
logger.atFine().log("Now processing %s", current);
@@ -261,10 +261,10 @@ public class SubmoduleOp {
Collection<SubmoduleSubscription> subscriptions =
superProjectSubscriptionsForSubmoduleBranch(current);
for (SubmoduleSubscription sub : subscriptions) {
- Branch.NameKey superBranch = sub.getSuperProject();
+ BranchNameKey superBranch = sub.getSuperProject();
searchForSuperprojects(superBranch, currentVisited, allVisited);
targets.put(superBranch, sub);
- branchesByProject.put(superBranch.getParentKey(), superBranch);
+ branchesByProject.put(superBranch.project(), superBranch);
affectedBranches.add(superBranch);
affectedBranches.add(sub.getSubmodule());
}
@@ -303,31 +303,33 @@ public class SubmoduleOp {
return sb.toString();
}
- private Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src, SubscribeSection s)
+ private Collection<BranchNameKey> getDestinationBranches(BranchNameKey src, SubscribeSection s)
throws IOException {
- Collection<Branch.NameKey> ret = new HashSet<>();
+ Collection<BranchNameKey> ret = new HashSet<>();
logger.atFine().log("Inspecting SubscribeSection %s", s);
for (RefSpec r : s.getMatchingRefSpecs()) {
logger.atFine().log("Inspecting [matching] ref %s", r);
- if (!r.matchSource(src.get())) {
+ if (!r.matchSource(src.branch())) {
continue;
}
if (r.isWildcard()) {
// refs/heads/*[:refs/somewhere/*]
- ret.add(new Branch.NameKey(s.getProject(), r.expandFromSource(src.get()).getDestination()));
+ ret.add(
+ BranchNameKey.create(
+ s.getProject(), r.expandFromSource(src.branch()).getDestination()));
} else {
// e.g. refs/heads/master[:refs/heads/stable]
String dest = r.getDestination();
if (dest == null) {
dest = r.getSource();
}
- ret.add(new Branch.NameKey(s.getProject(), dest));
+ ret.add(BranchNameKey.create(s.getProject(), dest));
}
}
for (RefSpec r : s.getMultiMatchRefSpecs()) {
logger.atFine().log("Inspecting [all] ref %s", r);
- if (!r.matchSource(src.get())) {
+ if (!r.matchSource(src.branch())) {
continue;
}
OpenRepo or;
@@ -344,7 +346,7 @@ public class SubmoduleOp {
if (r.getDestination() != null && !r.matchDestination(ref.getName())) {
continue;
}
- Branch.NameKey b = new Branch.NameKey(s.getProject(), ref.getName());
+ BranchNameKey b = BranchNameKey.create(s.getProject(), ref.getName());
if (!ret.contains(b)) {
ret.add(b);
}
@@ -356,18 +358,18 @@ public class SubmoduleOp {
@UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
public Collection<SubmoduleSubscription> superProjectSubscriptionsForSubmoduleBranch(
- Branch.NameKey srcBranch) throws IOException {
+ BranchNameKey srcBranch) throws IOException {
logger.atFine().log("Calculating possible superprojects for %s", srcBranch);
Collection<SubmoduleSubscription> ret = new ArrayList<>();
- Project.NameKey srcProject = srcBranch.getParentKey();
+ Project.NameKey srcProject = srcBranch.project();
for (SubscribeSection s : projectCache.get(srcProject).getSubscribeSections(srcBranch)) {
logger.atFine().log("Checking subscribe section %s", s);
- Collection<Branch.NameKey> branches = getDestinationBranches(srcBranch, s);
- for (Branch.NameKey targetBranch : branches) {
- Project.NameKey targetProject = targetBranch.getParentKey();
+ Collection<BranchNameKey> branches = getDestinationBranches(srcBranch, s);
+ for (BranchNameKey targetBranch : branches) {
+ Project.NameKey targetProject = targetBranch.project();
try {
OpenRepo or = orm.getRepo(targetProject);
- ObjectId id = or.repo.resolve(targetBranch.get());
+ ObjectId id = or.repo.resolve(targetBranch.branch());
if (id == null) {
logger.atFine().log("The branch %s doesn't exist.", targetBranch);
continue;
@@ -403,7 +405,7 @@ public class SubmoduleOp {
superProjects.add(project);
// get a new BatchUpdate for the super project
OpenRepo or = orm.getRepo(project);
- for (Branch.NameKey branch : branchesByProject.get(project)) {
+ for (BranchNameKey branch : branchesByProject.get(project)) {
addOp(or.getUpdate(), branch);
}
}
@@ -415,11 +417,11 @@ public class SubmoduleOp {
}
/** Create a separate gitlink commit */
- private CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber)
+ private CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber)
throws IOException, SubmoduleException {
OpenRepo or;
try {
- or = orm.getRepo(subscriber.getParentKey());
+ or = orm.getRepo(subscriber.project());
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access superproject", e);
}
@@ -428,7 +430,7 @@ public class SubmoduleOp {
if (branchTips.containsKey(subscriber)) {
currentCommit = branchTips.get(subscriber);
} else {
- Ref r = or.repo.exactRef(subscriber.get());
+ Ref r = or.repo.exactRef(subscriber.branch());
if (r == null) {
throw new SubmoduleException(
"The branch was probably deleted from the subscriber repository");
@@ -485,11 +487,11 @@ public class SubmoduleOp {
}
/** Amend an existing commit with gitlink updates */
- CodeReviewCommit composeGitlinksCommit(Branch.NameKey subscriber, CodeReviewCommit currentCommit)
+ CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber, CodeReviewCommit currentCommit)
throws IOException, SubmoduleException {
OpenRepo or;
try {
- or = orm.getRepo(subscriber.getParentKey());
+ or = orm.getRepo(subscriber.project());
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access superproject", e);
}
@@ -531,7 +533,7 @@ public class SubmoduleOp {
logger.atFine().log("Updating gitlink for %s", s);
OpenRepo subOr;
try {
- subOr = orm.getRepo(s.getSubmodule().getParentKey());
+ subOr = orm.getRepo(s.getSubmodule().project());
} catch (NoSuchProjectException | IOException e) {
throw new SubmoduleException("Cannot access submodule", e);
}
@@ -544,7 +546,7 @@ public class SubmoduleOp {
"Requested to update gitlink "
+ s.getPath()
+ " in "
- + s.getSubmodule().getParentKey().get()
+ + s.getSubmodule().project().get()
+ " but entry "
+ "doesn't have gitlink file mode.";
throw new SubmoduleException(errMsg);
@@ -576,7 +578,7 @@ public class SubmoduleOp {
// superproject is still subscribed to this branch. Re-read the ref to see if anything has
// changed since the last time the gitlink was updated, and roll that update into the same
// commit as all other submodule updates.
- Ref ref = subOr.repo.getRefDatabase().exactRef(s.getSubmodule().get());
+ Ref ref = subOr.repo.getRefDatabase().exactRef(s.getSubmodule().branch());
if (ref == null) {
ed.add(new DeletePath(s.getPath()));
return null;
@@ -615,7 +617,7 @@ public class SubmoduleOp {
msgbuf.append("* Update ");
msgbuf.append(s.getPath());
msgbuf.append(" from branch '");
- msgbuf.append(s.getSubmodule().getShortName());
+ msgbuf.append(s.getSubmodule().shortName());
msgbuf.append("'");
msgbuf.append("\n to ");
msgbuf.append(newCommit.getName());
@@ -675,8 +677,8 @@ public class SubmoduleOp {
addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
}
- for (Branch.NameKey branch : updatedBranches) {
- projects.add(branch.getParentKey());
+ for (BranchNameKey branch : updatedBranches) {
+ projects.add(branch.project());
}
return ImmutableSet.copyOf(projects);
}
@@ -697,10 +699,10 @@ public class SubmoduleOp {
current.add(project);
Set<Project.NameKey> subprojects = new HashSet<>();
- for (Branch.NameKey branch : branchesByProject.get(project)) {
+ for (BranchNameKey branch : branchesByProject.get(project)) {
Collection<SubmoduleSubscription> subscriptions = targets.get(branch);
for (SubmoduleSubscription s : subscriptions) {
- subprojects.add(s.getSubmodule().getParentKey());
+ subprojects.add(s.getSubmodule().project());
}
}
@@ -712,8 +714,8 @@ public class SubmoduleOp {
projects.add(project);
}
- ImmutableSet<Branch.NameKey> getBranchesInOrder() {
- LinkedHashSet<Branch.NameKey> branches = new LinkedHashSet<>();
+ ImmutableSet<BranchNameKey> getBranchesInOrder() {
+ LinkedHashSet<BranchNameKey> branches = new LinkedHashSet<>();
if (sortedBranches != null) {
branches.addAll(sortedBranches);
}
@@ -721,15 +723,15 @@ public class SubmoduleOp {
return ImmutableSet.copyOf(branches);
}
- boolean hasSubscription(Branch.NameKey branch) {
+ boolean hasSubscription(BranchNameKey branch) {
return targets.containsKey(branch);
}
- void addBranchTip(Branch.NameKey branch, CodeReviewCommit tip) {
+ void addBranchTip(BranchNameKey branch, CodeReviewCommit tip) {
branchTips.put(branch, tip);
}
- void addOp(BatchUpdate bu, Branch.NameKey branch) {
+ void addOp(BatchUpdate bu, BranchNameKey branch) {
bu.addRepoOnlyOp(new GitlinkOp(branch));
}
}
diff --git a/java/com/google/gerrit/server/submit/TestHelperOp.java b/java/com/google/gerrit/server/submit/TestHelperOp.java
index bbb198ad9a..7763e2f811 100644
--- a/java/com/google/gerrit/server/submit/TestHelperOp.java
+++ b/java/com/google/gerrit/server/submit/TestHelperOp.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.change.TestSubmitInput;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.RepoContext;
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index 0ca9f5e60f..ce16706b45 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -32,14 +32,15 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountState;
@@ -52,6 +53,7 @@ import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
+import com.google.gerrit.server.notedb.TooManyUpdatesException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -110,7 +112,6 @@ public class BatchUpdate implements AutoCloseable {
};
}
- // TODO(dborowitz): Make this package-private to force all callers to use RetryHelper.
public interface Factory {
BatchUpdate create(Project.NameKey project, CurrentUser user, Timestamp when);
}
@@ -178,23 +179,27 @@ public class BatchUpdate implements AutoCloseable {
}
private static void wrapAndThrowException(Exception e) throws UpdateException, RestApiException {
- Throwables.throwIfUnchecked(e);
-
- // Propagate REST API exceptions thrown by operations; they commonly throw exceptions like
- // ResourceConflictException to indicate an atomic update failure.
- Throwables.throwIfInstanceOf(e, UpdateException.class);
- Throwables.throwIfInstanceOf(e, RestApiException.class);
-
- // Convert other common non-REST exception types with user-visible messages to corresponding
- // REST exception types
- if (e instanceof InvalidChangeOperationException) {
+ // Convert common non-REST exception types with user-visible messages to corresponding REST
+ // exception types.
+ if (e instanceof InvalidChangeOperationException || e instanceof TooManyUpdatesException) {
throw new ResourceConflictException(e.getMessage(), e);
} else if (e instanceof NoSuchChangeException
|| e instanceof NoSuchRefException
|| e instanceof NoSuchProjectException) {
throw new ResourceNotFoundException(e.getMessage(), e);
+ } else if (e instanceof CommentsRejectedException) {
+ // SC_BAD_REQUEST is not ideal because it's not a syntactic error, but there is no better
+ // status code and it's isolated in monitoring.
+ throw new BadRequestException(e.getMessage(), e);
}
+ Throwables.throwIfUnchecked(e);
+
+ // Propagate REST API exceptions thrown by operations; they commonly throw exceptions like
+ // ResourceConflictException to indicate an atomic update failure.
+ Throwables.throwIfInstanceOf(e, UpdateException.class);
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+
// Otherwise, wrap in a generic UpdateException, which does not include a user-visible message.
throw new UpdateException(e);
}
@@ -289,7 +294,7 @@ public class BatchUpdate implements AutoCloseable {
private enum ChangeResult {
SKIPPED,
UPSERTED,
- DELETED;
+ DELETED
}
private final GitRepositoryManager repoManager;
@@ -566,9 +571,7 @@ public class BatchUpdate implements AutoCloseable {
handle.setResult(id, ChangeResult.SKIPPED);
continue;
}
- for (ChangeUpdate u : ctx.updates.values()) {
- handle.manager.add(u);
- }
+ ctx.updates.values().forEach(handle.manager::add);
if (ctx.deleted) {
logDebug("Change %s was deleted", id);
handle.manager.deleteChange(id);
diff --git a/java/com/google/gerrit/server/update/BatchUpdateOp.java b/java/com/google/gerrit/server/update/BatchUpdateOp.java
index 87a43a354c..a2c2394498 100644
--- a/java/com/google/gerrit/server/update/BatchUpdateOp.java
+++ b/java/com/google/gerrit/server/update/BatchUpdateOp.java
@@ -19,7 +19,7 @@ package com.google.gerrit.server.update;
*
* <p>Each operation has {@link #updateChange(ChangeContext)} called once the change is read in a
* transaction. Ops are associated with updates via {@link
- * BatchUpdate#addOp(com.google.gerrit.reviewdb.client.Change.Id, BatchUpdateOp)}.
+ * BatchUpdate#addOp(com.google.gerrit.entities.Change.Id, BatchUpdateOp)}.
*
* <p>Usually, a single {@code BatchUpdateOp} instance is only associated with a single change, i.e.
* {@code addOp} is only called once with that instance. Additionally, each method in {@code
diff --git a/java/com/google/gerrit/server/update/ChangeContext.java b/java/com/google/gerrit/server/update/ChangeContext.java
index 28674fcbe6..bd6d90bdc3 100644
--- a/java/com/google/gerrit/server/update/ChangeContext.java
+++ b/java/com/google/gerrit/server/update/ChangeContext.java
@@ -16,8 +16,8 @@ package com.google.gerrit.server.update;
import static java.util.Objects.requireNonNull;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
diff --git a/java/com/google/gerrit/server/update/CommentsRejectedException.java b/java/com/google/gerrit/server/update/CommentsRejectedException.java
new file mode 100644
index 0000000000..6b0c04d77a
--- /dev/null
+++ b/java/com/google/gerrit/server/update/CommentsRejectedException.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.update;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/** Thrown when comment validation rejected a comment, preventing it from being published. */
+public class CommentsRejectedException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final ImmutableList<CommentValidationFailure> commentValidationFailures;
+
+ public CommentsRejectedException(Collection<CommentValidationFailure> commentValidationFailures) {
+ this.commentValidationFailures = ImmutableList.copyOf(commentValidationFailures);
+ }
+
+ @Override
+ public String getMessage() {
+ return "One or more comments were rejected in validation: "
+ + commentValidationFailures.stream()
+ .map(CommentValidationFailure::getMessage)
+ .collect(Collectors.joining("; "));
+ }
+
+ /**
+ * Returns the validation failures that caused this exception. By contract this list is never
+ * empty.
+ */
+ public ImmutableList<CommentValidationFailure> getCommentValidationFailures() {
+ return commentValidationFailures;
+ }
+}
diff --git a/java/com/google/gerrit/server/update/Context.java b/java/com/google/gerrit/server/update/Context.java
index 8704cf03fe..99471686ce 100644
--- a/java/com/google/gerrit/server/update/Context.java
+++ b/java/com/google/gerrit/server/update/Context.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.update;
import static java.util.Objects.requireNonNull;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountState;
@@ -114,7 +114,7 @@ public interface Context {
/**
* Get the account of the user performing the update.
*
- * <p>Convenience method for {@code getIdentifiedUser().getAccount()}.
+ * <p>Convenience method for {@code getIdentifiedUser().account()}.
*
* @see CurrentUser#asIdentifiedUser()
* @return account.
diff --git a/java/com/google/gerrit/server/update/InsertChangeOp.java b/java/com/google/gerrit/server/update/InsertChangeOp.java
index 70600590fb..2676494cf4 100644
--- a/java/com/google/gerrit/server/update/InsertChangeOp.java
+++ b/java/com/google/gerrit/server/update/InsertChangeOp.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.update;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import java.io.IOException;
/**
diff --git a/java/com/google/gerrit/server/update/RepoView.java b/java/com/google/gerrit/server/update/RepoView.java
index 73dd12f35e..52467a4437 100644
--- a/java/com/google/gerrit/server/update/RepoView.java
+++ b/java/com/google/gerrit/server/update/RepoView.java
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toMap;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import java.io.IOException;
import java.util.Map;
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index ae8ba53725..bea38677a8 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -28,7 +28,6 @@ import com.github.rholder.retry.WaitStrategies;
import com.github.rholder.retry.WaitStrategy;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
@@ -36,18 +35,25 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.metrics.Counter1;
+import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
-import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.ExceptionHook;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import org.eclipse.jgit.lib.Config;
@Singleton
@@ -94,12 +100,24 @@ public class RetryHelper {
@Nullable
abstract Duration timeout();
+ abstract Optional<Class<?>> caller();
+
+ abstract Optional<Predicate<Throwable>> retryWithTrace();
+
+ abstract Optional<Consumer<String>> onAutoTrace();
+
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder listener(RetryListener listener);
public abstract Builder timeout(Duration timeout);
+ public abstract Builder caller(Class<?> caller);
+
+ public abstract Builder retryWithTrace(Predicate<Throwable> exceptionPredicate);
+
+ public abstract Builder onAutoTrace(Consumer<String> traceIdConsumer);
+
public abstract Options build();
}
}
@@ -107,21 +125,24 @@ public class RetryHelper {
@VisibleForTesting
@Singleton
public static class Metrics {
- final Histogram1<ActionType> attemptCounts;
+ final Counter1<ActionType> attemptCounts;
final Counter1<ActionType> timeoutCount;
+ final Counter2<ActionType, String> autoRetryCount;
+ final Counter2<ActionType, String> failuresOnAutoRetryCount;
@Inject
Metrics(MetricMaker metricMaker) {
- Field<ActionType> view = Field.ofEnum(ActionType.class, "action_type");
+ Field<ActionType> actionTypeField =
+ Field.ofEnum(ActionType.class, "action_type", Metadata.Builder::actionType).build();
attemptCounts =
- metricMaker.newHistogram(
- "action/retry_attempt_counts",
+ metricMaker.newCounter(
+ "action/retry_attempt_count",
new Description(
- "Distribution of number of attempts made by RetryHelper to execute an action"
- + " (1 == single attempt, no retry)")
+ "Number of retry attempts made by RetryHelper to execute an action"
+ + " (0 == single attempt, no retry)")
.setCumulative()
.setUnit("attempts"),
- view);
+ actionTypeField);
timeoutCount =
metricMaker.newCounter(
"action/retry_timeout_count",
@@ -129,7 +150,27 @@ public class RetryHelper {
"Number of action executions of RetryHelper that ultimately timed out")
.setCumulative()
.setUnit("timeouts"),
- view);
+ actionTypeField);
+ autoRetryCount =
+ metricMaker.newCounter(
+ "action/auto_retry_count",
+ new Description("Number of automatic retries with tracing")
+ .setCumulative()
+ .setUnit("retries"),
+ actionTypeField,
+ Field.ofString("operation_name", Metadata.Builder::operationName)
+ .description("The name of the operation that was retried.")
+ .build());
+ failuresOnAutoRetryCount =
+ metricMaker.newCounter(
+ "action/failures_on_auto_retry_count",
+ new Description("Number of failures on auto retry")
+ .setCumulative()
+ .setUnit("failures"),
+ actionTypeField,
+ Field.ofString("operation_name", Metadata.Builder::operationName)
+ .description("The name of the operation that was retried.")
+ .build());
}
}
@@ -143,13 +184,19 @@ public class RetryHelper {
private final Metrics metrics;
private final BatchUpdate.Factory updateFactory;
+ private final PluginSetContext<ExceptionHook> exceptionHooks;
private final Map<ActionType, Duration> defaultTimeouts;
private final WaitStrategy waitStrategy;
@Nullable private final Consumer<RetryerBuilder<?>> overwriteDefaultRetryerStrategySetup;
+ private final boolean retryWithTraceOnFailure;
@Inject
- RetryHelper(@GerritServerConfig Config cfg, Metrics metrics, BatchUpdate.Factory updateFactory) {
- this(cfg, metrics, updateFactory, null);
+ RetryHelper(
+ @GerritServerConfig Config cfg,
+ Metrics metrics,
+ PluginSetContext<ExceptionHook> exceptionHooks,
+ BatchUpdate.Factory updateFactory) {
+ this(cfg, metrics, updateFactory, exceptionHooks, null);
}
@VisibleForTesting
@@ -157,9 +204,11 @@ public class RetryHelper {
@GerritServerConfig Config cfg,
Metrics metrics,
BatchUpdate.Factory updateFactory,
+ PluginSetContext<ExceptionHook> exceptionHooks,
@Nullable Consumer<RetryerBuilder<?>> overwriteDefaultRetryerStrategySetup) {
this.metrics = metrics;
this.updateFactory = updateFactory;
+ this.exceptionHooks = exceptionHooks;
Duration defaultTimeout =
Duration.ofMillis(
@@ -185,6 +234,7 @@ public class RetryHelper {
MILLISECONDS),
WaitStrategies.randomWait(50, MILLISECONDS));
this.overwriteDefaultRetryerStrategySetup = overwriteDefaultRetryerStrategySetup;
+ this.retryWithTraceOnFailure = cfg.getBoolean("retry", "retryWithTraceOnFailure", false);
}
public Duration getDefaultTimeout(ActionType actionType) {
@@ -255,15 +305,57 @@ public class RetryHelper {
Predicate<Throwable> exceptionPredicate)
throws Throwable {
MetricListener listener = new MetricListener();
- try {
- RetryerBuilder<T> retryerBuilder = createRetryerBuilder(actionType, opts, exceptionPredicate);
+ try (TraceContext traceContext = TraceContext.open()) {
+ RetryerBuilder<T> retryerBuilder =
+ createRetryerBuilder(
+ actionType,
+ opts,
+ t -> {
+ // exceptionPredicate checks for temporary errors for which the operation should be
+ // retried (e.g. LockFailure). The retry has good chances to succeed.
+ if (exceptionPredicate.test(t)) {
+ return true;
+ }
+
+ // Exception hooks may identify additional exceptions for retry.
+ if (exceptionHooks.stream().anyMatch(h -> h.shouldRetry(t))) {
+ return true;
+ }
+
+ // A non-recoverable failure occurred. Check if we should retry to capture a trace
+ // of the failure. If a trace was already done there is no need to retry.
+ if (retryWithTraceOnFailure
+ && opts.retryWithTrace().isPresent()
+ && opts.retryWithTrace().get().test(t)) {
+ String caller = opts.caller().map(Class::getSimpleName).orElse("N/A");
+ if (!traceContext.isTracing()) {
+ String traceId = "retry-on-failure-" + new RequestId();
+ traceContext.addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
+ opts.onAutoTrace().ifPresent(c -> c.accept(traceId));
+ logger.atFine().withCause(t).log(
+ "AutoRetry: %s failed, retry with tracing enabled", caller);
+ metrics.autoRetryCount.increment(actionType, caller);
+ return true;
+ }
+
+ // A non-recoverable failure occurred. We retried the operation with tracing
+ // enabled and it failed again. Log the failure so that admin can see if it
+ // differs from the failure that triggered the retry.
+ logger.atFine().withCause(t).log(
+ "AutoRetry: auto-retry of %s has failed", caller);
+ metrics.failuresOnAutoRetryCount.increment(actionType, caller);
+ return false;
+ }
+
+ return false;
+ });
retryerBuilder.withRetryListener(listener);
return executeWithTimeoutCount(actionType, action, retryerBuilder.build());
} finally {
if (listener.getAttemptCount() > 1) {
logger.atFine().log("%s was attempted %d times", actionType, listener.getAttemptCount());
+ metrics.attemptCounts.incrementBy(actionType, listener.getAttemptCount() - 1);
}
- metrics.attemptCounts.record(actionType, listener.getAttemptCount());
}
}
@@ -295,7 +387,7 @@ public class RetryHelper {
private <O> RetryerBuilder<O> createRetryerBuilder(
ActionType actionType, Options opts, Predicate<Throwable> exceptionPredicate) {
RetryerBuilder<O> retryerBuilder =
- RetryerBuilder.<O>newBuilder().retryIfException(exceptionPredicate);
+ RetryerBuilder.<O>newBuilder().retryIfException(exceptionPredicate::test);
if (opts.listener() != null) {
retryerBuilder.withRetryListener(opts.listener());
}
diff --git a/java/com/google/gerrit/server/update/RetryingRestCollectionModifyView.java b/java/com/google/gerrit/server/update/RetryingRestCollectionModifyView.java
index cd4df4593c..bce1209bb9 100644
--- a/java/com/google/gerrit/server/update/RetryingRestCollectionModifyView.java
+++ b/java/com/google/gerrit/server/update/RetryingRestCollectionModifyView.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.update;
+import com.google.common.base.Throwables;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
import com.google.gerrit.extensions.restapi.RestResource;
+import java.util.concurrent.atomic.AtomicReference;
public abstract class RetryingRestCollectionModifyView<
P extends RestResource, C extends RestResource, I, O>
@@ -30,11 +34,25 @@ public abstract class RetryingRestCollectionModifyView<
}
@Override
- public final O apply(P parentResource, I input)
+ public final Response<O> apply(P parentResource, I input)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
- return retryHelper.execute(updateFactory -> applyImpl(updateFactory, parentResource, input));
+ AtomicReference<String> traceId = new AtomicReference<>(null);
+ try {
+ RetryHelper.Options retryOptions =
+ RetryHelper.options()
+ .caller(getClass())
+ .retryWithTrace(t -> !(t instanceof RestApiException))
+ .onAutoTrace(traceId::set)
+ .build();
+ return retryHelper
+ .execute(updateFactory -> applyImpl(updateFactory, parentResource, input), retryOptions)
+ .traceId(traceId.get());
+ } catch (Exception e) {
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+ return Response.<O>internalServerError(e).traceId(traceId.get());
+ }
}
- protected abstract O applyImpl(BatchUpdate.Factory updateFactory, P parentResource, I input)
- throws Exception;
+ protected abstract Response<O> applyImpl(
+ BatchUpdate.Factory updateFactory, P parentResource, I input) throws Exception;
}
diff --git a/java/com/google/gerrit/server/update/RetryingRestModifyView.java b/java/com/google/gerrit/server/update/RetryingRestModifyView.java
index 1b0b1f4d0a..56c3eec095 100644
--- a/java/com/google/gerrit/server/update/RetryingRestModifyView.java
+++ b/java/com/google/gerrit/server/update/RetryingRestModifyView.java
@@ -14,8 +14,12 @@
package com.google.gerrit.server.update;
+import com.google.common.base.Throwables;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestResource;
+import java.util.concurrent.atomic.AtomicReference;
public abstract class RetryingRestModifyView<R extends RestResource, I, O>
implements RestModifyView<R, I> {
@@ -26,10 +30,24 @@ public abstract class RetryingRestModifyView<R extends RestResource, I, O>
}
@Override
- public final O apply(R resource, I input) throws Exception {
- return retryHelper.execute(updateFactory -> applyImpl(updateFactory, resource, input));
+ public final Response<O> apply(R resource, I input) throws RestApiException {
+ AtomicReference<String> traceId = new AtomicReference<>(null);
+ try {
+ RetryHelper.Options retryOptions =
+ RetryHelper.options()
+ .caller(getClass())
+ .retryWithTrace(t -> !(t instanceof RestApiException))
+ .onAutoTrace(traceId::set)
+ .build();
+ return retryHelper
+ .execute(updateFactory -> applyImpl(updateFactory, resource, input), retryOptions)
+ .traceId(traceId.get());
+ } catch (Exception e) {
+ Throwables.throwIfInstanceOf(e, RestApiException.class);
+ return Response.<O>internalServerError(e).traceId(traceId.get());
+ }
}
- protected abstract O applyImpl(BatchUpdate.Factory updateFactory, R resource, I input)
+ protected abstract Response<O> applyImpl(BatchUpdate.Factory updateFactory, R resource, I input)
throws Exception;
}
diff --git a/java/com/google/gerrit/server/util/CommitMessageUtil.java b/java/com/google/gerrit/server/util/CommitMessageUtil.java
index 4ad226b7fd..1c8ce0c8ad 100644
--- a/java/com/google/gerrit/server/util/CommitMessageUtil.java
+++ b/java/com/google/gerrit/server/util/CommitMessageUtil.java
@@ -18,8 +18,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.Change;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.eclipse.jgit.lib.Constants;
@@ -34,7 +34,7 @@ public class CommitMessageUtil {
try {
rng = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("Cannot create RNG for Change-Id generator", e);
+ throw new IllegalStateException("Cannot create RNG for Change-Id generator", e);
}
}
@@ -71,6 +71,6 @@ public class CommitMessageUtil {
}
public static Change.Key generateKey() {
- return new Change.Key("I" + generateChangeId().name());
+ return Change.key("I" + generateChangeId().name());
}
}
diff --git a/java/com/google/gerrit/server/util/IdGenerator.java b/java/com/google/gerrit/server/util/IdGenerator.java
index 276df06d66..d4c2dc4855 100644
--- a/java/com/google/gerrit/server/util/IdGenerator.java
+++ b/java/com/google/gerrit/server/util/IdGenerator.java
@@ -45,8 +45,8 @@ public class IdGenerator {
public static int mix(int salt, int in) {
short v0 = hi16(in);
short v1 = lo16(in);
- v0 += ((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1;
- v1 += ((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3;
+ v0 += (short) (((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1);
+ v1 += (short) (((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3);
return result(v0, v1);
}
@@ -54,8 +54,8 @@ public class IdGenerator {
static int unmix(int in) {
short v0 = hi16(in);
short v1 = lo16(in);
- v1 -= ((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3;
- v0 -= ((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1;
+ v1 -= (short) (((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3);
+ v0 -= (short) (((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1);
return result(v0, v1);
}
diff --git a/java/com/google/gerrit/server/util/MagicBranch.java b/java/com/google/gerrit/server/util/MagicBranch.java
index 4e41be0e01..924c2887c6 100644
--- a/java/com/google/gerrit/server/util/MagicBranch.java
+++ b/java/com/google/gerrit/server/util/MagicBranch.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.util;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.Capable;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import java.io.IOException;
import java.util.List;
import org.eclipse.jgit.lib.Ref;
@@ -26,28 +26,19 @@ public final class MagicBranch {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String NEW_CHANGE = "refs/for/";
- // TODO(xchangcheng): remove after 'repo' supports private/wip changes.
- public static final String NEW_DRAFT_CHANGE = "refs/drafts/";
/** Extracts the destination from a ref name */
public static String getDestBranchName(String refName) {
- String magicBranch = NEW_CHANGE;
- if (refName.startsWith(NEW_DRAFT_CHANGE)) {
- magicBranch = NEW_DRAFT_CHANGE;
- }
- return refName.substring(magicBranch.length());
+ return refName.substring(NEW_CHANGE.length());
}
/** Checks if the supplied ref name is a magic branch */
public static boolean isMagicBranch(String refName) {
- return refName.startsWith(NEW_DRAFT_CHANGE) || refName.startsWith(NEW_CHANGE);
+ return refName.startsWith(NEW_CHANGE);
}
/** Returns the ref name prefix for a magic branch, {@code null} if the branch is not magic */
public static String getMagicRefNamePrefix(String refName) {
- if (refName.startsWith(NEW_DRAFT_CHANGE)) {
- return NEW_DRAFT_CHANGE;
- }
if (refName.startsWith(NEW_CHANGE)) {
return NEW_CHANGE;
}
@@ -63,15 +54,7 @@ public final class MagicBranch {
* branch.
*/
public static Capable checkMagicBranchRefs(Repository repo, Project project) {
- Capable result = checkMagicBranchRef(NEW_CHANGE, repo, project);
- if (result != Capable.OK) {
- return result;
- }
- result = checkMagicBranchRef(NEW_DRAFT_CHANGE, repo, project);
- if (result != Capable.OK) {
- return result;
- }
- return Capable.OK;
+ return checkMagicBranchRef(NEW_CHANGE, repo, project);
}
private static Capable checkMagicBranchRef(String branchName, Repository repo, Project project) {
diff --git a/java/com/google/gerrit/server/util/OneOffRequestContext.java b/java/com/google/gerrit/server/util/OneOffRequestContext.java
index 1788343bae..62683f0490 100644
--- a/java/com/google/gerrit/server/util/OneOffRequestContext.java
+++ b/java/com/google/gerrit/server/util/OneOffRequestContext.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.util;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/util/ReplicaUtil.java b/java/com/google/gerrit/server/util/ReplicaUtil.java
new file mode 100644
index 0000000000..bf6111a7e3
--- /dev/null
+++ b/java/com/google/gerrit/server/util/ReplicaUtil.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import org.eclipse.jgit.lib.Config;
+
+public class ReplicaUtil {
+ /** Provides backward compatibility for container.slave property. */
+ public static boolean isReplica(Config cfg) {
+ return cfg.getBoolean("container", "slave", false)
+ || cfg.getBoolean("container", "replica", false);
+ }
+}
diff --git a/java/com/google/gerrit/server/util/RequestScopePropagator.java b/java/com/google/gerrit/server/util/RequestScopePropagator.java
index 789b9b16a7..dc8a13681d 100644
--- a/java/com/google/gerrit/server/util/RequestScopePropagator.java
+++ b/java/com/google/gerrit/server/util/RequestScopePropagator.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.util;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Throwables;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.git.ProjectRunnable;
import com.google.inject.Key;
diff --git a/java/com/google/gerrit/server/util/git/BUILD b/java/com/google/gerrit/server/util/git/BUILD
index a8ae918829..4f4ba83d7a 100644
--- a/java/com/google/gerrit/server/util/git/BUILD
+++ b/java/com/google/gerrit/server/util/git/BUILD
@@ -5,7 +5,7 @@ java_library(
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/reviewdb:server",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//java/com/google/gerrit/entities",
+ "//lib:jgit",
],
)
diff --git a/java/com/google/gerrit/server/util/git/DelegateSystemReader.java b/java/com/google/gerrit/server/util/git/DelegateSystemReader.java
new file mode 100644
index 0000000000..279bb95f04
--- /dev/null
+++ b/java/com/google/gerrit/server/util/git/DelegateSystemReader.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util.git;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
+
+public class DelegateSystemReader extends SystemReader {
+ private final SystemReader delegate;
+
+ public DelegateSystemReader(SystemReader delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getHostname() {
+ return delegate.getHostname();
+ }
+
+ @Override
+ public String getenv(String variable) {
+ return delegate.getenv(variable);
+ }
+
+ @Override
+ public String getProperty(String key) {
+ return delegate.getProperty(key);
+ }
+
+ @Override
+ public FileBasedConfig openUserConfig(Config parent, FS fs) {
+ return delegate.openUserConfig(parent, fs);
+ }
+
+ @Override
+ public FileBasedConfig openSystemConfig(Config parent, FS fs) {
+ return delegate.openSystemConfig(parent, fs);
+ }
+
+ @Override
+ public FileBasedConfig openJGitConfig(Config parent, FS fs) {
+ return delegate.openJGitConfig(parent, fs);
+ }
+
+ @Override
+ public long getCurrentTime() {
+ return delegate.getCurrentTime();
+ }
+
+ @Override
+ public int getTimezone(long when) {
+ return delegate.getTimezone(when);
+ }
+}
diff --git a/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java b/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
index f05d1d774b..97132a32ae 100644
--- a/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
+++ b/java/com/google/gerrit/server/util/git/SubmoduleSectionParser.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.util.git;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmoduleSubscription;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
@@ -45,10 +45,10 @@ public class SubmoduleSectionParser {
private final Config config;
private final String canonicalWebUrl;
- private final Branch.NameKey superProjectBranch;
+ private final BranchNameKey superProjectBranch;
public SubmoduleSectionParser(
- Config config, String canonicalWebUrl, Branch.NameKey superProjectBranch) {
+ Config config, String canonicalWebUrl, BranchNameKey superProjectBranch) {
this.config = config;
this.canonicalWebUrl = canonicalWebUrl;
this.superProjectBranch = superProjectBranch;
@@ -81,13 +81,13 @@ public class SubmoduleSectionParser {
String project;
if (branch.equals(".")) {
- branch = superProjectBranch.get();
+ branch = superProjectBranch.branch();
}
// relative URL
if (url.startsWith("../")) {
// prefix with a slash for easier relative path walks
- project = '/' + superProjectBranch.getParentKey().get();
+ project = '/' + superProjectBranch.project().get();
String hostPart = url;
while (hostPart.startsWith("../")) {
int lastSlash = project.lastIndexOf('/');
@@ -133,9 +133,9 @@ public class SubmoduleSectionParser {
0, //
project.length() - Constants.DOT_GIT_EXT.length());
}
- Project.NameKey projectKey = new Project.NameKey(project);
+ Project.NameKey projectKey = Project.nameKey(project);
return new SubmoduleSubscription(
- superProjectBranch, new Branch.NameKey(projectKey, branch), path);
+ superProjectBranch, BranchNameKey.create(projectKey, branch), path);
}
} catch (URISyntaxException e) {
// Error in url syntax (in fact it is uri syntax)
diff --git a/java/com/google/gerrit/server/util/time/BUILD b/java/com/google/gerrit/server/util/time/BUILD
index ea39efe455..b1126f07a2 100644
--- a/java/com/google/gerrit/server/util/time/BUILD
+++ b/java/com/google/gerrit/server/util/time/BUILD
@@ -5,7 +5,9 @@ java_library(
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/server/util/git",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
],
)
diff --git a/java/com/google/gerrit/server/util/time/TimeUtil.java b/java/com/google/gerrit/server/util/time/TimeUtil.java
index 645dbb92f8..639d0a6589 100644
--- a/java/com/google/gerrit/server/util/time/TimeUtil.java
+++ b/java/com/google/gerrit/server/util/time/TimeUtil.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.util.time;
import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
+import com.google.gerrit.server.util.git.DelegateSystemReader;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.function.LongSupplier;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
/** Static utility methods for dealing with dates and times. */
@@ -43,6 +43,17 @@ public class TimeUtil {
return new Timestamp(nowMs());
}
+ /**
+ * Returns the magic timestamp representing no specific time.
+ *
+ * <p>This "null object" is helpful in contexts where using {@code null} directly is not possible.
+ */
+ @UsedAt(Project.PLUGIN_CHECKS)
+ public static Timestamp never() {
+ // Always create a new object as timestamps are mutable.
+ return new Timestamp(0);
+ }
+
public static Timestamp truncateToSecond(Timestamp t) {
return new Timestamp((t.getTime() / 1000) * 1000);
}
@@ -63,47 +74,15 @@ public class TimeUtil {
SystemReader.setInstance(null);
}
- private static class GerritSystemReader extends SystemReader {
- SystemReader delegate;
-
- GerritSystemReader(SystemReader delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public String getHostname() {
- return delegate.getHostname();
- }
-
- @Override
- public String getenv(String variable) {
- return delegate.getenv(variable);
- }
-
- @Override
- public String getProperty(String key) {
- return delegate.getProperty(key);
- }
-
- @Override
- public FileBasedConfig openUserConfig(Config parent, FS fs) {
- return delegate.openUserConfig(parent, fs);
- }
-
- @Override
- public FileBasedConfig openSystemConfig(Config parent, FS fs) {
- return delegate.openSystemConfig(parent, fs);
+ static class GerritSystemReader extends DelegateSystemReader {
+ GerritSystemReader(SystemReader reader) {
+ super(reader);
}
@Override
public long getCurrentTime() {
return currentMillisSupplier.getAsLong();
}
-
- @Override
- public int getTimezone(long when) {
- return delegate.getTimezone(when);
- }
}
private TimeUtil() {}
diff --git a/java/com/google/gerrit/server/validators/AccountActivationValidationListener.java b/java/com/google/gerrit/server/validators/AccountActivationValidationListener.java
index bc52308609..9fdc9e6d71 100644
--- a/java/com/google/gerrit/server/validators/AccountActivationValidationListener.java
+++ b/java/com/google/gerrit/server/validators/AccountActivationValidationListener.java
@@ -26,6 +26,9 @@ public interface AccountActivationValidationListener {
/**
* Called when an account should be activated to allow validation of the account activation.
*
+ * <p>See {@link com.google.gerrit.extensions.events.AccountActivationListener} for a listener
+ * that's run after the account got activated.
+ *
* @param account the account that should be activated
* @throws ValidationException if validation fails
*/
@@ -34,6 +37,9 @@ public interface AccountActivationValidationListener {
/**
* Called when an account should be deactivated to allow validation of the account deactivation.
*
+ * <p>See {@link com.google.gerrit.extensions.events.AccountActivationListener} for a listener
+ * that's run after the account got deactivated.
+ *
* @param account the account that should be deactivated
* @throws ValidationException if validation fails
*/
diff --git a/java/com/google/gerrit/server/validators/AssigneeValidationListener.java b/java/com/google/gerrit/server/validators/AssigneeValidationListener.java
index a97ce0b88f..514125f0cd 100644
--- a/java/com/google/gerrit/server/validators/AssigneeValidationListener.java
+++ b/java/com/google/gerrit/server/validators/AssigneeValidationListener.java
@@ -14,9 +14,9 @@
package com.google.gerrit.server.validators;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
/** Listener to provide validation of assignees. */
@ExtensionPoint
diff --git a/java/com/google/gerrit/server/validators/HashtagValidationListener.java b/java/com/google/gerrit/server/validators/HashtagValidationListener.java
index fbf8e76db0..5ea6bfa53d 100644
--- a/java/com/google/gerrit/server/validators/HashtagValidationListener.java
+++ b/java/com/google/gerrit/server/validators/HashtagValidationListener.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.validators;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.reviewdb.client.Change;
import java.util.Set;
/** Listener to provide validation of hashtag changes. */
diff --git a/java/com/google/gerrit/sshd/AbstractGitCommand.java b/java/com/google/gerrit/sshd/AbstractGitCommand.java
index 90f0c4365d..9efcff2d4c 100644
--- a/java/com/google/gerrit/sshd/AbstractGitCommand.java
+++ b/java/com/google/gerrit/sshd/AbstractGitCommand.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -24,11 +24,14 @@ import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import java.io.IOException;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.kohsuke.args4j.Argument;
public abstract class AbstractGitCommand extends BaseCommand {
+ private static final String GIT_PROTOCOL = "GIT_PROTOCOL";
+
@Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name")
protected ProjectState projectState;
@@ -45,10 +48,16 @@ public abstract class AbstractGitCommand extends BaseCommand {
protected Repository repo;
protected Project.NameKey projectName;
protected Project project;
+ protected String[] extraParameters;
@Override
- public void start(Environment env) {
+ public void start(ChannelSession channel, Environment env) {
enableGracefulStop();
+ String gitProtocol = env.getEnv().get(GIT_PROTOCOL);
+ if (gitProtocol != null) {
+ extraParameters = gitProtocol.split(":");
+ }
+
Context ctx = context.subContext(newSession(), context.getCommandLine());
final Context old = sshScope.set(ctx);
try {
diff --git a/java/com/google/gerrit/sshd/AliasCommand.java b/java/com/google/gerrit/sshd/AliasCommand.java
index 567cf00735..bf0dd91097 100644
--- a/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/java/com/google/gerrit/sshd/AliasCommand.java
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
/** Command that executes some other command. */
@@ -47,9 +48,9 @@ public class AliasCommand extends BaseCommand {
}
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
try {
- begin(env);
+ begin(channel, env);
} catch (Failure e) {
String msg = e.getMessage();
if (!msg.endsWith("\n")) {
@@ -61,7 +62,7 @@ public class AliasCommand extends BaseCommand {
}
}
- private void begin(Environment env) throws IOException, Failure {
+ private void begin(ChannelSession channel, Environment env) throws IOException, Failure {
Map<String, CommandProvider> map = root.getMap();
for (String name : chain(command)) {
CommandProvider p = map.get(name);
@@ -90,15 +91,15 @@ public class AliasCommand extends BaseCommand {
}
provideStateTo(cmd);
atomicCmd.set(cmd);
- cmd.start(env);
+ cmd.start(channel, env);
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
Command cmd = atomicCmd.getAndSet(null);
if (cmd != null) {
try {
- cmd.destroy();
+ cmd.destroy(channel);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index f6f9b52935..689c567dd8 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -7,25 +7,27 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/json",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
- "//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/logging",
"//lib:args4j",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-archive",
"//lib:jsch",
"//lib:servlet-api",
"//lib/auto:auto-value",
@@ -37,8 +39,6 @@ java_library(
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet", # SSH should not depend on servlet
- "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:log4j",
"//lib/mina:core",
"//lib/mina:sshd",
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index 1d9635f108..85d9eb24bd 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -20,10 +20,10 @@ import com.google.common.base.Joiner;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.DynamicOptions;
@@ -51,12 +51,14 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.SshException;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
@@ -182,7 +184,7 @@ public abstract class BaseCommand implements Command {
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
Future<?> future = task.getAndSet(null);
if (future != null && !future.isDone()) {
future.cancel(true);
@@ -264,7 +266,8 @@ public abstract class BaseCommand implements Command {
/**
* Spawn a function into its own thread.
*
- * <p>Typically this should be invoked within {@link Command#start(Environment)}, such as:
+ * <p>Typically this should be invoked within {@link Command#start(ChannelSession, Environment)},
+ * such as:
*
* <pre>
* startThread(new CommandRunnable() {
@@ -354,7 +357,7 @@ public abstract class BaseCommand implements Command {
}
m.append(" during ");
m.append(context.getCommandLine());
- logger.atSevere().withCause(e).log(m.toString());
+ logCauseIfRelevant(e, m);
}
if (e instanceof Failure) {
@@ -381,6 +384,20 @@ public abstract class BaseCommand implements Command {
return 128;
}
+ private void logCauseIfRelevant(Throwable e, StringBuilder message) {
+ String zeroLength = "length=0";
+ String streamAlreadyClosed = "stream is already closed";
+ boolean isZeroLength = false;
+
+ if (streamAlreadyClosed.equals(e.getMessage())) {
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ isZeroLength = Arrays.stream(stackTrace).anyMatch(s -> s.toString().contains(zeroLength));
+ }
+ if (!isZeroLength) {
+ logger.atSevere().withCause(e).log(message.toString());
+ }
+ }
+
protected UnloggedFailure die(String msg) {
return new UnloggedFailure(1, "fatal: " + msg);
}
diff --git a/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index ed6f87feab..491bcb84de 100644
--- a/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -15,10 +15,10 @@
package com.google.gerrit.sshd;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -122,7 +122,7 @@ public class ChangeArgumentParser {
private List<Change.Id> parseId(String id) throws UnloggedFailure {
try {
- return Arrays.asList(new Change.Id(Integer.parseInt(id)));
+ return Arrays.asList(Change.id(Integer.parseInt(id)));
} catch (NumberFormatException e) {
throw new UnloggedFailure(2, "Invalid change ID " + id, e);
}
diff --git a/java/com/google/gerrit/sshd/ChannelIdTrackingUnknownChannelReferenceHandler.java b/java/com/google/gerrit/sshd/ChannelIdTrackingUnknownChannelReferenceHandler.java
new file mode 100644
index 0000000000..f8ab90e56e
--- /dev/null
+++ b/java/com/google/gerrit/sshd/ChannelIdTrackingUnknownChannelReferenceHandler.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This file is based on sshd-contrib Apache SSHD Mina project. Original commit:
+ * https://github.com/apache/mina-sshd/commit/11b33dee37b5b9c71a40a8a98a42007e3687131e
+ */
+package com.google.gerrit.sshd;
+
+import com.google.common.flogger.FluentLogger;
+import java.io.IOException;
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.ChannelListener;
+import org.apache.sshd.common.channel.exception.SshChannelNotFoundException;
+import org.apache.sshd.common.session.ConnectionService;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * Makes sure that the referenced &quot;unknown&quot; channel identifier is one that was assigned in
+ * the past. <B>Note:</B> it relies on the fact that the default {@code ConnectionService}
+ * implementation assigns channels identifiers in ascending order.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ChannelIdTrackingUnknownChannelReferenceHandler
+ extends DefaultUnknownChannelReferenceHandler implements ChannelListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final AttributeKey<Integer> LAST_CHANNEL_ID_KEY = new AttributeKey<>();
+
+ public static final ChannelIdTrackingUnknownChannelReferenceHandler TRACKER =
+ new ChannelIdTrackingUnknownChannelReferenceHandler();
+
+ public ChannelIdTrackingUnknownChannelReferenceHandler() {
+ super();
+ }
+
+ @Override
+ public void channelInitialized(Channel channel) {
+ int channelId = channel.getId();
+ Session session = channel.getSession();
+ Integer lastTracked = session.setAttribute(LAST_CHANNEL_ID_KEY, channelId);
+ logger.atFine().log(
+ "channelInitialized(%s) updated last tracked channel ID %s => %s",
+ channel, lastTracked, channelId);
+ }
+
+ @Override
+ public Channel handleUnknownChannelCommand(
+ ConnectionService service, byte cmd, int channelId, Buffer buffer) throws IOException {
+ Session session = service.getSession();
+ Integer lastTracked = session.getAttribute(LAST_CHANNEL_ID_KEY);
+ if ((lastTracked != null) && (channelId <= lastTracked.intValue())) {
+ // Use TRACE level in order to avoid messages flooding
+ logger.atFinest().log(
+ "handleUnknownChannelCommand(%s) apply default handling for %s on channel=%s (lastTracked=%s)",
+ session, SshConstants.getCommandMessageName(cmd), channelId, lastTracked);
+ return super.handleUnknownChannelCommand(service, cmd, channelId, buffer);
+ }
+
+ throw new SshChannelNotFoundException(
+ channelId,
+ "Received "
+ + SshConstants.getCommandMessageName(cmd)
+ + " on unassigned channel "
+ + channelId
+ + " (last assigned="
+ + lastTracked
+ + ")");
+ }
+}
diff --git a/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index c003b467ae..38ac26daf8 100644
--- a/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.session.ServerSession;
@@ -91,13 +92,13 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
@Override
public CommandFactory get() {
- return requestCommand -> {
- String c = requestCommand;
+ return (channelSession, requestCommand) -> {
+ String command = requestCommand;
SshCreateCommandInterceptor interceptor = createCommandInterceptor.get();
if (interceptor != null) {
- c = interceptor.intercept(c);
+ command = interceptor.intercept(command);
}
- return new Trampoline(c);
+ return new Trampoline(command);
};
}
@@ -148,7 +149,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
}
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
this.env = env;
final Context ctx = this.ctx;
task.set(
@@ -157,7 +158,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
@Override
public void run() {
try {
- onStart();
+ onStart(channel);
} catch (Exception e) {
logger.atWarning().withCause(e).log(
"Cannot start command \"%s\" for user %s",
@@ -172,7 +173,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
}));
}
- private void onStart() throws IOException {
+ private void onStart(ChannelSession channel) throws IOException {
synchronized (this) {
final Context old = sshScope.set(ctx);
try {
@@ -195,7 +196,7 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
log(rc);
}
});
- cmd.start(env);
+ cmd.start(channel, env);
} finally {
sshScope.set(old);
}
@@ -231,20 +232,20 @@ class CommandFactoryProvider implements Provider<CommandFactory>, LifecycleListe
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
Future<?> future = task.getAndSet(null);
if (future != null) {
future.cancel(true);
- destroyExecutor.execute(this::onDestroy);
+ destroyExecutor.execute(() -> onDestroy(channel));
}
}
- private void onDestroy() {
+ private void onDestroy(ChannelSession channel) {
synchronized (this) {
if (cmd != null) {
final Context old = sshScope.set(ctx);
try {
- cmd.destroy();
+ cmd.destroy(channel);
log(BaseCommand.STATUS_CANCEL);
} finally {
ctx = null;
diff --git a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 1e32e1b246..6c0f3af040 100644
--- a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -31,6 +31,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Collection;
@@ -80,19 +81,24 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
}
private static Set<PublicKey> myHostKeys(KeyPairProvider p) {
- final Set<PublicKey> keys = new HashSet<>(6);
- addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
- addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
- addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
+ Set<PublicKey> keys = new HashSet<>(6);
+ try {
+ addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
+ addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
+ addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new IllegalStateException("Cannot load SSHD host key", e);
+ }
+
return keys;
}
- private static void addPublicKey(
- final Collection<PublicKey> out, KeyPairProvider p, String type) {
- final KeyPair pair = p.loadKey(type);
+ private static void addPublicKey(Collection<PublicKey> out, KeyPairProvider p, String type)
+ throws IOException, GeneralSecurityException {
+ KeyPair pair = p.loadKey(null, type);
if (pair != null && pair.getPublic() != null) {
out.add(pair.getPublic());
}
diff --git a/java/com/google/gerrit/sshd/DispatchCommand.java b/java/com/google/gerrit/sshd/DispatchCommand.java
index 68962db18b..7db65bd0b2 100644
--- a/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.kohsuke.args4j.Argument;
@@ -69,7 +70,7 @@ final class DispatchCommand extends BaseCommand {
}
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
try {
parseCommandLine();
if (Strings.isNullOrEmpty(commandName)) {
@@ -115,7 +116,7 @@ final class DispatchCommand extends BaseCommand {
provideStateTo(cmd);
atomicCmd.set(cmd);
- cmd.start(env);
+ cmd.start(channel, env);
} catch (UnloggedFailure e) {
String msg = e.getMessage();
@@ -145,11 +146,11 @@ final class DispatchCommand extends BaseCommand {
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
Command cmd = atomicCmd.getAndSet(null);
if (cmd != null) {
try {
- cmd.destroy();
+ cmd.destroy(channel);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
diff --git a/java/com/google/gerrit/sshd/GerritGSSAuthenticator.java b/java/com/google/gerrit/sshd/GerritGSSAuthenticator.java
index adb50852f6..67592751c1 100644
--- a/java/com/google/gerrit/sshd/GerritGSSAuthenticator.java
+++ b/java/com/google/gerrit/sshd/GerritGSSAuthenticator.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
@@ -66,7 +66,7 @@ class GerritGSSAuthenticator extends GSSAuthenticator {
}
Optional<Account> account =
- accounts.getByUsername(username).map(AccountState::getAccount).filter(Account::isActive);
+ accounts.getByUsername(username).map(AccountState::account).filter(Account::isActive);
if (!account.isPresent()) {
return false;
}
@@ -77,6 +77,6 @@ class GerritGSSAuthenticator extends GSSAuthenticator {
sshScope,
sshLog,
sd,
- SshUtil.createUser(sd, userFactory, account.get().getId()));
+ SshUtil.createUser(sd, userFactory, account.get().id()));
}
}
diff --git a/java/com/google/gerrit/sshd/HostKeyProvider.java b/java/com/google/gerrit/sshd/HostKeyProvider.java
index bffcfcd7b9..3578fb9107 100644
--- a/java/com/google/gerrit/sshd/HostKeyProvider.java
+++ b/java/com/google/gerrit/sshd/HostKeyProvider.java
@@ -18,7 +18,6 @@ import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
-import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -44,21 +43,21 @@ class HostKeyProvider implements Provider<KeyPairProvider> {
Path ecdsaKey_521 = site.ssh_ecdsa_521;
Path ed25519Key = site.ssh_ed25519;
- final List<File> stdKeys = new ArrayList<>(6);
+ final List<Path> stdKeys = new ArrayList<>(6);
if (Files.exists(rsaKey)) {
- stdKeys.add(rsaKey.toAbsolutePath().toFile());
+ stdKeys.add(rsaKey);
}
if (Files.exists(ecdsaKey_256)) {
- stdKeys.add(ecdsaKey_256.toAbsolutePath().toFile());
+ stdKeys.add(ecdsaKey_256);
}
if (Files.exists(ecdsaKey_384)) {
- stdKeys.add(ecdsaKey_384.toAbsolutePath().toFile());
+ stdKeys.add(ecdsaKey_384);
}
if (Files.exists(ecdsaKey_521)) {
- stdKeys.add(ecdsaKey_521.toAbsolutePath().toFile());
+ stdKeys.add(ecdsaKey_521);
}
if (Files.exists(ed25519Key)) {
- stdKeys.add(ed25519Key.toAbsolutePath().toFile());
+ stdKeys.add(ed25519Key);
}
if (Files.exists(objKey)) {
@@ -70,14 +69,14 @@ class HostKeyProvider implements Provider<KeyPairProvider> {
// Both formats of host key exist, we don't know which format
// should be authoritative. Complain and abort.
//
- stdKeys.add(objKey.toAbsolutePath().toFile());
+ stdKeys.add(objKey);
throw new ProvisionException("Multiple host keys exist: " + stdKeys);
}
if (stdKeys.isEmpty()) {
throw new ProvisionException("No SSH keys under " + site.etc_dir);
}
FileKeyPairProvider kp = new FileKeyPairProvider();
- kp.setFiles(stdKeys);
+ kp.setPaths(stdKeys);
return kp;
}
}
diff --git a/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java b/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java
new file mode 100644
index 0000000000..1086626ce6
--- /dev/null
+++ b/java/com/google/gerrit/sshd/InactiveAccountDisconnector.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.events.AccountActivationListener;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.sshd.BaseCommand.Failure;
+import com.google.inject.Inject;
+import java.io.IOException;
+
+/** Closes open SSH connections upon account deactivation. */
+public class InactiveAccountDisconnector implements AccountActivationListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final SshDaemon sshDaemon;
+
+ @Inject
+ InactiveAccountDisconnector(SshDaemon sshDaemon) {
+ this.sshDaemon = sshDaemon;
+ }
+
+ @Override
+ public void onAccountDeactivated(int id) {
+ try {
+ SshUtil.forEachSshSession(
+ sshDaemon,
+ (sshId, sshSession, abstractSession, ioSession) -> {
+ CurrentUser sessionUser = sshSession.getUser();
+ if (sessionUser.isIdentifiedUser() && sessionUser.getAccountId().get() == id) {
+ logger.atInfo().log(
+ "Disconnecting SSH session %s because user %s(%d) got deactivated",
+ abstractSession, sessionUser.getLoggableName(), id);
+ try {
+ abstractSession.disconnect(-1, "user deactivated");
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Failure while deactivating session %s", abstractSession);
+ }
+ }
+ });
+ } catch (Failure e) {
+ // Ssh Daemon no longer running. Since we're only disconnecting connections anyways, this is
+ // most likely ok, so we log only at info level.
+ logger.atInfo().withCause(e).log("Failure while disconnecting deactivated account %d", id);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/sshd/LogMaxConnectionsPerUserExceeded.java b/java/com/google/gerrit/sshd/LogMaxConnectionsPerUserExceeded.java
new file mode 100644
index 0000000000..6f568b1a5b
--- /dev/null
+++ b/java/com/google/gerrit/sshd/LogMaxConnectionsPerUserExceeded.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.apache.sshd.common.Service;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionDisconnectHandler;
+
+@Singleton
+public class LogMaxConnectionsPerUserExceeded implements SessionDisconnectHandler {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @Override
+ public boolean handleSessionsCountDisconnectReason(
+ Session session,
+ Service service,
+ String username,
+ int currentSessionCount,
+ int maxSessionCount)
+ throws IOException {
+ logger.atWarning().log(
+ "Max connection count for user %s exceeded, rejecting new connection."
+ + " currentSessionCount = %d, maxSessionCount = %d",
+ username, currentSessionCount, maxSessionCount);
+ return false;
+ }
+}
diff --git a/java/com/google/gerrit/sshd/NoShell.java b/java/com/google/gerrit/sshd/NoShell.java
index 5aaa647912..2a29a624a3 100644
--- a/java/com/google/gerrit/sshd/NoShell.java
+++ b/java/com/google/gerrit/sshd/NoShell.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -27,12 +27,17 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
-import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.io.IoInputStream;
+import org.apache.sshd.common.io.IoOutputStream;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.command.AsyncCommand;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.shell.ShellFactory;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.SystemReader;
@@ -42,7 +47,7 @@ import org.eclipse.jgit.util.SystemReader;
* <p>This implementation is used to ensure clients who try to SSH directly to this server without
* supplying a command will get a reasonable error message, but cannot continue further.
*/
-class NoShell implements Factory<Command> {
+class NoShell implements ShellFactory {
private final Provider<SendMessage> shell;
@Inject
@@ -51,17 +56,23 @@ class NoShell implements Factory<Command> {
}
@Override
- public Command create() {
+ public Command createShell(ChannelSession channel) {
return shell.get();
}
- static class SendMessage implements Command, SessionAware {
+ /**
+ * When AsyncCommand is implemented by a command as below, the usual blocking streams aren't set.
+ *
+ * @see org.apache.sshd.server.command.AsyncCommand
+ */
+ static class SendMessage implements AsyncCommand, SessionAware {
private final Provider<MessageFactory> messageFactory;
private final SshScope sshScope;
- private InputStream in;
- private OutputStream out;
- private OutputStream err;
+ private IoInputStream in;
+ private IoOutputStream out;
+ private IoOutputStream err;
+
private ExitCallback exit;
private Context context;
@@ -72,21 +83,36 @@ class NoShell implements Factory<Command> {
}
@Override
- public void setInputStream(InputStream in) {
+ public void setIoInputStream(IoInputStream in) {
this.in = in;
}
@Override
- public void setOutputStream(OutputStream out) {
+ public void setIoOutputStream(IoOutputStream out) {
this.out = out;
}
@Override
- public void setErrorStream(OutputStream err) {
+ public void setIoErrorStream(IoOutputStream err) {
this.err = err;
}
@Override
+ public void setInputStream(InputStream in) {
+ // ignored
+ }
+
+ @Override
+ public void setOutputStream(OutputStream out) {
+ // ignore
+ }
+
+ @Override
+ public void setErrorStream(OutputStream err) {
+ // ignore
+ }
+
+ @Override
public void setExitCallback(ExitCallback callback) {
this.exit = callback;
}
@@ -98,7 +124,7 @@ class NoShell implements Factory<Command> {
}
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
Context old = sshScope.set(context);
String message;
try {
@@ -106,8 +132,7 @@ class NoShell implements Factory<Command> {
} finally {
sshScope.set(old);
}
- err.write(Constants.encode(message));
- err.flush();
+ err.writePacket(new ByteArrayBuffer(Constants.encode(message)));
in.close();
out.close();
@@ -116,7 +141,7 @@ class NoShell implements Factory<Command> {
}
@Override
- public void destroy() {}
+ public void destroy(ChannelSession channel) {}
}
static class MessageFactory {
@@ -145,7 +170,7 @@ class NoShell implements Factory<Command> {
msg.append("\r\n");
Account account = user.getAccount();
- String name = account.getFullName();
+ String name = account.fullName();
if (name == null || name.isEmpty()) {
name = user.getUserName().orElse(anonymousCowardName);
}
diff --git a/java/com/google/gerrit/sshd/SshCommand.java b/java/com/google/gerrit/sshd/SshCommand.java
index 98289570b4..e60ba6d274 100644
--- a/java/com/google/gerrit/sshd/SshCommand.java
+++ b/java/com/google/gerrit/sshd/SshCommand.java
@@ -14,14 +14,28 @@
package com.google.gerrit.sshd;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.RequestInfo;
+import com.google.gerrit.server.RequestListener;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.logging.PerformanceLogContext;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
+import com.google.inject.Inject;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
+import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
public abstract class SshCommand extends BaseCommand {
+ @Inject private DynamicSet<PerformanceLogger> performanceLoggers;
+ @Inject private PluginSetContext<RequestListener> requestListeners;
+ @Inject @GerritServerConfig private Config config;
+
@Option(name = "--trace", usage = "enable request tracing")
private boolean trace;
@@ -32,13 +46,18 @@ public abstract class SshCommand extends BaseCommand {
protected PrintWriter stderr;
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
startThread(
() -> {
parseCommandLine();
stdout = toPrintWriter(out);
stderr = toPrintWriter(err);
- try (TraceContext traceContext = enableTracing()) {
+ try (TraceContext traceContext = enableTracing();
+ PerformanceLogContext performanceLogContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ RequestInfo requestInfo =
+ RequestInfo.builder(RequestInfo.RequestType.SSH, user, traceContext).build();
+ requestListeners.runEach(l -> l.onRequest(requestInfo));
SshCommand.this.run();
} finally {
stdout.flush();
diff --git a/java/com/google/gerrit/sshd/SshDaemon.java b/java/com/google/gerrit/sshd/SshDaemon.java
index 8265413d7b..f04ff7eadd 100644
--- a/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/java/com/google/gerrit/sshd/SshDaemon.java
@@ -51,6 +51,7 @@ import java.nio.file.PathMatcher;
import java.nio.file.WatchService;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
+import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -159,7 +160,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
SshLog sshLog,
@SshListenAddresses List<SocketAddress> listen,
@SshAdvertisedAddresses List<String> advertised,
- MetricMaker metricMaker) {
+ MetricMaker metricMaker,
+ LogMaxConnectionsPerUserExceeded logMaxConnectionsPerUserExceeded) {
setPort(IANA_SSH_PORT /* never used */);
this.cfg = cfg;
@@ -214,6 +216,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
final boolean enableCompression = cfg.getBoolean("sshd", "enableCompression", false);
SshSessionBackend backend = cfg.getEnum("sshd", null, "backend", SshSessionBackend.NIO2);
+ boolean channelIdTracking = cfg.getBoolean("sshd", "enableChannelIdTracking", true);
gracefulStopTimeout = cfg.getTimeUnit("sshd", null, "gracefulStopTimeout", 0, TimeUnit.SECONDS);
@@ -229,7 +232,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
initMacs(cfg);
initSignatures();
initChannels();
- initUnknownChannelReferenceHandler();
+ initUnknownChannelReferenceHandler(channelIdTracking);
initForwarding();
initFileSystemFactory();
initSubsystems();
@@ -238,6 +241,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setKeyPairProvider(hostKeyProvider);
setCommandFactory(commandFactory);
setShellFactory(noShell);
+ setSessionDisconnectHandler(logMaxConnectionsPerUserExceeded);
final AtomicInteger connected = new AtomicInteger();
metricMaker.newCallbackMetric(
@@ -429,12 +433,12 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
return Collections.emptyList();
}
- final List<PublicKey> keys = myHostKeys();
- final List<HostKey> r = new ArrayList<>();
+ List<HostKey> r = new ArrayList<>();
+ List<PublicKey> keys = myHostKeys();
for (PublicKey pub : keys) {
- final Buffer buf = new ByteArrayBuffer();
+ Buffer buf = new ByteArrayBuffer();
buf.putRawPublicKey(pub);
- final byte[] keyBin = buf.getCompactData();
+ byte[] keyBin = buf.getCompactData();
for (String addr : advertised) {
try {
@@ -445,24 +449,29 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
}
}
+
return Collections.unmodifiableList(r);
}
private List<PublicKey> myHostKeys() {
- final KeyPairProvider p = getKeyPairProvider();
- final List<PublicKey> keys = new ArrayList<>(6);
- addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
- addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
- addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
- addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
+ KeyPairProvider p = getKeyPairProvider();
+ List<PublicKey> keys = new ArrayList<>(6);
+ try {
+ addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
+ addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
+ addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
+ addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new IllegalStateException("Cannot load SSHD host key", e);
+ }
return keys;
}
- private static void addPublicKey(
- final Collection<PublicKey> out, KeyPairProvider p, String type) {
- final KeyPair pair = p.loadKey(type);
+ private static void addPublicKey(final Collection<PublicKey> out, KeyPairProvider p, String type)
+ throws IOException, GeneralSecurityException {
+ final KeyPair pair = p.loadKey(null, type);
if (pair != null && pair.getPublic() != null) {
out.add(pair.getPublic());
}
@@ -562,14 +571,14 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
@SuppressWarnings("unchecked")
private void initCiphers(Config cfg) {
- final List<NamedFactory<Cipher>> a = BaseBuilder.setUpDefaultCiphers(true);
+ List<NamedFactory<Cipher>> a = BaseBuilder.setUpDefaultCiphers(true);
for (Iterator<NamedFactory<Cipher>> i = a.iterator(); i.hasNext(); ) {
- final NamedFactory<Cipher> f = i.next();
+ NamedFactory<Cipher> f = i.next();
try {
- final Cipher c = f.create();
- final byte[] key = new byte[c.getBlockSize()];
- final byte[] iv = new byte[c.getIVSize()];
+ Cipher c = f.create();
+ byte[] key = new byte[c.getKdfSize()];
+ byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (InvalidKeyException e) {
logger.atWarning().log(
@@ -662,7 +671,8 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
}
private void initSignatures() {
- setSignatureFactories(BaseBuilder.setUpDefaultSignatures(true));
+ setSignatureFactories(
+ NamedFactory.setUpBuiltinFactories(false, ServerBuilder.DEFAULT_SIGNATURE_PREFERENCE));
}
private void initCompression(boolean enableCompression) {
@@ -678,10 +688,9 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
// However, if there are CPU in abundance and the server is reachable through
// slow networks, gits with huge amount of refs can benefit from SSH-compression
// since git does not compress the ref announcement during the handshake.
- //
- // Compression can be especially useful when Gerrit slaves are being used
- // for the larger clones and fetches and the master server mostly takes small
- // receive-packs.
+ // Compression can be especially useful when Gerrit replica are being used
+ // for the larger clones and fetches and the primary server handling write
+ // operations mostly takes small receive-packs.
if (enableCompression) {
compressionFactories.add(BuiltinCompressions.zlib);
@@ -694,8 +703,11 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
setChannelFactories(ServerBuilder.DEFAULT_CHANNEL_FACTORIES);
}
- private void initUnknownChannelReferenceHandler() {
- setUnknownChannelReferenceHandler(DefaultUnknownChannelReferenceHandler.INSTANCE);
+ private void initUnknownChannelReferenceHandler(boolean enableChannelIdTracking) {
+ setUnknownChannelReferenceHandler(
+ enableChannelIdTracking
+ ? ChannelIdTrackingUnknownChannelReferenceHandler.TRACKER
+ : DefaultUnknownChannelReferenceHandler.INSTANCE);
}
private void initSubsystems() {
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheEntry.java b/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
index 8e962e3a63..78fd7da330 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import java.security.PublicKey;
class SshKeyCacheEntry {
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index fb0b8f6f15..773c25bce6 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -24,6 +24,7 @@ import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.ssh.SshKeyCache;
@@ -106,7 +107,9 @@ public class SshKeyCacheImpl implements SshKeyCache {
@Override
public Iterable<SshKeyCacheEntry> load(String username) throws Exception {
try (TraceTimer timer =
- TraceContext.newTimer("Loading SSH keys for account with username %s", username)) {
+ TraceContext.newTimer(
+ "Loading SSH keys for account with username",
+ Metadata.builder().username(username).build())) {
Optional<ExternalId> user =
externalIds.get(ExternalId.Key.create(SCHEME_USERNAME, username));
if (!user.isPresent()) {
diff --git a/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java b/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
index b2853b9dfd..02a9e48bae 100644
--- a/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCreatorImpl.java
@@ -15,8 +15,8 @@
package com.google.gerrit.sshd;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.InvalidSshKeyException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.ssh.SshKeyCreator;
import java.security.NoSuchAlgorithmException;
diff --git a/java/com/google/gerrit/sshd/SshModule.java b/java/com/google/gerrit/sshd/SshModule.java
index e4aa14cf30..9301f8a39a 100644
--- a/java/com/google/gerrit/sshd/SshModule.java
+++ b/java/com/google/gerrit/sshd/SshModule.java
@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
+import com.google.gerrit.extensions.events.AccountActivationListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule;
@@ -103,6 +104,9 @@ public class SshModule extends LifecycleModule {
DynamicItem.itemOf(binder(), SshCreateCommandInterceptor.class);
DynamicSet.setOf(binder(), SshExecuteCommandInterceptor.class);
+ DynamicSet.bind(binder(), AccountActivationListener.class)
+ .to(InactiveAccountDisconnector.class);
+
listener().toInstance(registerInParentInjectors());
listener().to(SshLog.class);
listener().to(SshDaemon.class);
diff --git a/java/com/google/gerrit/sshd/SshSession.java b/java/com/google/gerrit/sshd/SshSession.java
index a2c3d93648..b39eaede24 100644
--- a/java/com/google/gerrit/sshd/SshSession.java
+++ b/java/com/google/gerrit/sshd/SshSession.java
@@ -19,7 +19,7 @@ import com.google.gerrit.server.CurrentUser;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
-import org.apache.sshd.common.AttributeStore.AttributeKey;
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
/** Global data related to an active SSH connection. */
public class SshSession {
diff --git a/java/com/google/gerrit/sshd/SshUtil.java b/java/com/google/gerrit/sshd/SshUtil.java
index 670d64ccc4..6176628c4d 100644
--- a/java/com/google/gerrit/sshd/SshUtil.java
+++ b/java/com/google/gerrit/sshd/SshUtil.java
@@ -14,10 +14,11 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountSshKey;
+import com.google.gerrit.sshd.BaseCommand.Failure;
import com.google.gerrit.sshd.SshScope.Context;
import java.io.BufferedReader;
import java.io.IOException;
@@ -30,7 +31,10 @@ import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import org.apache.commons.codec.binary.Base64;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Constants;
@@ -154,4 +158,30 @@ public class SshUtil {
final Account.Id account) {
return userFactory.create(sd.getRemoteAddress(), account);
}
+
+ public static void forEachSshSession(SshDaemon sshDaemon, SessionConsumer consumer)
+ throws Failure {
+ IoAcceptor ioAcceptor = sshDaemon.getIoAcceptor();
+ if (ioAcceptor == null) {
+ throw new Failure(1, "fatal: sshd no longer running");
+ }
+ ioAcceptor
+ .getManagedSessions()
+ .forEach(
+ (id, ioSession) -> {
+ AbstractSession abstractSession = AbstractSession.getSession(ioSession, true);
+ if (abstractSession != null) {
+ SshSession sshSession = abstractSession.getAttribute(SshSession.KEY);
+ if (sshSession != null) {
+ consumer.accept(id, sshSession, abstractSession, ioSession);
+ }
+ }
+ });
+ }
+
+ @FunctionalInterface
+ public interface SessionConsumer {
+ public void accept(
+ Long id, SshSession sshSession, AbstractSession abstractSession, IoSession ioSession);
+ }
}
diff --git a/java/com/google/gerrit/sshd/SuExec.java b/java/com/google/gerrit/sshd/SuExec.java
index 6d0bf2bc3f..ea163d52ab 100644
--- a/java/com/google/gerrit/sshd/SuExec.java
+++ b/java/com/google/gerrit/sshd/SuExec.java
@@ -18,8 +18,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
@@ -35,6 +35,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -90,7 +91,7 @@ public final class SuExec extends BaseCommand {
}
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
try {
checkCanRunAs();
parseCommandLine();
@@ -102,7 +103,7 @@ public final class SuExec extends BaseCommand {
cmd.setArguments(args.toArray(new String[args.size()]));
provideStateTo(cmd);
atomicCmd.set(cmd);
- cmd.start(env);
+ cmd.start(channel, env);
} finally {
sshScope.set(old);
}
@@ -158,11 +159,11 @@ public final class SuExec extends BaseCommand {
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
Command cmd = atomicCmd.getAndSet(null);
if (cmd != null) {
try {
- cmd.destroy();
+ cmd.destroy(channel);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
diff --git a/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index 42e5624718..134fb03828 100644
--- a/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -69,7 +69,7 @@ public class BanCommitCommand extends SshCommand {
BanCommitInput.fromCommits(Lists.transform(commitsToBan, ObjectId::getName));
input.reason = reason;
- BanResultInfo r = banCommit.apply(new ProjectResource(projectState, user), input);
+ BanResultInfo r = banCommit.apply(new ProjectResource(projectState, user), input).value();
printCommits(r.newlyBanned, "The following commits were banned");
printCommits(r.alreadyBanned, "The following commits were already banned");
printCommits(r.ignored, "The following ids do not represent commits and were ignored");
diff --git a/java/com/google/gerrit/sshd/commands/CloseConnection.java b/java/com/google/gerrit/sshd/commands/CloseConnection.java
index 6cd009b54b..e0b87f88cd 100644
--- a/java/com/google/gerrit/sshd/commands/CloseConnection.java
+++ b/java/com/google/gerrit/sshd/commands/CloseConnection.java
@@ -23,15 +23,12 @@ import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
-import com.google.gerrit.sshd.SshSession;
+import com.google.gerrit.sshd.SshUtil;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.sshd.common.future.CloseFuture;
-import org.apache.sshd.common.io.IoAcceptor;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.session.helpers.AbstractSession;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -61,36 +58,26 @@ final class CloseConnection extends SshCommand {
@Override
protected void run() throws Failure {
enableGracefulStop();
- IoAcceptor acceptor = sshDaemon.getIoAcceptor();
- if (acceptor == null) {
- throw new Failure(1, "fatal: sshd no longer running");
- }
- for (String sessionId : sessionIds) {
- boolean connectionFound = false;
- int id = (int) Long.parseLong(sessionId, 16);
- for (IoSession io : acceptor.getManagedSessions().values()) {
- AbstractSession serverSession = AbstractSession.getSession(io, true);
- SshSession sshSession =
- serverSession != null ? serverSession.getAttribute(SshSession.KEY) : null;
- if (sshSession != null && sshSession.getSessionId() == id) {
- connectionFound = true;
- stdout.println("closing connection " + sessionId + "...");
- CloseFuture future = io.close(true);
- if (wait) {
- try {
- future.await();
- stdout.println("closed connection " + sessionId);
- } catch (IOException e) {
- logger.atWarning().log(
- "Wait for connection to close interrupted: %s", e.getMessage());
+ SshUtil.forEachSshSession(
+ sshDaemon,
+ (k, sshSession, abstractSession, ioSession) -> {
+ String sessionId = String.format("%08x", sshSession.getSessionId());
+ if (sessionIds.remove(sessionId)) {
+ stdout.println("closing connection " + sessionId + "...");
+ CloseFuture future = ioSession.close(true);
+ if (wait) {
+ try {
+ future.await();
+ stdout.println("closed connection " + sessionId);
+ } catch (IOException e) {
+ logger.atWarning().log(
+ "Wait for connection to close interrupted: %s", e.getMessage());
+ }
}
}
- break;
- }
- }
- if (!connectionFound) {
- stderr.print("close connection " + sessionId + ": no such connection\n");
- }
+ });
+ for (String sessionId : sessionIds) {
+ stderr.print("close connection " + sessionId + ": no such connection\n");
}
}
}
diff --git a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index 00fc20fc02..4da55e22f5 100644
--- a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -18,12 +18,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.account.CreateAccount;
import com.google.gerrit.sshd.CommandMetaData;
diff --git a/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index 8169901677..b243748adc 100644
--- a/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -17,14 +17,14 @@ package com.google.gerrit.sshd.commands;
import static java.util.stream.Collectors.toList;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.group.GroupResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.group.AddMembers;
@@ -115,11 +115,12 @@ final class CreateGroupCommand extends SshCommand {
}
} catch (RestApiException e) {
throw die(e);
+ } catch (Exception e) {
+ throw new Failure(1, "unavailable", e);
}
}
- private GroupResource createGroup()
- throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
+ private GroupResource createGroup() throws Exception {
GroupInput input = new GroupInput();
input.description = groupDescription;
input.visibleToAll = visibleToAll;
@@ -129,7 +130,9 @@ final class CreateGroupCommand extends SshCommand {
}
GroupInfo group =
- createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName), input);
+ createGroup
+ .apply(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName), input)
+ .value();
return groups.parse(TopLevelResource.INSTANCE, IdString.fromUrl(group.id));
}
diff --git a/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index ad7021b12c..f2ab4e8520 100644
--- a/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -18,6 +18,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -25,8 +27,6 @@ import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SuggestParentCandidates;
diff --git a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 87b6f02144..905ba9c17e 100644
--- a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.reviewdb.client.CoreDownloadSchemes;
+import com.google.gerrit.entities.CoreDownloadSchemes;
import com.google.gerrit.server.config.DownloadConfig;
import com.google.gerrit.sshd.CommandModule;
import com.google.gerrit.sshd.CommandName;
diff --git a/java/com/google/gerrit/sshd/commands/FlushCaches.java b/java/com/google/gerrit/sshd/commands/FlushCaches.java
index e048eae557..fe2a8972f8 100644
--- a/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -23,7 +23,6 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.config.ListCaches;
import com.google.gerrit.server.restapi.config.ListCaches.OutputFormat;
import com.google.gerrit.server.restapi.config.PostCaches;
@@ -82,15 +81,16 @@ final class FlushCaches extends SshCommand {
}
} catch (RestApiException e) {
throw die(e.getMessage());
- } catch (PermissionBackendException e) {
+ } catch (Exception e) {
throw new Failure(1, "unavailable", e);
}
}
@SuppressWarnings("unchecked")
- private void doList() {
+ private void doList() throws Exception {
for (String name :
- (List<String>) listCaches.setFormat(OutputFormat.LIST).apply(new ConfigResource())) {
+ (List<String>)
+ listCaches.setFormat(OutputFormat.LIST).apply(new ConfigResource()).value()) {
stderr.print(name);
stderr.print('\n');
}
diff --git a/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index 97737b0e01..28a7804498 100644
--- a/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -21,8 +21,8 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GarbageCollectionResult;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
diff --git a/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java b/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
index 376b8370fa..041c85c856 100644
--- a/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
+++ b/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
@@ -14,9 +14,9 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.common.Input;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.restapi.change.Index;
diff --git a/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index a6fe833479..7bf42eb06a 100644
--- a/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -18,9 +18,9 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.ioutil.ColumnFormatter;
@@ -63,7 +63,7 @@ public class ListGroupsCommand extends SshCommand {
if (verboseOutput) {
Optional<InternalGroup> group =
info.ownerId != null
- ? groupCache.get(new AccountGroup.UUID(Url.decode(info.ownerId)))
+ ? groupCache.get(AccountGroup.uuid(Url.decode(info.ownerId)))
: Optional.empty();
formatter.addColumn(Url.decode(info.id));
diff --git a/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index 53d4ac6407..3269c2b0c4 100644
--- a/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -18,8 +18,8 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
@@ -70,7 +70,7 @@ public class ListMembersCommand extends SshCommand {
}
void display(PrintWriter writer) throws PermissionBackendException {
- Optional<InternalGroup> group = groupCache.get(new AccountGroup.NameKey(name));
+ Optional<InternalGroup> group = groupCache.get(AccountGroup.nameKey(name));
String errorText = "Group not found or not visible\n";
if (!group.isPresent()) {
diff --git a/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index a1af9a084b..b8fc55a09d 100644
--- a/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -17,12 +17,12 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -77,7 +77,7 @@ public class LsUserRefs extends SshCommand {
enableGracefulStop();
Account.Id userAccountId;
try {
- userAccountId = accountResolver.resolve(userName).asUnique().getAccount().getId();
+ userAccountId = accountResolver.resolve(userName).asUnique().account().id();
} catch (UnprocessableEntityException e) {
stdout.println(e.getMessage());
stdout.flush();
diff --git a/java/com/google/gerrit/sshd/commands/PatchSetParser.java b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
index 45210f6c1b..f804c2dae2 100644
--- a/java/com/google/gerrit/sshd/commands/PatchSetParser.java
+++ b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
@@ -15,10 +15,10 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -56,7 +56,7 @@ public class PatchSetParser {
throws UnloggedFailure {
// By commit?
//
- if (token.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
+ if (token.matches("^([0-9a-fA-F]{4," + ObjectIds.STR_LEN + "})$")) {
InternalChangeQuery query = queryProvider.get();
List<ChangeData> cds;
if (projectState != null) {
@@ -76,7 +76,7 @@ public class PatchSetParser {
continue;
}
for (PatchSet ps : cd.patchSets()) {
- if (ps.getRevision().matches(token)) {
+ if (ObjectIds.matchesAbbreviation(ps.commitId(), token)) {
matches.add(ps);
}
}
@@ -101,7 +101,7 @@ public class PatchSetParser {
} catch (IllegalArgumentException e) {
throw error("\"" + token + "\" is not a valid patch set", e);
}
- ChangeNotes notes = getNotes(projectState, patchSetId.getParentKey());
+ ChangeNotes notes = getNotes(projectState, patchSetId.changeId());
PatchSet patchSet = psUtil.get(notes, patchSetId);
if (patchSet == null) {
throw error("\"" + token + "\" no such patch set");
@@ -147,7 +147,7 @@ public class PatchSetParser {
// No --branch option, so they want every branch.
return true;
}
- return change.getDest().get().equals(branch);
+ return change.getDest().branch().equals(branch);
}
public static UnloggedFailure error(String msg) {
diff --git a/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 7216ed9bbd..504b239d68 100644
--- a/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -42,7 +42,7 @@ public class PluginLsCommand extends SshCommand {
@Override
public void run() throws Exception {
enableGracefulStop();
- Map<String, PluginInfo> output = list.apply(TopLevelResource.INSTANCE);
+ Map<String, PluginInfo> output = list.apply(TopLevelResource.INSTANCE).value();
if (format.isJson()) {
format
diff --git a/java/com/google/gerrit/sshd/commands/Receive.java b/java/com/google/gerrit/sshd/commands/Receive.java
index 0b089b68ce..92666f3b13 100644
--- a/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/java/com/google/gerrit/sshd/commands/Receive.java
@@ -18,10 +18,9 @@ import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -31,12 +30,8 @@ import com.google.gerrit.sshd.AbstractGitCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
import org.eclipse.jgit.errors.TooLargeObjectInPackException;
import org.eclipse.jgit.errors.UnpackException;
-import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.ReceivePack;
import org.kohsuke.args4j.Option;
@@ -118,52 +113,17 @@ final class Receive extends AbstractGitCommand {
logger.atInfo().log(msg.toString());
throw new UnloggedFailure(128, "error: " + badStream.getCause().getMessage());
}
-
- // This may have been triggered by branch level access controls.
- // Log what the heck is going on, as detailed as we can.
- //
StringBuilder msg = new StringBuilder();
msg.append("Unpack error on project \"").append(projectState.getName()).append("\":\n");
msg.append(" AdvertiseRefsHook: ").append(rp.getAdvertiseRefsHook());
if (rp.getAdvertiseRefsHook() == AdvertiseRefsHook.DEFAULT) {
msg.append("DEFAULT");
- } else if (rp.getAdvertiseRefsHook() instanceof DefaultAdvertiseRefsHook) {
- msg.append("DefaultAdvertiseRefsHook");
} else {
msg.append(rp.getAdvertiseRefsHook().getClass());
}
msg.append("\n");
- if (rp.getAdvertiseRefsHook() instanceof DefaultAdvertiseRefsHook) {
- Map<String, Ref> adv = rp.getAdvertisedRefs();
- msg.append(" Visible references (").append(adv.size()).append("):\n");
- for (Ref ref : adv.values()) {
- msg.append(" - ")
- .append(ref.getObjectId().abbreviate(8).name())
- .append(" ")
- .append(ref.getName())
- .append("\n");
- }
-
- List<Ref> allRefs = rp.getRepository().getRefDatabase().getRefs();
- List<Ref> hidden = new ArrayList<>();
- for (Ref ref : allRefs) {
- if (!adv.containsKey(ref.getName())) {
- hidden.add(ref);
- }
- }
-
- msg.append(" Hidden references (").append(hidden.size()).append("):\n");
- for (Ref ref : hidden) {
- msg.append(" - ")
- .append(ref.getObjectId().abbreviate(8).name())
- .append(" ")
- .append(ref.getName())
- .append("\n");
- }
- }
-
IOException detail = new IOException(msg.toString(), badStream);
throw new Failure(128, "fatal: Unpack error, check server log", detail);
}
diff --git a/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 4202aac9d4..61e529f121 100644
--- a/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -19,10 +19,12 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.CharStreams;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.AbandonInput;
@@ -34,7 +36,6 @@ import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
@@ -228,11 +229,11 @@ public class ReviewCommand extends SshCommand {
writeError("error", e.getMessage() + "\n");
} catch (NoSuchChangeException e) {
ok = false;
- writeError("error", "no such change " + patchSet.getId().getParentKey().get());
+ writeError("error", "no such change " + patchSet.id().changeId().get());
} catch (Exception e) {
ok = false;
- writeError("fatal", "internal server error while reviewing " + patchSet.getId() + "\n");
- logger.atSevere().withCause(e).log("internal error while reviewing %s", patchSet.getId());
+ writeError("fatal", "internal server error while reviewing " + patchSet.id() + "\n");
+ logger.atSevere().withCause(e).log("internal error while reviewing %s", patchSet.id());
}
}
@@ -243,8 +244,8 @@ public class ReviewCommand extends SshCommand {
private void applyReview(PatchSet patchSet, ReviewInput review) throws RestApiException {
gApi.changes()
- .id(patchSet.getId().getParentKey().get())
- .revision(patchSet.getRevision().get())
+ .id(patchSet.id().changeId().get())
+ .revision(patchSet.commitId().name())
.review(review);
}
@@ -311,11 +312,11 @@ public class ReviewCommand extends SshCommand {
}
private ChangeApi changeApi(PatchSet patchSet) throws RestApiException {
- return gApi.changes().id(patchSet.getId().getParentKey().get());
+ return gApi.changes().id(patchSet.id().changeId().get());
}
private RevisionApi revisionApi(PatchSet patchSet) throws RestApiException {
- return changeApi(patchSet).revision(patchSet.getRevision().get());
+ return changeApi(patchSet).revision(patchSet.commitId().name());
}
@Override
@@ -350,15 +351,15 @@ public class ReviewCommand extends SshCommand {
private static Option newApproveOption(LabelType type, String usage) {
return OptionUtil.newOption(
asOptionName(type),
- new String[0],
+ ImmutableList.of(),
usage,
"N",
false,
false,
false,
LabelHandler.class,
- new String[0],
- new String[0]);
+ ImmutableList.of(),
+ ImmutableList.of());
}
private static class LabelSetter implements Setter<Short> {
diff --git a/java/com/google/gerrit/sshd/commands/ScpCommand.java b/java/com/google/gerrit/sshd/commands/ScpCommand.java
index 5122b3503b..6912795c1d 100644
--- a/java/com/google/gerrit/sshd/commands/ScpCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ScpCommand.java
@@ -34,6 +34,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
final class ScpCommand extends BaseCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -81,7 +82,7 @@ final class ScpCommand extends BaseCommand {
}
@Override
- public void start(Environment env) {
+ public void start(ChannelSession channel, Environment env) {
startThread(this::runImp, AccessPath.SSH_COMMAND);
}
diff --git a/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 1ecb591d8d..43a1670891 100644
--- a/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -18,6 +18,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.accounts.SshKeyInput;
@@ -31,7 +32,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
@@ -212,8 +212,7 @@ final class SetAccountCommand extends SshCommand {
}
}
- private void setAccount()
- throws IOException, UnloggedFailure, ConfigInvalidException, PermissionBackendException {
+ private void setAccount() throws Failure {
user = genericUserFactory.create(id);
rsrc = new AccountResource(user.asIdentifiedUser());
try {
@@ -268,6 +267,8 @@ final class SetAccountCommand extends SshCommand {
}
} catch (RestApiException e) {
throw die(e.getMessage());
+ } catch (Exception e) {
+ throw new Failure(1, "unavailable", e);
}
}
@@ -280,10 +281,8 @@ final class SetAccountCommand extends SshCommand {
}
}
- private void deleteSshKeys(List<String> sshKeys)
- throws RestApiException, RepositoryNotFoundException, IOException, ConfigInvalidException,
- PermissionBackendException {
- List<SshKeyInfo> infos = getSshKeys.apply(rsrc);
+ private void deleteSshKeys(List<String> sshKeys) throws Exception {
+ List<SshKeyInfo> infos = getSshKeys.apply(rsrc).value();
if (sshKeys.contains("ALL")) {
for (SshKeyInfo i : infos) {
deleteSshKey(i);
@@ -319,10 +318,9 @@ final class SetAccountCommand extends SshCommand {
}
}
- private void deleteEmail(String email)
- throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
+ private void deleteEmail(String email) throws Exception {
if (email.equals("ALL")) {
- List<EmailInfo> emails = getEmails.apply(rsrc);
+ List<EmailInfo> emails = getEmails.apply(rsrc).value();
for (EmailInfo e : emails) {
deleteEmail.apply(new AccountResource.Email(user.asIdentifiedUser(), e.email), new Input());
}
@@ -331,9 +329,8 @@ final class SetAccountCommand extends SshCommand {
}
}
- private void putPreferred(String email)
- throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
- for (EmailInfo e : getEmails.apply(rsrc)) {
+ private void putPreferred(String email) throws Exception {
+ for (EmailInfo e : getEmails.apply(rsrc).value()) {
if (e.email.equals(email)) {
putPreferred.apply(new AccountResource.Email(user.asIdentifiedUser(), email), null);
return;
diff --git a/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
index 5557b16ace..db8e42a3c1 100644
--- a/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -19,11 +19,11 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.GroupCache;
@@ -141,7 +141,7 @@ public class SetMembersCommand extends SshCommand {
return "n/a";
}
return MoreObjects.firstNonNull(
- accountState.get().getAccount().getPreferredEmail(), "n/a");
+ accountState.get().account().preferredEmail(), "n/a");
})
.collect(joining(", "));
out.write(
diff --git a/java/com/google/gerrit/sshd/commands/SetParentCommand.java b/java/com/google/gerrit/sshd/commands/SetParentCommand.java
index 87722d926b..bfdbff9032 100644
--- a/java/com/google/gerrit/sshd/commands/SetParentCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetParentCommand.java
@@ -16,16 +16,14 @@ package com.google.gerrit.sshd.commands;
import static java.util.stream.Collectors.toList;
-import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
@@ -113,7 +111,7 @@ final class SetParentCommand extends SshCommand {
childProjects.addAll(getChildrenForReparenting(oldParent));
} catch (PermissionBackendException e) {
throw new Failure(1, "permissions unavailable", e);
- } catch (StorageException | RestApiException e) {
+ } catch (Exception e) {
throw new Failure(1, "failure in request", e);
}
}
@@ -149,8 +147,7 @@ final class SetParentCommand extends SshCommand {
* list of child projects does not contain projects that were specified to be excluded from
* reparenting.
*/
- private List<Project.NameKey> getChildrenForReparenting(ProjectState parent)
- throws PermissionBackendException, RestApiException {
+ private List<Project.NameKey> getChildrenForReparenting(ProjectState parent) throws Exception {
final List<Project.NameKey> childProjects = new ArrayList<>();
final List<Project.NameKey> excluded = new ArrayList<>(excludedChildren.size());
for (ProjectState excludedChild : excludedChildren) {
@@ -160,8 +157,8 @@ final class SetParentCommand extends SshCommand {
if (newParentKey != null) {
automaticallyExcluded.addAll(getAllParents(newParentKey));
}
- for (ProjectInfo child : listChildProjects.apply(new ProjectResource(parent, user))) {
- final Project.NameKey childName = new Project.NameKey(child.name);
+ for (ProjectInfo child : listChildProjects.apply(new ProjectResource(parent, user)).value()) {
+ final Project.NameKey childName = Project.nameKey(child.name);
if (!excluded.contains(childName)) {
if (!automaticallyExcluded.contains(childName)) {
childProjects.add(childName);
diff --git a/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index 5a04026638..0e6fa65d27 100644
--- a/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -15,12 +15,12 @@
package com.google.gerrit.sshd.commands;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -142,7 +142,7 @@ public class SetReviewersCommand extends SshCommand {
input.confirmed = true;
String error;
try {
- error = postReviewers.apply(changeRsrc, input).error;
+ error = postReviewers.apply(changeRsrc, input).value().error;
} catch (Exception e) {
error = String.format("could not add %s: %s", reviewer, e.getMessage());
}
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 039b74e3d6..63a0dda771 100644
--- a/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -51,6 +51,7 @@ import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.kohsuke.args4j.Option;
/** Show the current cache states. */
@@ -97,7 +98,7 @@ final class ShowCaches extends SshCommand {
private int nw;
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
String s = env.getEnv().get(Environment.ENV_COLUMNS);
if (s != null && !s.isEmpty()) {
try {
@@ -106,11 +107,11 @@ final class ShowCaches extends SshCommand {
columns = 80;
}
}
- super.start(env);
+ super.start(channel, env);
}
@Override
- protected void run() throws UnloggedFailure {
+ protected void run() throws Failure {
enableGracefulStop();
nw = columns - 50;
Date now = new Date();
@@ -162,39 +163,45 @@ final class ShowCaches extends SshCommand {
}
stdout.print("+---------------------+---------+---------+\n");
- Collection<CacheInfo> caches = getCaches();
- printMemoryCoreCaches(caches);
- printMemoryPluginCaches(caches);
- printDiskCaches(caches);
- stdout.print('\n');
-
- boolean showJvm;
try {
- permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
- showJvm = true;
- } catch (AuthException | PermissionBackendException e) {
- // Silently ignore and do not display detailed JVM information.
- showJvm = false;
- }
- if (showJvm) {
- sshSummary();
+ Collection<CacheInfo> caches = getCaches();
+ printMemoryCoreCaches(caches);
+ printMemoryPluginCaches(caches);
+ printDiskCaches(caches);
+ stdout.print('\n');
- SummaryInfo summary = getSummary.setGc(gc).setJvm(showJVM).apply(new ConfigResource());
- taskSummary(summary.taskSummary);
- memSummary(summary.memSummary);
- threadSummary(summary.threadSummary);
+ boolean showJvm;
+ try {
+ permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
+ showJvm = true;
+ } catch (AuthException | PermissionBackendException e) {
+ // Silently ignore and do not display detailed JVM information.
+ showJvm = false;
+ }
+ if (showJvm) {
+ sshSummary();
- if (showJVM && summary.jvmSummary != null) {
- jvmSummary(summary.jvmSummary);
+ SummaryInfo summary =
+ getSummary.setGc(gc).setJvm(showJVM).apply(new ConfigResource()).value();
+ taskSummary(summary.taskSummary);
+ memSummary(summary.memSummary);
+ threadSummary(summary.threadSummary);
+
+ if (showJVM && summary.jvmSummary != null) {
+ jvmSummary(summary.jvmSummary);
+ }
}
+ } catch (Exception e) {
+ throw new Failure(1, "unavailable", e);
}
stdout.flush();
}
- private Collection<CacheInfo> getCaches() {
+ private Collection<CacheInfo> getCaches() throws Exception {
@SuppressWarnings("unchecked")
- Map<String, CacheInfo> caches = (Map<String, CacheInfo>) listCaches.apply(new ConfigResource());
+ Map<String, CacheInfo> caches =
+ (Map<String, CacheInfo>) listCaches.apply(new ConfigResource()).value();
for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
CacheInfo cache = entry.getValue();
cache.name = entry.getKey();
diff --git a/java/com/google/gerrit/sshd/commands/ShowConnections.java b/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 52e824b483..d27136469b 100644
--- a/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -44,6 +44,7 @@ import org.apache.sshd.common.io.mina.MinaSession;
import org.apache.sshd.common.io.nio2.Nio2Acceptor;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.kohsuke.args4j.Option;
/** Show the current SSH connections. */
@@ -71,7 +72,7 @@ final class ShowConnections extends SshCommand {
private int columns = 80;
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
String s = env.getEnv().get(Environment.ENV_COLUMNS);
if (s != null && !s.isEmpty()) {
try {
@@ -80,7 +81,7 @@ final class ShowConnections extends SshCommand {
columns = 80;
}
}
- super.start(env);
+ super.start(channel, env);
}
@Override
@@ -149,7 +150,7 @@ final class ShowConnections extends SshCommand {
}
stdout.print("--\n");
- stdout.print("SSHD Backend: " + getBackend() + "\n");
+ stdout.print(String.format(" %d connections; SSHD Backend: %s\n", list.size(), getBackend()));
}
private String getBackend() {
diff --git a/java/com/google/gerrit/sshd/commands/ShowQueue.java b/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 04d06eebb9..779f2df591 100644
--- a/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -40,6 +40,7 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.kohsuke.args4j.Option;
/** Display the current work queue. */
@@ -70,7 +71,7 @@ final class ShowQueue extends SshCommand {
private int maxCommandWidth;
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
String s = env.getEnv().get(Environment.ENV_COLUMNS);
if (s != null && !s.isEmpty()) {
try {
@@ -79,7 +80,7 @@ final class ShowQueue extends SshCommand {
columns = 80;
}
}
- super.start(env);
+ super.start(channel, env);
}
@Override
@@ -95,11 +96,13 @@ final class ShowQueue extends SshCommand {
List<TaskInfo> tasks;
try {
- tasks = listTasks.apply(new ConfigResource());
+ tasks = listTasks.apply(new ConfigResource()).value();
} catch (AuthException e) {
throw die(e);
} catch (PermissionBackendException e) {
throw new Failure(1, "permission backend unavailable", e);
+ } catch (Exception e) {
+ throw new Failure(1, "unavailable", e);
}
boolean viewAll = permissionBackend.user(currentUser).testOrFalse(GlobalPermission.VIEW_QUEUE);
diff --git a/java/com/google/gerrit/sshd/commands/StreamEvents.java b/java/com/google/gerrit/sshd/commands/StreamEvents.java
index ffd98d575c..45540a0a76 100644
--- a/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -16,26 +16,22 @@ package com.google.gerrit.sshd.commands;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.base.Supplier;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventGson;
import com.google.gerrit.server.events.EventTypes;
-import com.google.gerrit.server.events.ProjectNameKeySerializer;
-import com.google.gerrit.server.events.SupplierSerializer;
import com.google.gerrit.server.events.UserScopedEventListener;
import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.StreamCommandExecutor;
import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
import com.google.inject.Inject;
import java.io.IOException;
import java.io.PrintWriter;
@@ -45,11 +41,12 @@ import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.ChannelSession;
import org.kohsuke.args4j.Option;
@RequiresCapability(GlobalCapability.STREAM_EVENTS)
@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time")
-final class StreamEvents extends BaseCommand {
+public final class StreamEvents extends BaseCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Maximum number of events that may be queued up for each connection. */
@@ -71,11 +68,11 @@ final class StreamEvents extends BaseCommand {
@Inject @StreamCommandExecutor private ScheduledThreadPoolExecutor pool;
+ @Inject @EventGson private Gson gson;
+
/** Queue of events to stream to the connected user. */
private final LinkedBlockingQueue<Event> queue = new LinkedBlockingQueue<>(MAX_EVENTS);
- private Gson gson;
-
private RegistrationHandle eventListenerRegistration;
/** Special event to notify clients they missed other events. */
@@ -91,29 +88,6 @@ final class StreamEvents extends BaseCommand {
EventTypes.register(DroppedOutputEvent.TYPE, DroppedOutputEvent.class);
}
- private final CancelableRunnable writer =
- new CancelableRunnable() {
- @Override
- public void run() {
- writeEvents();
- }
-
- @Override
- public void cancel() {
- onExit(0);
- }
-
- @Override
- public String toString() {
- StringBuilder b = new StringBuilder();
- b.append("Stream Events");
- if (currentUser.getUserName().isPresent()) {
- b.append(" (").append(currentUser.getUserName().get()).append(")");
- }
- return b.toString();
- }
- };
-
/** True if {@link DroppedOutputEvent} needs to be sent. */
private volatile boolean dropped;
@@ -131,10 +105,8 @@ final class StreamEvents extends BaseCommand {
*/
private Future<?> task;
- private PrintWriter stdout;
-
@Override
- public void start(Environment env) throws IOException {
+ public void start(ChannelSession channel, Environment env) throws IOException {
try {
parseCommandLine();
} catch (UnloggedFailure e) {
@@ -148,7 +120,30 @@ final class StreamEvents extends BaseCommand {
return;
}
- stdout = toPrintWriter(out);
+ PrintWriter stdout = toPrintWriter(out);
+ CancelableRunnable writer =
+ new CancelableRunnable() {
+ @Override
+ public void run() {
+ writeEvents(this, stdout);
+ }
+
+ @Override
+ public void cancel() {
+ onExit(0);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("Stream Events");
+ if (currentUser.getUserName().isPresent()) {
+ b.append(" (").append(currentUser.getUserName().get()).append(")");
+ }
+ return b.toString();
+ }
+ };
+
eventListenerRegistration =
eventListeners.add(
"gerrit",
@@ -156,7 +151,7 @@ final class StreamEvents extends BaseCommand {
@Override
public void onEvent(Event event) {
if (subscribedToEvents.isEmpty() || subscribedToEvents.contains(event.getType())) {
- offer(event);
+ offer(writer, event);
}
}
@@ -165,12 +160,6 @@ final class StreamEvents extends BaseCommand {
return currentUser;
}
});
-
- gson =
- new GsonBuilder()
- .registerTypeAdapter(Supplier.class, new SupplierSerializer())
- .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeySerializer())
- .create();
}
private void removeEventListenerRegistration() {
@@ -191,7 +180,7 @@ final class StreamEvents extends BaseCommand {
}
@Override
- public void destroy() {
+ public void destroy(ChannelSession channel) {
removeEventListenerRegistration();
final boolean exit;
@@ -209,7 +198,7 @@ final class StreamEvents extends BaseCommand {
}
}
- private void offer(Event event) {
+ private void offer(CancelableRunnable writer, Event event) {
synchronized (taskLock) {
if (!queue.offer(event)) {
dropped = true;
@@ -231,7 +220,7 @@ final class StreamEvents extends BaseCommand {
}
}
- private void writeEvents() {
+ private void writeEvents(CancelableRunnable writer, PrintWriter stdout) {
int processed = 0;
while (processed < BATCH_SIZE) {
@@ -241,13 +230,13 @@ final class StreamEvents extends BaseCommand {
// accepting output. Either way terminate this instance.
//
removeEventListenerRegistration();
- flush();
+ flush(stdout);
onExit(0);
return;
}
if (dropped) {
- write(new DroppedOutputEvent());
+ write(stdout, new DroppedOutputEvent());
dropped = false;
}
@@ -256,11 +245,11 @@ final class StreamEvents extends BaseCommand {
break;
}
- write(event);
+ write(stdout, event);
processed++;
}
- flush();
+ flush(stdout);
if (BATCH_SIZE <= processed) {
// We processed the limit, but more might remain in the queue.
@@ -273,7 +262,7 @@ final class StreamEvents extends BaseCommand {
}
}
- private void write(Object message) {
+ private void write(PrintWriter stdout, Object message) {
String msg = null;
try {
msg = gson.toJson(message) + "\n";
@@ -287,7 +276,7 @@ final class StreamEvents extends BaseCommand {
}
}
- private void flush() {
+ private void flush(PrintWriter stdout) {
synchronized (stdout) {
stdout.flush();
}
diff --git a/java/com/google/gerrit/sshd/commands/Upload.java b/java/com/google/gerrit/sshd/commands/Upload.java
index e195098a64..b80b879f58 100644
--- a/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/java/com/google/gerrit/sshd/commands/Upload.java
@@ -14,22 +14,29 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
+import com.google.gerrit.server.RequestInfo;
+import com.google.gerrit.server.RequestListener;
+import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
+import com.google.gerrit.server.git.TracingHook;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackInitializer;
+import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
import com.google.gerrit.server.git.validators.UploadValidationException;
import com.google.gerrit.server.git.validators.UploadValidators;
+import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.sshd.AbstractGitCommand;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.List;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.pack.PackStatistics;
import org.eclipse.jgit.transport.PostUploadHook;
import org.eclipse.jgit.transport.PostUploadHookChain;
@@ -43,8 +50,10 @@ final class Upload extends AbstractGitCommand {
@Inject private DynamicSet<PreUploadHook> preUploadHooks;
@Inject private DynamicSet<PostUploadHook> postUploadHooks;
@Inject private DynamicSet<UploadPackInitializer> uploadPackInitializers;
+ @Inject private PluginSetContext<RequestListener> requestListeners;
@Inject private UploadValidators.Factory uploadValidatorsFactory;
@Inject private PermissionBackend permissionBackend;
+ @Inject private UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
private PackStatistics stats;
@@ -53,7 +62,6 @@ final class Upload extends AbstractGitCommand {
PermissionBackend.ForProject perm =
permissionBackend.user(user).project(projectState.getNameKey());
try {
-
perm.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
throw new Failure(1, "fatal: upload-pack not permitted on this server", e);
@@ -61,20 +69,35 @@ final class Upload extends AbstractGitCommand {
throw new Failure(1, "fatal: unable to check permissions ", e);
}
- final UploadPack up = new UploadPack(repo);
- up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
+ Repository permissionAwareRepo = PermissionAwareRepositoryManager.wrap(repo, perm);
+ UploadPack up = new UploadPack(permissionAwareRepo);
+
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
+ if (projectState.isAllUsers()) {
+ up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
+ }
+ if (extraParameters != null) {
+ up.setExtraParameters(ImmutableList.copyOf(extraParameters));
+ }
List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks);
allPreUploadHooks.add(
- uploadValidatorsFactory.create(project, repo, session.getRemoteAddressAsString()));
+ uploadValidatorsFactory.create(
+ project, permissionAwareRepo, session.getRemoteAddressAsString()));
up.setPreUploadHook(PreUploadHookChain.newChain(allPreUploadHooks));
for (UploadPackInitializer initializer : uploadPackInitializers) {
initializer.init(projectState.getNameKey(), up);
}
- try {
+ try (TraceContext traceContext = TraceContext.open();
+ TracingHook tracingHook = new TracingHook()) {
+ RequestInfo requestInfo =
+ RequestInfo.builder(RequestInfo.RequestType.GIT_UPLOAD, user, traceContext)
+ .project(projectState.getNameKey())
+ .build();
+ requestListeners.runEach(l -> l.onRequest(requestInfo));
+ up.setProtocolV2Hook(tracingHook);
up.upload(in, out, err);
session.setPeerAgent(up.getPeerUserAgent());
stats = up.getStatistics();
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 24f82a78ab..c25a1a8f66 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -139,7 +139,7 @@ public class UploadArchive extends AbstractGitCommand {
PacketLineIn packetIn = new PacketLineIn(in);
for (; ; ) {
String s = packetIn.readString();
- if (s == PacketLineIn.END) {
+ if (PacketLineIn.isEnd(s)) {
break;
}
if (!s.startsWith(argCmd)) {
diff --git a/java/com/google/gerrit/testing/AssertableExecutorService.java b/java/com/google/gerrit/testing/AssertableExecutorService.java
new file mode 100644
index 0000000000..18ac2e9773
--- /dev/null
+++ b/java/com/google/gerrit/testing/AssertableExecutorService.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testing;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.util.concurrent.ForwardingExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Forwards all calls to a direct executor making it so that the submitted {@link Runnable}s run
+ * synchronously. Holds a count of the number of tasks that were executed.
+ */
+public class AssertableExecutorService extends ForwardingExecutorService {
+
+ private final ExecutorService delegate = MoreExecutors.newDirectExecutorService();
+ private final AtomicInteger numInteractions = new AtomicInteger();
+
+ @Override
+ protected ExecutorService delegate() {
+ return delegate;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ numInteractions.incrementAndGet();
+ return super.submit(task);
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ numInteractions.incrementAndGet();
+ return super.submit(task);
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ numInteractions.incrementAndGet();
+ return super.submit(task, result);
+ }
+
+ /** Asserts and resets the number of executions this executor observed. */
+ public void assertInteractions(int expectedNumInteractions) {
+ assertWithMessage("expectedRunnablesSubmittedOnExecutor")
+ .that(numInteractions.get())
+ .isEqualTo(expectedNumInteractions);
+ numInteractions.set(0);
+ }
+}
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index dc1c150853..c610d073e2 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -3,19 +3,19 @@ load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "gerrit-test-util",
testonly = True,
- srcs = glob(["**/*.java"]),
+ srcs = glob(
+ ["**/*.java"],
+ exclude = ["AssertableExecutorService.java"],
+ ),
visibility = ["//visibility:public"],
exports = [
- "//lib/easymock",
- "//lib/powermock:powermock-api-easymock",
- "//lib/powermock:powermock-api-support",
- "//lib/powermock:powermock-core",
- "//lib/powermock:powermock-module-junit4",
- "//lib/powermock:powermock-module-junit4-common",
+ "//lib:junit",
+ "//lib/mockito",
],
deps = [
+ "//java/com/google/gerrit/acceptance/testsuite/project",
"//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/gpg",
@@ -24,8 +24,6 @@ java_library(
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/mail",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/pgm/init",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server:module",
"//java/com/google/gerrit/server/api",
@@ -38,16 +36,28 @@ java_library(
"//java/com/google/gerrit/server/util/time",
"//lib:guava",
"//lib:h2",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:junit",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/log:impl-log4j",
"//lib/log:log4j",
"//lib/truth",
],
)
+
+java_library(
+ # This can't be part of gerrit-test-util because of https://github.com/google/guava/issues/2837
+ name = "assertable-executor",
+ testonly = True,
+ srcs = ["AssertableExecutorService.java"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//lib:guava",
+ "//lib/truth",
+ ],
+)
diff --git a/java/com/google/gerrit/testing/ConfigSuite.java b/java/com/google/gerrit/testing/ConfigSuite.java
index 0fcca240a9..d7ae397a21 100644
--- a/java/com/google/gerrit/testing/ConfigSuite.java
+++ b/java/com/google/gerrit/testing/ConfigSuite.java
@@ -57,6 +57,7 @@ import org.junit.runners.model.InitializationError;
* public static Config firstConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "a");
+ * return cfg;
* }
* }
*
@@ -65,6 +66,7 @@ import org.junit.runners.model.InitializationError;
* public static Config secondConfig() {
* Config cfg = new Config();
* cfg.setString("gerrit", null, "testValue", "b");
+ * return cfg;
* }
*
* {@literal @}Test
diff --git a/java/com/google/gerrit/testing/FakeAccountCache.java b/java/com/google/gerrit/testing/FakeAccountCache.java
index b99a32d7df..0178c7211b 100644
--- a/java/com/google/gerrit/testing/FakeAccountCache.java
+++ b/java/com/google/gerrit/testing/FakeAccountCache.java
@@ -17,11 +17,9 @@ package com.google.gerrit.testing;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.util.time.TimeUtil;
import java.util.HashMap;
import java.util.Map;
@@ -42,7 +40,10 @@ public class FakeAccountCache implements AccountCache {
if (state != null) {
return state;
}
- return newState(new Account(accountId, TimeUtil.nowTs()));
+ return newState(
+ Account.builder(accountId, TimeUtil.nowTs())
+ .setMetaId("1234567812345678123456781234567812345678")
+ .build());
}
@Override
@@ -74,10 +75,10 @@ public class FakeAccountCache implements AccountCache {
public synchronized void put(Account account) {
AccountState state = newState(account);
- byId.put(account.getId(), state);
+ byId.put(account.id(), state);
}
private static AccountState newState(Account account) {
- return AccountState.forAccount(new AllUsersName(AllUsersNameProvider.DEFAULT), account);
+ return AccountState.forAccount(account);
}
}
diff --git a/java/com/google/gerrit/testing/GerritServerTests.java b/java/com/google/gerrit/testing/GerritServerTests.java
index 9a922d6449..ad985b6976 100644
--- a/java/com/google/gerrit/testing/GerritServerTests.java
+++ b/java/com/google/gerrit/testing/GerritServerTests.java
@@ -21,7 +21,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
@RunWith(ConfigSuite.class)
-public class GerritServerTests extends GerritBaseTests {
+public class GerritServerTests {
@ConfigSuite.Parameter public Config config;
@ConfigSuite.Name private String configName;
diff --git a/java/com/google/gerrit/testing/GerritBaseTests.java b/java/com/google/gerrit/testing/GerritTestName.java
index 500d791685..d287837430 100644
--- a/java/com/google/gerrit/testing/GerritBaseTests.java
+++ b/java/com/google/gerrit/testing/GerritTestName.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,23 +16,21 @@ package com.google.gerrit.testing;
import com.google.common.base.CharMatcher;
import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
-@Ignore
-public abstract class GerritBaseTests {
- @Rule public ExpectedException exception = ExpectedException.none();
- @Rule public final TestName testName = new TestName();
+public class GerritTestName implements TestRule {
+ private final TestName delegate = new TestName();
@BeforeClass
public static void beforeClassTest() {
TestLoggingActivator.configureLogging();
}
- protected String getSanitizedMethodName() {
- String name = testName.getMethodName().toLowerCase();
+ public String getSanitizedMethodName() {
+ String name = delegate.getMethodName().toLowerCase();
name =
CharMatcher.inRange('a', 'z')
.or(CharMatcher.inRange('A', 'Z'))
@@ -42,4 +40,9 @@ public abstract class GerritBaseTests {
name = CharMatcher.is('_').trimTrailingFrom(name);
return name;
}
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return delegate.apply(base, description);
+ }
}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index 83b9e2b6e0..abb5955ecc 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -19,10 +19,13 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperationsImpl;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.gpg.GpgModule;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.SchemaDefinitions;
import com.google.gerrit.index.project.ProjectSchemaDefinitions;
import com.google.gerrit.metrics.DisabledMetricMaker;
@@ -61,7 +64,6 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
import com.google.gerrit.server.index.account.AllAccountsIndexer;
import com.google.gerrit.server.index.change.AllChangesIndexer;
@@ -82,6 +84,7 @@ import com.google.gerrit.server.securestore.DefaultSecureStore;
import com.google.gerrit.server.securestore.SecureStore;
import com.google.gerrit.server.ssh.NoSshKeyCache;
import com.google.gerrit.server.submit.LocalMergeSuperSetComputation;
+import com.google.gerrit.server.util.ReplicaUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -172,8 +175,8 @@ public class InMemoryModule extends FactoryModule {
bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
- // TODO(dborowitz): Use Jimfs. The biggest blocker is that JGit does not support Path-based
- // Configs, only FileBasedConfig.
+ // It would be nice to use Jimfs for the SitePath, but the biggest blocker is that JGit does not
+ // support Path-based Configs, only FileBasedConfig.
bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false));
@@ -218,28 +221,19 @@ public class InMemoryModule extends FactoryModule {
bind(AllChangesIndexer.class).toProvider(Providers.of(null));
bind(AllGroupsIndexer.class).toProvider(Providers.of(null));
- IndexType indexType = null;
- try {
- indexType = cfg.getEnum("index", null, "type", IndexType.LUCENE);
- } catch (IllegalArgumentException e) {
- // Custom index type, caller must provide their own module.
- }
- if (indexType != null) {
- switch (indexType) {
- case LUCENE:
- install(luceneIndexModule());
- break;
- case ELASTICSEARCH:
- install(elasticIndexModule());
- break;
- default:
- throw new ProvisionException("index type unsupported in tests: " + indexType);
- }
+ IndexType indexType = new IndexType(cfg.getString("index", null, "type"));
+ // For custom index types, callers must provide their own module.
+ if (indexType.isLucene()) {
+ install(luceneIndexModule());
+ } else if (indexType.isElasticsearch()) {
+ install(elasticIndexModule());
}
bind(ServerInformationImpl.class);
bind(ServerInformation.class).to(ServerInformationImpl.class);
install(new RestApiModule());
install(new DefaultProjectNameLockManager.Module());
+
+ bind(ProjectOperations.class).to(ProjectOperationsImpl.class);
}
/** Copy of SchemaModule with a slightly different server ID provider. */
@@ -301,11 +295,10 @@ public class InMemoryModule extends FactoryModule {
private Module indexModule(String moduleClassName) {
try {
- boolean slave = cfg.getBoolean("container", "slave", false);
Class<?> clazz = Class.forName(moduleClassName);
Method m =
clazz.getMethod("singleVersionWithExplicitVersions", Map.class, int.class, boolean.class);
- return (Module) m.invoke(null, getSingleSchemaVersions(), 0, slave);
+ return (Module) m.invoke(null, getSingleSchemaVersions(), 0, ReplicaUtil.isReplica(cfg));
} catch (ClassNotFoundException
| SecurityException
| NoSuchMethodException
diff --git a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
index e44d8d381e..09ae115dae 100644
--- a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
+++ b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
@@ -16,7 +16,7 @@ package com.google.gerrit.testing;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
import com.google.inject.Inject;
@@ -103,7 +103,7 @@ public class InMemoryRepositoryManager implements GitRepositoryManager {
public synchronized SortedSet<Project.NameKey> list() {
SortedSet<Project.NameKey> names = Sets.newTreeSet();
for (DfsRepository repo : repos.values()) {
- names.add(new Project.NameKey(repo.getDescription().getRepositoryName()));
+ names.add(Project.nameKey(repo.getDescription().getRepositoryName()));
}
return ImmutableSortedSet.copyOf(names);
}
diff --git a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
index 05672aa044..44d5cead97 100644
--- a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
+++ b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
@@ -14,8 +14,8 @@
package com.google.gerrit.testing;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
diff --git a/java/com/google/gerrit/testing/IndexConfig.java b/java/com/google/gerrit/testing/IndexConfig.java
index 9cace8879b..21c49dda06 100644
--- a/java/com/google/gerrit/testing/IndexConfig.java
+++ b/java/com/google/gerrit/testing/IndexConfig.java
@@ -23,6 +23,7 @@ public class IndexConfig {
public static Config createFromExistingConfig(Config cfg) {
cfg.setInt("index", null, "maxPages", 10);
+ cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
cfg.setString("trackingid", "query-bug", "footer", "Bug:");
cfg.setString("trackingid", "query-bug", "match", "QUERY\\d{2,8}");
cfg.setString("trackingid", "query-bug", "system", "querytests");
diff --git a/java/com/google/gerrit/testing/TestChanges.java b/java/com/google/gerrit/testing/TestChanges.java
index d0a939da61..b795c5bb41 100644
--- a/java/com/google/gerrit/testing/TestChanges.java
+++ b/java/com/google/gerrit/testing/TestChanges.java
@@ -17,14 +17,13 @@ package com.google.gerrit.testing;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.common.collect.Ordering;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.AbstractChangeNotes;
@@ -52,13 +51,13 @@ public class TestChanges {
}
public static Change newChange(Project.NameKey project, Account.Id userId, int id) {
- Change.Id changeId = new Change.Id(id);
+ Change.Id changeId = Change.id(id);
Change c =
new Change(
- new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
+ Change.key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
changeId,
userId,
- new Branch.NameKey(project, "master"),
+ BranchNameKey.create(project, "master"),
TimeUtil.nowTs());
incrementPatchSet(c);
return c;
@@ -69,11 +68,12 @@ public class TestChanges {
}
public static PatchSet newPatchSet(PatchSet.Id id, String revision, Account.Id userId) {
- PatchSet ps = new PatchSet(id);
- ps.setRevision(new RevId(revision));
- ps.setUploader(userId);
- ps.setCreatedOn(TimeUtil.nowTs());
- return ps;
+ return PatchSet.builder()
+ .id(id)
+ .commitId(ObjectId.fromString(revision))
+ .uploader(userId)
+ .createdOn(TimeUtil.nowTs())
+ .build();
}
public static ChangeUpdate newUpdate(
@@ -115,11 +115,11 @@ public class TestChanges {
.author(ident)
.committer(ident)
.message(firstNonNull(c.getSubject(), "Test change"));
- Ref parent = repo.exactRef(c.getDest().get());
+ Ref parent = repo.exactRef(c.getDest().branch());
if (parent != null) {
cb.parent(tr.getRevWalk().parseCommit(parent.getObjectId()));
}
- update.setBranch(c.getDest().get());
+ update.setBranch(c.getDest().branch());
update.setChangeId(c.getKey().get());
update.setCommit(tr.getRevWalk(), cb.create());
return update;
@@ -129,7 +129,7 @@ public class TestChanges {
public static void incrementPatchSet(Change change) {
PatchSet.Id curr = change.currentPatchSetId();
PatchSetInfo ps =
- new PatchSetInfo(new PatchSet.Id(change.getId(), curr != null ? curr.get() + 1 : 1));
+ new PatchSetInfo(PatchSet.id(change.getId(), curr != null ? curr.get() + 1 : 1));
ps.setSubject("Change subject");
change.setCurrentPatchSet(ps);
}
diff --git a/java/com/google/gerrit/testing/TestCommentHelper.java b/java/com/google/gerrit/testing/TestCommentHelper.java
new file mode 100644
index 0000000000..b72cca7f01
--- /dev/null
+++ b/java/com/google/gerrit/testing/TestCommentHelper.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testing;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.client.Comment;
+import com.google.gerrit.extensions.client.Comment.Range;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.inject.Inject;
+import java.util.Collection;
+
+/** Test helper for dealing with comments/drafts. */
+public class TestCommentHelper {
+ private final GerritApi gApi;
+
+ @Inject
+ public TestCommentHelper(GerritApi gerritApi) {
+ gApi = gerritApi;
+ }
+
+ public DraftInput newDraft(String message) {
+ return populate(new DraftInput(), "file", message);
+ }
+
+ public DraftInput newDraft(String path, Side side, int line, String message) {
+ DraftInput d = new DraftInput();
+ return populate(d, path, side, line, message);
+ }
+
+ public void addDraft(String changeId, String revId, DraftInput in) throws Exception {
+ gApi.changes().id(changeId).revision(revId).createDraft(in).get();
+ }
+
+ public Collection<CommentInfo> getPublishedComments(String changeId) throws Exception {
+ return gApi.changes().id(changeId).comments().values().stream()
+ .flatMap(Collection::stream)
+ .collect(toList());
+ }
+
+ public static <C extends Comment> C populate(C c, String path, String message) {
+ return populate(c, path, createLineRange(), message);
+ }
+
+ private static <C extends Comment> C populate(C c, String path, Range range, String message) {
+ int line = range.startLine;
+ c.path = path;
+ c.side = Side.REVISION;
+ c.parent = null;
+ c.line = line != 0 ? line : null;
+ c.message = message;
+ c.unresolved = false;
+ if (line != 0) c.range = range;
+ return c;
+ }
+
+ private static <C extends Comment> C populate(
+ C c, String path, Side side, Range range, String message) {
+ int line = range.startLine;
+ c.path = path;
+ c.side = side;
+ c.parent = null;
+ c.line = line != 0 ? line : null;
+ c.message = message;
+ c.unresolved = false;
+ if (line != 0) c.range = range;
+ return c;
+ }
+
+ private static <C extends Comment> C populate(
+ C c, String path, Side side, int line, String message) {
+ return populate(c, path, side, createLineRange(line), message);
+ }
+
+ private static Range createLineRange() {
+ Range range = new Range();
+ range.startLine = 0;
+ range.startCharacter = 1;
+ range.endLine = 0;
+ range.endCharacter = 5;
+ return range;
+ }
+
+ private static Range createLineRange(int line) {
+ Range range = new Range();
+ range.startLine = line;
+ range.startCharacter = 1;
+ range.endLine = line;
+ range.endCharacter = 5;
+ return range;
+ }
+}
diff --git a/java/com/google/gerrit/truth/BUILD b/java/com/google/gerrit/truth/BUILD
index f21e3c9548..b0dfef0cb1 100644
--- a/java/com/google/gerrit/truth/BUILD
+++ b/java/com/google/gerrit/truth/BUILD
@@ -8,6 +8,7 @@ java_library(
deps = [
"//java/com/google/gerrit/common:annotations",
"//lib:guava",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/java/com/google/gerrit/truth/CacheStatsSubject.java b/java/com/google/gerrit/truth/CacheStatsSubject.java
index f1a9393a50..ff943341cf 100644
--- a/java/com/google/gerrit/truth/CacheStatsSubject.java
+++ b/java/com/google/gerrit/truth/CacheStatsSubject.java
@@ -24,7 +24,7 @@ import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
@UsedAt(Project.PLUGINS_ALL)
-public class CacheStatsSubject extends Subject<CacheStatsSubject, CacheStats> {
+public class CacheStatsSubject extends Subject {
public static CacheStatsSubject assertThat(CacheStats stats) {
return assertAbout(CacheStatsSubject::new).that(stats);
}
@@ -39,10 +39,12 @@ public class CacheStatsSubject extends Subject<CacheStatsSubject, CacheStats> {
other.evictionCount());
}
+ private final CacheStats stats;
private CacheStats start = new CacheStats(0, 0, 0, 0, 0, 0);
private CacheStatsSubject(FailureMetadata failureMetadata, CacheStats stats) {
super(failureMetadata, stats);
+ this.stats = stats;
}
public CacheStatsSubject since(CacheStats start) {
@@ -52,11 +54,11 @@ public class CacheStatsSubject extends Subject<CacheStatsSubject, CacheStats> {
public void hasHitCount(int expectedHitCount) {
isNotNull();
- check("hitCount()").that(actual().minus(start).hitCount()).isEqualTo(expectedHitCount);
+ check("hitCount()").that(stats.minus(start).hitCount()).isEqualTo(expectedHitCount);
}
public void hasMissCount(int expectedMissCount) {
isNotNull();
- check("missCount()").that(actual().minus(start).missCount()).isEqualTo(expectedMissCount);
+ check("missCount()").that(stats.minus(start).missCount()).isEqualTo(expectedMissCount);
}
}
diff --git a/java/com/google/gerrit/truth/ConfigSubject.java b/java/com/google/gerrit/truth/ConfigSubject.java
new file mode 100644
index 0000000000..dd55b7115e
--- /dev/null
+++ b/java/com/google/gerrit/truth/ConfigSubject.java
@@ -0,0 +1,129 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.truth;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.LongSubject;
+import com.google.common.truth.MultimapSubject;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.gerrit.common.Nullable;
+import java.util.Arrays;
+import org.eclipse.jgit.lib.Config;
+
+public class ConfigSubject extends Subject {
+ public static ConfigSubject assertThat(Config config) {
+ return assertAbout(ConfigSubject::new).that(config);
+ }
+
+ private final Config config;
+
+ private ConfigSubject(FailureMetadata metadata, Config actual) {
+ super(metadata, actual);
+ this.config = actual;
+ }
+
+ public IterableSubject sections() {
+ isNotNull();
+ return check("getSections()").that(config.getSections());
+ }
+
+ public IterableSubject subsections(String section) {
+ requireNonNull(section);
+ isNotNull();
+ return check("getSubsections(%s)", section).that(config.getSubsections(section));
+ }
+
+ public MultimapSubject sectionValues(String section) {
+ requireNonNull(section);
+ return sectionValuesImpl(section, null);
+ }
+
+ public MultimapSubject subsectionValues(String section, String subsection) {
+ requireNonNull(section);
+ requireNonNull(subsection);
+ return sectionValuesImpl(section, subsection);
+ }
+
+ private MultimapSubject sectionValuesImpl(String section, @Nullable String subsection) {
+ isNotNull();
+ ImmutableListMultimap.Builder<String, String> b = ImmutableListMultimap.builder();
+ config
+ .getNames(section, subsection, true)
+ .forEach(
+ n ->
+ Arrays.stream(config.getStringList(section, subsection, n))
+ .forEach(v -> b.put(n, v)));
+ return check("getSection(%s, %s)", section, subsection).that(b.build());
+ }
+
+ public void isEmpty() {
+ sections().isEmpty();
+ }
+
+ public StringSubject text() {
+ isNotNull();
+ return check("toText()").that(config.toText());
+ }
+
+ public IterableSubject stringValues(String section, @Nullable String subsection, String name) {
+ requireNonNull(section);
+ requireNonNull(name);
+ isNotNull();
+ return check("getStringList(%s, %s, %s)", section, subsection, name)
+ .that(Arrays.asList(config.getStringList(section, subsection, name)));
+ }
+
+ public StringSubject stringValue(String section, @Nullable String subsection, String name) {
+ requireNonNull(section);
+ requireNonNull(name);
+ isNotNull();
+ return check("getString(%s, %s, %s)", section, subsection, name)
+ .that(config.getString(section, subsection, name));
+ }
+
+ public IntegerSubject intValue(
+ String section, @Nullable String subsection, String name, int defaultValue) {
+ requireNonNull(section);
+ requireNonNull(name);
+ isNotNull();
+ return check("getInt(%s, %s, %s, %s)", section, subsection, name, defaultValue)
+ .that(config.getInt(section, subsection, name, defaultValue));
+ }
+
+ public LongSubject longValue(String section, String subsection, String name, long defaultValue) {
+ requireNonNull(section);
+ requireNonNull(name);
+ isNotNull();
+ return check("getLong(%s, %s, %s, %s)", section, subsection, name, defaultValue)
+ .that(config.getLong(section, subsection, name, defaultValue));
+ }
+
+ public BooleanSubject booleanValue(
+ String section, String subsection, String name, boolean defaultValue) {
+ requireNonNull(section);
+ requireNonNull(name);
+ isNotNull();
+ return check("getBoolean(%s, %s, %s, %s)", section, subsection, name, defaultValue)
+ .that(config.getBoolean(section, subsection, name, defaultValue));
+ }
+}
diff --git a/java/com/google/gerrit/truth/ListSubject.java b/java/com/google/gerrit/truth/ListSubject.java
index 9a839dda89..9f939640bb 100644
--- a/java/com/google/gerrit/truth/ListSubject.java
+++ b/java/com/google/gerrit/truth/ListSubject.java
@@ -27,12 +27,13 @@ import com.google.common.truth.Subject;
import java.util.List;
import java.util.function.BiFunction;
-public class ListSubject<S extends Subject<S, E>, E> extends IterableSubject {
+public class ListSubject<S extends Subject, E> extends IterableSubject {
- private final BiFunction<StandardSubjectBuilder, E, S> elementSubjectCreator;
+ private final List<E> list;
+ private final BiFunction<StandardSubjectBuilder, ? super E, ? extends S> elementSubjectCreator;
- public static <S extends Subject<S, E>, E> ListSubject<S, E> assertThat(
- List<E> list, Subject.Factory<S, E> subjectFactory) {
+ public static <S extends Subject, E> ListSubject<S, E> assertThat(
+ List<E> list, Subject.Factory<? extends S, ? super E> subjectFactory) {
return assertAbout(elements()).thatCustom(list, subjectFactory);
}
@@ -43,15 +44,15 @@ public class ListSubject<S extends Subject<S, E>, E> extends IterableSubject {
private ListSubject(
FailureMetadata failureMetadata,
List<E> list,
- BiFunction<StandardSubjectBuilder, E, S> elementSubjectCreator) {
+ BiFunction<StandardSubjectBuilder, ? super E, ? extends S> elementSubjectCreator) {
super(failureMetadata, list);
+ this.list = list;
this.elementSubjectCreator = elementSubjectCreator;
}
public S element(int index) {
checkArgument(index >= 0, "index(%s) must be >= 0", index);
isNotNull();
- List<E> list = getActualList();
if (index >= list.size()) {
failWithoutActual(fact("expected to have element at index", index));
}
@@ -61,43 +62,29 @@ public class ListSubject<S extends Subject<S, E>, E> extends IterableSubject {
public S onlyElement() {
isNotNull();
hasSize(1);
- List<E> list = getActualList();
return elementSubjectCreator.apply(check("onlyElement()"), Iterables.getOnlyElement(list));
}
public S lastElement() {
isNotNull();
isNotEmpty();
- List<E> list = getActualList();
return elementSubjectCreator.apply(check("lastElement()"), Iterables.getLast(list));
}
- @SuppressWarnings("unchecked")
- private List<E> getActualList() {
- // The constructor only accepts lists. -> Casting is appropriate.
- return (List<E>) actual();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public ListSubject<S, E> named(String s, Object... objects) {
- // This object is returned which is of type ListSubject. -> Casting is appropriate.
- return (ListSubject<S, E>) super.named(s, objects);
- }
-
public static class ListSubjectBuilder extends CustomSubjectBuilder {
ListSubjectBuilder(FailureMetadata failureMetadata) {
super(failureMetadata);
}
- public <S extends Subject<S, E>, E> ListSubject<S, E> thatCustom(
- List<E> list, Subject.Factory<S, E> subjectFactory) {
+ public <S extends Subject, E> ListSubject<S, E> thatCustom(
+ List<E> list, Subject.Factory<? extends S, ? super E> subjectFactory) {
return that(list, (builder, element) -> builder.about(subjectFactory).that(element));
}
- public <S extends Subject<S, E>, E> ListSubject<S, E> that(
- List<E> list, BiFunction<StandardSubjectBuilder, E, S> elementSubjectCreator) {
+ public <S extends Subject, E> ListSubject<S, E> that(
+ List<E> list,
+ BiFunction<StandardSubjectBuilder, ? super E, ? extends S> elementSubjectCreator) {
return new ListSubject<>(metadata(), list, elementSubjectCreator);
}
}
diff --git a/java/com/google/gerrit/truth/NullAwareCorrespondence.java b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
new file mode 100644
index 0000000000..5b107a6929
--- /dev/null
+++ b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.truth;
+
+import com.google.common.base.Function;
+import com.google.common.truth.Correspondence;
+import java.util.Optional;
+
+/** Utility class for constructing null aware {@link Correspondence}s. */
+public class NullAwareCorrespondence {
+ /**
+ * Constructs a {@link Correspondence} that compares elements by transforming the actual elements
+ * using the given function and testing for equality with the expected elements.
+ *
+ * <p>If the actual element is null, it will correspond to a null expected element. This is
+ * different to {@link Correspondence#transforming(Function, String)} which would invoke the
+ * function with a {@code null} argument, requiring the function being able to handle {@code
+ * null}.
+ *
+ * @param actualTransform a {@link Function} taking an actual value and returning a new value
+ * which will be compared with an expected value to determine whether they correspond
+ * @param description should fill the gap in a failure message of the form {@code "not true that
+ * <some actual element> is an element that <description> <some expected element>"}, e.g.
+ * {@code "has an ID of"}
+ */
+ public static <A, E> Correspondence<A, E> transforming(
+ Function<A, ? extends E> actualTransform, String description) {
+ return Correspondence.transforming(
+ actualValue -> Optional.ofNullable(actualValue).map(actualTransform).orElse(null),
+ description);
+ }
+
+ /**
+ * Constructs a {@link Correspondence} that compares elements by transforming the actual elements
+ * using the given function and testing for equality with the expected elements.
+ *
+ * <p>If the actual element is null, it will correspond to a null expected element. This is
+ * different to {@link Correspondence#transforming(Function, Function, String)} which would invoke
+ * the function with a {@code null} argument, requiring the function being able to handle {@code
+ * null}.
+ *
+ * <p>If the expected element is null, it will correspond to a new null expected element. This is
+ * different to {@link Correspondence#transforming(Function, Function, String)} which would invoke
+ * the function with a {@code null} argument, requiring the function being able to handle {@code
+ * null}.
+ *
+ * @param actualTransform a {@link Function} taking an actual value and returning a new value
+ * which will be compared with an expected value to determine whether they correspond
+ * @param expectedTransform a {@link Function} taking an expected value and returning a new value
+ * which will be compared with a transformed actual value
+ * @param description should fill the gap in a failure message of the form {@code "not true that
+ * <some actual element> is an element that <description> <some expected element>"}, e.g.
+ * {@code "has an ID of"}
+ */
+ public static <A, E> Correspondence<A, E> transforming(
+ Function<A, ? extends E> actualTransform,
+ Function<E, ?> expectedTransform,
+ String description) {
+ return Correspondence.transforming(
+ actualValue -> Optional.ofNullable(actualValue).map(actualTransform).orElse(null),
+ expectedValue -> Optional.ofNullable(expectedValue).map(expectedTransform).orElse(null),
+ description);
+ }
+
+ /**
+ * Private constructor to prevent instantiation of this class.
+ *
+ * <p>This class contains only static method and hence never needs to be instantiated.
+ */
+ private NullAwareCorrespondence() {}
+}
diff --git a/java/com/google/gerrit/truth/OptionalSubject.java b/java/com/google/gerrit/truth/OptionalSubject.java
index b5fc5d0472..2023765d17 100644
--- a/java/com/google/gerrit/truth/OptionalSubject.java
+++ b/java/com/google/gerrit/truth/OptionalSubject.java
@@ -18,39 +18,24 @@ import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.CustomSubjectBuilder;
-import com.google.common.truth.DefaultSubject;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StandardSubjectBuilder;
import com.google.common.truth.Subject;
import java.util.Optional;
import java.util.function.BiFunction;
-import java.util.function.Function;
-public class OptionalSubject<S extends Subject<S, ? super T>, T>
- extends Subject<OptionalSubject<S, T>, Optional<T>> {
+public class OptionalSubject<S extends Subject, T> extends Subject {
+ private final Optional<T> optional;
private final BiFunction<StandardSubjectBuilder, ? super T, ? extends S> valueSubjectCreator;
- // TODO(aliceks): Remove when all relevant usages are adapted to new check()/factory approach.
- public static <S extends Subject<S, T>, T> OptionalSubject<S, T> assertThat(
- Optional<T> optional, Function<? super T, ? extends S> elementAssertThatFunction) {
- Subject.Factory<S, T> valueSubjectFactory =
- (metadata, value) -> elementAssertThatFunction.apply(value);
- return assertThat(optional, valueSubjectFactory);
- }
-
- public static <S extends Subject<S, T>, T> OptionalSubject<S, T> assertThat(
- Optional<T> optional, Subject.Factory<S, T> valueSubjectFactory) {
+ public static <S extends Subject, T> OptionalSubject<S, T> assertThat(
+ Optional<T> optional, Subject.Factory<? extends S, ? super T> valueSubjectFactory) {
return assertAbout(optionals()).thatCustom(optional, valueSubjectFactory);
}
- public static OptionalSubject<DefaultSubject, ?> assertThat(Optional<?> optional) {
- // Unfortunately, we need to cast to DefaultSubject as StandardSubjectBuilder#that
- // only returns Subject<DefaultSubject, Object>. There shouldn't be a way
- // for that method not to return a DefaultSubject because the generic type
- // definitions of a Subject are quite strict.
- return assertAbout(optionals())
- .that(optional, (builder, value) -> (DefaultSubject) builder.that(value));
+ public static OptionalSubject<Subject, ?> assertThat(Optional<?> optional) {
+ return assertAbout(optionals()).that(optional);
}
public static CustomSubjectBuilder.Factory<OptionalSubjectBuilder> optionals() {
@@ -62,12 +47,12 @@ public class OptionalSubject<S extends Subject<S, ? super T>, T>
Optional<T> optional,
BiFunction<StandardSubjectBuilder, ? super T, ? extends S> valueSubjectCreator) {
super(failureMetadata, optional);
+ this.optional = optional;
this.valueSubjectCreator = valueSubjectCreator;
}
public void isPresent() {
isNotNull();
- Optional<T> optional = actual();
if (!optional.isPresent()) {
failWithoutActual(fact("expected to have", "value"));
}
@@ -75,7 +60,6 @@ public class OptionalSubject<S extends Subject<S, ? super T>, T>
public void isAbsent() {
isNotNull();
- Optional<T> optional = actual();
if (optional.isPresent()) {
failWithoutActual(fact("expected not to have", "value"));
}
@@ -88,7 +72,6 @@ public class OptionalSubject<S extends Subject<S, ? super T>, T>
public S value() {
isNotNull();
isPresent();
- Optional<T> optional = actual();
return valueSubjectCreator.apply(check("value()"), optional.get());
}
@@ -98,16 +81,16 @@ public class OptionalSubject<S extends Subject<S, ? super T>, T>
super(failureMetadata);
}
- public <S extends Subject<S, T>, T> OptionalSubject<S, T> thatCustom(
- Optional<T> optional, Subject.Factory<S, T> valueSubjectFactory) {
+ public <S extends Subject, T> OptionalSubject<S, T> thatCustom(
+ Optional<T> optional, Subject.Factory<? extends S, ? super T> valueSubjectFactory) {
return that(optional, (builder, value) -> builder.about(valueSubjectFactory).that(value));
}
- public OptionalSubject<DefaultSubject, ?> that(Optional<?> optional) {
- return that(optional, (builder, value) -> (DefaultSubject) builder.that(value));
+ public OptionalSubject<Subject, ?> that(Optional<?> optional) {
+ return that(optional, StandardSubjectBuilder::that);
}
- public <S extends Subject<S, ? super T>, T> OptionalSubject<S, T> that(
+ public <S extends Subject, T> OptionalSubject<S, T> that(
Optional<T> optional,
BiFunction<StandardSubjectBuilder, ? super T, ? extends S> valueSubjectCreator) {
return new OptionalSubject<>(metadata(), optional, valueSubjectCreator);
diff --git a/java/com/google/gerrit/util/cli/CmdLineParser.java b/java/com/google/gerrit/util/cli/CmdLineParser.java
index 2917257450..e002eeb114 100644
--- a/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -35,13 +35,14 @@
package com.google.gerrit.util.cli;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.util.cli.Localizable.localizable;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
-import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -313,23 +314,13 @@ public class CmdLineParser {
parser.parseArgument(tmp.toArray(new String[tmp.size()]));
}
- public void parseOptionMap(Map<String, String[]> parameters) throws CmdLineException {
- ListMultimap<String, String> map = MultimapBuilder.hashKeys().arrayListValues().build();
- for (Map.Entry<String, String[]> ent : parameters.entrySet()) {
- for (String val : ent.getValue()) {
- map.put(ent.getKey(), val);
- }
- }
- parseOptionMap(map);
- }
-
public void parseOptionMap(ListMultimap<String, String> params) throws CmdLineException {
logger.atFinest().log("Command-line parameters: %s", params.keySet());
List<String> tmp = Lists.newArrayListWithCapacity(2 * params.size());
for (String key : params.keySet()) {
String name = makeOption(key);
- if (isBoolean(name)) {
+ if (isBooleanOption(name)) {
boolean on = false;
for (String value : params.get(key)) {
on = toBoolean(key, value);
@@ -347,10 +338,6 @@ public class CmdLineParser {
parser.parseArgument(tmp.toArray(new String[tmp.size()]));
}
- public boolean isBoolean(String name) {
- return findHandler(makeOption(name)) instanceof BooleanOptionHandler;
- }
-
public void parseWithPrefix(String prefix, Object bean) {
parser.parseWithPrefix(prefix, bean);
}
@@ -359,6 +346,10 @@ public class CmdLineParser {
parser.addOptionsWithMetRequirements();
}
+ private boolean isBooleanOption(String name) {
+ return findHandler(makeOption(name)) instanceof BooleanOptionHandler;
+ }
+
private String makeOption(String name) {
if (!name.startsWith("-")) {
if (name.length() == 1) {
@@ -422,7 +413,8 @@ public class CmdLineParser {
private static Option newPrefixedOption(String prefix, Option o) {
requireNonNull(prefix);
checkArgument(o.name().startsWith("-"), "Option name must start with '-': %s", o);
- String[] aliases = Arrays.stream(o.aliases()).map(prefix::concat).toArray(String[]::new);
+ ImmutableList<String> aliases =
+ Arrays.stream(o.aliases()).map(prefix::concat).collect(toImmutableList());
return OptionUtil.newOption(
prefix + o.name(),
aliases,
@@ -432,8 +424,8 @@ public class CmdLineParser {
false,
o.hidden(),
o.handler(),
- o.depends(),
- new String[0]);
+ ImmutableList.copyOf(o.depends()),
+ ImmutableList.of());
}
public class MyParser extends org.kohsuke.args4j.CmdLineParser {
@@ -626,15 +618,15 @@ public class CmdLineParser {
private Option newHelpOption() {
return OptionUtil.newOption(
"--help",
- new String[] {"-h"},
+ ImmutableList.of("-h"),
"display this help text",
"",
false,
false,
false,
BooleanOptionHandler.class,
- new String[0],
- new String[0]);
+ ImmutableList.of(),
+ ImmutableList.of());
}
private boolean isHandlerSpecified(OptionDef option) {
diff --git a/java/com/google/gerrit/util/cli/OptionUtil.java b/java/com/google/gerrit/util/cli/OptionUtil.java
index 1125a0d367..68cd7170cd 100644
--- a/java/com/google/gerrit/util/cli/OptionUtil.java
+++ b/java/com/google/gerrit/util/cli/OptionUtil.java
@@ -15,6 +15,7 @@
package com.google.gerrit.util.cli;
import com.google.auto.value.AutoAnnotation;
+import com.google.common.collect.ImmutableList;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.OptionHandler;
@@ -24,15 +25,15 @@ public class OptionUtil {
@SuppressWarnings("rawtypes")
public static Option newOption(
String name,
- String[] aliases,
+ ImmutableList<String> aliases,
String usage,
String metaVar,
boolean required,
boolean help,
boolean hidden,
Class<? extends OptionHandler> handler,
- String[] depends,
- String[] forbids) {
+ ImmutableList<String> depends,
+ ImmutableList<String> forbids) {
return new AutoAnnotation_OptionUtil_newOption(
name, aliases, usage, metaVar, required, help, hidden, handler, depends, forbids);
}
diff --git a/java/com/google/gwtorm/BUILD b/java/com/google/gwtorm/BUILD
deleted file mode 100644
index baf7a8cb76..0000000000
--- a/java/com/google/gwtorm/BUILD
+++ /dev/null
@@ -1,7 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-
-java_library(
- name = "gwtorm",
- srcs = glob(["**/*.java"]),
- visibility = ["//visibility:public"],
-)
diff --git a/java/com/google/gwtorm/client/CompoundKey.java b/java/com/google/gwtorm/client/CompoundKey.java
deleted file mode 100644
index 1c66d186c3..0000000000
--- a/java/com/google/gwtorm/client/CompoundKey.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2008 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtorm.client;
-
-import java.io.Serializable;
-
-/**
- * Abstract key type composed of other keys.
- *
- * <p>Applications should subclass this type to create their own entity-specific key classes.
- *
- * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
- */
-@SuppressWarnings("serial")
-public abstract class CompoundKey<P extends Key<?>> implements Key<P>, Serializable {
- /** @return the member key components, minus the parent key. */
- public abstract Key<?>[] members();
-
- /** @return the parent key instance; null if this is a root level key. */
- @Override
- public P getParentKey() {
- return null;
- }
-
- @Override
- public int hashCode() {
- int hc = 0;
- if (getParentKey() != null) {
- hc = getParentKey().hashCode();
- }
- for (final Key<?> k : members()) {
- hc *= 31;
- hc += k.hashCode();
- }
- return hc;
- }
-
- @Override
- public boolean equals(final Object b) {
- if (b == null || b.getClass() != getClass()) {
- return false;
- }
-
- final CompoundKey<P> q = cast(b);
- if (getParentKey() != null && !getParentKey().equals(q.getParentKey())) {
- return false;
- }
-
- final Key<?>[] aMembers = members();
- final Key<?>[] bMembers = q.members();
- if (aMembers.length != bMembers.length) {
- return false;
- }
- for (int i = 0; i < aMembers.length; i++) {
- if (!aMembers[i].equals(bMembers[i])) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public String toString() {
- final StringBuffer r = new StringBuffer();
- boolean first = true;
- if (getParentKey() != null) {
- r.append(KeyUtil.encode(getParentKey().toString()));
- first = false;
- }
- for (final Key<?> k : members()) {
- if (!first) {
- r.append(',');
- }
- r.append(KeyUtil.encode(k.toString()));
- first = false;
- }
- return r.toString();
- }
-
- @Override
- public void fromString(final String in) {
- final String[] parts = in.split(",");
- int p = 0;
- if (getParentKey() != null) {
- getParentKey().fromString(KeyUtil.decode(parts[p++]));
- }
- for (final Key<?> k : members()) {
- k.fromString(KeyUtil.decode(parts[p++]));
- }
- }
-
- @SuppressWarnings("unchecked")
- private static <A extends Key<?>> CompoundKey<A> cast(final Object b) {
- return (CompoundKey<A>) b;
- }
-}
diff --git a/java/com/google/gwtorm/client/IntKey.java b/java/com/google/gwtorm/client/IntKey.java
deleted file mode 100644
index 08c90e08f8..0000000000
--- a/java/com/google/gwtorm/client/IntKey.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2008 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtorm.client;
-
-import java.io.Serializable;
-
-/**
- * Abstract key type using a single integer value.
- *
- * <p>Applications should subclass this type to create their own entity-specific key classes.
- *
- * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
- */
-@SuppressWarnings("serial")
-public abstract class IntKey<P extends Key<?>> implements Key<P>, Serializable {
- /** @return id of the entity instance. */
- public abstract int get();
-
- /** @param newValue the new value of this key. */
- protected abstract void set(int newValue);
-
- /** @return the parent key instance; null if this is a root level key. */
- @Override
- public P getParentKey() {
- return null;
- }
-
- @Override
- public int hashCode() {
- int hc = get();
- if (getParentKey() != null) {
- hc *= 31;
- hc += getParentKey().hashCode();
- }
- return hc;
- }
-
- @Override
- public boolean equals(final Object b) {
- if (b == null || b.getClass() != getClass()) {
- return false;
- }
-
- final IntKey<P> q = cast(b);
- return get() == q.get() && KeyUtil.eq(getParentKey(), q.getParentKey());
- }
-
- @Override
- public String toString() {
- final StringBuffer r = new StringBuffer();
- if (getParentKey() != null) {
- r.append(getParentKey().toString());
- r.append(',');
- }
- r.append(get());
- return r.toString();
- }
-
- @Override
- public void fromString(final String in) {
- set(Integer.parseInt(KeyUtil.parseFromString(getParentKey(), in)));
- }
-
- @SuppressWarnings("unchecked")
- private static <A extends Key<?>> IntKey<A> cast(final Object b) {
- return (IntKey<A>) b;
- }
-}
diff --git a/java/com/google/gwtorm/client/Key.java b/java/com/google/gwtorm/client/Key.java
deleted file mode 100644
index 69a224864e..0000000000
--- a/java/com/google/gwtorm/client/Key.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2008 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtorm.client;
-
-/**
- * Generic type for an entity key.
- *
- * <p>Although not required, entities should make their primary key type implement this interface,
- * permitting traversal up through the containment hierarchy of the entity keys.
- *
- * @param <P> type of the parent key. If no parent, use {@link Key} itself.
- */
-public interface Key<P extends Key<?>> {
- /**
- * Get the parent key instance.
- *
- * @return the parent key; null if this entity key is a root-level key.
- */
- public P getParentKey();
-
- @Override
- public int hashCode();
-
- @Override
- public boolean equals(Object o);
-
- /** @return the key, encoded in a string format . */
- @Override
- public String toString();
-
- /** Reset this key instance to represent the data in the supplied string. */
- public void fromString(String in);
-}
diff --git a/java/com/google/gwtorm/client/KeyUtil.java b/java/com/google/gwtorm/client/KeyUtil.java
deleted file mode 100644
index e236d370cb..0000000000
--- a/java/com/google/gwtorm/client/KeyUtil.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2008 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtorm.client;
-
-/** Common utility functions for {@link Key} implementors. */
-public class KeyUtil {
- private static Encoder ENCODER_IMPL = new StandardKeyEncoder();
-
- /**
- * Determine if two keys are equal, supporting null references.
- *
- * @param <T> type of the key entity.
- * @param a first key to test; may be null.
- * @param b second key to test; may be null.
- * @return true if both <code>a</code> and <code>b</code> are null, or if both are not-null and
- * <code>a.equals(b)</code> is true. Otherwise false.
- */
- public static <T extends Key<?>> boolean eq(final T a, final T b) {
- if (a == b) {
- return true;
- }
- if (a == null || b == null) {
- return false;
- }
- return a.equals(b);
- }
-
- /**
- * Encode a string to be safe for use within a URL like string.
- *
- * <p>The returned encoded string has URL component characters escaped with hex escapes (e.g. ' '
- * is '+' and '%' is '%25'). The special character '/' is left literal. The comma character (',')
- * is always encoded, permitting multiple encoded string values to be joined together safely.
- *
- * @param e the string to encode, must not be null.
- * @return the encoded string.
- */
- public static String encode(final String e) {
- return ENCODER_IMPL.encode(e);
- }
-
- /**
- * Decode a string previously encoded by {@link #encode(String)}.
- *
- * @param e the string to decode, must not be null.
- * @return the decoded string.
- */
- public static String decode(final String e) {
- return ENCODER_IMPL.decode(e);
- }
-
- /**
- * Split a string along the last comma and parse into the parent.
- *
- * @param parent parent key; <code>parent.fromString(in[0..comma])</code>.
- * @param in the input string.
- * @return text (if any) after the last comma in the input.
- */
- public static String parseFromString(final Key<?> parent, final String in) {
- final int comma = in.lastIndexOf(',');
- if (comma < 0 && parent == null) {
- return decode(in);
- }
- if (comma < 0 && parent != null) {
- throw new IllegalArgumentException("Not enough components: " + in);
- }
- assert (parent != null);
- parent.fromString(in.substring(0, comma));
- return decode(in.substring(comma + 1));
- }
-
- public abstract static class Encoder {
- public abstract String encode(String e);
-
- public abstract String decode(String e);
- }
-
- private KeyUtil() {}
-}
diff --git a/java/com/google/gwtorm/client/StringKey.java b/java/com/google/gwtorm/client/StringKey.java
deleted file mode 100644
index e56661ff1c..0000000000
--- a/java/com/google/gwtorm/client/StringKey.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2008 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtorm.client;
-
-import java.io.Serializable;
-
-/**
- * Abstract key type using a single string value.
- *
- * <p>Applications should subclass this type to create their own entity-specific key classes.
- *
- * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
- */
-@SuppressWarnings("serial")
-public abstract class StringKey<P extends Key<?>>
- implements Key<P>, Serializable, Comparable<StringKey<?>> {
- /** @return name of the entity instance. */
- public abstract String get();
-
- /** @param newValue the new value of this key. */
- protected abstract void set(String newValue);
-
- /** @return the parent key instance; null if this is a root level key. */
- @Override
- public P getParentKey() {
- return null;
- }
-
- @Override
- public int hashCode() {
- int hc = get() != null ? get().hashCode() : 0;
- if (getParentKey() != null) {
- hc *= 31;
- hc += getParentKey().hashCode();
- }
- return hc;
- }
-
- @Override
- public boolean equals(final Object b) {
- if (b == null || get() == null || b.getClass() != getClass()) {
- return false;
- }
-
- final StringKey<P> q = cast(b);
- return get().equals(q.get()) && KeyUtil.eq(getParentKey(), q.getParentKey());
- }
-
- @Override
- public int compareTo(final StringKey<?> other) {
- return get().compareTo(other.get());
- }
-
- @Override
- public String toString() {
- final StringBuffer r = new StringBuffer();
- if (getParentKey() != null) {
- r.append(getParentKey().toString());
- r.append(',');
- }
- r.append(KeyUtil.encode(get()));
- return r.toString();
- }
-
- @Override
- public void fromString(final String in) {
- set(KeyUtil.parseFromString(getParentKey(), in));
- }
-
- @SuppressWarnings("unchecked")
- private static <A extends Key<?>> StringKey<A> cast(final Object b) {
- return (StringKey<A>) b;
- }
-}
diff --git a/java/gerrit/AbstractCommitUserIdentityPredicate.java b/java/gerrit/AbstractCommitUserIdentityPredicate.java
index 1bfc95c46c..bd8cf1af44 100644
--- a/java/gerrit/AbstractCommitUserIdentityPredicate.java
+++ b/java/gerrit/AbstractCommitUserIdentityPredicate.java
@@ -15,7 +15,7 @@
package gerrit;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.Emails;
import com.google.gerrit.server.rules.PrologEnvironment;
import com.googlecode.prolog_cafe.exceptions.PrologException;
diff --git a/java/gerrit/BUILD b/java/gerrit/BUILD
index d7e23069ba..7dbf751494 100644
--- a/java/gerrit/BUILD
+++ b/java/gerrit/BUILD
@@ -6,12 +6,11 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//lib:jgit",
"//lib/flogger:api",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/prolog:runtime",
"@guava//jar",
],
diff --git a/java/gerrit/PRED__load_commit_labels_1.java b/java/gerrit/PRED__load_commit_labels_1.java
index 693c89e4d6..90a2cbf1d4 100644
--- a/java/gerrit/PRED__load_commit_labels_1.java
+++ b/java/gerrit/PRED__load_commit_labels_1.java
@@ -4,7 +4,7 @@ package gerrit;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
@@ -38,16 +38,15 @@ class PRED__load_commit_labels_1 extends Predicate.P1 {
LabelTypes types = cd.getLabelTypes();
for (PatchSetApproval a : cd.currentApprovals()) {
- LabelType t = types.byLabel(a.getLabelId());
+ LabelType t = types.byLabel(a.labelId());
if (t == null) {
continue;
}
StructureTerm labelTerm =
- new StructureTerm(
- sym_label, SymbolTerm.intern(t.getName()), new IntegerTerm(a.getValue()));
+ new StructureTerm(sym_label, SymbolTerm.intern(t.getName()), new IntegerTerm(a.value()));
- StructureTerm userTerm = new StructureTerm(sym_user, new IntegerTerm(a.getAccountId().get()));
+ StructureTerm userTerm = new StructureTerm(sym_user, new IntegerTerm(a.accountId().get()));
listHead = new ListTerm(new StructureTerm(sym_commit_label, labelTerm, userTerm), listHead);
}
diff --git a/java/gerrit/PRED_change_branch_1.java b/java/gerrit/PRED_change_branch_1.java
index 0a7bb74184..4501169c88 100644
--- a/java/gerrit/PRED_change_branch_1.java
+++ b/java/gerrit/PRED_change_branch_1.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.Operation;
@@ -34,9 +34,9 @@ public class PRED_change_branch_1 extends Predicate.P1 {
engine.setB0();
Term a1 = arg1.dereference();
- Branch.NameKey name = StoredValues.getChange(engine).getDest();
+ BranchNameKey name = StoredValues.getChange(engine).getDest();
- if (!a1.unify(SymbolTerm.create(name.get()), engine.trail)) {
+ if (!a1.unify(SymbolTerm.create(name.branch()), engine.trail)) {
return engine.fail();
}
return cont;
diff --git a/java/gerrit/PRED_change_owner_1.java b/java/gerrit/PRED_change_owner_1.java
index 937b761979..d42c0e1e38 100644
--- a/java/gerrit/PRED_change_owner_1.java
+++ b/java/gerrit/PRED_change_owner_1.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
diff --git a/java/gerrit/PRED_change_project_1.java b/java/gerrit/PRED_change_project_1.java
index 28e637a40e..a973e1c93a 100644
--- a/java/gerrit/PRED_change_project_1.java
+++ b/java/gerrit/PRED_change_project_1.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.Operation;
diff --git a/java/gerrit/PRED_change_topic_1.java b/java/gerrit/PRED_change_topic_1.java
index 564878fe02..11d737ab54 100644
--- a/java/gerrit/PRED_change_topic_1.java
+++ b/java/gerrit/PRED_change_topic_1.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.Operation;
diff --git a/java/gerrit/PRED_commit_delta_4.java b/java/gerrit/PRED_commit_delta_4.java
index d2634ea81a..6e971fcc71 100644
--- a/java/gerrit/PRED_commit_delta_4.java
+++ b/java/gerrit/PRED_commit_delta_4.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.rules.StoredValues;
diff --git a/java/gerrit/PRED_commit_edits_2.java b/java/gerrit/PRED_commit_edits_2.java
index 6ca533874d..12e7086fab 100644
--- a/java/gerrit/PRED_commit_edits_2.java
+++ b/java/gerrit/PRED_commit_edits_2.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.Text;
diff --git a/java/gerrit/PRED_commit_stats_3.java b/java/gerrit/PRED_commit_stats_3.java
index c1666d849a..286bc2c42e 100644
--- a/java/gerrit/PRED_commit_stats_3.java
+++ b/java/gerrit/PRED_commit_stats_3.java
@@ -14,7 +14,7 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.rules.StoredValues;
diff --git a/java/gerrit/PRED_uploader_1.java b/java/gerrit/PRED_uploader_1.java
index 029b84af09..681d86cace 100644
--- a/java/gerrit/PRED_uploader_1.java
+++ b/java/gerrit/PRED_uploader_1.java
@@ -15,8 +15,8 @@
package gerrit;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -50,7 +50,7 @@ public class PRED_uploader_1 extends Predicate.P1 {
return engine.fail();
}
- Account.Id uploaderId = patchSet.getUploader();
+ Account.Id uploaderId = patchSet.uploader();
if (!a1.unify(new StructureTerm(user, new IntegerTerm(uploaderId.get())), engine.trail)) {
return engine.fail();
diff --git a/javatests/com/google/gerrit/acceptance/BUILD b/javatests/com/google/gerrit/acceptance/BUILD
index 54b3626b50..75c90f249e 100644
--- a/javatests/com/google/gerrit/acceptance/BUILD
+++ b/javatests/com/google/gerrit/acceptance/BUILD
@@ -7,8 +7,9 @@ junit_tests(
"//java/com/google/gerrit/acceptance:lib",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//java/com/google/gerrit/truth",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/acceptance/MergeableFileBasedConfigTest.java b/javatests/com/google/gerrit/acceptance/MergeableFileBasedConfigTest.java
index 69fdc7e6ba..3d17de06c9 100644
--- a/javatests/com/google/gerrit/acceptance/MergeableFileBasedConfigTest.java
+++ b/javatests/com/google/gerrit/acceptance/MergeableFileBasedConfigTest.java
@@ -15,17 +15,17 @@
package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.File;
import java.nio.file.Files;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
-public class MergeableFileBasedConfigTest extends GerritBaseTests {
+public class MergeableFileBasedConfigTest {
@Test
public void mergeNull() throws Exception {
MergeableFileBasedConfig cfg = newConfig();
@@ -112,7 +112,7 @@ public class MergeableFileBasedConfigTest extends GerritBaseTests {
}
private void assertConfig(MergeableFileBasedConfig cfg, String expected) throws Exception {
- assertThat(cfg.toText()).isEqualTo(expected);
+ assertThat(cfg).text().isEqualTo(expected);
cfg.save();
assertThat(new String(Files.readAllBytes(cfg.getFile().toPath()), UTF_8)).isEqualTo(expected);
}
diff --git a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
index 53d8ef8799..1a09aa17f1 100644
--- a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
+++ b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
@@ -15,13 +15,17 @@
package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupIncludeCache;
@@ -31,12 +35,10 @@ import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.TestTimeUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
-import org.easymock.EasyMock;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -50,7 +52,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class ProjectResetterTest extends GerritBaseTests {
+public class ProjectResetterTest {
private InMemoryRepositoryManager repoManager;
private Project.NameKey project;
private Repository repo;
@@ -58,7 +60,7 @@ public class ProjectResetterTest extends GerritBaseTests {
@Before
public void setUp() throws Exception {
repoManager = new InMemoryRepositoryManager();
- project = new Project.NameKey("foo");
+ project = Project.nameKey("foo");
repo = repoManager.createRepository(project);
}
@@ -135,7 +137,7 @@ public class ProjectResetterTest extends GerritBaseTests {
@Test
public void onlyResetMatchingRefsMultipleProjects() throws Exception {
- Project.NameKey project2 = new Project.NameKey("bar");
+ Project.NameKey project2 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
Ref matchingRefProject1 = createRef("refs/foo/test");
@@ -170,7 +172,7 @@ public class ProjectResetterTest extends GerritBaseTests {
@Test
public void onlyDeleteNewlyCreatedMatchingRefsMultipleProjects() throws Exception {
- Project.NameKey project2 = new Project.NameKey("bar");
+ Project.NameKey project2 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
Ref matchingRefProject1;
@@ -216,14 +218,11 @@ public class ProjectResetterTest extends GerritBaseTests {
@Test
public void projectEvictionIfRefsMetaConfigIsReset() throws Exception {
- Project.NameKey project2 = new Project.NameKey("bar");
+ Project.NameKey project2 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
Ref metaConfig = createRef(repo2, RefNames.REFS_CONFIG);
- ProjectCache projectCache = EasyMock.createNiceMock(ProjectCache.class);
- projectCache.evict(project2);
- EasyMock.expectLastCall();
- EasyMock.replay(projectCache);
+ ProjectCache projectCache = mock(ProjectCache.class);
Ref nonMetaConfig = createRef("refs/heads/master");
@@ -234,18 +233,15 @@ public class ProjectResetterTest extends GerritBaseTests {
updateRef(repo2, metaConfig);
}
- EasyMock.verify(projectCache);
+ verify(projectCache, only()).evict(project2);
}
@Test
public void projectEvictionIfRefsMetaConfigIsDeleted() throws Exception {
- Project.NameKey project2 = new Project.NameKey("bar");
+ Project.NameKey project2 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
- ProjectCache projectCache = EasyMock.createNiceMock(ProjectCache.class);
- projectCache.evict(project2);
- EasyMock.expectLastCall();
- EasyMock.replay(projectCache);
+ ProjectCache projectCache = mock(ProjectCache.class);
try (ProjectResetter resetProject =
builder(null, null, null, null, null, null, projectCache)
@@ -254,28 +250,21 @@ public class ProjectResetterTest extends GerritBaseTests {
createRef(repo2, RefNames.REFS_CONFIG);
}
- EasyMock.verify(projectCache);
+ verify(projectCache, only()).evict(project2);
}
@Test
public void accountEvictionIfUserBranchIsReset() throws Exception {
- Account.Id accountId = new Account.Id(1);
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ Account.Id accountId = Account.id(1);
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
Ref userBranch = createRef(allUsersRepo, RefNames.refsUsers(accountId));
- AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class);
- accountCache.evict(accountId);
- EasyMock.expectLastCall();
- EasyMock.replay(accountCache);
-
- AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class);
- accountIndexer.index(accountId);
- EasyMock.expectLastCall();
- EasyMock.replay(accountIndexer);
+ AccountCache accountCache = mock(AccountCache.class);
+ AccountIndexer accountIndexer = mock(AccountIndexer.class);
// Non-user branch because it's not in All-Users.
- Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(2)));
+ Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(2)));
try (ProjectResetter resetProject =
builder(null, accountCache, accountIndexer, null, null, null, null)
@@ -284,63 +273,47 @@ public class ProjectResetterTest extends GerritBaseTests {
updateRef(allUsersRepo, userBranch);
}
- EasyMock.verify(accountCache, accountIndexer);
+ verify(accountCache, only()).evict(accountId);
+ verify(accountIndexer, only()).index(accountId);
}
@Test
public void accountEvictionIfUserBranchIsDeleted() throws Exception {
- Account.Id accountId = new Account.Id(1);
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ Account.Id accountId = Account.id(1);
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
- AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class);
- accountCache.evict(accountId);
- EasyMock.expectLastCall();
- EasyMock.replay(accountCache);
-
- AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class);
- accountIndexer.index(accountId);
- EasyMock.expectLastCall();
- EasyMock.replay(accountIndexer);
+ AccountCache accountCache = mock(AccountCache.class);
+ AccountIndexer accountIndexer = mock(AccountIndexer.class);
try (ProjectResetter resetProject =
builder(null, accountCache, accountIndexer, null, null, null, null)
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
// Non-user branch because it's not in All-Users.
- createRef(RefNames.refsUsers(new Account.Id(2)));
+ createRef(RefNames.refsUsers(Account.id(2)));
createRef(allUsersRepo, RefNames.refsUsers(accountId));
}
- EasyMock.verify(accountCache, accountIndexer);
+ verify(accountCache, only()).evict(accountId);
+ verify(accountIndexer, only()).index(accountId);
}
@Test
public void accountEvictionIfExternalIdsBranchIsReset() throws Exception {
- Account.Id accountId = new Account.Id(1);
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ Account.Id accountId = Account.id(1);
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
Ref externalIds = createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
createRef(allUsersRepo, RefNames.refsUsers(accountId));
- Account.Id accountId2 = new Account.Id(2);
-
- AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class);
- accountCache.evict(accountId);
- EasyMock.expectLastCall();
- accountCache.evict(accountId2);
- EasyMock.expectLastCall();
- EasyMock.replay(accountCache);
+ Account.Id accountId2 = Account.id(2);
- AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class);
- accountIndexer.index(accountId);
- EasyMock.expectLastCall();
- accountIndexer.index(accountId2);
- EasyMock.expectLastCall();
- EasyMock.replay(accountIndexer);
+ AccountCache accountCache = mock(AccountCache.class);
+ AccountIndexer accountIndexer = mock(AccountIndexer.class);
// Non-user branch because it's not in All-Users.
- Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3)));
+ Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(3)));
try (ProjectResetter resetProject =
builder(null, accountCache, accountIndexer, null, null, null, null)
@@ -350,34 +323,27 @@ public class ProjectResetterTest extends GerritBaseTests {
createRef(allUsersRepo, RefNames.refsUsers(accountId2));
}
- EasyMock.verify(accountCache, accountIndexer);
+ verify(accountCache).evict(accountId);
+ verify(accountCache).evict(accountId2);
+ verify(accountIndexer).index(accountId);
+ verify(accountIndexer).index(accountId2);
+ verifyNoMoreInteractions(accountCache, accountIndexer);
}
@Test
public void accountEvictionIfExternalIdsBranchIsDeleted() throws Exception {
- Account.Id accountId = new Account.Id(1);
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ Account.Id accountId = Account.id(1);
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
createRef(allUsersRepo, RefNames.refsUsers(accountId));
- Account.Id accountId2 = new Account.Id(2);
+ Account.Id accountId2 = Account.id(2);
- AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class);
- accountCache.evict(accountId);
- EasyMock.expectLastCall();
- accountCache.evict(accountId2);
- EasyMock.expectLastCall();
- EasyMock.replay(accountCache);
-
- AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class);
- accountIndexer.index(accountId);
- EasyMock.expectLastCall();
- accountIndexer.index(accountId2);
- EasyMock.expectLastCall();
- EasyMock.replay(accountIndexer);
+ AccountCache accountCache = mock(AccountCache.class);
+ AccountIndexer accountIndexer = mock(AccountIndexer.class);
// Non-user branch because it's not in All-Users.
- Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3)));
+ Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(3)));
try (ProjectResetter resetProject =
builder(null, accountCache, accountIndexer, null, null, null, null)
@@ -387,19 +353,20 @@ public class ProjectResetterTest extends GerritBaseTests {
createRef(allUsersRepo, RefNames.refsUsers(accountId2));
}
- EasyMock.verify(accountCache, accountIndexer);
+ verify(accountCache).evict(accountId);
+ verify(accountCache).evict(accountId2);
+ verify(accountIndexer).index(accountId);
+ verify(accountIndexer).index(accountId2);
+ verifyNoMoreInteractions(accountCache, accountIndexer);
}
@Test
public void accountEvictionFromAccountCreatorIfUserBranchIsDeleted() throws Exception {
- Account.Id accountId = new Account.Id(1);
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ Account.Id accountId = Account.id(1);
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
- AccountCreator accountCreator = EasyMock.createNiceMock(AccountCreator.class);
- accountCreator.evict(ImmutableSet.of(accountId));
- EasyMock.expectLastCall();
- EasyMock.replay(accountCreator);
+ AccountCreator accountCreator = mock(AccountCreator.class);
try (ProjectResetter resetProject =
builder(accountCreator, null, null, null, null, null, null)
@@ -407,29 +374,20 @@ public class ProjectResetterTest extends GerritBaseTests {
createRef(allUsersRepo, RefNames.refsUsers(accountId));
}
- EasyMock.verify(accountCreator);
+ verify(accountCreator, only()).evict(ImmutableSet.of(accountId));
}
@Test
public void groupEviction() throws Exception {
- AccountGroup.UUID uuid1 = new AccountGroup.UUID("abcd1");
- AccountGroup.UUID uuid2 = new AccountGroup.UUID("abcd2");
- AccountGroup.UUID uuid3 = new AccountGroup.UUID("abcd3");
- Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+ AccountGroup.UUID uuid1 = AccountGroup.uuid("abcd1");
+ AccountGroup.UUID uuid2 = AccountGroup.uuid("abcd2");
+ AccountGroup.UUID uuid3 = AccountGroup.uuid("abcd3");
+ Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
- GroupCache cache = EasyMock.createNiceMock(GroupCache.class);
- GroupIndexer indexer = EasyMock.createNiceMock(GroupIndexer.class);
- GroupIncludeCache includeCache = EasyMock.createNiceMock(GroupIncludeCache.class);
- cache.evict(uuid2);
- indexer.index(uuid2);
- includeCache.evictParentGroupsOf(uuid2);
- cache.evict(uuid3);
- indexer.index(uuid3);
- includeCache.evictParentGroupsOf(uuid3);
- EasyMock.expectLastCall();
-
- EasyMock.replay(cache, indexer);
+ GroupCache cache = mock(GroupCache.class);
+ GroupIndexer indexer = mock(GroupIndexer.class);
+ GroupIncludeCache includeCache = mock(GroupIncludeCache.class);
createRef(allUsersRepo, RefNames.refsGroups(uuid1));
Ref ref2 = createRef(allUsersRepo, RefNames.refsGroups(uuid2));
@@ -440,7 +398,13 @@ public class ProjectResetterTest extends GerritBaseTests {
createRef(allUsersRepo, RefNames.refsGroups(uuid3));
}
- EasyMock.verify(cache, indexer);
+ verify(cache).evict(uuid2);
+ verify(indexer).index(uuid2);
+ verify(includeCache).evictParentGroupsOf(uuid2);
+ verify(cache).evict(uuid3);
+ verify(indexer).index(uuid3);
+ verify(includeCache).evictParentGroupsOf(uuid3);
+ verifyNoMoreInteractions(cache, indexer, includeCache);
}
private Ref createRef(String ref) throws IOException {
diff --git a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
index bf387fdf12..448629c893 100644
--- a/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
+++ b/javatests/com/google/gerrit/acceptance/TestGroupBackendTest.java
@@ -16,21 +16,19 @@ package com.google.gerrit.acceptance;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.UniversalGroupBackend;
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.inject.Inject;
import org.junit.Test;
public class TestGroupBackendTest extends AbstractDaemonTest {
- @Inject private DynamicSet<GroupBackend> groupBackends;
@Inject private UniversalGroupBackend universalGroupBackend;
+ @Inject private ExtensionRegistry extensionRegistry;
private final TestGroupBackend testGroupBackend = new TestGroupBackend();
- private final AccountGroup.UUID testUUID = new AccountGroup.UUID("testbackend:test");
+ private final AccountGroup.UUID testUUID = AccountGroup.uuid("testbackend:test");
@Test
public void handlesTestGroup() throws Exception {
@@ -39,17 +37,14 @@ public class TestGroupBackendTest extends AbstractDaemonTest {
@Test
public void universalGroupBackendHandlesTestGroup() throws Exception {
- RegistrationHandle registrationHandle = groupBackends.add("gerrit", testGroupBackend);
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(testGroupBackend)) {
assertThat(universalGroupBackend.handles(testUUID)).isTrue();
- } finally {
- registrationHandle.remove();
}
}
@Test
public void doesNotHandleLDAP() throws Exception {
- assertThat(testGroupBackend.handles(new AccountGroup.UUID("ldap:1234"))).isFalse();
+ assertThat(testGroupBackend.handles(AccountGroup.uuid("ldap:1234"))).isFalse();
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/annotation/BUILD b/javatests/com/google/gerrit/acceptance/annotation/BUILD
index 5476bb6baf..8dd99c9b77 100644
--- a/javatests/com/google/gerrit/acceptance/annotation/BUILD
+++ b/javatests/com/google/gerrit/acceptance/annotation/BUILD
@@ -4,4 +4,5 @@ acceptance_tests(
srcs = glob(["*.java"]),
group = "annotation",
labels = ["annotation"],
+ deps = ["//java/com/google/gerrit/server/util/time"],
)
diff --git a/javatests/com/google/gerrit/acceptance/annotation/UseClockStepTest.java b/javatests/com/google/gerrit/acceptance/annotation/UseClockStepTest.java
new file mode 100644
index 0000000000..ecfe3f58ba
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/annotation/UseClockStepTest.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.annotation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.server.util.time.TimeUtil;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+public class UseClockStepTest extends AbstractDaemonTest {
+ @Test
+ @UseClockStep
+ public void useClockStepWithDefaults() {
+ long firstTimestamp = TimeUtil.nowMs();
+ long secondTimestamp = TimeUtil.nowMs();
+ assertThat(secondTimestamp - firstTimestamp).isEqualTo(1000);
+ }
+
+ @Test
+ @UseClockStep(clockStepUnit = TimeUnit.MINUTES)
+ public void useClockStepWithTimeUnit() {
+ long firstTimestamp = TimeUtil.nowMs();
+ long secondTimestamp = TimeUtil.nowMs();
+ assertThat(secondTimestamp - firstTimestamp).isEqualTo(60 * 1000);
+ }
+
+ @Test
+ @UseClockStep(clockStep = 5)
+ public void useClockStepWithClockStep() {
+ long firstTimestamp = TimeUtil.nowMs();
+ long secondTimestamp = TimeUtil.nowMs();
+ assertThat(secondTimestamp - firstTimestamp).isEqualTo(5 * 1000);
+ }
+
+ @Test
+ @UseClockStep(startAtEpoch = true)
+ public void useClockStepWithStartAtEpoch() {
+ assertThat(TimeUtil.nowTs()).isEqualTo(Timestamp.from(Instant.EPOCH));
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/annotation/UseSystemTimeTest.java b/javatests/com/google/gerrit/acceptance/annotation/UseSystemTimeTest.java
new file mode 100644
index 0000000000..7480200907
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/annotation/UseSystemTimeTest.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.annotation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseSystemTime;
+import com.google.gerrit.server.util.time.TimeUtil;
+import org.junit.Test;
+
+@UseClockStep
+public class UseSystemTimeTest extends AbstractDaemonTest {
+ @Test
+ @UseSystemTime
+ public void useSystemTimeAlthoughClassIsAnnotatedWithUseClockStep() {
+ long firstTimestamp = TimeUtil.nowMs();
+ long secondTimestamp = TimeUtil.nowMs();
+ assertThat(secondTimestamp - firstTimestamp).isLessThan(1000);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/annotation/UseTimezoneTest.java b/javatests/com/google/gerrit/acceptance/annotation/UseTimezoneTest.java
new file mode 100644
index 0000000000..abf5eda63d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/annotation/UseTimezoneTest.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.annotation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.UseTimezone;
+import org.junit.Test;
+
+public class UseTimezoneTest extends AbstractDaemonTest {
+ @Test
+ @UseTimezone(timezone = "US/Eastern")
+ public void usEastern() {
+ assertThat(System.getProperty("user.timezone")).isEqualTo("US/Eastern");
+ }
+
+ @Test
+ @UseTimezone(timezone = "UTC")
+ public void utc() {
+ assertThat(System.getProperty("user.timezone")).isEqualTo("UTC");
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 7dcff20820..5719c7ffd9 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -17,10 +17,14 @@ package com.google.gerrit.acceptance.api.accounts;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.Truth.assert_;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.gpg.PublicKeyStore.REFS_GPG_KEYS;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static com.google.gerrit.gpg.testing.TestKeys.allValidKeys;
@@ -33,11 +37,17 @@ import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPG
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import com.github.rholder.retry.StopStrategies;
import com.google.common.cache.LoadingCache;
@@ -48,14 +58,17 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.io.BaseEncoding;
import com.google.common.truth.Correspondence;
-import com.google.common.truth.Correspondence.BinaryPredicate;
import com.google.common.util.concurrent.AtomicLongMap;
import com.google.common.util.concurrent.Runnables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseClockStep;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
@@ -68,6 +81,12 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.AccountInput;
@@ -90,10 +109,9 @@ import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.SshKeyInfo;
-import com.google.gerrit.extensions.events.AccountIndexedListener;
+import com.google.gerrit.extensions.events.AccountActivationListener;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -106,19 +124,11 @@ import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.gpg.testing.TestKey;
import com.google.gerrit.httpd.CacheBasedWebSession;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountProperties;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.Emails;
-import com.google.gerrit.server.account.ProjectWatches;
-import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
@@ -128,17 +138,17 @@ import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.account.StalenessChecker;
import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.server.validators.AccountActivationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
@@ -147,7 +157,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -170,6 +179,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -208,8 +218,6 @@ public class AccountIT extends AbstractDaemonTest {
@Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider;
@Inject private AccountIndexer accountIndexer;
- @Inject private DynamicSet<AccountIndexedListener> accountIndexedListeners;
- @Inject private DynamicSet<GitReferenceUpdatedListener> refUpdateListeners;
@Inject private ExternalIdNotes.Factory extIdNotesFactory;
@Inject private ExternalIds externalIds;
@Inject private GitReferenceUpdated gitReferenceUpdated;
@@ -222,53 +230,21 @@ public class AccountIT extends AbstractDaemonTest {
@Inject private Sequences seq;
@Inject private StalenessChecker stalenessChecker;
@Inject private VersionedAuthorizedKeys.Accessor authorizedKeys;
+ @Inject private ExtensionRegistry extensionRegistry;
@Inject protected Emails emails;
@Inject
@Named("accounts")
- private LoadingCache<Account.Id, Optional<AccountState>> accountsCache;
+ private LoadingCache<Account.Id, AccountState> accountsCache;
@Inject private AccountOperations accountOperations;
- @Inject
- private DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners;
-
@Inject protected GroupOperations groupOperations;
- private AccountIndexedCounter accountIndexedCounter;
- private RegistrationHandle accountIndexEventCounterHandle;
- private RefUpdateCounter refUpdateCounter;
- private RegistrationHandle refUpdateCounterHandle;
private BasicCookieStore httpCookieStore;
private CloseableHttpClient httpclient;
- @Before
- public void addAccountIndexEventCounter() {
- accountIndexedCounter = new AccountIndexedCounter();
- accountIndexEventCounterHandle = accountIndexedListeners.add("gerrit", accountIndexedCounter);
- }
-
- @After
- public void removeAccountIndexEventCounter() {
- if (accountIndexEventCounterHandle != null) {
- accountIndexEventCounterHandle.remove();
- }
- }
-
- @Before
- public void addRefUpdateCounter() {
- refUpdateCounter = new RefUpdateCounter();
- refUpdateCounterHandle = refUpdateListeners.add("gerrit", refUpdateCounter);
- }
-
- @After
- public void removeRefUpdateCounter() {
- if (refUpdateCounterHandle != null) {
- refUpdateCounterHandle.remove();
- }
- }
-
@After
public void clearPublicKeyStore() throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
@@ -327,11 +303,14 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void createByAccountCreator() throws Exception {
- Account.Id accountId = createByAccountCreator(1);
- refUpdateCounter.assertRefUpdateFor(
- RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
- RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
- RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS));
+ RefUpdateCounter refUpdateCounter = new RefUpdateCounter();
+ try (Registration registration = extensionRegistry.newRegistration().add(refUpdateCounter)) {
+ Account.Id accountId = createByAccountCreator(1);
+ refUpdateCounter.assertRefUpdateFor(
+ RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
+ RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
+ RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS));
+ }
}
@Test
@@ -350,42 +329,54 @@ public class AccountIT extends AbstractDaemonTest {
}
private Account.Id createByAccountCreator(int expectedAccountReindexCalls) throws Exception {
- String name = "foo";
- TestAccount foo = accountCreator.create(name);
- AccountInfo info = gApi.accounts().id(foo.id().get()).get();
- assertThat(info.username).isEqualTo(name);
- assertThat(info.name).isEqualTo(name);
- accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls);
- assertUserBranch(foo.id(), name, null);
- return foo.id();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String name = "foo";
+ TestAccount foo = accountCreator.create(name);
+ AccountInfo info = gApi.accounts().id(foo.id().get()).get();
+ assertThat(info.username).isEqualTo(name);
+ assertThat(info.name).isEqualTo(name);
+ accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls);
+ assertUserBranch(foo.id(), name, null);
+ return foo.id();
+ }
}
@Test
public void createAnonymousCowardByAccountCreator() throws Exception {
- TestAccount anonymousCoward = accountCreator.create();
- accountIndexedCounter.assertReindexOf(anonymousCoward);
- assertUserBranchWithoutAccountConfig(anonymousCoward.id());
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount anonymousCoward = accountCreator.create();
+ accountIndexedCounter.assertReindexOf(anonymousCoward);
+ assertUserBranchWithoutAccountConfig(anonymousCoward.id());
+ }
}
@Test
public void create() throws Exception {
- AccountInput input = new AccountInput();
- input.username = "foo";
- input.name = "Foo";
- input.email = "foo@example.com";
- AccountInfo accountInfo = gApi.accounts().create(input).get();
- assertThat(accountInfo._accountId).isNotNull();
- assertThat(accountInfo.username).isEqualTo(input.username);
- assertThat(accountInfo.name).isEqualTo(input.name);
- assertThat(accountInfo.email).isEqualTo(input.email);
- assertThat(accountInfo.status).isNull();
-
- Account.Id accountId = new Account.Id(accountInfo._accountId);
- accountIndexedCounter.assertReindexOf(accountId, 1);
- assertThat(externalIds.byAccount(accountId))
- .containsExactly(
- ExternalId.createUsername(input.username, accountId, null),
- ExternalId.createEmail(accountId, input.email));
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ AccountInput input = new AccountInput();
+ input.username = "foo";
+ input.name = "Foo";
+ input.email = "foo@example.com";
+ AccountInfo accountInfo = gApi.accounts().create(input).get();
+ assertThat(accountInfo._accountId).isNotNull();
+ assertThat(accountInfo.username).isEqualTo(input.username);
+ assertThat(accountInfo.name).isEqualTo(input.name);
+ assertThat(accountInfo.email).isEqualTo(input.email);
+ assertThat(accountInfo.status).isNull();
+
+ Account.Id accountId = Account.id(accountInfo._accountId);
+ accountIndexedCounter.assertReindexOf(accountId, 1);
+ assertThat(externalIds.byAccount(accountId))
+ .containsExactly(
+ ExternalId.createUsername(input.username, accountId, null),
+ ExternalId.createEmail(accountId, input.email));
+ }
}
@Test
@@ -393,9 +384,11 @@ public class AccountIT extends AbstractDaemonTest {
AccountInput input = new AccountInput();
input.username = admin.username();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("username '" + admin.username() + "' already exists");
- gApi.accounts().create(input);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.accounts().create(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("username '" + admin.username() + "' already exists");
}
@Test
@@ -404,15 +397,15 @@ public class AccountIT extends AbstractDaemonTest {
input.username = "foo";
input.email = admin.email();
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("email '" + admin.email() + "' already exists");
- gApi.accounts().create(input);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> gApi.accounts().create(input));
+ assertThat(thrown).hasMessageThat().contains("email '" + admin.email() + "' already exists");
}
@Test
public void commitMessageOnAccountUpdates() throws Exception {
AccountsUpdate au = accountsUpdateProvider.get();
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
au.insert("Create Test Account", accountId, u -> {});
assertLastCommitMessageOfUserBranch(accountId, "Create Test Account");
@@ -431,39 +424,37 @@ public class AccountIT extends AbstractDaemonTest {
}
@Test
+ @UseClockStep
public void createAtomically() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- try {
- Account.Id accountId = new Account.Id(seq.nextAccountId());
- String fullName = "Foo";
- ExternalId extId = ExternalId.createEmail(accountId, "foo@example.com");
- AccountState accountState =
- accountsUpdateProvider
- .get()
- .insert(
- "Create Account Atomically",
- accountId,
- u -> u.setFullName(fullName).addExternalId(extId));
- assertThat(accountState.getAccount().getFullName()).isEqualTo(fullName);
-
- AccountInfo info = gApi.accounts().id(accountId.get()).get();
- assertThat(info.name).isEqualTo(fullName);
-
- List<EmailInfo> emails = gApi.accounts().id(accountId.get()).getEmails();
- assertThat(emails.stream().map(e -> e.email).collect(toSet())).containsExactly(extId.email());
-
- RevCommit commitUserBranch = getRemoteHead(allUsers, RefNames.refsUsers(accountId));
- RevCommit commitRefsMetaExternalIds = getRemoteHead(allUsers, RefNames.REFS_EXTERNAL_IDS);
- assertThat(commitUserBranch.getCommitTime())
- .isEqualTo(commitRefsMetaExternalIds.getCommitTime());
- } finally {
- TestTimeUtil.useSystemTime();
- }
+ Account.Id accountId = Account.id(seq.nextAccountId());
+ String fullName = "Foo";
+ ExternalId extId = ExternalId.createEmail(accountId, "foo@example.com");
+ AccountState accountState =
+ accountsUpdateProvider
+ .get()
+ .insert(
+ "Create Account Atomically",
+ accountId,
+ u -> u.setFullName(fullName).addExternalId(extId));
+ assertThat(accountState.account().fullName()).isEqualTo(fullName);
+
+ AccountInfo info = gApi.accounts().id(accountId.get()).get();
+ assertThat(info.name).isEqualTo(fullName);
+
+ List<EmailInfo> emails = gApi.accounts().id(accountId.get()).getEmails();
+ assertThat(emails.stream().map(e -> e.email).collect(toSet())).containsExactly(extId.email());
+
+ RevCommit commitUserBranch =
+ projectOperations.project(allUsers).getHead(RefNames.refsUsers(accountId));
+ RevCommit commitRefsMetaExternalIds =
+ projectOperations.project(allUsers).getHead(RefNames.REFS_EXTERNAL_IDS);
+ assertThat(commitUserBranch.getCommitTime())
+ .isEqualTo(commitRefsMetaExternalIds.getCommitTime());
}
@Test
public void updateNonExistingAccount() throws Exception {
- Account.Id nonExistingAccountId = new Account.Id(999999);
+ Account.Id nonExistingAccountId = Account.id(999999);
AtomicBoolean consumerCalled = new AtomicBoolean();
Optional<AccountState> accountState =
accountsUpdateProvider
@@ -485,9 +476,9 @@ public class AccountIT extends AbstractDaemonTest {
.get()
.update("Set status", anonymousCoward.id(), u -> u.setStatus(status));
assertThat(accountState).isPresent();
- Account account = accountState.get().getAccount();
- assertThat(account.getFullName()).isNull();
- assertThat(account.getStatus()).isEqualTo(status);
+ Account account = accountState.get().account();
+ assertThat(account.fullName()).isNull();
+ assertThat(account.status()).isEqualTo(status);
assertUserBranch(anonymousCoward.id(), null, status);
}
@@ -504,7 +495,7 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(ref).isNotNull();
RevCommit c = rw.parseCommit(ref.getObjectId());
long timestampDiffMs =
- Math.abs(c.getCommitTime() * 1000L - getAccount(accountId).getRegisteredOn().getTime());
+ Math.abs(c.getCommitTime() * 1000L - getAccount(accountId).registeredOn().getTime());
assertThat(timestampDiffMs).isAtMost(SECONDS.toMillis(1));
// Check the 'account.config' file.
@@ -513,10 +504,11 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(tw).isNotNull();
Config cfg = new Config();
cfg.fromText(new String(or.open(tw.getObjectId(0), OBJ_BLOB).getBytes(), UTF_8));
- assertThat(
- cfg.getString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_FULL_NAME))
+ assertThat(cfg)
+ .stringValue(AccountProperties.ACCOUNT, null, AccountProperties.KEY_FULL_NAME)
.isEqualTo(name);
- assertThat(cfg.getString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS))
+ assertThat(cfg)
+ .stringValue(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS)
.isEqualTo(status);
} else {
// No account properties were set, hence an 'account.config' file was not created.
@@ -528,76 +520,98 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void get() throws Exception {
- AccountInfo info = gApi.accounts().id("admin").get();
- assertThat(info.name).isEqualTo("Administrator");
- assertThat(info.email).isEqualTo("admin@example.com");
- assertThat(info.username).isEqualTo("admin");
- accountIndexedCounter.assertNoReindex();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ AccountInfo info = gApi.accounts().id("admin").get();
+ assertThat(info.name).isEqualTo("Administrator");
+ assertThat(info.email).isEqualTo("admin@example.com");
+ assertThat(info.username).isEqualTo("admin");
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
public void getByIntId() throws Exception {
- AccountInfo info = gApi.accounts().id("admin").get();
- AccountInfo infoByIntId = gApi.accounts().id(info._accountId).get();
- assertThat(info.name).isEqualTo(infoByIntId.name);
- accountIndexedCounter.assertNoReindex();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ AccountInfo info = gApi.accounts().id("admin").get();
+ AccountInfo infoByIntId = gApi.accounts().id(info._accountId).get();
+ assertThat(info.name).isEqualTo(infoByIntId.name);
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
public void self() throws Exception {
- AccountInfo info = gApi.accounts().self().get();
- assertUser(info, admin);
-
- info = gApi.accounts().id("self").get();
- assertUser(info, admin);
- accountIndexedCounter.assertNoReindex();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ AccountInfo info = gApi.accounts().self().get();
+ assertUser(info, admin);
+
+ info = gApi.accounts().id("self").get();
+ assertUser(info, admin);
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
public void active() throws Exception {
- int id = gApi.accounts().id("user").get()._accountId;
- assertThat(gApi.accounts().id("user").getActive()).isTrue();
- gApi.accounts().id("user").setActive(false);
- accountIndexedCounter.assertReindexOf(user);
-
- // Inactive users may only be resolved by ID.
- try {
- gApi.accounts().id("user");
- assert_().fail("expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- assertThat(e)
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ int id = gApi.accounts().id("user").get()._accountId;
+ assertThat(gApi.accounts().id("user").getActive()).isTrue();
+ gApi.accounts().id("user").setActive(false);
+ accountIndexedCounter.assertReindexOf(user);
+
+ // Inactive users may only be resolved by ID.
+ ResourceNotFoundException thrown =
+ assertThrows(ResourceNotFoundException.class, () -> gApi.accounts().id("user"));
+ assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Account 'user' only matches inactive accounts. To use an inactive account, retry"
+ " with one of the following exact account IDs:\n"
+ id
+ ": User <user@example.com>");
- }
- assertThat(gApi.accounts().id(id).getActive()).isFalse();
+ assertThat(gApi.accounts().id(id).getActive()).isFalse();
- gApi.accounts().id(id).setActive(true);
- assertThat(gApi.accounts().id("user").getActive()).isTrue();
- accountIndexedCounter.assertReindexOf(user);
+ gApi.accounts().id(id).setActive(true);
+ assertThat(gApi.accounts().id("user").getActive()).isTrue();
+ accountIndexedCounter.assertReindexOf(user);
+ }
}
@Test
public void shouldAllowQueryByEmailForInactiveUser() throws Exception {
- Account.Id activatableAccountId =
- accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
- accountIndexedCounter.assertReindexOf(activatableAccountId, 1);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ Account.Id activatableAccountId =
+ accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
+ accountIndexedCounter.assertReindexOf(activatableAccountId, 1);
+ }
gApi.changes().query("owner:foo@activatable.com").get();
}
@Test
public void shouldAllowQueryByUserNameForInactiveUser() throws Exception {
- Account.Id activatableAccountId =
- accountOperations.newAccount().inactive().username("foo").create();
- accountIndexedCounter.assertReindexOf(activatableAccountId, 1);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ Account.Id activatableAccountId =
+ accountOperations.newAccount().inactive().username("foo").create();
+ accountIndexedCounter.assertReindexOf(activatableAccountId, 1);
+ }
gApi.changes().query("owner:foo").get();
}
+ @Test
@GerritConfig(name = "auth.type", value = "DEVELOPMENT_BECOME_ANY_ACCOUNT")
public void activeUserGetSessionCookieOnLogin() throws Exception {
Integer accountId = accountIdApi().get()._accountId;
@@ -640,90 +654,98 @@ public class AccountIT extends AbstractDaemonTest {
accountOperations.newAccount().inactive().preferredEmail("foo@activatable.com").create();
Account.Id deactivatableAccountId =
accountOperations.newAccount().preferredEmail("foo@deactivatable.com").create();
- RegistrationHandle registrationHandle =
- accountActivationValidationListeners.add(
- "gerrit",
- new AccountActivationValidationListener() {
- @Override
- public void validateActivation(AccountState account) throws ValidationException {
- String preferredEmail = account.getAccount().getPreferredEmail();
- if (preferredEmail == null || !preferredEmail.endsWith("@activatable.com")) {
- throw new ValidationException("not allowed to active account");
- }
- }
- @Override
- public void validateDeactivation(AccountState account) throws ValidationException {
- String preferredEmail = account.getAccount().getPreferredEmail();
- if (preferredEmail == null || !preferredEmail.endsWith("@deactivatable.com")) {
- throw new ValidationException("not allowed to deactive account");
- }
- }
- });
- try {
+ AccountActivationValidationListener validationListener =
+ new AccountActivationValidationListener() {
+ @Override
+ public void validateActivation(AccountState account) throws ValidationException {
+ String preferredEmail = account.account().preferredEmail();
+ if (preferredEmail == null || !preferredEmail.endsWith("@activatable.com")) {
+ throw new ValidationException("not allowed to active account");
+ }
+ }
+
+ @Override
+ public void validateDeactivation(AccountState account) throws ValidationException {
+ String preferredEmail = account.account().preferredEmail();
+ if (preferredEmail == null || !preferredEmail.endsWith("@deactivatable.com")) {
+ throw new ValidationException("not allowed to deactive account");
+ }
+ }
+ };
+
+ AccountActivationListener listener = mock(AccountActivationListener.class);
+
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(validationListener).add(listener)) {
/* Test account that can be activated, but not deactivated */
// Deactivate account that is already inactive
- try {
- gApi.accounts().id(activatableAccountId.get()).setActive(false);
- fail("Expected exception");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("account not active");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(activatableAccountId.get()).setActive(false));
+ assertThat(thrown).hasMessageThat().isEqualTo("account not active");
assertThat(accountOperations.account(activatableAccountId).get().active()).isFalse();
+ verifyZeroInteractions(listener);
// Activate account that can be activated
gApi.accounts().id(activatableAccountId.get()).setActive(true);
assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
+ verify(listener).onAccountActivated(activatableAccountId.get());
+ verifyNoMoreInteractions(listener);
// Activate account that is already active
gApi.accounts().id(activatableAccountId.get()).setActive(true);
assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
+ verifyZeroInteractions(listener);
// Try deactivating account that cannot be deactivated
- try {
- gApi.accounts().id(activatableAccountId.get()).setActive(false);
- fail("Expected exception");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("not allowed to deactive account");
- }
+ thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(activatableAccountId.get()).setActive(false));
+ assertThat(thrown).hasMessageThat().isEqualTo("not allowed to deactive account");
assertThat(accountOperations.account(activatableAccountId).get().active()).isTrue();
+ verifyZeroInteractions(listener);
/* Test account that can be deactivated, but not activated */
// Activate account that is already inactive
gApi.accounts().id(deactivatableAccountId.get()).setActive(true);
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isTrue();
+ verifyZeroInteractions(listener);
// Deactivate account that can be deactivated
gApi.accounts().id(deactivatableAccountId.get()).setActive(false);
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
+ verify(listener).onAccountDeactivated(deactivatableAccountId.get());
+ verifyNoMoreInteractions(listener);
// Deactivate account that is already inactive
- try {
- gApi.accounts().id(deactivatableAccountId.get()).setActive(false);
- fail("Expected exception");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("account not active");
- }
+ thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(deactivatableAccountId.get()).setActive(false));
+ assertThat(thrown).hasMessageThat().isEqualTo("account not active");
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
+ verifyZeroInteractions(listener);
// Try activating account that cannot be activated
- try {
- gApi.accounts().id(deactivatableAccountId.get()).setActive(true);
- fail("Expected exception");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("not allowed to active account");
- }
+ thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(deactivatableAccountId.get()).setActive(true));
+ assertThat(thrown).hasMessageThat().isEqualTo("not allowed to active account");
assertThat(accountOperations.account(deactivatableAccountId).get().active()).isFalse();
- } finally {
- registrationHandle.remove();
+ verifyZeroInteractions(listener);
}
}
@Test
public void deactivateSelf() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cannot deactivate own account");
- gApi.accounts().self().setActive(false);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.accounts().self().setActive(false));
+ assertThat(thrown).hasMessageThat().contains("cannot deactivate own account");
}
@Test
@@ -732,107 +754,125 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(gApi.accounts().id("user").getActive()).isTrue();
gApi.accounts().id("user").setActive(false);
assertThat(gApi.accounts().id(id).getActive()).isFalse();
- try {
- gApi.accounts().id(id).setActive(false);
- fail("Expected exception");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("account not active");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.accounts().id(id).setActive(false));
+ assertThat(thrown).hasMessageThat().isEqualTo("account not active");
gApi.accounts().id(id).setActive(true);
}
@Test
public void starUnstarChange() throws Exception {
- PushOneCommit.Result r = createChange();
- String triplet = project.get() + "~master~" + r.getChangeId();
- refUpdateCounter.clear();
-
- gApi.accounts().self().starChange(triplet);
- ChangeInfo change = info(triplet);
- assertThat(change.starred).isTrue();
- assertThat(change.stars).contains(DEFAULT_LABEL);
- refUpdateCounter.assertRefUpdateFor(
- RefUpdateCounter.projectRef(
- allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id())));
-
- gApi.accounts().self().unstarChange(triplet);
- change = info(triplet);
- assertThat(change.starred).isNull();
- assertThat(change.stars).isNull();
- refUpdateCounter.assertRefUpdateFor(
- RefUpdateCounter.projectRef(
- allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id())));
-
- accountIndexedCounter.assertNoReindex();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ RefUpdateCounter refUpdateCounter = new RefUpdateCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter).add(refUpdateCounter)) {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ refUpdateCounter.clear();
+
+ gApi.accounts().self().starChange(triplet);
+ ChangeInfo change = info(triplet);
+ assertThat(change.starred).isTrue();
+ assertThat(change.stars).contains(DEFAULT_LABEL);
+ refUpdateCounter.assertRefUpdateFor(
+ RefUpdateCounter.projectRef(
+ allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
+
+ gApi.accounts().self().unstarChange(triplet);
+ change = info(triplet);
+ assertThat(change.starred).isNull();
+ assertThat(change.stars).isNull();
+ refUpdateCounter.assertRefUpdateFor(
+ RefUpdateCounter.projectRef(
+ allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
+
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
public void starUnstarChangeWithLabels() throws Exception {
- PushOneCommit.Result r = createChange();
- String triplet = project.get() + "~master~" + r.getChangeId();
- refUpdateCounter.clear();
-
- assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
- assertThat(gApi.accounts().self().getStarredChanges()).isEmpty();
-
- gApi.accounts()
- .self()
- .setStars(triplet, new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "red", "blue")));
- ChangeInfo change = info(triplet);
- assertThat(change.starred).isTrue();
- assertThat(change.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
- assertThat(gApi.accounts().self().getStars(triplet))
- .containsExactly("blue", "red", DEFAULT_LABEL)
- .inOrder();
- List<ChangeInfo> starredChanges = gApi.accounts().self().getStarredChanges();
- assertThat(starredChanges).hasSize(1);
- ChangeInfo starredChange = starredChanges.get(0);
- assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
- assertThat(starredChange.starred).isTrue();
- assertThat(starredChange.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
- refUpdateCounter.assertRefUpdateFor(
- RefUpdateCounter.projectRef(
- allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id())));
-
- gApi.accounts()
- .self()
- .setStars(
- triplet,
- new StarsInput(ImmutableSet.of("yellow"), ImmutableSet.of(DEFAULT_LABEL, "blue")));
- change = info(triplet);
- assertThat(change.starred).isNull();
- assertThat(change.stars).containsExactly("red", "yellow").inOrder();
- assertThat(gApi.accounts().self().getStars(triplet)).containsExactly("red", "yellow").inOrder();
- starredChanges = gApi.accounts().self().getStarredChanges();
- assertThat(starredChanges).hasSize(1);
- starredChange = starredChanges.get(0);
- assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
- assertThat(starredChange.starred).isNull();
- assertThat(starredChange.stars).containsExactly("red", "yellow").inOrder();
- refUpdateCounter.assertRefUpdateFor(
- RefUpdateCounter.projectRef(
- allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id())));
-
- accountIndexedCounter.assertNoReindex();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ RefUpdateCounter refUpdateCounter = new RefUpdateCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter).add(refUpdateCounter)) {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ refUpdateCounter.clear();
+
+ assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
+ assertThat(gApi.accounts().self().getStarredChanges()).isEmpty();
+
+ gApi.accounts()
+ .self()
+ .setStars(triplet, new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "red", "blue")));
+ ChangeInfo change = info(triplet);
+ assertThat(change.starred).isTrue();
+ assertThat(change.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
+ assertThat(gApi.accounts().self().getStars(triplet))
+ .containsExactly("blue", "red", DEFAULT_LABEL)
+ .inOrder();
+ List<ChangeInfo> starredChanges = gApi.accounts().self().getStarredChanges();
+ assertThat(starredChanges).hasSize(1);
+ ChangeInfo starredChange = starredChanges.get(0);
+ assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
+ assertThat(starredChange.starred).isTrue();
+ assertThat(starredChange.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
+ refUpdateCounter.assertRefUpdateFor(
+ RefUpdateCounter.projectRef(
+ allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
+
+ gApi.accounts()
+ .self()
+ .setStars(
+ triplet,
+ new StarsInput(ImmutableSet.of("yellow"), ImmutableSet.of(DEFAULT_LABEL, "blue")));
+ change = info(triplet);
+ assertThat(change.starred).isNull();
+ assertThat(change.stars).containsExactly("red", "yellow").inOrder();
+ assertThat(gApi.accounts().self().getStars(triplet))
+ .containsExactly("red", "yellow")
+ .inOrder();
+ starredChanges = gApi.accounts().self().getStarredChanges();
+ assertThat(starredChanges).hasSize(1);
+ starredChange = starredChanges.get(0);
+ assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
+ assertThat(starredChange.starred).isNull();
+ assertThat(starredChange.stars).containsExactly("red", "yellow").inOrder();
+ refUpdateCounter.assertRefUpdateFor(
+ RefUpdateCounter.projectRef(
+ allUsers, RefNames.refsStarredChanges(Change.id(change._number), admin.id())));
+
+ accountIndexedCounter.assertNoReindex();
- requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to get stars of another account");
- gApi.accounts().id(Integer.toString((admin.id().get()))).getStars(triplet);
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().id(Integer.toString((admin.id().get()))).getStars(triplet));
+ assertThat(thrown).hasMessageThat().contains("not allowed to get stars of another account");
+ }
}
@Test
public void starWithInvalidLabels() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid labels: another invalid label, invalid label");
- gApi.accounts()
- .self()
- .setStars(
- triplet,
- new StarsInput(
- ImmutableSet.of(DEFAULT_LABEL, "invalid label", "blue", "another invalid label")));
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .setStars(
+ triplet,
+ new StarsInput(
+ ImmutableSet.of(
+ DEFAULT_LABEL, "invalid label", "blue", "another invalid label"))));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("invalid labels: another invalid label, invalid label");
}
@Test
@@ -850,65 +890,84 @@ public class AccountIT extends AbstractDaemonTest {
public void starWithDefaultAndIgnoreLabel() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "The labels "
- + DEFAULT_LABEL
- + " and "
- + IGNORE_LABEL
- + " are mutually exclusive."
- + " Only one of them can be set.");
- gApi.accounts()
- .self()
- .setStars(triplet, new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "blue", IGNORE_LABEL)));
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .setStars(
+ triplet,
+ new StarsInput(ImmutableSet.of(DEFAULT_LABEL, "blue", IGNORE_LABEL))));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The labels "
+ + DEFAULT_LABEL
+ + " and "
+ + IGNORE_LABEL
+ + " are mutually exclusive."
+ + " Only one of them can be set.");
}
@Test
public void ignoreChangeBySetStars() throws Exception {
- TestAccount user2 = accountCreator.user2();
- accountIndexedCounter.clear();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount user2 = accountCreator.user2();
+ accountIndexedCounter.clear();
- PushOneCommit.Result r = createChange();
+ PushOneCommit.Result r = createChange();
- AddReviewerInput in = new AddReviewerInput();
- in.reviewer = user.email();
- gApi.changes().id(r.getChangeId()).addReviewer(in);
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email();
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
- in = new AddReviewerInput();
- in.reviewer = user2.email();
- gApi.changes().id(r.getChangeId()).addReviewer(in);
+ in = new AddReviewerInput();
+ in.reviewer = user2.email();
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
- requestScopeOperations.setApiUser(user.id());
- gApi.accounts().self().setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
+ requestScopeOperations.setApiUser(user.id());
+ gApi.accounts()
+ .self()
+ .setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
- sender.clear();
- requestScopeOperations.setApiUser(admin.id());
- gApi.changes().id(r.getChangeId()).abandon();
- List<Message> messages = sender.getMessages();
- assertThat(messages).hasSize(1);
- assertThat(messages.get(0).rcpt()).containsExactly(user2.getEmailAddress());
- accountIndexedCounter.assertNoReindex();
+ sender.clear();
+ requestScopeOperations.setApiUser(admin.id());
+ gApi.changes().id(r.getChangeId()).abandon();
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ assertThat(messages.get(0).rcpt()).containsExactly(user2.getEmailAddress());
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
public void addReviewerToIgnoredChange() throws Exception {
- PushOneCommit.Result r = createChange();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ PushOneCommit.Result r = createChange();
- requestScopeOperations.setApiUser(user.id());
- gApi.accounts().self().setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
+ requestScopeOperations.setApiUser(user.id());
+ gApi.accounts()
+ .self()
+ .setStars(r.getChangeId(), new StarsInput(ImmutableSet.of(IGNORE_LABEL)));
- sender.clear();
- requestScopeOperations.setApiUser(admin.id());
+ sender.clear();
+ requestScopeOperations.setApiUser(admin.id());
- AddReviewerInput in = new AddReviewerInput();
- in.reviewer = user.email();
- gApi.changes().id(r.getChangeId()).addReviewer(in);
- List<Message> messages = sender.getMessages();
- assertThat(messages).hasSize(1);
- Message message = messages.get(0);
- assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
- assertMailReplyTo(message, admin.email());
- accountIndexedCounter.assertNoReindex();
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email();
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message message = messages.get(0);
+ assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
+ assertMailReplyTo(message, admin.email());
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
@@ -1006,17 +1065,21 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void suggestAccounts() throws Exception {
- String adminUsername = "admin";
- List<AccountInfo> result = gApi.accounts().suggestAccounts().withQuery(adminUsername).get();
- assertThat(result).hasSize(1);
- assertThat(result.get(0).username).isEqualTo(adminUsername);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String adminUsername = "admin";
+ List<AccountInfo> result = gApi.accounts().suggestAccounts().withQuery(adminUsername).get();
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).username).isEqualTo(adminUsername);
- List<AccountInfo> resultShortcutApi = gApi.accounts().suggestAccounts(adminUsername).get();
- assertThat(resultShortcutApi).hasSize(result.size());
+ List<AccountInfo> resultShortcutApi = gApi.accounts().suggestAccounts(adminUsername).get();
+ assertThat(resultShortcutApi).hasSize(result.size());
- List<AccountInfo> emptyResult = gApi.accounts().suggestAccounts("unknown").get();
- assertThat(emptyResult).isEmpty();
- accountIndexedCounter.assertNoReindex();
+ List<AccountInfo> emptyResult = gApi.accounts().suggestAccounts("unknown").get();
+ assertThat(emptyResult).isEmpty();
+ accountIndexedCounter.assertNoReindex();
+ }
}
@Test
@@ -1040,7 +1103,7 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(detail.email).isEqualTo(email);
assertThat(detail.secondaryEmails).containsExactly(secondaryEmail);
assertThat(detail.status).isEqualTo(status);
- assertThat(detail.registeredOn).isEqualTo(getAccount(foo.id()).getRegisteredOn());
+ assertThat(detail.registeredOn).isEqualTo(getAccount(foo.id()).registeredOn());
assertThat(detail.inactive).isNull();
assertThat(detail._moreAccounts).isNull();
}
@@ -1082,7 +1145,7 @@ public class AccountIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(admin.id());
String secondaryEmail = "secondary@example.com";
EmailInput input = newEmailInput(secondaryEmail);
- gApi.accounts().id(foo.id().hashCode()).addEmail(input);
+ gApi.accounts().id(foo.id().get()).addEmail(input);
requestScopeOperations.setApiUser(foo.id());
assertThat(getEmails()).containsExactly(email, secondaryEmail);
@@ -1094,9 +1157,9 @@ public class AccountIT extends AbstractDaemonTest {
TestAccount foo = accountCreator.create(name("foo"), email, "Foo");
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("modify account not permitted");
- gApi.accounts().id(foo.id().get()).getEmails();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.accounts().id(foo.id().get()).getEmails());
+ assertThat(thrown).hasMessageThat().contains("modify account not permitted");
}
@Test
@@ -1105,7 +1168,7 @@ public class AccountIT extends AbstractDaemonTest {
String secondaryEmail = "secondary3@example.com";
TestAccount foo = accountCreator.create(name("foo"), email, "Foo");
EmailInput input = newEmailInput(secondaryEmail);
- gApi.accounts().id(foo.id().hashCode()).addEmail(input);
+ gApi.accounts().id(foo.id().get()).addEmail(input);
assertThat(
gApi.accounts().id(foo.id().get()).getEmails().stream()
@@ -1116,17 +1179,21 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void addEmail() throws Exception {
- List<String> emails = ImmutableList.of("new.email@example.com", "new.email@example.systems");
- Set<String> currentEmails = getEmails();
- for (String email : emails) {
- assertThat(currentEmails).doesNotContain(email);
- EmailInput input = newEmailInput(email);
- gApi.accounts().self().addEmail(input);
- accountIndexedCounter.assertReindexOf(admin);
- }
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ List<String> emails = ImmutableList.of("new.email@example.com", "new.email@example.systems");
+ Set<String> currentEmails = getEmails();
+ for (String email : emails) {
+ assertThat(currentEmails).doesNotContain(email);
+ EmailInput input = newEmailInput(email);
+ gApi.accounts().self().addEmail(input);
+ accountIndexedCounter.assertReindexOf(admin);
+ }
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).containsAtLeastElementsIn(emails);
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).containsAtLeastElementsIn(emails);
+ }
}
@Test
@@ -1144,16 +1211,17 @@ public class AccountIT extends AbstractDaemonTest {
// Non-supported TLD (see tlds-alpha-by-domain.txt)
"new.email@example.africa");
- for (String email : emails) {
- EmailInput input = newEmailInput(email);
- try {
- gApi.accounts().self().addEmail(input);
- fail("Expected BadRequestException for invalid email address: " + email);
- } catch (BadRequestException e) {
- assertThat(e).hasMessageThat().isEqualTo("invalid email address");
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ for (String email : emails) {
+ EmailInput input = newEmailInput(email);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.accounts().self().addEmail(input));
+ assertWithMessage(email).that(thrown).hasMessageThat().isEqualTo("invalid email address");
}
+ accountIndexedCounter.assertNoReindex();
}
- accountIndexedCounter.assertNoReindex();
}
@Test
@@ -1161,8 +1229,7 @@ public class AccountIT extends AbstractDaemonTest {
TestAccount account = accountCreator.create(name("user"));
EmailInput input = newEmailInput("test@test.com");
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(account.username()).addEmail(input);
+ assertThrows(AuthException.class, () -> gApi.accounts().id(account.username()).addEmail(input));
}
@Test
@@ -1170,9 +1237,13 @@ public class AccountIT extends AbstractDaemonTest {
String email = "new.email@example.com";
EmailInput input = newEmailInput(email);
gApi.accounts().self().addEmail(input);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Identity 'mailto:" + email + "' in use by another account");
- gApi.accounts().id(user.username()).addEmail(input);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(user.username()).addEmail(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Identity 'mailto:" + email + "' in use by another account");
}
@Test
@@ -1208,9 +1279,14 @@ public class AccountIT extends AbstractDaemonTest {
TestAccount user = accountCreator.create();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("modify account not permitted");
- gApi.accounts().id(admin.id().get()).addEmail(newEmailInput("foo@example.com", false));
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.accounts()
+ .id(admin.id().get())
+ .addEmail(newEmailInput("foo@example.com", false)));
+ assertThat(thrown).hasMessageThat().contains("modify account not permitted");
}
@Test
@@ -1225,62 +1301,74 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void addEmailAndSetPreferred() throws Exception {
- String email = "foo.bar@example.com";
- EmailInput input = new EmailInput();
- input.email = email;
- input.noConfirmation = true;
- input.preferred = true;
- gApi.accounts().self().addEmail(input);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String email = "foo.bar@example.com";
+ EmailInput input = new EmailInput();
+ input.email = email;
+ input.noConfirmation = true;
+ input.preferred = true;
+ gApi.accounts().self().addEmail(input);
- // Account is reindexed twice; once on adding the new email,
- // and then again on setting the email preferred.
- accountIndexedCounter.assertReindexOf(admin, 2);
+ // Account is reindexed twice; once on adding the new email,
+ // and then again on setting the email preferred.
+ accountIndexedCounter.assertReindexOf(admin, 2);
- String preferred = gApi.accounts().self().get().email;
- assertThat(preferred).isEqualTo(email);
+ String preferred = gApi.accounts().self().get().email;
+ assertThat(preferred).isEqualTo(email);
+ }
}
@Test
public void deleteEmail() throws Exception {
- String email = "foo.bar@example.com";
- EmailInput input = newEmailInput(email);
- gApi.accounts().self().addEmail(input);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String email = "foo.bar@example.com";
+ EmailInput input = newEmailInput(email);
+ gApi.accounts().self().addEmail(input);
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).contains(email);
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).contains(email);
- accountIndexedCounter.clear();
- gApi.accounts().self().deleteEmail(input.email);
- accountIndexedCounter.assertReindexOf(admin);
+ accountIndexedCounter.clear();
+ gApi.accounts().self().deleteEmail(input.email);
+ accountIndexedCounter.assertReindexOf(admin);
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).doesNotContain(email);
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).doesNotContain(email);
+ }
}
@Test
public void deletePreferredEmail() throws Exception {
- String previous = gApi.accounts().self().get().email;
- String email = "foo.bar.baz@example.com";
- EmailInput input = new EmailInput();
- input.email = email;
- input.noConfirmation = true;
- input.preferred = true;
- gApi.accounts().self().addEmail(input);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String previous = gApi.accounts().self().get().email;
+ String email = "foo.bar.baz@example.com";
+ EmailInput input = new EmailInput();
+ input.email = email;
+ input.noConfirmation = true;
+ input.preferred = true;
+ gApi.accounts().self().addEmail(input);
- // Account is reindexed twice; once on adding the new email,
- // and then again on setting the email preferred.
- accountIndexedCounter.assertReindexOf(admin, 2);
+ // Account is reindexed twice; once on adding the new email,
+ // and then again on setting the email preferred.
+ accountIndexedCounter.assertReindexOf(admin, 2);
- // The new preferred email is set
- assertThat(gApi.accounts().self().get().email).isEqualTo(email);
+ // The new preferred email is set
+ assertThat(gApi.accounts().self().get().email).isEqualTo(email);
- accountIndexedCounter.clear();
- gApi.accounts().self().deleteEmail(input.email);
- accountIndexedCounter.assertReindexOf(admin);
+ accountIndexedCounter.clear();
+ gApi.accounts().self().deleteEmail(input.email);
+ accountIndexedCounter.assertReindexOf(admin);
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).containsExactly(previous);
- assertThat(gApi.accounts().self().get().email).isNull();
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).containsExactly(previous);
+ assertThat(gApi.accounts().self().get().email).isNull();
+ }
}
@Test
@@ -1306,36 +1394,45 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void deleteEmailFromCustomExternalIdSchemes() throws Exception {
- String email = "foo.bar@example.com";
- String extId1 = "foo:bar";
- String extId2 = "foo:baz";
- accountsUpdateProvider
- .get()
- .update(
- "Add External IDs",
- admin.id(),
- u ->
- u.addExternalId(
- ExternalId.createWithEmail(ExternalId.Key.parse(extId1), admin.id(), email))
- .addExternalId(
- ExternalId.createWithEmail(
- ExternalId.Key.parse(extId2), admin.id(), email)));
- accountIndexedCounter.assertReindexOf(admin);
- assertThat(
- gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
- .containsAtLeast(extId1, extId2);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String email = "foo.bar@example.com";
+ String extId1 = "foo:bar";
+ String extId2 = "foo:baz";
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Add External IDs",
+ admin.id(),
+ u ->
+ u.addExternalId(
+ ExternalId.createWithEmail(
+ ExternalId.Key.parse(extId1), admin.id(), email))
+ .addExternalId(
+ ExternalId.createWithEmail(
+ ExternalId.Key.parse(extId2), admin.id(), email)));
+ accountIndexedCounter.assertReindexOf(admin);
+ assertThat(
+ gApi.accounts().self().getExternalIds().stream()
+ .map(e -> e.identity)
+ .collect(toSet()))
+ .containsAtLeast(extId1, extId2);
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).contains(email);
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).contains(email);
- gApi.accounts().self().deleteEmail(email);
- accountIndexedCounter.assertReindexOf(admin);
+ gApi.accounts().self().deleteEmail(email);
+ accountIndexedCounter.assertReindexOf(admin);
- requestScopeOperations.resetCurrentApiUser();
- assertThat(getEmails()).doesNotContain(email);
- assertThat(
- gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
- .containsNoneOf(extId1, extId2);
+ requestScopeOperations.resetCurrentApiUser();
+ assertThat(getEmails()).doesNotContain(email);
+ assertThat(
+ gApi.accounts().self().getExternalIds().stream()
+ .map(e -> e.identity)
+ .collect(toSet()))
+ .containsNoneOf(extId1, extId2);
+ }
}
@Test
@@ -1352,7 +1449,6 @@ public class AccountIT extends AbstractDaemonTest {
u.addExternalId(
ExternalId.createWithEmail(
ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail)));
- accountIndexedCounter.assertReindexOf(admin);
assertThat(
gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
.contains(ldapExternalId);
@@ -1391,7 +1487,6 @@ public class AccountIT extends AbstractDaemonTest {
.addExternalId(
ExternalId.createWithEmail(
ExternalId.Key.parse(ldapExternalId), admin.id(), ldapEmail)));
- accountIndexedCounter.assertReindexOf(admin);
assertThat(
gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
.containsAtLeast(ldapExternalId, nonLdapExternalId);
@@ -1410,28 +1505,34 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void deleteEmailOfOtherUser() throws Exception {
- String email = "foo.bar@example.com";
- EmailInput input = new EmailInput();
- input.email = email;
- input.noConfirmation = true;
- gApi.accounts().id(user.id().get()).addEmail(input);
- accountIndexedCounter.assertReindexOf(user);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String email = "foo.bar@example.com";
+ EmailInput input = new EmailInput();
+ input.email = email;
+ input.noConfirmation = true;
+ gApi.accounts().id(user.id().get()).addEmail(input);
+ accountIndexedCounter.assertReindexOf(user);
- requestScopeOperations.setApiUser(user.id());
- assertThat(getEmails()).contains(email);
-
- // admin can delete email of user
- requestScopeOperations.setApiUser(admin.id());
- gApi.accounts().id(user.id().get()).deleteEmail(email);
- accountIndexedCounter.assertReindexOf(user);
+ requestScopeOperations.setApiUser(user.id());
+ assertThat(getEmails()).contains(email);
- requestScopeOperations.setApiUser(user.id());
- assertThat(getEmails()).doesNotContain(email);
+ // admin can delete email of user
+ requestScopeOperations.setApiUser(admin.id());
+ gApi.accounts().id(user.id().get()).deleteEmail(email);
+ accountIndexedCounter.assertReindexOf(user);
- // user cannot delete email of admin
- exception.expect(AuthException.class);
- exception.expectMessage("modify account not permitted");
- gApi.accounts().id(admin.id().get()).deleteEmail(admin.email());
+ requestScopeOperations.setApiUser(user.id());
+ assertThat(getEmails()).doesNotContain(email);
+
+ // user cannot delete email of admin
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().id(admin.id().get()).deleteEmail(admin.email()));
+ assertThat(thrown).hasMessageThat().contains("modify account not permitted");
+ }
}
@Test
@@ -1497,17 +1598,21 @@ public class AccountIT extends AbstractDaemonTest {
public void putStatus() throws Exception {
List<String> statuses = ImmutableList.of("OOO", "Busy");
AccountInfo info;
- for (String status : statuses) {
- gApi.accounts().self().setStatus(status);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ for (String status : statuses) {
+ gApi.accounts().self().setStatus(status);
+ info = gApi.accounts().self().get();
+ assertUser(info, admin, status);
+ accountIndexedCounter.assertReindexOf(admin);
+ }
+
+ gApi.accounts().self().setStatus(null);
info = gApi.accounts().self().get();
- assertUser(info, admin, status);
+ assertUser(info, admin);
accountIndexedCounter.assertReindexOf(admin);
}
-
- gApi.accounts().self().setStatus(null);
- info = gApi.accounts().self().get();
- assertUser(info, admin);
- accountIndexedCounter.assertReindexOf(admin);
}
@Test
@@ -1525,617 +1630,92 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void userCannotSetNameOfOtherUser() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(admin.username()).setName("Admin McAdminface");
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().id(admin.username()).setName("Admin McAdminface"));
}
@Test
@Sandboxed
public void userCanSetNameOfOtherUserWithModifyAccountPermission() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.MODIFY_ACCOUNT);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.MODIFY_ACCOUNT).group(REGISTERED_USERS))
+ .update();
gApi.accounts().id(admin.username()).setName("Admin McAdminface");
assertThat(gApi.accounts().id(admin.username()).get().name).isEqualTo("Admin McAdminface");
}
@Test
public void fetchUserBranch() throws Exception {
- requestScopeOperations.setApiUser(user.id());
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
- String userRefName = RefNames.refsUsers(user.id());
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ requestScopeOperations.setApiUser(user.id());
- // remove default READ permissions
- try (ProjectConfigUpdate u = updateProject(allUsers)) {
- u.getConfig()
- .getAccessSection(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", true)
- .remove(new Permission(Permission.READ));
- u.save();
- }
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
+ String userRefName = RefNames.refsUsers(user.id());
- // deny READ permission that is inherited from All-Projects
- deny(allUsers, RefNames.REFS + "*", Permission.READ, ANONYMOUS_USERS);
+ // remove default READ permissions
+ try (ProjectConfigUpdate u = updateProject(allUsers)) {
+ u.getConfig()
+ .getAccessSection(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", true)
+ .remove(new Permission(Permission.READ));
+ u.save();
+ }
- // fetching user branch without READ permission fails
- try {
+ // deny READ permission that is inherited from All-Projects
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(deny(Permission.READ).ref(RefNames.REFS + "*").group(ANONYMOUS_USERS))
+ .update();
+
+ // fetching user branch without READ permission fails
+ assertThrows(TransportException.class, () -> fetch(allUsersRepo, userRefName + ":userRef"));
+
+ // allow each user to read its own user branch
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(
+ allow(Permission.READ)
+ .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}")
+ .group(REGISTERED_USERS))
+ .update();
+
+ // fetch user branch using refs/users/YY/XXXXXXX
fetch(allUsersRepo, userRefName + ":userRef");
- fail("user branch is visible although no READ permission is granted");
- } catch (TransportException e) {
- // expected because no READ granted on user branch
- }
-
- // allow each user to read its own user branch
- grant(
- allUsers,
- RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}",
- Permission.READ,
- false,
- REGISTERED_USERS);
-
- // fetch user branch using refs/users/YY/XXXXXXX
- fetch(allUsersRepo, userRefName + ":userRef");
- Ref userRef = allUsersRepo.getRepository().exactRef("userRef");
- assertThat(userRef).isNotNull();
-
- // fetch user branch using refs/users/self
- fetch(allUsersRepo, RefNames.REFS_USERS_SELF + ":userSelfRef");
- Ref userSelfRef = allUsersRepo.getRepository().getRefDatabase().exactRef("userSelfRef");
- assertThat(userSelfRef).isNotNull();
- assertThat(userSelfRef.getObjectId()).isEqualTo(userRef.getObjectId());
-
- accountIndexedCounter.assertNoReindex();
-
- // fetching user branch of another user fails
- String otherUserRefName = RefNames.refsUsers(admin.id());
- exception.expect(TransportException.class);
- exception.expectMessage("Remote does not have " + otherUserRefName + " available for fetch.");
- fetch(allUsersRepo, otherUserRefName + ":otherUserRef");
- }
-
- @Test
- public void pushToUserBranch() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
- allUsersRepo.reset("userRef");
- PushOneCommit push = pushFactory.create(admin.newIdent(), allUsersRepo);
- push.to(RefNames.refsUsers(admin.id())).assertOkStatus();
- accountIndexedCounter.assertReindexOf(admin);
-
- push = pushFactory.create(admin.newIdent(), allUsersRepo);
- push.to(RefNames.REFS_USERS_SELF).assertOkStatus();
- accountIndexedCounter.assertReindexOf(admin);
- }
-
- @Test
- public void pushToUserBranchForReview() throws Exception {
- String userRefName = RefNames.refsUsers(admin.id());
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRefName + ":userRef");
- allUsersRepo.reset("userRef");
- PushOneCommit push = pushFactory.create(admin.newIdent(), allUsersRepo);
- PushOneCommit.Result r = push.to(MagicBranch.NEW_CHANGE + userRefName);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRefName);
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).current().submit();
- accountIndexedCounter.assertReindexOf(admin);
-
- push = pushFactory.create(admin.newIdent(), allUsersRepo);
- r = push.to(MagicBranch.NEW_CHANGE + RefNames.REFS_USERS_SELF);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRefName);
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).current().submit();
- accountIndexedCounter.assertReindexOf(admin);
- }
-
- @Test
- public void pushAccountConfigToUserBranchForReviewAndSubmit() throws Exception {
- String userRef = RefNames.refsUsers(admin.id());
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, "out-of-office");
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).current().submit();
- accountIndexedCounter.assertReindexOf(admin);
-
- AccountInfo info = gApi.accounts().self().get();
- assertThat(info.email).isEqualTo(admin.email());
- assertThat(info.name).isEqualTo(admin.fullName());
- assertThat(info.status).isEqualTo("out-of-office");
- }
-
- @Test
- public void pushAccountConfigWithPrefEmailThatDoesNotExistAsExtIdToUserBranchForReviewAndSubmit()
- throws Exception {
- TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
- String userRef = RefNames.refsUsers(foo.id());
- accountIndexedCounter.clear();
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- String email = "some.email@example.com";
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, email);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- foo.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- requestScopeOperations.setApiUser(foo.id());
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).current().submit();
-
- accountIndexedCounter.assertReindexOf(foo);
-
- AccountInfo info = gApi.accounts().self().get();
- assertThat(info.email).isEqualTo(email);
- assertThat(info.name).isEqualTo(foo.fullName());
- }
-
- @Test
- public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfConfigIsInvalid()
- throws Exception {
- String userRef = RefNames.refsUsers(admin.id());
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- "invalid config")
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format(
- "invalid account configuration: commit '%s' has an invalid '%s' file for account '%s':"
- + " Invalid config file %s in commit %s",
- r.getCommit().name(),
- AccountProperties.ACCOUNT_CONFIG,
- admin.id(),
- AccountProperties.ACCOUNT_CONFIG,
- r.getCommit().name()));
- gApi.changes().id(r.getChangeId()).current().submit();
- }
-
- @Test
- public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfPreferredEmailIsInvalid()
- throws Exception {
- String userRef = RefNames.refsUsers(admin.id());
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- String noEmail = "no.email";
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, noEmail);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format(
- "invalid account configuration: invalid preferred email '%s' for account '%s'",
- noEmail, admin.id()));
- gApi.changes().id(r.getChangeId()).current().submit();
- }
-
- @Test
- public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfOwnAccountIsDeactivated()
- throws Exception {
- String userRef = RefNames.refsUsers(admin.id());
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("invalid account configuration: cannot deactivate own account");
- gApi.changes().id(r.getChangeId()).current().submit();
- }
-
- @Test
- public void pushAccountConfigToUserBranchForReviewDeactivateOtherAccount() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
-
- TestAccount foo = accountCreator.create(name("foo"));
- assertThat(gApi.accounts().id(foo.id().get()).getActive()).isTrue();
- String userRef = RefNames.refsUsers(foo.id());
- accountIndexedCounter.clear();
-
- grant(allUsers, userRef, Permission.PUSH, false, adminGroupUuid());
- grantLabel("Code-Review", -2, 2, allUsers, userRef, false, adminGroupUuid(), false);
- grant(allUsers, userRef, Permission.SUBMIT, false, adminGroupUuid());
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(MagicBranch.NEW_CHANGE + userRef);
- r.assertOkStatus();
- accountIndexedCounter.assertNoReindex();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(userRef);
-
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).current().submit();
- accountIndexedCounter.assertReindexOf(foo);
-
- assertThat(gApi.accounts().id(foo.id().get()).getActive()).isFalse();
- }
-
- @Test
- public void pushWatchConfigToUserBranch() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config wc = new Config();
- wc.setString(
- ProjectWatches.PROJECT,
- project.get(),
- ProjectWatches.KEY_NOTIFY,
- ProjectWatches.NotifyValue.create(null, EnumSet.of(NotifyType.ALL_COMMENTS)).toString());
- PushOneCommit push =
- pushFactory.create(
- admin.newIdent(),
- allUsersRepo,
- "Add project watch",
- ProjectWatches.WATCH_CONFIG,
- wc.toText());
- push.to(RefNames.REFS_USERS_SELF).assertOkStatus();
- accountIndexedCounter.assertReindexOf(admin);
-
- String invalidNotifyValue = "]invalid[";
- wc.setString(
- ProjectWatches.PROJECT, project.get(), ProjectWatches.KEY_NOTIFY, invalidNotifyValue);
- push =
- pushFactory.create(
- admin.newIdent(),
- allUsersRepo,
- "Add invalid project watch",
- ProjectWatches.WATCH_CONFIG,
- wc.toText());
- PushOneCommit.Result r = push.to(RefNames.REFS_USERS_SELF);
- r.assertErrorStatus("invalid account configuration");
- r.assertMessage(
- String.format(
- "%s: Invalid project watch of account %d for project %s: %s",
- ProjectWatches.WATCH_CONFIG, admin.id().get(), project.get(), invalidNotifyValue));
- }
-
- @Test
- public void pushAccountConfigToUserBranch() throws Exception {
- TestAccount oooUser = accountCreator.create("away", "away@mail.invalid", "Ambrose Way");
- requestScopeOperations.setApiUser(oooUser.id());
-
- // Must clone as oooUser to ensure the push is allowed.
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, oooUser);
- fetch(allUsersRepo, RefNames.refsUsers(oooUser.id()) + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, "out-of-office");
-
- accountIndexedCounter.clear();
- pushFactory
- .create(
- oooUser.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(RefNames.refsUsers(oooUser.id()))
- .assertOkStatus();
-
- accountIndexedCounter.assertReindexOf(oooUser);
-
- AccountInfo info = gApi.accounts().self().get();
- assertThat(info.email).isEqualTo(oooUser.email());
- assertThat(info.name).isEqualTo(oooUser.fullName());
- assertThat(info.status).isEqualTo("out-of-office");
- }
-
- @Test
- public void pushAccountConfigToUserBranchIsRejectedIfConfigIsInvalid() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
- allUsersRepo.reset("userRef");
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- "invalid config")
- .to(RefNames.REFS_USERS_SELF);
- r.assertErrorStatus("invalid account configuration");
- r.assertMessage(
- String.format(
- "commit '%s' has an invalid '%s' file for account '%s':"
- + " Invalid config file %s in commit %s",
- r.getCommit().name(),
- AccountProperties.ACCOUNT_CONFIG,
- admin.id(),
- AccountProperties.ACCOUNT_CONFIG,
- r.getCommit().name()));
- accountIndexedCounter.assertNoReindex();
- }
-
- @Test
- public void pushAccountConfigToUserBranchIsRejectedIfPreferredEmailIsInvalid() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
- allUsersRepo.reset("userRef");
-
- String noEmail = "no.email";
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, noEmail);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(RefNames.REFS_USERS_SELF);
- r.assertErrorStatus("invalid account configuration");
- r.assertMessage(
- String.format("invalid preferred email '%s' for account '%s'", noEmail, admin.id()));
- accountIndexedCounter.assertNoReindex();
- }
-
- @Test
- public void pushAccountConfigToUserBranchInvalidPreferredEmailButNotChanged() throws Exception {
- TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
- String userRef = RefNames.refsUsers(foo.id());
-
- String noEmail = "no.email";
- accountsUpdateProvider
- .get()
- .update("Set Preferred Email", foo.id(), u -> u.setPreferredEmail(noEmail));
- accountIndexedCounter.clear();
-
- grant(allUsers, userRef, Permission.PUSH, false, REGISTERED_USERS);
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- String status = "in vacation";
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, status);
-
- pushFactory
- .create(
- foo.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(userRef)
- .assertOkStatus();
- accountIndexedCounter.assertReindexOf(foo);
-
- AccountInfo info = gApi.accounts().id(foo.id().get()).get();
- assertThat(info.email).isEqualTo(noEmail);
- assertThat(info.name).isEqualTo(foo.fullName());
- assertThat(info.status).isEqualTo(status);
- }
-
- @Test
- public void pushAccountConfigToUserBranchIfPreferredEmailDoesNotExistAsExtId() throws Exception {
- TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
- String userRef = RefNames.refsUsers(foo.id());
- accountIndexedCounter.clear();
-
- grant(allUsers, userRef, Permission.PUSH, false, adminGroupUuid());
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- String email = "some.email@example.com";
- Config ac = getAccountConfig(allUsersRepo);
- ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, email);
-
- pushFactory
- .create(
- foo.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(userRef)
- .assertOkStatus();
- accountIndexedCounter.assertReindexOf(foo);
-
- AccountInfo info = gApi.accounts().id(foo.id().get()).get();
- assertThat(info.email).isEqualTo(email);
- assertThat(info.name).isEqualTo(foo.fullName());
- }
-
- @Test
- public void pushAccountConfigToUserBranchIsRejectedIfOwnAccountIsDeactivated() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
-
- PushOneCommit.Result r =
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(RefNames.REFS_USERS_SELF);
- r.assertErrorStatus("invalid account configuration");
- r.assertMessage("cannot deactivate own account");
- accountIndexedCounter.assertNoReindex();
- }
-
- @Test
- public void pushAccountConfigToUserBranchDeactivateOtherAccount() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
-
- TestAccount foo = accountCreator.create(name("foo"));
- assertThat(gApi.accounts().id(foo.id().get()).getActive()).isTrue();
- String userRef = RefNames.refsUsers(foo.id());
- accountIndexedCounter.clear();
-
- grant(allUsers, userRef, Permission.PUSH, false, adminGroupUuid());
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- fetch(allUsersRepo, userRef + ":userRef");
- allUsersRepo.reset("userRef");
-
- Config ac = getAccountConfig(allUsersRepo);
- ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
-
- pushFactory
- .create(
- admin.newIdent(),
- allUsersRepo,
- "Update account config",
- AccountProperties.ACCOUNT_CONFIG,
- ac.toText())
- .to(userRef)
- .assertOkStatus();
- accountIndexedCounter.assertReindexOf(foo);
-
- assertThat(gApi.accounts().id(foo.id().get()).getActive()).isFalse();
- }
-
- @Test
- public void cannotCreateUserBranch() throws Exception {
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.CREATE);
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.PUSH);
-
- String userRef = RefNames.refsUsers(new Account.Id(seq.nextAccountId()));
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef);
- r.assertErrorStatus();
- assertThat(r.getMessage()).contains("Not allowed to create user branch.");
-
- try (Repository repo = repoManager.openRepository(allUsers)) {
- assertThat(repo.exactRef(userRef)).isNull();
+ Ref userRef = allUsersRepo.getRepository().exactRef("userRef");
+ assertThat(userRef).isNotNull();
+
+ // fetch user branch using refs/users/self
+ fetch(allUsersRepo, RefNames.REFS_USERS_SELF + ":userSelfRef");
+ Ref userSelfRef = allUsersRepo.getRepository().getRefDatabase().exactRef("userSelfRef");
+ assertThat(userSelfRef).isNotNull();
+ assertThat(userSelfRef.getObjectId()).isEqualTo(userRef.getObjectId());
+
+ accountIndexedCounter.assertNoReindex();
+
+ // fetching user branch of another user fails
+ String otherUserRefName = RefNames.refsUsers(admin.id());
+ TransportException thrown =
+ assertThrows(
+ TransportException.class,
+ () -> fetch(allUsersRepo, otherUserRefName + ":otherUserRef"));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Remote does not have " + otherUserRefName + " available for fetch.");
}
}
@Test
- public void createUserBranchWithAccessDatabaseCapability() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.CREATE);
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.PUSH);
-
- String userRef = RefNames.refsUsers(new Account.Id(seq.nextAccountId()));
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef).assertOkStatus();
-
- try (Repository repo = repoManager.openRepository(allUsers)) {
- assertThat(repo.exactRef(userRef)).isNotNull();
- }
- }
-
- @Test
- public void cannotCreateNonUserBranchUnderRefsUsersWithAccessDatabaseCapability()
- throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.CREATE);
- grant(allUsers, RefNames.REFS_USERS + "*", Permission.PUSH);
-
- String userRef = RefNames.REFS_USERS + "foo";
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef);
- r.assertErrorStatus();
- assertThat(r.getMessage()).contains("Not allowed to create non-user branch under refs/users/.");
-
- try (Repository repo = repoManager.openRepository(allUsers)) {
- assertThat(repo.exactRef(userRef)).isNull();
+ public void refsUsersSelfIsAdvertised() throws Exception {
+ TestRepository<?> testRepository = cloneProject(allUsers, user);
+ try (Git git = testRepository.git()) {
+ List<String> advertisedRefs =
+ git.lsRemote().call().stream().map(Ref::getName).collect(toList());
+ assertThat(advertisedRefs).contains(RefNames.REFS_USERS_SELF);
}
}
@@ -2145,8 +1725,12 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(repo.exactRef(RefNames.REFS_USERS_DEFAULT)).isNull();
}
- grant(allUsers, RefNames.REFS_USERS_DEFAULT, Permission.CREATE);
- grant(allUsers, RefNames.REFS_USERS_DEFAULT, Permission.PUSH);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid()))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
pushFactory
@@ -2161,12 +1745,15 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void cannotDeleteUserBranch() throws Exception {
- grant(
- allUsers,
- RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}",
- Permission.DELETE,
- true,
- REGISTERED_USERS);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(
+ allow(Permission.DELETE)
+ .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}")
+ .group(REGISTERED_USERS)
+ .force(true))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
String userRef = RefNames.refsUsers(admin.id());
@@ -2182,13 +1769,19 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void deleteUserBranchWithAccessDatabaseCapability() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
- grant(
- allUsers,
- RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}",
- Permission.DELETE,
- true,
- REGISTERED_USERS);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(
+ allow(Permission.DELETE)
+ .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}")
+ .group(REGISTERED_USERS)
+ .force(true))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
String userRef = RefNames.refsUsers(admin.id());
@@ -2217,9 +1810,10 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(sender.getMessages().get(0).body()).contains("new GPG keys have been added");
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage(id);
- gApi.accounts().self().gpgKey(id).get();
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.accounts().self().gpgKey(id).get());
+ assertThat(thrown).hasMessageThat().contains(id);
}
@Test
@@ -2229,8 +1823,7 @@ public class AccountIT extends AbstractDaemonTest {
sender.clear();
requestScopeOperations.setApiUser(admin.id());
- exception.expect(ResourceNotFoundException.class);
- addGpgKey(user, key.getPublicKeyArmored());
+ assertThrows(ResourceNotFoundException.class, () -> addGpgKey(user, key.getPublicKeyArmored()));
}
@Test
@@ -2259,212 +1852,251 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void addOtherUsersGpgKey_Conflict() throws Exception {
- // Both users have a matching external ID for this key.
- addExternalIdEmail(admin, "test5@example.com");
- accountsUpdateProvider
- .get()
- .update(
- "Add External ID",
- user.id(),
- u -> u.addExternalId(ExternalId.create("foo", "myId", user.id())));
- accountIndexedCounter.assertReindexOf(user);
-
- TestKey key = validKeyWithSecondUserId();
- addGpgKey(key.getPublicKeyArmored());
- requestScopeOperations.setApiUser(user.id());
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ // Both users have a matching external ID for this key.
+ addExternalIdEmail(admin, "test5@example.com");
+ accountIndexedCounter.clear();
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Add External ID",
+ user.id(),
+ u -> u.addExternalId(ExternalId.create("foo", "myId", user.id())));
+ accountIndexedCounter.assertReindexOf(user);
+
+ TestKey key = validKeyWithSecondUserId();
+ addGpgKey(key.getPublicKeyArmored());
+ requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("GPG key already associated with another account");
- addGpgKey(user, key.getPublicKeyArmored());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> addGpgKey(user, key.getPublicKeyArmored()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("GPG key already associated with another account");
+ }
}
@Test
public void listGpgKeys() throws Exception {
- List<TestKey> keys = allValidKeys();
- List<String> toAdd = new ArrayList<>(keys.size());
- for (TestKey key : keys) {
- addExternalIdEmail(admin, PushCertificateIdent.parse(key.getFirstUserId()).getEmailAddress());
- toAdd.add(key.getPublicKeyArmored());
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ List<TestKey> keys = allValidKeys();
+ List<String> toAdd = new ArrayList<>(keys.size());
+ for (TestKey key : keys) {
+ addExternalIdEmail(
+ admin, PushCertificateIdent.parse(key.getFirstUserId()).getEmailAddress());
+ toAdd.add(key.getPublicKeyArmored());
+ }
+ accountIndexedCounter.clear();
+ gApi.accounts().self().putGpgKeys(toAdd, ImmutableList.of());
+ assertKeys(keys);
+ accountIndexedCounter.assertReindexOf(admin);
}
- gApi.accounts().self().putGpgKeys(toAdd, ImmutableList.of());
- assertKeys(keys);
- accountIndexedCounter.assertReindexOf(admin);
}
@Test
public void deleteGpgKey() throws Exception {
- TestKey key = validKeyWithoutExpiration();
- String id = key.getKeyIdString();
- addExternalIdEmail(admin, "test1@example.com");
- addGpgKey(key.getPublicKeyArmored());
- assertKeys(key);
-
- sender.clear();
- gApi.accounts().self().gpgKey(id).delete();
- accountIndexedCounter.assertReindexOf(admin);
- assertKeys();
- assertThat(sender.getMessages()).hasSize(1);
- assertThat(sender.getMessages().get(0).body()).contains("GPG keys have been deleted");
-
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage(id);
- gApi.accounts().self().gpgKey(id).get();
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestKey key = validKeyWithoutExpiration();
+ String id = key.getKeyIdString();
+ addExternalIdEmail(admin, "test1@example.com");
+ addGpgKey(key.getPublicKeyArmored());
+ assertKeys(key);
+ accountIndexedCounter.clear();
+
+ sender.clear();
+ gApi.accounts().self().gpgKey(id).delete();
+ accountIndexedCounter.assertReindexOf(admin);
+ assertKeys();
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("GPG keys have been deleted");
+
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.accounts().self().gpgKey(id).get());
+ assertThat(thrown).hasMessageThat().contains(id);
+ }
}
@Test
public void addAndRemoveGpgKeys() throws Exception {
- for (TestKey key : allValidKeys()) {
- addExternalIdEmail(admin, PushCertificateIdent.parse(key.getFirstUserId()).getEmailAddress());
- }
- TestKey key1 = validKeyWithoutExpiration();
- TestKey key2 = validKeyWithExpiration();
- TestKey key5 = validKeyWithSecondUserId();
-
- Map<String, GpgKeyInfo> infos =
- gApi.accounts()
- .self()
- .putGpgKeys(
- ImmutableList.of(key1.getPublicKeyArmored(), key2.getPublicKeyArmored()),
- ImmutableList.of(key5.getKeyIdString()));
- assertThat(infos.keySet()).containsExactly(key1.getKeyIdString(), key2.getKeyIdString());
- assertKeys(key1, key2);
- accountIndexedCounter.assertReindexOf(admin);
-
- infos =
- gApi.accounts()
- .self()
- .putGpgKeys(
- ImmutableList.of(key5.getPublicKeyArmored()),
- ImmutableList.of(key1.getKeyIdString()));
- assertThat(infos.keySet()).containsExactly(key1.getKeyIdString(), key5.getKeyIdString());
- assertKeyMapContains(key5, infos);
- assertThat(infos.get(key1.getKeyIdString()).key).isNull();
- assertKeys(key2, key5);
- accountIndexedCounter.assertReindexOf(admin);
-
- exception.expect(BadRequestException.class);
- exception.expectMessage("Cannot both add and delete key: " + keyToString(key2.getPublicKey()));
- gApi.accounts()
- .self()
- .putGpgKeys(
- ImmutableList.of(key2.getPublicKeyArmored()), ImmutableList.of(key2.getKeyIdString()));
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ for (TestKey key : allValidKeys()) {
+ addExternalIdEmail(
+ admin, PushCertificateIdent.parse(key.getFirstUserId()).getEmailAddress());
+ }
+ accountIndexedCounter.clear();
+ TestKey key1 = validKeyWithoutExpiration();
+ TestKey key2 = validKeyWithExpiration();
+ TestKey key5 = validKeyWithSecondUserId();
+
+ Map<String, GpgKeyInfo> infos =
+ gApi.accounts()
+ .self()
+ .putGpgKeys(
+ ImmutableList.of(key1.getPublicKeyArmored(), key2.getPublicKeyArmored()),
+ ImmutableList.of(key5.getKeyIdString()));
+ assertThat(infos.keySet()).containsExactly(key1.getKeyIdString(), key2.getKeyIdString());
+ assertKeys(key1, key2);
+ accountIndexedCounter.assertReindexOf(admin);
+
+ infos =
+ gApi.accounts()
+ .self()
+ .putGpgKeys(
+ ImmutableList.of(key5.getPublicKeyArmored()),
+ ImmutableList.of(key1.getKeyIdString()));
+ assertThat(infos.keySet()).containsExactly(key1.getKeyIdString(), key5.getKeyIdString());
+ assertKeyMapContains(key5, infos);
+ assertThat(infos.get(key1.getKeyIdString()).key).isNull();
+ assertKeys(key2, key5);
+ accountIndexedCounter.assertReindexOf(admin);
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .putGpgKeys(
+ ImmutableList.of(key2.getPublicKeyArmored()),
+ ImmutableList.of(key2.getKeyIdString())));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cannot both add and delete key: " + keyToString(key2.getPublicKey()));
+ }
}
@Test
public void addMalformedGpgKey() throws Exception {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\ntest\n-----END PGP PUBLIC KEY BLOCK-----";
- exception.expect(BadRequestException.class);
- exception.expectMessage("Failed to parse GPG keys");
- addGpgKey(key);
+ BadRequestException thrown = assertThrows(BadRequestException.class, () -> addGpgKey(key));
+ assertThat(thrown).hasMessageThat().contains("Failed to parse GPG keys");
}
@Test
@UseSsh
public void sshKeys() throws Exception {
- // The test account should initially have exactly one ssh key
- List<SshKeyInfo> info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(1);
- assertSequenceNumbers(info);
- SshKeyInfo key = info.get(0);
- KeyPair keyPair = sshKeys.getKeyPair(admin);
- String initial = TestSshKeys.publicKey(keyPair, admin.email());
- assertThat(key.sshPublicKey).isEqualTo(initial);
- accountIndexedCounter.assertNoReindex();
-
- // Add a new key
- sender.clear();
- String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email());
- gApi.accounts().self().addSshKey(newKey);
- info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(2);
- assertSequenceNumbers(info);
- accountIndexedCounter.assertReindexOf(admin);
- assertThat(sender.getMessages()).hasSize(1);
- assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
-
- // Add an existing key (the request succeeds, but the key isn't added again)
- sender.clear();
- gApi.accounts().self().addSshKey(initial);
- info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(2);
- assertSequenceNumbers(info);
- accountIndexedCounter.assertNoReindex();
- // TODO: Issue 10769: Adding an already existing key should not result in a notification email
- assertThat(sender.getMessages()).hasSize(1);
- assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
-
- // Add another new key
- sender.clear();
- String newKey2 = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email());
- gApi.accounts().self().addSshKey(newKey2);
- info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(3);
- assertSequenceNumbers(info);
- accountIndexedCounter.assertReindexOf(admin);
- assertThat(sender.getMessages()).hasSize(1);
- assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
-
- // Delete second key
- sender.clear();
- gApi.accounts().self().deleteSshKey(2);
- info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(2);
- assertThat(info.get(0).seq).isEqualTo(1);
- assertThat(info.get(1).seq).isEqualTo(3);
- accountIndexedCounter.assertReindexOf(admin);
-
- assertThat(sender.getMessages()).hasSize(1);
- assertThat(sender.getMessages().get(0).body()).contains("SSH keys have been deleted");
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ // The test account should initially have exactly one ssh key
+ List<SshKeyInfo> info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(1);
+ assertSequenceNumbers(info);
+ SshKeyInfo key = info.get(0);
+ KeyPair keyPair = sshKeys.getKeyPair(admin);
+ String initial = TestSshKeys.publicKey(keyPair, admin.email());
+ assertThat(key.sshPublicKey).isEqualTo(initial);
+ accountIndexedCounter.assertNoReindex();
+
+ // Add a new key
+ sender.clear();
+ String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email());
+ gApi.accounts().self().addSshKey(newKey);
+ info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(2);
+ assertSequenceNumbers(info);
+ accountIndexedCounter.assertReindexOf(admin);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
+
+ // Add an existing key (the request succeeds, but the key isn't added again)
+ sender.clear();
+ gApi.accounts().self().addSshKey(initial);
+ info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(2);
+ assertSequenceNumbers(info);
+ accountIndexedCounter.assertNoReindex();
+ // TODO: Issue 10769: Adding an already existing key should not result in a notification email
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
+
+ // Add another new key
+ sender.clear();
+ String newKey2 = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email());
+ gApi.accounts().self().addSshKey(newKey2);
+ info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(3);
+ assertSequenceNumbers(info);
+ accountIndexedCounter.assertReindexOf(admin);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("new SSH keys have been added");
+
+ // Delete second key
+ sender.clear();
+ gApi.accounts().self().deleteSshKey(2);
+ info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(2);
+ assertThat(info.get(0).seq).isEqualTo(1);
+ assertThat(info.get(1).seq).isEqualTo(3);
+ accountIndexedCounter.assertReindexOf(admin);
- // Mark first key as invalid
- assertThat(info.get(0).valid).isTrue();
- authorizedKeys.markKeyInvalid(admin.id(), 1);
- info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(2);
- assertThat(info.get(0).seq).isEqualTo(1);
- assertThat(info.get(0).valid).isFalse();
- assertThat(info.get(1).seq).isEqualTo(3);
- accountIndexedCounter.assertReindexOf(admin);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).body()).contains("SSH keys have been deleted");
+
+ // Mark first key as invalid
+ assertThat(info.get(0).valid).isTrue();
+ authorizedKeys.markKeyInvalid(admin.id(), 1);
+ info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(2);
+ assertThat(info.get(0).seq).isEqualTo(1);
+ assertThat(info.get(0).valid).isFalse();
+ assertThat(info.get(1).seq).isEqualTo(3);
+ accountIndexedCounter.assertReindexOf(admin);
+ }
}
@Test
@UseSsh
public void adminCanAddOrRemoveSshKeyOnOtherAccount() throws Exception {
- // The test account should initially have exactly one ssh key
- List<SshKeyInfo> info = gApi.accounts().self().listSshKeys();
- assertThat(info).hasSize(1);
- assertSequenceNumbers(info);
- SshKeyInfo key = info.get(0);
- KeyPair keyPair = sshKeys.getKeyPair(admin);
- String initial = TestSshKeys.publicKey(keyPair, admin.email());
- assertThat(key.sshPublicKey).isEqualTo(initial);
- accountIndexedCounter.assertNoReindex();
-
- // Add a new key
- sender.clear();
- String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), user.email());
- gApi.accounts().id(user.username()).addSshKey(newKey);
- info = gApi.accounts().id(user.username()).listSshKeys();
- assertThat(info).hasSize(2);
- assertSequenceNumbers(info);
- accountIndexedCounter.assertReindexOf(user);
-
- assertThat(sender.getMessages()).hasSize(1);
- Message message = sender.getMessages().get(0);
- assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
- assertThat(message.body()).contains("new SSH keys have been added");
-
- // Delete key
- sender.clear();
- gApi.accounts().id(user.username()).deleteSshKey(1);
- info = gApi.accounts().id(user.username()).listSshKeys();
- assertThat(info).hasSize(1);
- accountIndexedCounter.assertReindexOf(user);
-
- assertThat(sender.getMessages()).hasSize(1);
- message = sender.getMessages().get(0);
- assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
- assertThat(message.body()).contains("SSH keys have been deleted");
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ // The test account should initially have exactly one ssh key
+ List<SshKeyInfo> info = gApi.accounts().self().listSshKeys();
+ assertThat(info).hasSize(1);
+ assertSequenceNumbers(info);
+ SshKeyInfo key = info.get(0);
+ KeyPair keyPair = sshKeys.getKeyPair(admin);
+ String initial = TestSshKeys.publicKey(keyPair, admin.email());
+ assertThat(key.sshPublicKey).isEqualTo(initial);
+ accountIndexedCounter.assertNoReindex();
+
+ // Add a new key
+ sender.clear();
+ String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), user.email());
+ gApi.accounts().id(user.username()).addSshKey(newKey);
+ info = gApi.accounts().id(user.username()).listSshKeys();
+ assertThat(info).hasSize(2);
+ assertSequenceNumbers(info);
+ accountIndexedCounter.assertReindexOf(user);
+
+ assertThat(sender.getMessages()).hasSize(1);
+ Message message = sender.getMessages().get(0);
+ assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
+ assertThat(message.body()).contains("new SSH keys have been added");
+
+ // Delete key
+ sender.clear();
+ gApi.accounts().id(user.username()).deleteSshKey(1);
+ info = gApi.accounts().id(user.username()).listSshKeys();
+ assertThat(info).hasSize(1);
+ accountIndexedCounter.assertReindexOf(user);
+
+ assertThat(sender.getMessages()).hasSize(1);
+ message = sender.getMessages().get(0);
+ assertThat(message.rcpt()).containsExactly(user.getEmailAddress());
+ assertThat(message.body()).contains("SSH keys have been deleted");
+ }
}
@Test
@@ -2472,40 +2104,47 @@ public class AccountIT extends AbstractDaemonTest {
public void userCannotAddSshKeyToOtherAccount() throws Exception {
String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email());
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(admin.username()).addSshKey(newKey);
+ assertThrows(AuthException.class, () -> gApi.accounts().id(admin.username()).addSshKey(newKey));
}
@Test
@UseSsh
public void userCannotDeleteSshKeyOfOtherAccount() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- gApi.accounts().id(admin.username()).deleteSshKey(0);
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.accounts().id(admin.username()).deleteSshKey(0));
}
// reindex is tested by {@link AbstractQueryAccountsTest#reindex}
@Test
public void reindexPermissions() throws Exception {
- // admin can reindex any account
- requestScopeOperations.setApiUser(admin.id());
- gApi.accounts().id(user.username()).index();
- accountIndexedCounter.assertReindexOf(user);
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ // admin can reindex any account
+ requestScopeOperations.setApiUser(admin.id());
+ gApi.accounts().id(user.username()).index();
+ accountIndexedCounter.assertReindexOf(user);
- // user can reindex own account
- requestScopeOperations.setApiUser(user.id());
- gApi.accounts().self().index();
- accountIndexedCounter.assertReindexOf(user);
+ // user can reindex own account
+ requestScopeOperations.setApiUser(user.id());
+ gApi.accounts().self().index();
+ accountIndexedCounter.assertReindexOf(user);
- // user cannot reindex any account
- exception.expect(AuthException.class);
- exception.expectMessage("modify account not permitted");
- gApi.accounts().id(admin.username()).index();
+ // user cannot reindex any account
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.accounts().id(admin.username()).index());
+ assertThat(thrown).hasMessageThat().contains("modify account not permitted");
+ }
}
@Test
public void checkConsistency() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
requestScopeOperations.resetCurrentApiUser();
// Create an account with a preferred email.
@@ -2560,22 +2199,21 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void checkMetaId() throws Exception {
// metaId is set when account is loaded
- assertThat(accounts.get(admin.id()).get().getAccount().getMetaId())
- .isEqualTo(getMetaId(admin.id()));
+ assertThat(accounts.get(admin.id()).get().account().metaId()).isEqualTo(getMetaId(admin.id()));
// metaId is set when account is created
AccountsUpdate au = accountsUpdateProvider.get();
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
AccountState accountState = au.insert("Create Test Account", accountId, u -> {});
- assertThat(accountState.getAccount().getMetaId()).isEqualTo(getMetaId(accountId));
+ assertThat(accountState.account().metaId()).isEqualTo(getMetaId(accountId));
// metaId is set when account is updated
Optional<AccountState> updatedAccountState =
au.update("Set Full Name", accountId, u -> u.setFullName("foo"));
assertThat(updatedAccountState).isPresent();
- Account updatedAccount = updatedAccountState.get().getAccount();
- assertThat(accountState.getAccount().getMetaId()).isNotEqualTo(updatedAccount.getMetaId());
- assertThat(updatedAccount.getMetaId()).isEqualTo(getMetaId(accountId));
+ Account updatedAccount = updatedAccountState.get().account();
+ assertThat(accountState.account().metaId()).isNotEqualTo(updatedAccount.metaId());
+ assertThat(updatedAccount.metaId()).isEqualTo(getMetaId(accountId));
}
private EmailInput newEmailInput(String email, boolean noConfirmation) {
@@ -2629,12 +2267,9 @@ public class AccountIT extends AbstractDaemonTest {
"@", "@foo", "-", "-foo", "_", "_foo", "!", "+", "{", "}", "*", "%", "#", "$", "&", "’",
"^", "=", "~");
for (String name : invalidNames) {
- try {
- gApi.accounts().create(name);
- fail(String.format("Expected BadRequestException for username [%s]", name));
- } catch (BadRequestException e) {
- assertThat(e).hasMessageThat().isEqualTo(String.format("Invalid username '%s'", name));
- }
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.accounts().create(name));
+ assertThat(thrown).hasMessageThat().isEqualTo(String.format("Invalid username '%s'", name));
}
}
@@ -2693,7 +2328,11 @@ public class AccountIT extends AbstractDaemonTest {
externalIds,
metaDataUpdateInternalFactory,
new RetryHelper(
- cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
+ cfg,
+ retryMetrics,
+ null,
+ new PluginSetContext<>(DynamicSet.emptySet(), PluginMetrics.DISABLED_INSTANCE),
+ r -> r.withBlockStrategy(noSleepBlockStrategy)),
extIdNotesFactory,
ident,
ident,
@@ -2719,9 +2358,9 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(doneBgUpdate.get()).isTrue();
assertThat(updatedAccountState).isPresent();
- Account updatedAccount = updatedAccountState.get().getAccount();
- assertThat(updatedAccount.getStatus()).isEqualTo(status);
- assertThat(updatedAccount.getFullName()).isEqualTo(fullName);
+ Account updatedAccount = updatedAccountState.get().account();
+ assertThat(updatedAccount.status()).isEqualTo(status);
+ assertThat(updatedAccount.fullName()).isEqualTo(fullName);
accountInfo = gApi.accounts().id(admin.id().get()).get();
assertThat(accountInfo.status).isEqualTo(status);
@@ -2746,6 +2385,7 @@ public class AccountIT extends AbstractDaemonTest {
cfg,
retryMetrics,
null,
+ new PluginSetContext<>(DynamicSet.emptySet(), PluginMetrics.DISABLED_INSTANCE),
r ->
r.withStopStrategy(StopStrategies.stopAfterAttempt(status.size()))
.withBlockStrategy(noSleepBlockStrategy)),
@@ -2770,17 +2410,14 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(accountInfo.status).isNull();
assertThat(accountInfo.name).isNotEqualTo(fullName);
- try {
- update.update("Set Full Name", admin.id(), u -> u.setFullName(fullName));
- fail("expected LockFailureException");
- } catch (LockFailureException e) {
- // Ignore, expected
- }
+ assertThrows(
+ LockFailureException.class,
+ () -> update.update("Set Full Name", admin.id(), u -> u.setFullName(fullName)));
assertThat(bgCounter.get()).isEqualTo(status.size());
- Account updatedAccount = accounts.get(admin.id()).get().getAccount();
- assertThat(updatedAccount.getStatus()).isEqualTo(Iterables.getLast(status));
- assertThat(updatedAccount.getFullName()).isEqualTo(admin.fullName());
+ Account updatedAccount = accounts.get(admin.id()).get().account();
+ assertThat(updatedAccount.status()).isEqualTo(Iterables.getLast(status));
+ assertThat(updatedAccount.fullName()).isEqualTo(admin.fullName());
accountInfo = gApi.accounts().id(admin.id().get()).get();
assertThat(accountInfo.status).isEqualTo(Iterables.getLast(status));
@@ -2803,7 +2440,11 @@ public class AccountIT extends AbstractDaemonTest {
externalIds,
metaDataUpdateInternalFactory,
new RetryHelper(
- cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
+ cfg,
+ retryMetrics,
+ null,
+ new PluginSetContext<>(DynamicSet.emptySet(), PluginMetrics.DISABLED_INSTANCE),
+ r -> r.withBlockStrategy(noSleepBlockStrategy)),
extIdNotesFactory,
ident,
ident,
@@ -2826,12 +2467,12 @@ public class AccountIT extends AbstractDaemonTest {
"Set Status",
admin.id(),
(a, u) -> {
- if ("A-1".equals(a.getAccount().getStatus())) {
+ if ("A-1".equals(a.account().status())) {
bgCounterA1.getAndIncrement();
u.setStatus("B-1");
}
- if ("A-2".equals(a.getAccount().getStatus())) {
+ if ("A-2".equals(a.account().status())) {
bgCounterA2.getAndIncrement();
u.setStatus("B-2");
}
@@ -2841,16 +2482,19 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(bgCounterA2.get()).isEqualTo(1);
assertThat(updatedAccountState).isPresent();
- assertThat(updatedAccountState.get().getAccount().getStatus()).isEqualTo("B-2");
- assertThat(accounts.get(admin.id()).get().getAccount().getStatus()).isEqualTo("B-2");
+ assertThat(updatedAccountState.get().account().status()).isEqualTo("B-2");
+ assertThat(accounts.get(admin.id()).get().account().status()).isEqualTo("B-2");
assertThat(gApi.accounts().id(admin.id().get()).get().status).isEqualTo("B-2");
}
@Test
public void atomicReadMofifyWriteExternalIds() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId extIdA1 = ExternalId.create("foo", "A-1", accountId);
accountsUpdateProvider
.get()
@@ -2869,7 +2513,11 @@ public class AccountIT extends AbstractDaemonTest {
externalIds,
metaDataUpdateInternalFactory,
new RetryHelper(
- cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
+ cfg,
+ retryMetrics,
+ null,
+ new PluginSetContext<>(DynamicSet.emptySet(), PluginMetrics.DISABLED_INSTANCE),
+ r -> r.withBlockStrategy(noSleepBlockStrategy)),
extIdNotesFactory,
ident,
ident,
@@ -2901,12 +2549,12 @@ public class AccountIT extends AbstractDaemonTest {
"Update External ID",
accountId,
(a, u) -> {
- if (a.getExternalIds().contains(extIdA1)) {
+ if (a.externalIds().contains(extIdA1)) {
bgCounterA1.getAndIncrement();
u.replaceExternalId(extIdA1, extIdB1);
}
- if (a.getExternalIds().contains(extIdA2)) {
+ if (a.externalIds().contains(extIdA2)) {
bgCounterA2.getAndIncrement();
u.replaceExternalId(extIdA2, extIdB2);
}
@@ -2916,8 +2564,8 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(bgCounterA2.get()).isEqualTo(1);
assertThat(updatedAccount).isPresent();
- assertThat(updatedAccount.get().getExternalIds()).containsExactly(extIdB2);
- assertThat(accounts.get(accountId).get().getExternalIds()).containsExactly(extIdB2);
+ assertThat(updatedAccount.get().externalIds()).containsExactly(extIdB2);
+ assertThat(accounts.get(accountId).get().externalIds()).containsExactly(extIdB2);
assertThat(
gApi.accounts().id(accountId.get()).getExternalIds().stream()
.map(i -> i.identity)
@@ -2929,7 +2577,7 @@ public class AccountIT extends AbstractDaemonTest {
public void stalenessChecker() throws Exception {
// Newly created account is not stale.
AccountInfo accountInfo = gApi.accounts().create(name("foo")).get();
- Account.Id accountId = new Account.Id(accountInfo._accountId);
+ Account.Id accountId = Account.id(accountInfo._accountId);
assertThat(stalenessChecker.isStale(accountId)).isFalse();
// Manually updating the user ref makes the index document stale.
@@ -3006,9 +2654,9 @@ public class AccountIT extends AbstractDaemonTest {
}
@Test
+ @UseClockStep
public void deleteAllDraftComments() throws Exception {
try {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
Project.NameKey project2 = projectOperations.newProject().create();
PushOneCommit.Result r1 = createChange();
@@ -3093,12 +2741,14 @@ public class AccountIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
createDraft(r, PushOneCommit.FILE_NAME, "draft");
requestScopeOperations.setApiUser(admin.id());
- try {
- gApi.accounts().id(user.id().get()).deleteDraftComments(new DeleteDraftCommentsInput());
- assert_().fail("expected AuthException");
- } catch (AuthException e) {
- assertThat(e).hasMessageThat().isEqualTo("Cannot delete drafts of other user");
- }
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.accounts()
+ .id(user.id().get())
+ .deleteDraftComments(new DeleteDraftCommentsInput()));
+ assertThat(thrown).hasMessageThat().isEqualTo("Cannot delete drafts of other user");
} finally {
cleanUpDrafts();
}
@@ -3107,7 +2757,7 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void deleteDraftCommentsSkipsInvisibleChanges() throws Exception {
try {
- createBranch(new Branch.NameKey(project, "secret"));
+ createBranch(BranchNameKey.create(project, "secret"));
PushOneCommit.Result r1 = createChange();
PushOneCommit.Result r2 = createChange("refs/for/secret");
@@ -3117,14 +2767,22 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(r1.getChangeId()).current().draftsAsList()).hasSize(1);
assertThat(gApi.changes().id(r2.getChangeId()).current().draftsAsList()).hasSize(1);
- block(project, "refs/heads/secret", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/secret").group(REGISTERED_USERS))
+ .update();
List<DeletedDraftCommentInfo> result =
gApi.accounts().self().deleteDraftComments(new DeleteDraftCommentsInput());
assertThat(result).hasSize(1);
assertThat(result.get(0).change.changeId).isEqualTo(r1.getChangeId());
assertThat(result.get(0).deleted.stream().map(c -> c.message)).containsExactly("draft a");
- removePermission(project, "refs/heads/secret", Permission.READ);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.READ).ref("refs/heads/secret"))
+ .update();
assertThat(gApi.changes().id(r1.getChangeId()).current().draftsAsList()).isEmpty();
// Draft still exists since change wasn't visible when drafts where deleted.
assertThat(gApi.changes().id(r2.getChangeId()).current().draftsAsList()).hasSize(1);
@@ -3155,22 +2813,23 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void userCannotGenerateNewHttpPasswordForOtherUser() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(admin.username()).generateHttpPassword();
+ assertThrows(
+ AuthException.class, () -> gApi.accounts().id(admin.username()).generateHttpPassword());
}
@Test
public void userCannotExplicitlySetHttpPassword() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().self().setHttpPassword("my-new-password");
+ assertThrows(
+ AuthException.class, () -> gApi.accounts().self().setHttpPassword("my-new-password"));
}
@Test
public void userCannotExplicitlySetHttpPasswordForOtherUser() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(admin.username()).setHttpPassword("my-new-password");
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().id(admin.username()).setHttpPassword("my-new-password"));
}
@Test
@@ -3185,8 +2844,8 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void userCannotRemoveHttpPasswordForOtherUser() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.accounts().id(admin.username()).setHttpPassword(null);
+ assertThrows(
+ AuthException.class, () -> gApi.accounts().id(admin.username()).setHttpPassword(null));
}
@Test
@@ -3214,9 +2873,11 @@ public class AccountIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(admin.id());
int userId = accountCreator.create().id().get();
assertThat(gApi.accounts().id(userId).get().username).isNull();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("username");
- gApi.accounts().id(userId).generateHttpPassword();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().id(userId).generateHttpPassword());
+ assertThat(thrown).hasMessageThat().contains("username");
}
private void createDraft(PushOneCommit.Result r, String path, String message) throws Exception {
@@ -3243,12 +2904,9 @@ public class AccountIT extends AbstractDaemonTest {
private static Correspondence<GroupInfo, String> getGroupToNameCorrespondence() {
return Correspondence.from(
- new BinaryPredicate<GroupInfo, String>() {
- @Override
- public boolean apply(GroupInfo actualGroup, String expectedName) {
- String groupName = actualGroup == null ? null : actualGroup.name;
- return Objects.equals(groupName, expectedName);
- }
+ (actualGroup, expectedName) -> {
+ String groupName = actualGroup == null ? null : actualGroup.name;
+ return Objects.equals(groupName, expectedName);
},
"has name");
}
@@ -3297,8 +2955,8 @@ public class AccountIT extends AbstractDaemonTest {
// Check via API.
FluentIterable<TestKey> expected = FluentIterable.from(expectedKeys);
Map<String, GpgKeyInfo> keyMap = gApi.accounts().self().listGpgKeys();
- assertThat(keyMap.keySet())
- .named("keys returned by listGpgKeys()")
+ assertWithMessage("keys returned by listGpgKeys()")
+ .that(keyMap.keySet())
.containsExactlyElementsIn(expected.transform(TestKey::getKeyIdString));
for (TestKey key : expected) {
@@ -3320,7 +2978,9 @@ public class AccountIT extends AbstractDaemonTest {
externalIds.byAccount(currAccountId, SCHEME_GPGKEY).stream()
.map(e -> e.key().id())
.collect(toSet());
- assertThat(actualFps).named("external IDs in database").containsExactlyElementsIn(expectedFps);
+ assertWithMessage("external IDs in database")
+ .that(actualFps)
+ .containsExactlyElementsIn(expectedFps);
// Check raw stored keys.
for (TestKey key : expected) {
@@ -3330,31 +2990,35 @@ public class AccountIT extends AbstractDaemonTest {
private static void assertKeyEquals(TestKey expected, GpgKeyInfo actual) {
String id = expected.getKeyIdString();
- assertThat(actual.id).named(id).isEqualTo(id);
- assertThat(actual.fingerprint)
- .named(id)
+ assertWithMessage(id).that(actual.id).isEqualTo(id);
+ assertWithMessage(id)
+ .that(actual.fingerprint)
.isEqualTo(Fingerprint.toString(expected.getPublicKey().getFingerprint()));
List<String> userIds = ImmutableList.copyOf(expected.getPublicKey().getUserIDs());
- assertThat(actual.userIds).named(id).containsExactlyElementsIn(userIds);
+ assertWithMessage(id).that(actual.userIds).containsExactlyElementsIn(userIds);
String key = actual.key;
- assertThat(key).named(id).startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n");
- assertThat(key).named(id).endsWith("-----END PGP PUBLIC KEY BLOCK-----\n");
+ assertWithMessage(id).that(key).startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n");
+ assertWithMessage(id).that(key).endsWith("-----END PGP PUBLIC KEY BLOCK-----\n");
assertThat(actual.status).isEqualTo(GpgKeyInfo.Status.TRUSTED);
assertThat(actual.problems).isEmpty();
}
private void addExternalIdEmail(TestAccount account, String email) throws Exception {
- requireNonNull(email);
- accountsUpdateProvider
- .get()
- .update(
- "Add Email",
- account.id(),
- u ->
- u.addExternalId(
- ExternalId.createWithEmail(name("test"), email, account.id(), email)));
- accountIndexedCounter.assertReindexOf(account);
- requestScopeOperations.setApiUser(account.id());
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ requireNonNull(email);
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Add Email",
+ account.id(),
+ u ->
+ u.addExternalId(
+ ExternalId.createWithEmail(name("test"), email, account.id(), email)));
+ accountIndexedCounter.assertReindexOf(account);
+ requestScopeOperations.setApiUser(account.id());
+ }
}
private Map<String, GpgKeyInfo> addGpgKey(String armored) throws Exception {
@@ -3362,12 +3026,16 @@ public class AccountIT extends AbstractDaemonTest {
}
private Map<String, GpgKeyInfo> addGpgKey(TestAccount account, String armored) throws Exception {
- Map<String, GpgKeyInfo> gpgKeys =
- gApi.accounts()
- .id(account.username())
- .putGpgKeys(ImmutableList.of(armored), ImmutableList.<String>of());
- accountIndexedCounter.assertReindexOf(gApi.accounts().id(account.username()).get());
- return gpgKeys;
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ Map<String, GpgKeyInfo> gpgKeys =
+ gApi.accounts()
+ .id(account.username())
+ .putGpgKeys(ImmutableList.of(armored), ImmutableList.<String>of());
+ accountIndexedCounter.assertReindexOf(gApi.accounts().id(account.username()).get());
+ return gpgKeys;
+ }
}
private Map<String, GpgKeyInfo> addGpgKeyNoReindex(String armored) throws Exception {
@@ -3395,26 +3063,6 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(Iterables.getOnlyElement(accounts)).isEqualTo(expectedAccount.id());
}
- private Config getAccountConfig(TestRepository<?> allUsersRepo) throws Exception {
- Config ac = new Config();
- try (TreeWalk tw =
- TreeWalk.forPath(
- allUsersRepo.getRepository(),
- AccountProperties.ACCOUNT_CONFIG,
- getHead(allUsersRepo.getRepository(), "HEAD").getTree())) {
- assertThat(tw).isNotNull();
- ac.fromText(
- new String(
- allUsersRepo
- .getRevWalk()
- .getObjectReader()
- .open(tw.getObjectId(0), OBJ_BLOB)
- .getBytes(),
- UTF_8));
- }
- return ac;
- }
-
private AccountApi accountIdApi() throws RestApiException {
return gApi.accounts().id(user.id().get());
}
@@ -3439,47 +3087,6 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(loginResponse.getStatusLine().getStatusCode()).isEqualTo(expectedHttpStatus);
}
- /** Checks if an account is indexed the correct number of times. */
- private static class AccountIndexedCounter implements AccountIndexedListener {
- private final AtomicLongMap<Integer> countsByAccount = AtomicLongMap.create();
-
- @Override
- public void onAccountIndexed(int id) {
- countsByAccount.incrementAndGet(id);
- }
-
- void clear() {
- countsByAccount.clear();
- }
-
- long getCount(Account.Id accountId) {
- return countsByAccount.get(accountId.get());
- }
-
- void assertReindexOf(TestAccount testAccount) {
- assertReindexOf(testAccount, 1);
- }
-
- void assertReindexOf(AccountInfo accountInfo) {
- assertReindexOf(new Account.Id(accountInfo._accountId), 1);
- }
-
- void assertReindexOf(TestAccount testAccount, int expectedCount) {
- assertThat(getCount(testAccount.id())).isEqualTo(expectedCount);
- assertThat(countsByAccount).hasSize(1);
- clear();
- }
-
- void assertReindexOf(Account.Id accountId, int expectedCount) {
- assertThat(getCount(accountId)).isEqualTo(expectedCount);
- countsByAccount.remove(accountId.get());
- }
-
- void assertNoReindex() {
- assertThat(countsByAccount).isEmpty();
- }
- }
-
private static class RefUpdateCounter implements GitReferenceUpdatedListener {
private final AtomicLongMap<String> countsByProjectRefs = AtomicLongMap.create();
@@ -3500,23 +3107,17 @@ public class AccountIT extends AbstractDaemonTest {
countsByProjectRefs.clear();
}
- long getCount(String projectRef) {
- return countsByProjectRefs.get(projectRef);
- }
-
void assertRefUpdateFor(String... projectRefs) {
- Map<String, Integer> expectedRefUpdateCounts = new HashMap<>();
+ Map<String, Long> expectedRefUpdateCounts = new HashMap<>();
for (String projectRef : projectRefs) {
- expectedRefUpdateCounts.put(projectRef, 1);
+ expectedRefUpdateCounts.put(projectRef, 1L);
}
assertRefUpdateFor(expectedRefUpdateCounts);
}
- void assertRefUpdateFor(Map<String, Integer> expectedProjectRefUpdateCounts) {
- for (Map.Entry<String, Integer> e : expectedProjectRefUpdateCounts.entrySet()) {
- assertThat(getCount(e.getKey())).isEqualTo(e.getValue());
- }
- assertThat(countsByProjectRefs).hasSize(expectedProjectRefUpdateCounts.size());
+ void assertRefUpdateFor(Map<String, Long> expectedProjectRefUpdateCounts) {
+ assertThat(countsByProjectRefs.asMap())
+ .containsExactlyEntriesIn(expectedProjectRefUpdateCounts);
clear();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
index 60a61d1788..72a826425f 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIndexerIT.java
@@ -17,10 +17,10 @@ package com.google.gerrit.acceptance.api.accounts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountConfig;
@@ -66,7 +66,7 @@ public class AccountIndexerIT {
List<AccountState> matchedAccountStates =
accountQueryProvider.get().byPreferredEmail(preferredEmail);
assertThat(matchedAccountStates).hasSize(1);
- assertThat(matchedAccountStates.get(0).getAccount().getId()).isEqualTo(accountId);
+ assertThat(matchedAccountStates.get(0).account().id()).isEqualTo(accountId);
}
@Test
@@ -82,7 +82,7 @@ public class AccountIndexerIT {
List<AccountState> matchedAccountStates =
accountQueryProvider.get().byPreferredEmail(preferredEmail);
assertThat(matchedAccountStates).hasSize(1);
- assertThat(matchedAccountStates.get(0).getAccount().getId()).isEqualTo(accountId);
+ assertThat(matchedAccountStates.get(0).account().id()).isEqualTo(accountId);
}
@Test
@@ -91,10 +91,10 @@ public class AccountIndexerIT {
loadAccountToCache(accountId);
String status = "ooo";
updateAccountWithoutCacheOrIndex(accountId, newAccountUpdate().setStatus(status).build());
- assertThat(accountCache.get(accountId).get().getAccount().getStatus()).isNull();
+ assertThat(accountCache.get(accountId).get().account().status()).isNull();
accountIndexer.index(accountId);
- assertThat(accountCache.get(accountId).get().getAccount().getStatus()).isEqualTo(status);
+ assertThat(accountCache.get(accountId).get().account().status()).isEqualTo(status);
}
@Test
@@ -109,7 +109,7 @@ public class AccountIndexerIT {
List<AccountState> matchedAccountStates =
accountQueryProvider.get().byPreferredEmail(preferredEmail);
assertThat(matchedAccountStates).hasSize(1);
- assertThat(matchedAccountStates.get(0).getAccount().getId()).isEqualTo(accountId);
+ assertThat(matchedAccountStates.get(0).account().id()).isEqualTo(accountId);
}
@Test
@@ -136,7 +136,7 @@ public class AccountIndexerIT {
private Account.Id createAccount(String name) throws RestApiException {
AccountInfo account = gApi.accounts().create(name).get();
- return new Account.Id(account._accountId);
+ return Account.id(account._accountId);
}
private void reloadAccountToCache(Account.Id accountId) {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountListenersIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountListenersIT.java
new file mode 100644
index 0000000000..80eff9606a
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountListenersIT.java
@@ -0,0 +1,212 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.accounts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.extensions.events.AccountActivationListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.validators.AccountActivationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the wiring of a real plugin's account listeners
+ *
+ * <p>This test really puts focus on the wiring of the account listeners. Tests for the inner
+ * workings of account activation/deactivation can be found in {@link AccountIT}.
+ */
+@TestPlugin(
+ name = "account-listener-it-plugin",
+ sysModule = "com.google.gerrit.acceptance.api.accounts.AccountListenersIT$Module")
+public class AccountListenersIT extends LightweightPluginDaemonTest {
+ @Inject private AccountOperations accountOperations;
+
+ public static class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ DynamicSet.bind(binder(), AccountActivationValidationListener.class).to(Validator.class);
+ DynamicSet.bind(binder(), AccountActivationListener.class).to(Listener.class);
+ }
+ }
+
+ Validator validator;
+ Listener listener;
+
+ @Before
+ public void setUp() {
+ validator = plugin.getSysInjector().getInstance(Validator.class);
+
+ listener = plugin.getSysInjector().getInstance(Listener.class);
+ }
+
+ @Test
+ public void testActivation() throws RestApiException {
+ int id = accountOperations.newAccount().inactive().create().get();
+
+ gApi.accounts().id(id).setActive(true);
+
+ validator.assertActivationValidation(id);
+ listener.assertActivated(id);
+ assertNoMoreEvents();
+ assertThat(gApi.accounts().id(id).getActive()).isTrue();
+ }
+
+ @Test
+ public void testActivationProhibited() throws RestApiException {
+ int id = accountOperations.newAccount().inactive().create().get();
+
+ validator.failActivationValidations();
+
+ assertThrows(
+ ResourceConflictException.class,
+ () -> {
+ gApi.accounts().id(id).setActive(true);
+ });
+
+ validator.assertActivationValidation(id);
+ // No call to activation listener as validation failed
+ assertNoMoreEvents();
+ assertThat(gApi.accounts().id(id).getActive()).isFalse();
+ }
+
+ @Test
+ public void testDeactivation() throws RestApiException {
+ int id = accountOperations.newAccount().active().create().get();
+
+ gApi.accounts().id(id).setActive(false);
+
+ validator.assertDeactivationValidation(id);
+ listener.assertDeactivated(id);
+ assertNoMoreEvents();
+ assertThat(gApi.accounts().id(id).getActive()).isFalse();
+ }
+
+ @Test
+ public void testDeactivationProhibited() throws RestApiException {
+ int id = accountOperations.newAccount().active().create().get();
+
+ validator.failDeactivationValidations();
+
+ assertThrows(
+ ResourceConflictException.class,
+ () -> {
+ gApi.accounts().id(id).setActive(false);
+ });
+
+ validator.assertDeactivationValidation(id);
+ // No call to activation listener as validation failed
+ assertNoMoreEvents();
+ assertThat(gApi.accounts().id(id).getActive()).isTrue();
+ }
+
+ private void assertNoMoreEvents() {
+ validator.assertNoMoreEvents();
+ listener.assertNoMoreEvents();
+ }
+
+ @Singleton
+ public static class Validator implements AccountActivationValidationListener {
+ private Integer lastIdActivationValidation;
+ private Integer lastIdDeactivationValidation;
+ private boolean failActivationValidations;
+ private boolean failDeactivationValidations;
+
+ @Override
+ public void validateActivation(AccountState account) throws ValidationException {
+ assertThat(lastIdActivationValidation).isNull();
+ lastIdActivationValidation = account.account().id().get();
+ if (failActivationValidations) {
+ throw new ValidationException("testing validation failure");
+ }
+ }
+
+ @Override
+ public void validateDeactivation(AccountState account) throws ValidationException {
+ assertThat(lastIdDeactivationValidation).isNull();
+ lastIdDeactivationValidation = account.account().id().get();
+ if (failDeactivationValidations) {
+ throw new ValidationException("testing validation failure");
+ }
+ }
+
+ public void failActivationValidations() {
+ failActivationValidations = true;
+ }
+
+ public void failDeactivationValidations() {
+ failDeactivationValidations = true;
+ }
+
+ private void assertNoMoreEvents() {
+ assertThat(lastIdActivationValidation).isNull();
+ assertThat(lastIdDeactivationValidation).isNull();
+ }
+
+ private void assertActivationValidation(int id) {
+ assertThat(lastIdActivationValidation).isEqualTo(id);
+ lastIdActivationValidation = null;
+ }
+
+ private void assertDeactivationValidation(int id) {
+ assertThat(lastIdDeactivationValidation).isEqualTo(id);
+ lastIdDeactivationValidation = null;
+ }
+ }
+
+ @Singleton
+ public static class Listener implements AccountActivationListener {
+ private Integer lastIdActivated;
+ private Integer lastIdDeactivated;
+
+ @Override
+ public void onAccountActivated(int id) {
+ assertThat(lastIdActivated).isNull();
+ lastIdActivated = id;
+ }
+
+ @Override
+ public void onAccountDeactivated(int id) {
+ assertThat(lastIdDeactivated).isNull();
+ lastIdDeactivated = id;
+ }
+
+ private void assertNoMoreEvents() {
+ assertThat(lastIdActivated).isNull();
+ assertThat(lastIdDeactivated).isNull();
+ }
+
+ private void assertDeactivated(int id) {
+ assertThat(lastIdDeactivated).isEqualTo(id);
+ lastIdDeactivated = null;
+ }
+
+ private void assertActivated(int id) {
+ assertThat(lastIdActivated).isEqualTo(id);
+ lastIdActivated = null;
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
index 7fd6af945f..5bc0473022 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
@@ -14,16 +14,19 @@
package com.google.gerrit.acceptance.api.accounts;
+import static com.google.common.truth.OptionalSubject.optionals;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountException;
@@ -137,7 +140,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
@Test
public void authenticateWithEmail() throws Exception {
String email = "foo@example.com";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key mailtoExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_MAILTO, email);
accountsUpdate.insert(
"Create Test Account",
@@ -152,7 +155,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
@Test
public void authenticateWithUsername() throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -167,7 +170,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
@Test
public void authenticateWithExternalUser() throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -183,7 +186,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
public void authenticateWithUsernameAndUpdateEmail() throws Exception {
String username = "foo";
String email = "foo@example.com";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -204,7 +207,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().getPreferredEmail()).isEqualTo(newEmail);
+ assertThat(accountState.get().account().preferredEmail()).isEqualTo(newEmail);
}
@Test
@@ -236,7 +239,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
private void authenticateWithUsernameAndUpdateDisplayName(AccountManager am) throws Exception {
String username = "foo";
String email = "foo@example.com";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -254,7 +257,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().getFullName()).isEqualTo(newName);
+ assertThat(accountState.get().account().fullName()).isEqualTo(newName);
}
@Test
@@ -264,7 +267,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
assertNoSuchExternalIds(gerritExtIdKey);
// Create orphaned SCHEME_GERRIT external ID.
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId gerritExtId = ExternalId.create(gerritExtIdKey, accountId);
try (Repository allUsersRepo = repoManager.openRepository(allUsers);
MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
@@ -274,15 +277,15 @@ public class AccountManagerIT extends AbstractDaemonTest {
}
AuthRequest who = AuthRequest.forUser(username);
- exception.expect(AccountException.class);
- exception.expectMessage("Authentication error, account not found");
- accountManager.authenticate(who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown).hasMessageThat().contains("Authentication error, account not found");
}
@Test
public void cannotAuthenticateWithInactiveAccount() throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -290,16 +293,16 @@ public class AccountManagerIT extends AbstractDaemonTest {
u -> u.setActive(false).addExternalId(ExternalId.create(gerritExtIdKey, accountId)));
AuthRequest who = AuthRequest.forUser(username);
- exception.expect(AccountException.class);
- exception.expectMessage("Authentication error, account inactive");
- accountManager.authenticate(who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown).hasMessageThat().contains("Authentication error, account inactive");
}
@Test
public void cannotActivateAccountOnAuthenticationWhenAutoUpdateAccountActiveStatusIsDisabled()
throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -309,9 +312,9 @@ public class AccountManagerIT extends AbstractDaemonTest {
AuthRequest who = AuthRequest.forUser(username);
who.setActive(true);
who.setAuthProvidesAccountActiveStatus(true);
- exception.expect(AccountException.class);
- exception.expectMessage("Authentication error, account inactive");
- accountManager.authenticate(who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown).hasMessageThat().contains("Authentication error, account inactive");
}
@Test
@@ -319,7 +322,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
public void activateAccountOnAuthenticationWhenAutoUpdateAccountActiveStatusIsEnabled()
throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -333,14 +336,14 @@ public class AccountManagerIT extends AbstractDaemonTest {
assertAuthResultForExistingAccount(authResult, accountId, gerritExtIdKey);
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().isActive()).isTrue();
+ assertThat(accountState.get().account().isActive()).isTrue();
}
@Test
public void cannotDeactivateAccountOnAuthenticationWhenAutoUpdateAccountActiveStatusIsDisabled()
throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -354,7 +357,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
assertAuthResultForExistingAccount(authResult, accountId, gerritExtIdKey);
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().isActive()).isTrue();
+ assertThat(accountState.get().account().isActive()).isTrue();
}
@Test
@@ -362,7 +365,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
public void deactivateAccountOnAuthenticationWhenAutoUpdateAccountActiveStatusIsEnabled()
throws Exception {
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -372,16 +375,13 @@ public class AccountManagerIT extends AbstractDaemonTest {
AuthRequest who = AuthRequest.forUser(username);
who.setActive(false);
who.setAuthProvidesAccountActiveStatus(true);
- try {
- accountManager.authenticate(who);
- fail("Expected AccountException");
- } catch (AccountException e) {
- assertThat(e).hasMessageThat().isEqualTo("Authentication error, account inactive");
- }
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown).hasMessageThat().isEqualTo("Authentication error, account inactive");
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().isActive()).isFalse();
+ assertThat(accountState.get().account().isActive()).isFalse();
}
@Test
@@ -390,7 +390,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -400,9 +400,11 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Try to authenticate with this email to create a new account with a SCHEME_MAILTO external ID.
// Expect that this fails because the email is already assigned to the other account.
AuthRequest who = AuthRequest.forEmail(email);
- exception.expect(AccountException.class);
- exception.expectMessage("Email 'foo@example.com' in use by another account");
- accountManager.authenticate(who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Email 'foo@example.com' in use by another account");
}
@Test
@@ -411,7 +413,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -422,9 +424,11 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Expect that this fails because the email is already assigned to the other account.
AuthRequest who = AuthRequest.forUser("bar");
who.setEmailAddress(email);
- exception.expect(AccountException.class);
- exception.expectMessage("Email 'foo@example.com' in use by another account");
- accountManager.authenticate(who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Email 'foo@example.com' in use by another account");
}
@Test
@@ -434,7 +438,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with a SCHEME_GERRIT external ID and an email.
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -444,7 +448,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
.addExternalId(ExternalId.createWithEmail(gerritExtIdKey, accountId, email)));
// Create another account with an SCHEME_EXTERNAL external ID that occupies the new email.
- Account.Id accountId2 = new Account.Id(seq.nextAccountId());
+ Account.Id accountId2 = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, "bar");
accountsUpdate.insert(
"Create Test Account",
@@ -455,12 +459,11 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Expect that this fails because the new email is already assigned to the other account.
AuthRequest who = AuthRequest.forUser(username);
who.setEmailAddress(newEmail);
- try {
- accountManager.authenticate(who);
- fail("Expected AccountException");
- } catch (AccountException e) {
- assertThat(e).hasMessageThat().isEqualTo("Email 'bar@example.com' in use by another account");
- }
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.authenticate(who));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Email 'bar@example.com' in use by another account");
// Verify that the email in the external ID was not updated.
Optional<ExternalId> gerritExtId = externalIds.get(gerritExtIdKey);
@@ -470,7 +473,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Verify that the preferred email was not updated.
Optional<AccountState> accountState = accounts.get(accountId);
assertThat(accountState).isPresent();
- assertThat(accountState.get().getAccount().getPreferredEmail()).isEqualTo(email);
+ assertThat(accountState.get().account().preferredEmail()).isEqualTo(email);
}
@Test
@@ -480,7 +483,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with a SCHEME_GERRIT external ID
String username = "foo";
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
accountsUpdate.insert(
"Create Test Account",
accountId,
@@ -502,20 +505,20 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Verify that the account external ids with scheme 'mailto:' contains the email
AccountState account = accounts.get(authResult.getAccountId()).get();
- ImmutableSet<ExternalId> accountExternalIds = account.getExternalIds(ExternalId.SCHEME_MAILTO);
+ ImmutableSet<ExternalId> accountExternalIds = account.externalIds();
assertThat(accountExternalIds).isNotEmpty();
Set<String> emails = ExternalId.getEmails(accountExternalIds).collect(toSet());
assertThat(emails).contains(email);
// Verify the preferred email
- assertThat(account.getAccount().getPreferredEmail()).isEqualTo(email);
+ assertThat(account.account().preferredEmail()).isEqualTo(email);
}
@Test
public void linkNewExternalId() throws Exception {
// Create an account with a SCHEME_GERRIT external ID and no email
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username);
accountsUpdate.insert(
"Create Test Account",
@@ -539,7 +542,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
public void updateExternalIdOnLink() throws Exception {
// Create an account with a SCHEME_GERRIT external ID and no email
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -562,7 +565,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
public void cannotLinkExternalIdThatIsAlreadyUsed() throws Exception {
// Create an account with a SCHEME_EXTERNAL external ID
String username1 = "foo";
- Account.Id accountId1 = new Account.Id(seq.nextAccountId());
+ Account.Id accountId1 = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey1 = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username1);
accountsUpdate.insert(
"Create Test Account",
@@ -571,7 +574,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create another account with a SCHEME_EXTERNAL external ID
String username2 = "bar";
- Account.Id accountId2 = new Account.Id(seq.nextAccountId());
+ Account.Id accountId2 = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey2 = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username2);
accountsUpdate.insert(
"Create Test Account",
@@ -581,9 +584,11 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Try to link external ID of the first account to the second account.
// Expect that this fails because the external ID is already assigned to the first account.
AuthRequest who = AuthRequest.forExternalUser(username1);
- exception.expect(AccountException.class);
- exception.expectMessage("Identity 'external:foo' in use by another account");
- accountManager.link(accountId2, who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.link(accountId2, who));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Identity 'external:foo' in use by another account");
}
@Test
@@ -592,7 +597,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -601,7 +606,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create another account with a SCHEME_GERRIT external ID and no email
String username2 = "foo";
- Account.Id accountId2 = new Account.Id(seq.nextAccountId());
+ Account.Id accountId2 = Account.id(seq.nextAccountId());
ExternalId.Key gerritExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_GERRIT, username2);
accountsUpdate.insert(
"Create Test Account",
@@ -611,9 +616,11 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Try to link the email to the second account (via a new MAILTO external ID) and expect that
// this fails because the email is already assigned to the first account.
AuthRequest who = AuthRequest.forEmail(email);
- exception.expect(AccountException.class);
- exception.expectMessage("Email 'foo@example.com' in use by another account");
- accountManager.link(accountId2, who);
+ AccountException thrown =
+ assertThrows(AccountException.class, () -> accountManager.link(accountId2, who));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Email 'foo@example.com' in use by another account");
}
@Test
@@ -622,7 +629,7 @@ public class AccountManagerIT extends AbstractDaemonTest {
// Create an account with an SCHEME_EXTERNAL external ID that occupies the email.
String username = "foo";
- Account.Id accountId = new Account.Id(seq.nextAccountId());
+ Account.Id accountId = Account.id(seq.nextAccountId());
ExternalId.Key externalExtIdKey = ExternalId.Key.create(ExternalId.SCHEME_EXTERNAL, username);
accountsUpdate.insert(
"Create Test Account",
@@ -637,7 +644,10 @@ public class AccountManagerIT extends AbstractDaemonTest {
private void assertNoSuchExternalIds(ExternalId.Key... extIdKeys) throws Exception {
for (ExternalId.Key extIdKey : extIdKeys) {
- assertThat(externalIds.get(extIdKey)).named(extIdKey.get()).isEmpty();
+ assertWithMessage(extIdKey.get())
+ .about(optionals())
+ .that(externalIds.get(extIdKey))
+ .isEmpty();
}
}
@@ -658,13 +668,15 @@ public class AccountManagerIT extends AbstractDaemonTest {
@Nullable String expectedEmail)
throws Exception {
Optional<ExternalId> extId = externalIds.get(extIdKey);
- assertThat(extId).named(extIdKey.get()).isPresent();
+ assertWithMessage(extIdKey.get()).about(optionals()).that(extId).isPresent();
if (expectedAccountId != null) {
- assertThat(extId.get().accountId())
- .named("account ID of " + extIdKey.get())
+ assertWithMessage("account ID of " + extIdKey.get())
+ .that(extId.get().accountId())
.isEqualTo(expectedAccountId);
}
- assertThat(extId.get().email()).named("email of " + extIdKey.get()).isEqualTo(expectedEmail);
+ assertWithMessage("email of " + extIdKey.get())
+ .that(extId.get().email())
+ .isEqualTo(expectedEmail);
}
private void assertAuthResultForNewAccount(
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
index a4a5745e0a..5550d98cc4 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
@@ -16,19 +16,22 @@ package com.google.gerrit.acceptance.api.accounts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -44,21 +47,17 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.lib.Config;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
+@UseClockStep
public class AgreementsIT extends AbstractDaemonTest {
private ContributorAgreement caAutoVerify;
private ContributorAgreement caNoAutoVerify;
@@ -81,7 +80,7 @@ public class AgreementsIT extends AbstractDaemonTest {
AccountGroup.UUID g = groupOperations.newGroup().name(name).create();
GroupApi groupApi = gApi.groups().id(g.get());
groupApi.description("CLA test group");
- InternalGroup caGroup = group(new AccountGroup.UUID(groupApi.detail().id));
+ InternalGroup caGroup = group(AccountGroup.uuid(groupApi.detail().id));
GroupReference groupRef = new GroupReference(caGroup.getGroupUUID(), caGroup.getName());
PermissionRule rule = new PermissionRule(groupRef);
rule.setAction(PermissionRule.Action.ALLOW);
@@ -111,16 +110,6 @@ public class AgreementsIT extends AbstractDaemonTest {
return cfg;
}
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
-
@Before
public void setUp() throws Exception {
caAutoVerify = configureContributorAgreement(true);
@@ -145,17 +134,21 @@ public class AgreementsIT extends AbstractDaemonTest {
@Test
public void signNonExistingAgreement() throws Exception {
assume().that(isContributorAgreementsEnabled()).isTrue();
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("contributor agreement not found");
- gApi.accounts().self().signAgreement("does-not-exist");
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.accounts().self().signAgreement("does-not-exist"));
+ assertThat(thrown).hasMessageThat().contains("contributor agreement not found");
}
@Test
public void signAgreementNoAutoVerify() throws Exception {
assume().that(isContributorAgreementsEnabled()).isTrue();
- exception.expect(BadRequestException.class);
- exception.expectMessage("cannot enter a non-autoVerify agreement");
- gApi.accounts().self().signAgreement(caNoAutoVerify.getName());
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.accounts().self().signAgreement(caNoAutoVerify.getName()));
+ assertThat(thrown).hasMessageThat().contains("cannot enter a non-autoVerify agreement");
}
@Test
@@ -188,33 +181,40 @@ public class AgreementsIT extends AbstractDaemonTest {
public void signAgreementAsOtherUser() throws Exception {
assume().that(isContributorAgreementsEnabled()).isTrue();
assertThat(gApi.accounts().self().get().name).isNotEqualTo("admin");
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to enter contributor agreement");
- gApi.accounts().id("admin").signAgreement(caAutoVerify.getName());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().id("admin").signAgreement(caAutoVerify.getName()));
+ assertThat(thrown).hasMessageThat().contains("not allowed to enter contributor agreement");
}
@Test
public void signAgreementAnonymous() throws Exception {
requestScopeOperations.setApiUserAnonymous();
- exception.expect(AuthException.class);
- exception.expectMessage("Authentication required");
- gApi.accounts().self().signAgreement(caAutoVerify.getName());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.accounts().self().signAgreement(caAutoVerify.getName()));
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
}
@Test
public void agreementsDisabledSign() throws Exception {
assume().that(isContributorAgreementsEnabled()).isFalse();
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("contributor agreements disabled");
- gApi.accounts().self().signAgreement(caAutoVerify.getName());
+ MethodNotAllowedException thrown =
+ assertThrows(
+ MethodNotAllowedException.class,
+ () -> gApi.accounts().self().signAgreement(caAutoVerify.getName()));
+ assertThat(thrown).hasMessageThat().contains("contributor agreements disabled");
}
@Test
public void agreementsDisabledList() throws Exception {
assume().that(isContributorAgreementsEnabled()).isFalse();
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("contributor agreements disabled");
- gApi.accounts().self().listAgreements();
+ MethodNotAllowedException thrown =
+ assertThrows(
+ MethodNotAllowedException.class, () -> gApi.accounts().self().listAgreements());
+ assertThat(thrown).hasMessageThat().contains("contributor agreements disabled");
}
@Test
@@ -233,9 +233,9 @@ public class AgreementsIT extends AbstractDaemonTest {
// Revert is not allowed when CLA is required but not signed
requestScopeOperations.setApiUser(user.id());
setUseContributorAgreements(InheritableBoolean.TRUE);
- exception.expect(AuthException.class);
- exception.expectMessage("Contributor Agreement");
- gApi.changes().id(change.changeId).revert();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(change.changeId).revert());
+ assertThat(thrown).hasMessageThat().contains("Contributor Agreement");
}
@Test
@@ -287,9 +287,10 @@ public class AgreementsIT extends AbstractDaemonTest {
CherryPickInput in = new CherryPickInput();
in.destination = dest.ref;
in.message = change.subject;
- exception.expect(AuthException.class);
- exception.expectMessage("Contributor Agreement");
- gApi.changes().id(change.changeId).current().cherryPick(in);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(change.changeId).current().cherryPick(in));
+ assertThat(thrown).hasMessageThat().contains("Contributor Agreement");
}
@Test
@@ -302,12 +303,9 @@ public class AgreementsIT extends AbstractDaemonTest {
// Create a change is not allowed when CLA is required but not signed
setUseContributorAgreements(InheritableBoolean.TRUE);
- try {
- gApi.changes().create(newChangeInput());
- fail("Expected AuthException");
- } catch (AuthException e) {
- assertThat(e.getMessage()).contains("Contributor Agreement");
- }
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().create(newChangeInput()));
+ assertThat(thrown).hasMessageThat().contains("Contributor Agreement");
// Sign the agreement
gApi.accounts().self().signAgreement(caAutoVerify.getName());
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index c905d3fee0..6717fb7ec1 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -16,8 +16,11 @@ package com.google.gerrit.acceptance.api.accounts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -26,23 +29,18 @@ import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.config.DownloadScheme;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class GeneralPreferencesIT extends AbstractDaemonTest {
- @Inject private DynamicMap<DownloadScheme> downloadSchemes;
+ @Inject private ExtensionRegistry extensionRegistry;
private TestAccount user42;
@@ -63,15 +61,13 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
new MenuItem("Edits", "#/q/has:edit", null),
new MenuItem("Watched Changes", "#/q/is:watched+is:open", null),
new MenuItem("Starred Changes", "#/q/is:starred", null),
- new MenuItem("Groups", "#/groups/self", null));
+ new MenuItem("Groups", "#/settings/#Groups", null));
assertThat(o.changeTable).isEmpty();
GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
// change all default values
i.changesPerPage *= -1;
- i.showSiteHeader ^= true;
- i.useFlashClipboard ^= true;
i.dateFormat = DateFormat.US;
i.timeFormat = TimeFormat.HHMM_24;
i.emailStrategy = EmailStrategy.DISABLED;
@@ -84,7 +80,6 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
i.legacycidInChangeTable ^= true;
i.muteCommonPathPrefixes ^= true;
i.signedOffBy ^= true;
- i.reviewCategoryStrategy = ReviewCategoryStrategy.ABBREV;
i.diffView = DiffView.UNIFIED_DIFF;
i.my = new ArrayList<>();
i.my.add(new MenuItem("name", "url"));
@@ -155,9 +150,11 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
i.my = new ArrayList<>();
i.my.add(new MenuItem(null, "url"));
- exception.expect(BadRequestException.class);
- exception.expectMessage("name for menu item is required");
- gApi.accounts().id(user42.id().toString()).setPreferences(i);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.accounts().id(user42.id().toString()).setPreferences(i));
+ assertThat(thrown).hasMessageThat().contains("name for menu item is required");
}
@Test
@@ -166,9 +163,11 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
i.my = new ArrayList<>();
i.my.add(new MenuItem("name", null));
- exception.expect(BadRequestException.class);
- exception.expectMessage("URL for menu item is required");
- gApi.accounts().id(user42.id().toString()).setPreferences(i);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.accounts().id(user42.id().toString()).setPreferences(i));
+ assertThat(thrown).hasMessageThat().contains("URL for menu item is required");
}
@Test
@@ -186,18 +185,20 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
i.downloadScheme = "foo";
- exception.expect(BadRequestException.class);
- exception.expectMessage("Unsupported download scheme: " + i.downloadScheme);
- gApi.accounts().id(user42.id().toString()).setPreferences(i);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.accounts().id(user42.id().toString()).setPreferences(i));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Unsupported download scheme: " + i.downloadScheme);
}
@Test
public void setDownloadScheme() throws Exception {
String schemeName = "foo";
- RegistrationHandle registrationHandle =
- ((PrivateInternals_DynamicMapImpl<DownloadScheme>) downloadSchemes)
- .put("myPlugin", schemeName, Providers.of(new TestDownloadScheme()));
- try {
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(new TestDownloadScheme(), schemeName)) {
GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
i.downloadScheme = schemeName;
@@ -206,8 +207,6 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
o = gApi.accounts().id(user42.id().toString()).getPreferences();
assertThat(o.downloadScheme).isEqualTo(schemeName);
- } finally {
- registrationHandle.remove();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
index 925c66ab7b..8aebc69845 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
@@ -15,10 +15,11 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.concurrent.TimeUnit.HOURS;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
@@ -26,13 +27,15 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.AbandonUtil;
import com.google.gerrit.server.config.ChangeCleanupConfig;
@@ -46,8 +49,9 @@ import org.junit.Test;
public class AbandonIT extends AbstractDaemonTest {
@Inject private AbandonUtil abandonUtil;
- @Inject private RequestScopeOperations requestScopeOperations;
@Inject private ChangeCleanupConfig cleanupConfig;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
@Test
public void abandon() throws Exception {
@@ -59,9 +63,9 @@ public class AbandonIT extends AbstractDaemonTest {
assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED);
assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("abandoned");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("change is abandoned");
- gApi.changes().id(changeId).abandon();
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.changes().id(changeId).abandon());
+ assertThat(thrown).hasMessageThat().contains("change is abandoned");
}
@Test
@@ -89,24 +93,29 @@ public class AbandonIT extends AbstractDaemonTest {
String project2Name = name("Project2");
gApi.projects().create(project1Name);
gApi.projects().create(project2Name);
- TestRepository<InMemoryRepository> project1 = cloneProject(new Project.NameKey(project1Name));
- TestRepository<InMemoryRepository> project2 = cloneProject(new Project.NameKey(project2Name));
+ TestRepository<InMemoryRepository> project1 = cloneProject(Project.nameKey(project1Name));
+ TestRepository<InMemoryRepository> project2 = cloneProject(Project.nameKey(project2Name));
CurrentUser user = atrScope.get().getUser();
PushOneCommit.Result a = createChange(project1, "master", "x", "x", "x", "");
PushOneCommit.Result b = createChange(project2, "master", "x", "x", "x", "");
List<ChangeData> list = ImmutableList.of(a.getChange(), b.getChange());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format("Project name \"%s\" doesn't match \"%s\"", project2Name, project1Name));
- batchAbandon.batchAbandon(batchUpdateFactory, new Project.NameKey(project1Name), user, list);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ batchAbandon.batchAbandon(
+ batchUpdateFactory, Project.nameKey(project1Name), user, list));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format("Project name \"%s\" doesn't match \"%s\"", project2Name, project1Name));
}
@Test
+ @UseClockStep
@GerritConfig(name = "changeCleanup.abandonAfter", value = "1w")
public void abandonInactiveOpenChanges() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
-
// create 2 changes which will be abandoned ...
int id1 = createChange().getChange().getId().get();
int id2 = createChange().getChange().getId().get();
@@ -157,9 +166,9 @@ public class AbandonIT extends AbstractDaemonTest {
String changeId = r.getChangeId();
assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("abandon not permitted");
- gApi.changes().id(changeId).abandon();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).abandon());
+ assertThat(thrown).hasMessageThat().contains("abandon not permitted");
}
@Test
@@ -167,7 +176,11 @@ public class AbandonIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW);
- grant(project, "refs/heads/master", Permission.ABANDON, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId).abandon();
assertThat(info(changeId).status).isEqualTo(ChangeStatus.ABANDONED);
@@ -188,9 +201,9 @@ public class AbandonIT extends AbstractDaemonTest {
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("restored");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("change is new");
- gApi.changes().id(changeId).restore();
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.changes().id(changeId).restore());
+ assertThat(thrown).hasMessageThat().contains("change is new");
}
@Test
@@ -201,9 +214,9 @@ public class AbandonIT extends AbstractDaemonTest {
gApi.changes().id(changeId).abandon();
requestScopeOperations.setApiUser(user.id());
assertThat(info(changeId).status).isEqualTo(ChangeStatus.ABANDONED);
- exception.expect(AuthException.class);
- exception.expectMessage("restore not permitted");
- gApi.changes().id(changeId).restore();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).restore());
+ assertThat(thrown).hasMessageThat().contains("restore not permitted");
}
private List<Integer> toChangeNumbers(List<ChangeInfo> changes) {
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index f59238c532..6a360c9610 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -15,12 +15,21 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.blockLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
import static com.google.gerrit.extensions.client.ListChangesOption.CHANGE_ACTIONS;
import static com.google.gerrit.extensions.client.ListChangesOption.CHECK;
@@ -38,20 +47,23 @@ import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.CacheStatsSubject.assertThat;
+import static com.google.gerrit.truth.CacheStatsSubject.cloneStats;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheStats;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -60,20 +72,33 @@ import com.google.common.collect.Lists;
import com.google.common.truth.ThrowableSubject;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.ChangeIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseTimezone;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
@@ -87,7 +112,6 @@ import com.google.gerrit.extensions.api.changes.NotifyInfo;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
-import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
import com.google.gerrit.extensions.api.changes.ReviewResult;
@@ -98,7 +122,6 @@ import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ConfigInput;
-import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.client.ChangeStatus;
@@ -119,12 +142,8 @@ import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.common.MergePatchSetInput;
-import com.google.gerrit.extensions.common.PureRevertInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -135,22 +154,21 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.query.PostFilterPredicate;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.git.ChangeMessageModifier;
+import com.google.gerrit.server.change.testing.TestChangeETagComputation;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
+import com.google.gerrit.server.patch.IntraLineDiff;
+import com.google.gerrit.server.patch.IntraLineDiffKey;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.ChangeOperatorFactory;
import com.google.gerrit.server.restapi.change.PostReview;
@@ -159,9 +177,9 @@ import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
+import com.google.inject.name.Named;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -184,49 +202,31 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushResult;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
+@UseTimezone(timezone = "US/Eastern")
public class ChangeIT extends AbstractDaemonTest {
- private String systemTimeZone;
@Inject private AccountOperations accountOperations;
@Inject private ChangeIndexCollection changeIndexCollection;
- @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
- @Inject private DynamicSet<ChangeMessageModifier> changeMessageModifiers;
@Inject private GroupOperations groupOperations;
@Inject private IndexConfig indexConfig;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
- private ChangeIndexedCounter changeIndexedCounter;
- private RegistrationHandle changeIndexedCounterHandle;
+ @Inject
+ @Named("diff")
+ private Cache<PatchListKey, PatchList> fileCache;
- @Before
- public void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- System.setProperty("user.timezone", systemTimeZone);
- }
+ @Inject
+ @Named("diff_intraline")
+ private Cache<IntraLineDiffKey, IntraLineDiff> intraCache;
- @Before
- public void addChangeIndexedCounter() {
- changeIndexedCounter = new ChangeIndexedCounter();
- changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
- }
-
- @After
- public void removeChangeIndexedCounter() {
- if (changeIndexedCounterHandle != null) {
- changeIndexedCounterHandle.remove();
- }
- }
+ @Inject
+ @Named("diff_summary")
+ private Cache<DiffSummaryKey, DiffSummary> diffSummaryCache;
@Test
public void get() throws Exception {
@@ -252,6 +252,48 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void diffStatShouldComputeInsertionsAndDeletions() throws Exception {
+ String fileName = "a_new_file.txt";
+ String fileContent = "First line\nSecond line\n";
+ PushOneCommit.Result result = createChange("Add a file", fileName, fileContent);
+ String triplet = project.get() + "~master~" + result.getChangeId();
+ ChangeInfo change = gApi.changes().id(triplet).get();
+ assertThat(change.insertions).isNotNull();
+ assertThat(change.deletions).isNotNull();
+ }
+
+ @Test
+ public void diffStatShouldSkipInsertionsAndDeletions() throws Exception {
+ String fileName = "a_new_file.txt";
+ String fileContent = "First line\nSecond line\n";
+ PushOneCommit.Result result = createChange("Add a file", fileName, fileContent);
+ String triplet = project.get() + "~master~" + result.getChangeId();
+ ChangeInfo change =
+ gApi.changes().id(triplet).get(ImmutableList.of(ListChangesOption.SKIP_DIFFSTAT));
+ assertThat(change.insertions).isNull();
+ assertThat(change.deletions).isNull();
+ }
+
+ @Test
+ public void skipDiffstatOptionAvoidsAllDiffComputations() throws Exception {
+ String fileName = "a_new_file.txt";
+ String fileContent = "First line\nSecond line\n";
+ PushOneCommit.Result result = createChange("Add a file", fileName, fileContent);
+ String triplet = project.get() + "~master~" + result.getChangeId();
+ CacheStats startPatch = cloneStats(fileCache.stats());
+ CacheStats startIntra = cloneStats(intraCache.stats());
+ CacheStats startSummary = cloneStats(diffSummaryCache.stats());
+ gApi.changes().id(triplet).get(ImmutableList.of(ListChangesOption.SKIP_DIFFSTAT));
+
+ assertThat(fileCache.stats()).since(startPatch).hasMissCount(0);
+ assertThat(fileCache.stats()).since(startPatch).hasHitCount(0);
+ assertThat(intraCache.stats()).since(startIntra).hasMissCount(0);
+ assertThat(intraCache.stats()).since(startIntra).hasHitCount(0);
+ assertThat(diffSummaryCache.stats()).since(startSummary).hasMissCount(0);
+ assertThat(diffSummaryCache.stats()).since(startSummary).hasHitCount(0);
+ }
+
+ @Test
public void skipMergeable() throws Exception {
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
@@ -277,9 +319,9 @@ public class ChangeIT extends AbstractDaemonTest {
String changeId = rwip.getChangeId();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("toggle work in progress state not permitted");
- gApi.changes().id(changeId).setWorkInProgress();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).setWorkInProgress());
+ assertThat(thrown).hasMessageThat().contains("toggle work in progress state not permitted");
}
@Test
@@ -300,7 +342,11 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().create(new ChangeInput(project.get(), "master", "Test Change")).get().id;
com.google.gerrit.acceptance.TestAccount user2 = accountCreator.user2();
- grant(project, "refs/*", Permission.OWNER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user2.id());
gApi.changes().id(changeId).setWorkInProgress();
assertThat(gApi.changes().id(changeId).get().workInProgress).isTrue();
@@ -323,9 +369,9 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).setWorkInProgress();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("toggle work in progress state not permitted");
- gApi.changes().id(changeId).setReadyForReview();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).setReadyForReview());
+ assertThat(thrown).hasMessageThat().contains("toggle work in progress state not permitted");
}
@Test
@@ -348,7 +394,11 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).setWorkInProgress();
com.google.gerrit.acceptance.TestAccount user2 = accountCreator.user2();
- grant(project, "refs/*", Permission.OWNER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user2.id());
gApi.changes().id(changeId).setReadyForReview();
assertThat(gApi.changes().id(changeId).get().workInProgress).isNull();
@@ -367,7 +417,7 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
- public void pendingReviewersInNoteDb() throws Exception {
+ public void pendingReviewers() throws Exception {
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(conf);
@@ -415,14 +465,13 @@ public class ChangeIT extends AbstractDaemonTest {
.reviewer("byemail2@example.com")
.reviewer("byemail3@example.com", CC, false)
.reviewer("byemail4@example.com", CC, false);
- ReviewResult result = gApi.changes().id(changeId).revision("current").review(in);
+ ReviewResult result = gApi.changes().id(changeId).current().review(in);
assertThat(result.reviewers).isNotEmpty();
ChangeInfo info = gApi.changes().id(changeId).get();
Function<Collection<AccountInfo>, Collection<String>> toEmails =
ais -> ais.stream().map(ai -> ai.email).collect(toSet());
assertThat(toEmails.apply(info.pendingReviewers.get(REVIEWER)))
- .containsExactly(
- admin.email(), email1, email2, "byemail1@example.com", "byemail2@example.com");
+ .containsExactly(email1, email2, "byemail1@example.com", "byemail2@example.com");
assertThat(toEmails.apply(info.pendingReviewers.get(CC)))
.containsExactly(email3, email4, "byemail3@example.com", "byemail4@example.com");
assertThat(info.pendingReviewers.get(REMOVED)).isNull();
@@ -434,7 +483,7 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).reviewer("byemail3@example.com").remove();
info = gApi.changes().id(changeId).get();
assertThat(toEmails.apply(info.pendingReviewers.get(REVIEWER)))
- .containsExactly(admin.email(), email2, "byemail2@example.com");
+ .containsExactly(email2, "byemail2@example.com");
assertThat(toEmails.apply(info.pendingReviewers.get(CC)))
.containsExactly(email4, "byemail4@example.com");
assertThat(toEmails.apply(info.pendingReviewers.get(REMOVED)))
@@ -442,10 +491,10 @@ public class ChangeIT extends AbstractDaemonTest {
// "Undo" a removal.
in = ReviewInput.noScore().reviewer(email1);
- gApi.changes().id(changeId).revision("current").review(in);
+ gApi.changes().id(changeId).current().review(in);
info = gApi.changes().id(changeId).get();
assertThat(toEmails.apply(info.pendingReviewers.get(REVIEWER)))
- .containsExactly(admin.email(), email1, email2, "byemail2@example.com");
+ .containsExactly(email1, email2, "byemail2@example.com");
assertThat(toEmails.apply(info.pendingReviewers.get(CC)))
.containsExactly(email4, "byemail4@example.com");
assertThat(toEmails.apply(info.pendingReviewers.get(REMOVED)))
@@ -456,7 +505,7 @@ public class ChangeIT extends AbstractDaemonTest {
info = gApi.changes().id(changeId).get();
assertThat(info.pendingReviewers).isEmpty();
assertThat(toEmails.apply(info.reviewers.get(REVIEWER)))
- .containsExactly(admin.email(), email1, email2, "byemail2@example.com");
+ .containsExactly(email1, email2, "byemail2@example.com");
assertThat(toEmails.apply(info.reviewers.get(CC)))
.containsExactly(email4, "byemail4@example.com");
assertThat(info.reviewers.get(REMOVED)).isNull();
@@ -507,12 +556,14 @@ public class ChangeIT extends AbstractDaemonTest {
String refactor = "Needs some refactoring";
String ptal = "PTAL";
- grant(
- project,
- "refs/heads/master",
- Permission.TOGGLE_WORK_IN_PROGRESS_STATE,
- false,
- REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allow(Permission.TOGGLE_WORK_IN_PROGRESS_STATE)
+ .ref("refs/heads/master")
+ .group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId).setWorkInProgress(refactor);
@@ -538,7 +589,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(r.getChange().change().isWorkInProgress()).isTrue();
ReviewInput in = ReviewInput.noScore().setWorkInProgress(false);
- ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ ReviewResult result = gApi.changes().id(r.getChangeId()).current().review(in);
assertThat(result.ready).isTrue();
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
@@ -551,7 +602,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(r.getChange().change().isWorkInProgress()).isFalse();
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
- ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ ReviewResult result = gApi.changes().id(r.getChangeId()).current().review(in);
assertThat(result.ready).isNull();
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
@@ -568,7 +619,7 @@ public class ChangeIT extends AbstractDaemonTest {
.reviewer(user.email())
.label("Code-Review", 1)
.setWorkInProgress(true);
- gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ gApi.changes().id(r.getChangeId()).current().review(in);
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
assertThat(info.workInProgress).isTrue();
@@ -583,7 +634,7 @@ public class ChangeIT extends AbstractDaemonTest {
ReviewInput in = ReviewInput.noScore();
in.ready = true;
in.workInProgress = true;
- ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
+ ReviewResult result = gApi.changes().id(r.getChangeId()).current().review(in);
assertThat(result.error).isEqualTo(PostReview.ERROR_WIP_READY_MUTUALLY_EXCLUSIVE);
}
@@ -622,21 +673,24 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("toggle work in progress state not permitted");
- gApi.changes().id(r.getChangeId()).current().review(in);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(r.getChangeId()).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("toggle work in progress state not permitted");
}
@Test
public void reviewWithWorkInProgressByNonOwnerWithPermission() throws Exception {
PushOneCommit.Result r = createChange();
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
- grant(
- project,
- "refs/heads/master",
- Permission.TOGGLE_WORK_IN_PROGRESS_STATE,
- false,
- REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allow(Permission.TOGGLE_WORK_IN_PROGRESS_STATE)
+ .ref("refs/heads/master")
+ .group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(r.getChangeId()).current().review(in);
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
@@ -648,9 +702,10 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
ReviewInput in = ReviewInput.noScore().setReady(true);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("toggle work in progress state not permitted");
- gApi.changes().id(r.getChangeId()).current().review(in);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(r.getChangeId()).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("toggle work in progress state not permitted");
}
@Test
@@ -674,105 +729,9 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r2 = push2.to("refs/for/other");
assertThat(r2.getChangeId()).isEqualTo(changeId);
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Multiple changes found for " + changeId);
- gApi.changes().id(changeId).get();
- }
-
- @Test
- public void revert() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
- ChangeInfo revertChange = gApi.changes().id(r.getChangeId()).revert().get();
-
- // expected messages on source change:
- // 1. Uploaded patch set 1.
- // 2. Patch Set 1: Code-Review+2
- // 3. Change has been successfully merged by Administrator
- // 4. Patch Set 1: Reverted
- List<ChangeMessageInfo> sourceMessages =
- new ArrayList<>(gApi.changes().id(r.getChangeId()).get().messages);
- assertThat(sourceMessages).hasSize(4);
- String expectedMessage =
- String.format("Created a revert of this change as %s", revertChange.changeId);
- assertThat(sourceMessages.get(3).message).isEqualTo(expectedMessage);
-
- assertThat(revertChange.messages).hasSize(1);
- assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
- assertThat(revertChange.revertOf).isEqualTo(gApi.changes().id(r.getChangeId()).get()._number);
- }
-
- @Test
- public void revertNotifications() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes().id(r.getChangeId()).addReviewer(user.email());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
-
- sender.clear();
- ChangeInfo revertChange = gApi.changes().id(r.getChangeId()).revert().get();
-
- List<Message> messages = sender.getMessages();
- assertThat(messages).hasSize(2);
- assertThat(sender.getMessages(revertChange.changeId, "newchange")).hasSize(1);
- assertThat(sender.getMessages(r.getChangeId(), "revert")).hasSize(1);
- }
-
- @Test
- public void suppressRevertNotifications() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes().id(r.getChangeId()).addReviewer(user.email());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
-
- RevertInput revertInput = new RevertInput();
- revertInput.notify = NotifyHandling.NONE;
-
- sender.clear();
- gApi.changes().id(r.getChangeId()).revert(revertInput).get();
- assertThat(sender.getMessages()).isEmpty();
- }
-
- @Test
- public void revertPreservesReviewersAndCcs() throws Exception {
- PushOneCommit.Result r = createChange();
-
- ReviewInput in = ReviewInput.approve();
- in.reviewer(user.email());
- in.reviewer(accountCreator.user2().email(), ReviewerState.CC, true);
- // Add user as reviewer that will create the revert
- in.reviewer(accountCreator.admin2().email());
-
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
-
- // expect both the original reviewers and CCs to be preserved
- // original owner should be added as reviewer, user requesting the revert (new owner) removed
- requestScopeOperations.setApiUser(accountCreator.admin2().id());
- Map<ReviewerState, Collection<AccountInfo>> result =
- gApi.changes().id(r.getChangeId()).revert().get().reviewers;
- assertThat(result).containsKey(ReviewerState.REVIEWER);
-
- List<Integer> reviewers =
- result.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
- assertThat(result).containsKey(ReviewerState.CC);
- List<Integer> ccs =
- result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
- assertThat(ccs).containsExactly(accountCreator.user2().id().get());
- assertThat(reviewers).containsExactly(user.id().get(), admin.id().get());
- }
-
- @Test
- @TestProjectInput(createEmptyCommit = false)
- public void revertInitialCommit() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
-
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Cannot revert initial commit");
- gApi.changes().id(r.getChangeId()).revert();
+ ResourceNotFoundException thrown =
+ assertThrows(ResourceNotFoundException.class, () -> gApi.changes().id(changeId).get());
+ assertThat(thrown).hasMessageThat().contains("Multiple changes found for " + changeId);
}
@FunctionalInterface
@@ -827,19 +786,10 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(cr.all.get(0).value).isEqualTo(1);
// Rebasing the second change again should fail
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Change is already up to date");
- gApi.changes().id(changeId).current().rebase();
- }
-
- @Test
- public void rebaseOnNonExistingChange() throws Exception {
- String changeId = createChange().getChangeId();
- RebaseInput in = new RebaseInput();
- in.base = "999999";
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Base change not found: " + in.base);
- gApi.changes().id(changeId).rebase(in);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(changeId).current().rebase());
+ assertThat(thrown).hasMessageThat().contains("Change is already up to date");
}
@Test
@@ -902,6 +852,17 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void rebaseOnNonExistingChange() throws Exception {
+ String changeId = createChange().getChangeId();
+ RebaseInput in = new RebaseInput();
+ in.base = "999999";
+ UnprocessableEntityException exception =
+ assertThrows(
+ UnprocessableEntityException.class, () -> gApi.changes().id(changeId).rebase(in));
+ assertThat(exception).hasMessageThat().isEqualTo("Base change not found: " + in.base);
+ }
+
+ @Test
public void rebaseFromRelationChainToClosedChange() throws Exception {
PushOneCommit.Result r1 = createChange();
testRepo.reset("HEAD~1");
@@ -942,9 +903,9 @@ public class ChangeIT extends AbstractDaemonTest {
// Rebase the second
String changeId = r2.getChangeId();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("rebase not permitted");
- gApi.changes().id(changeId).rebase();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).rebase());
+ assertThat(thrown).hasMessageThat().contains("rebase not permitted");
}
@Test
@@ -959,7 +920,11 @@ public class ChangeIT extends AbstractDaemonTest {
revision.review(ReviewInput.approve());
revision.submit();
- grant(project, "refs/heads/master", Permission.REBASE, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.REBASE).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
// Rebase the second
String changeId = r2.getChangeId();
@@ -979,15 +944,19 @@ public class ChangeIT extends AbstractDaemonTest {
revision.review(ReviewInput.approve());
revision.submit();
- grant(project, "refs/heads/master", Permission.REBASE, false, REGISTERED_USERS);
- block("refs/for/*", Permission.PUSH, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.REBASE).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(block(Permission.PUSH).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Rebase the second
String changeId = r2.getChangeId();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("rebase not permitted");
- gApi.changes().id(changeId).rebase();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).rebase());
+ assertThat(thrown).hasMessageThat().contains("rebase not permitted");
}
@Test
@@ -1002,13 +971,17 @@ public class ChangeIT extends AbstractDaemonTest {
revision.review(ReviewInput.approve());
revision.submit();
- block("refs/for/*", Permission.PUSH, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Rebase the second
String changeId = r2.getChangeId();
- exception.expect(AuthException.class);
- exception.expectMessage("rebase not permitted");
- gApi.changes().id(changeId).rebase();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).rebase());
+ assertThat(thrown).hasMessageThat().contains("rebase not permitted");
}
@Test
@@ -1024,14 +997,18 @@ public class ChangeIT extends AbstractDaemonTest {
String changeId = changeResult.getChangeId();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("delete not permitted");
- gApi.changes().id(changeId).delete();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown).hasMessageThat().contains("delete not permitted");
}
@Test
public void deleteNewChangeAsUserWithDeleteChangesPermissionForGroup() throws Exception {
- allow("refs/*", Permission.DELETE_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
deleteChangeAsUser(admin, user);
}
@@ -1040,32 +1017,40 @@ public class ChangeIT extends AbstractDaemonTest {
GroupApi groupApi = gApi.groups().create(name("delete-change"));
groupApi.addMembers("user");
+ Project.NameKey nameKey = Project.nameKey(name("delete-change"));
ProjectInput in = new ProjectInput();
- in.name = name("delete-change");
+ in.name = nameKey.get();
in.owners = Lists.newArrayListWithCapacity(1);
in.owners.add(groupApi.name());
in.createEmptyCommit = true;
- ProjectApi api = gApi.projects().create(in);
+ gApi.projects().create(in);
- Project.NameKey nameKey = new Project.NameKey(api.get().name);
-
- try (ProjectConfigUpdate u = updateProject(nameKey)) {
- Util.allow(u.getConfig(), Permission.DELETE_CHANGES, PROJECT_OWNERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(nameKey)
+ .forUpdate()
+ .add(allow(Permission.DELETE_CHANGES).ref("refs/*").group(PROJECT_OWNERS))
+ .update();
deleteChangeAsUser(nameKey, admin, user);
}
@Test
public void deleteChangeAsUserWithDeleteOwnChangesPermissionForGroup() throws Exception {
- allow("refs/*", Permission.DELETE_OWN_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE_OWN_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
deleteChangeAsUser(user, user);
}
@Test
public void deleteChangeAsUserWithDeleteOwnChangesPermissionForOwners() throws Exception {
- allow("refs/*", Permission.DELETE_OWN_CHANGES, CHANGE_OWNER);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE_OWN_CHANGES).ref("refs/*").group(CHANGE_OWNER))
+ .update();
deleteChangeAsUser(user, user);
}
@@ -1082,7 +1067,11 @@ public class ChangeIT extends AbstractDaemonTest {
com.google.gerrit.acceptance.TestAccount deleteAs)
throws Exception {
try {
- allow(projectName, "refs/*", Permission.VIEW_PRIVATE_CHANGES, ANONYMOUS_USERS);
+ projectOperations
+ .project(projectName)
+ .forUpdate()
+ .add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(ANONYMOUS_USERS))
+ .update();
requestScopeOperations.setApiUser(owner.id());
ChangeInput in = new ChangeInput();
in.project = projectName.get();
@@ -1100,12 +1089,16 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(query(changeId)).isEmpty();
- String ref = new Change.Id(id).toRefPrefix() + "1";
+ String ref = Change.id(id).toRefPrefix() + "1";
eventRecorder.assertRefUpdatedEvents(projectName.get(), ref, null, commit, commit, null);
eventRecorder.assertChangeDeletedEvents(changeId, deleteAs.email());
} finally {
- removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES);
- removePermission(project, "refs/*", Permission.DELETE_CHANGES);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.DELETE_OWN_CHANGES).ref("refs/*"))
+ .remove(permissionKey(Permission.DELETE_CHANGES).ref("refs/*"))
+ .update();
}
}
@@ -1116,18 +1109,26 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void deleteNewChangeOfAnotherUserWithDeleteOwnChangesPermission() throws Exception {
- allow("refs/*", Permission.DELETE_OWN_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE_OWN_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
try {
PushOneCommit.Result changeResult = createChange();
String changeId = changeResult.getChangeId();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("delete not permitted");
- gApi.changes().id(changeId).delete();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown).hasMessageThat().contains("delete not permitted");
} finally {
- removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.DELETE_OWN_CHANGES).ref("refs/*"))
+ .update();
}
}
@@ -1152,9 +1153,9 @@ public class ChangeIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId).abandon();
- exception.expect(AuthException.class);
- exception.expectMessage("delete not permitted");
- gApi.changes().id(changeId).delete();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown).hasMessageThat().contains("delete not permitted");
}
@Test
@@ -1178,15 +1179,19 @@ public class ChangeIT extends AbstractDaemonTest {
merge(changeResult);
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("delete not permitted");
- gApi.changes().id(changeId).delete();
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown).hasMessageThat().contains("delete not permitted");
}
@Test
@TestProjectInput(cloneAs = "user")
public void deleteMergedChangeWithDeleteOwnChangesPermission() throws Exception {
- allow("refs/*", Permission.DELETE_OWN_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE_OWN_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
try {
PushOneCommit.Result changeResult =
@@ -1196,11 +1201,15 @@ public class ChangeIT extends AbstractDaemonTest {
merge(changeResult);
requestScopeOperations.setApiUser(user.id());
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("delete not permitted");
- gApi.changes().id(changeId).delete();
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown).hasMessageThat().contains("delete not permitted");
} finally {
- removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.DELETE_OWN_CHANGES).ref("refs/*"))
+ .update();
}
}
@@ -1213,10 +1222,11 @@ public class ChangeIT extends AbstractDaemonTest {
merge(changeResult);
setChangeStatus(id, Change.Status.NEW);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format("Cannot delete change %s: patch set 1 is already merged", id));
- gApi.changes().id(changeId).delete();
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.changes().id(changeId).delete());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("Cannot delete change %s: patch set 1 is already merged", id));
}
@Test
@@ -1230,10 +1240,10 @@ public class ChangeIT extends AbstractDaemonTest {
Optional<ChangeData> result =
idx.get(id, IndexedChangeQuery.createOptions(indexConfig, 0, 1, ImmutableSet.of()));
- assertThat(result.isPresent()).isTrue();
+ assertThat(result).isPresent();
gApi.changes().id(changeId).delete();
result = idx.get(id, IndexedChangeQuery.createOptions(indexConfig, 0, 1, ImmutableSet.of()));
- assertThat(result.isPresent()).isFalse();
+ assertThat(result).isEmpty();
}
@Test
@@ -1265,18 +1275,64 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void deleteChangeRemovesItsChangeEdit() throws Exception {
+ PushOneCommit.Result result = createChange();
+
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = result.getChangeId();
+ gApi.changes().id(changeId).edit().create();
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyFile(FILE_NAME, RawInputUtil.create("foo".getBytes(UTF_8)));
+
+ requestScopeOperations.setApiUser(admin.id());
+ try (Repository repo = repoManager.openRepository(project)) {
+ String expected =
+ RefNames.refsUsers(user.id()) + "/edit-" + result.getChange().getId() + "/1";
+ assertThat(repo.getRefDatabase().getRefsByPrefix(expected)).isNotEmpty();
+ gApi.changes().id(changeId).delete();
+ assertThat(repo.getRefDatabase().getRefsByPrefix(expected)).isEmpty();
+ }
+ }
+
+ @Test
+ public void deleteChangeDoesntRemoveOtherChangeEdits() throws Exception {
+ PushOneCommit.Result result = createChange();
+ PushOneCommit.Result irrelevantChangeResult = createChange();
+ requestScopeOperations.setApiUser(admin.id());
+ String changeId = result.getChangeId();
+ String irrelevantChangeId = irrelevantChangeResult.getChangeId();
+
+ gApi.changes().id(irrelevantChangeId).edit().create();
+ gApi.changes()
+ .id(irrelevantChangeId)
+ .edit()
+ .modifyFile(FILE_NAME, RawInputUtil.create("foo".getBytes(UTF_8)));
+
+ gApi.changes().id(changeId).delete();
+
+ assertThat(gApi.changes().id(irrelevantChangeId).edit().get()).isPresent();
+ }
+
+ @Test
public void rebaseUpToDateChange() throws Exception {
PushOneCommit.Result r = createChange();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Change is already up to date");
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).rebase();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).rebase());
+ assertThat(thrown).hasMessageThat().contains("Change is already up to date");
}
@Test
public void rebaseConflict() throws Exception {
- PushOneCommit.Result r = createChange();
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+ PushOneCommit.Result r1 = createChange();
+ gApi.changes()
+ .id(r1.getChangeId())
+ .revision(r1.getCommit().name())
+ .review(ReviewInput.approve());
+ gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).submit();
PushOneCommit push =
pushFactory.create(
@@ -1286,11 +1342,11 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.FILE_NAME,
"other content",
"If09d8782c1e59dd0b33de2b1ec3595d69cc10ad5");
- r = push.to("refs/for/master");
- r.assertOkStatus();
-
- exception.expect(ResourceConflictException.class);
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).rebase();
+ PushOneCommit.Result r2 = push.to("refs/for/master");
+ r2.assertOkStatus();
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase());
}
@Test
@@ -1304,23 +1360,23 @@ public class ChangeIT extends AbstractDaemonTest {
ri.base = "";
gApi.changes().id(r3.getChangeId()).revision(r3.getCommit().name()).rebase(ri);
PatchSet ps3 = r3.getPatchSet();
- assertThat(ps3.getId().get()).isEqualTo(2);
+ assertThat(ps3.id().get()).isEqualTo(2);
// rebase r2 onto r3 (referenced by ref)
- ri.base = ps3.getId().toRefName();
+ ri.base = ps3.id().toRefName();
gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase(ri);
PatchSet ps2 = r2.getPatchSet();
- assertThat(ps2.getId().get()).isEqualTo(2);
+ assertThat(ps2.id().get()).isEqualTo(2);
// rebase r1 onto r2 (referenced by commit)
- ri.base = ps2.getRevision().get();
+ ri.base = ps2.commitId().name();
gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).rebase(ri);
PatchSet ps1 = r1.getPatchSet();
- assertThat(ps1.getId().get()).isEqualTo(2);
+ assertThat(ps1.id().get()).isEqualTo(2);
// rebase r1 onto r3 (referenced by change number)
ri.base = String.valueOf(r3.getChange().getId().get());
- gApi.changes().id(r1.getChangeId()).revision(ps1.getRevision().get()).rebase(ri);
+ gApi.changes().id(r1.getChangeId()).revision(ps1.commitId().name()).rebase(ri);
assertThat(r1.getPatchSetId().get()).isEqualTo(3);
}
@@ -1335,9 +1391,11 @@ public class ChangeIT extends AbstractDaemonTest {
"base change "
+ r2.getChangeId()
+ " is a descendant of the current change - recursion not allowed";
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(expectedMessage);
- gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).rebase(ri);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).rebase(ri));
+ assertThat(thrown).hasMessageThat().contains(expectedMessage);
}
@Test
@@ -1349,9 +1407,11 @@ public class ChangeIT extends AbstractDaemonTest {
ChangeInfo info = info(changeId);
assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("change is abandoned");
- gApi.changes().id(changeId).revision(r.getCommit().name()).rebase();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).revision(r.getCommit().name()).rebase());
+ assertThat(thrown).hasMessageThat().contains("change is abandoned");
}
@Test
@@ -1371,9 +1431,11 @@ public class ChangeIT extends AbstractDaemonTest {
RebaseInput ri = new RebaseInput();
ri.base = r.getCommit().name();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("base change is abandoned: " + changeId);
- gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase(ri);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase(ri));
+ assertThat(thrown).hasMessageThat().contains("base change is abandoned: " + changeId);
}
@Test
@@ -1383,9 +1445,11 @@ public class ChangeIT extends AbstractDaemonTest {
String commit = r.getCommit().name();
RebaseInput ri = new RebaseInput();
ri.base = commit;
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cannot rebase change onto itself");
- gApi.changes().id(changeId).revision(commit).rebase(ri);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).revision(commit).rebase(ri));
+ assertThat(thrown).hasMessageThat().contains("cannot rebase change onto itself");
}
@Test
@@ -1467,11 +1531,12 @@ public class ChangeIT extends AbstractDaemonTest {
public void pushCommitOfOtherUserThatCannotSeeChange() throws Exception {
// create hidden project that is only visible to administrators
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
- Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(adminGroupUuid()))
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// admin pushes commit of user
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
@@ -1487,12 +1552,8 @@ public class ChangeIT extends AbstractDaemonTest {
// check the user cannot see the change
requestScopeOperations.setApiUser(user.id());
- try {
- gApi.changes().id(result.getChangeId()).get();
- fail("Expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- // Expected.
- }
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(result.getChangeId()).get());
// check that the author/committer was NOT added as reviewer (he can't see
// the change)
@@ -1540,11 +1601,12 @@ public class ChangeIT extends AbstractDaemonTest {
public void pushCommitWithFooterOfOtherUserThatCannotSeeChange() throws Exception {
// create hidden project that is only visible to administrators
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
- Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(adminGroupUuid()))
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// admin pushes commit that references 'user' in a footer
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
@@ -1564,12 +1626,8 @@ public class ChangeIT extends AbstractDaemonTest {
// check that 'user' cannot see the change
requestScopeOperations.setApiUser(user.id());
- try {
- gApi.changes().id(result.getChangeId()).get();
- fail("Expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- // Expected.
- }
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(result.getChangeId()).get());
// check that 'user' was NOT added as cc ('user' can't see the change)
requestScopeOperations.setApiUser(admin.id());
@@ -1583,11 +1641,12 @@ public class ChangeIT extends AbstractDaemonTest {
public void addReviewerThatCannotSeeChange() throws Exception {
// create hidden project that is only visible to administrators
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
- Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(adminGroupUuid()))
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// create change
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
@@ -1597,12 +1656,8 @@ public class ChangeIT extends AbstractDaemonTest {
// check the user cannot see the change
requestScopeOperations.setApiUser(user.id());
- try {
- gApi.changes().id(result.getChangeId()).get();
- fail("Expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- // Expected.
- }
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(result.getChangeId()).get());
// try to add user as reviewer
requestScopeOperations.setApiUser(admin.id());
@@ -1685,16 +1740,36 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ @UseClockStep
public void addReviewer() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
+ testAddReviewerViaPostReview(
+ (changeId, reviewer) -> {
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = reviewer;
+ gApi.changes().id(changeId).addReviewer(in);
+ });
+ }
+
+ @Test
+ @UseClockStep
+ public void addReviewerViaPostReview() throws Exception {
+ testAddReviewerViaPostReview(
+ (changeId, reviewer) -> {
+ AddReviewerInput addReviewerInput = new AddReviewerInput();
+ addReviewerInput.reviewer = reviewer;
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.reviewers = ImmutableList.of(addReviewerInput);
+ gApi.changes().id(changeId).current().review(reviewInput);
+ });
+ }
+
+ private void testAddReviewerViaPostReview(AddReviewerCaller addReviewer) throws Exception {
PushOneCommit.Result r = createChange();
ChangeResource rsrc = parseResource(r);
String oldETag = rsrc.getETag();
Timestamp oldTs = rsrc.getChange().getLastUpdatedOn();
- AddReviewerInput in = new AddReviewerInput();
- in.reviewer = user.email();
- gApi.changes().id(r.getChangeId()).addReviewer(in);
+ addReviewer.call(r.getChangeId(), user.email());
List<Message> messages = sender.getMessages();
assertThat(messages).hasSize(1);
@@ -1706,15 +1781,15 @@ public class ChangeIT extends AbstractDaemonTest {
assertMailReplyTo(m, admin.email());
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
+ // Nobody was added as CC.
+ assertThat(c.reviewers.get(CC)).isNull();
+
// Ensure ETag and lastUpdatedOn are updated.
rsrc = parseResource(r);
assertThat(rsrc.getETag()).isNotEqualTo(oldETag);
@@ -1728,6 +1803,19 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void postingMessageOnOwnChangeDoesntAddCallerAsReviewer() throws Exception {
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.message = "Foo Bar";
+ gApi.changes().id(r.getChangeId()).current().review(reviewInput);
+
+ ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
+ assertThat(c.reviewers.get(REVIEWER)).isNull();
+ assertThat(c.reviewers.get(CC)).isNull();
+ }
+
+ @Test
public void listReviewers() throws Exception {
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
@@ -1773,20 +1861,20 @@ public class ChangeIT extends AbstractDaemonTest {
// In this case, the child ReviewerInput has a notify=OWNER_REVIEWERS
// that should be ignored.
r = createWorkInProgressChange();
- gApi.changes().id(r.getChangeId()).revision("current").review(batchIn);
+ gApi.changes().id(r.getChangeId()).current().review(batchIn);
assertThat(sender.getMessages()).isEmpty();
// Top-level notify property can force notifications when adding reviewer
// via PostReview.
r = createWorkInProgressChange();
batchIn.notify = NotifyHandling.OWNER_REVIEWERS;
- gApi.changes().id(r.getChangeId()).revision("current").review(batchIn);
+ gApi.changes().id(r.getChangeId()).current().review(batchIn);
assertThat(sender.getMessages()).hasSize(1);
}
@Test
+ @UseClockStep
public void addReviewerThatIsNotPerfectMatch() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
PushOneCommit.Result r = createChange();
ChangeResource rsrc = parseResource(r);
String oldETag = rsrc.getETag();
@@ -1821,10 +1909,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertMailReplyTo(m, email);
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
@@ -1837,8 +1922,8 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ @UseClockStep
public void addGroupAsReviewersWhenANotPerfectMatchedUserExists() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
PushOneCommit.Result r = createChange();
ChangeResource rsrc = parseResource(r);
String oldETag = rsrc.getETag();
@@ -1885,10 +1970,7 @@ public class ChangeIT extends AbstractDaemonTest {
assertMailReplyTo(m, myGroupUserEmail);
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
@@ -1901,8 +1983,8 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ @UseClockStep
public void addSelfAsReviewer() throws Exception {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
PushOneCommit.Result r = createChange();
ChangeResource rsrc = parseResource(r);
String oldETag = rsrc.getETag();
@@ -1916,10 +1998,7 @@ public class ChangeIT extends AbstractDaemonTest {
// There should be no email notification when adding self
assertThat(sender.getMessages()).isEmpty();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
@@ -1975,7 +2054,7 @@ public class ChangeIT extends AbstractDaemonTest {
.containsExactly(user.id().get());
// Further test: remove the vote, then comment again. The user should be
- // implicitly re-added to the ReviewerSet, as a CC if we're using NoteDb.
+ // implicitly re-added to the ReviewerSet, as a CC.
requestScopeOperations.setApiUser(admin.id());
gApi.changes().id(r.getChangeId()).reviewer(user.id().toString()).remove();
c = gApi.changes().id(r.getChangeId()).get();
@@ -2028,6 +2107,45 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void pluginCanContributeToETagComputation() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String oldETag = parseResource(r).getETag();
+
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag("foo"))) {
+ assertThat(parseResource(r).getETag()).isNotEqualTo(oldETag);
+ }
+
+ assertThat(parseResource(r).getETag()).isEqualTo(oldETag);
+ }
+
+ @Test
+ public void returningNullFromETagComputationDoesNotBreakGerrit() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String oldETag = parseResource(r).getETag();
+
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag(null))) {
+ assertThat(parseResource(r).getETag()).isEqualTo(oldETag);
+ }
+ }
+
+ @Test
+ public void throwingExceptionFromETagComputationDoesNotBreakGerrit() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String oldETag = parseResource(r).getETag();
+
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ TestChangeETagComputation.withException(
+ new StorageException("exception during test")))) {
+ assertThat(parseResource(r).getETag()).isEqualTo(oldETag);
+ }
+ }
+
+ @Test
public void emailNotificationForFileLevelComment() throws Exception {
String changeId = createChange().getChangeId();
@@ -2068,8 +2186,8 @@ public class ChangeIT extends AbstractDaemonTest {
comment.message = "comment 1";
review.comments = ImmutableMap.of(comment.path, Lists.newArrayList(comment));
- exception.expect(BadRequestException.class);
- gApi.changes().id(changeId).current().review(review);
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).current().review(review));
}
@Test
@@ -2094,21 +2212,21 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void removeReviewerNoVotes() throws Exception {
+ LabelType verified =
+ label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType verified =
- category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
u.getConfig().getLabelSections().put(verified.getName(), verified);
- AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- String heads = RefNames.REFS_HEADS + "*";
- Util.allow(
- u.getConfig(),
- Permission.forLabel(Util.verified().getName()),
- -1,
- 1,
- registeredUsers,
- heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(verified.getName())
+ .ref(RefNames.REFS_HEADS + "*")
+ .group(REGISTERED_USERS)
+ .range(-1, 1))
+ .update();
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
@@ -2136,8 +2254,9 @@ public class ChangeIT extends AbstractDaemonTest {
// Remove again, and then try to remove once more to verify 404 is
// returned.
gApi.changes().id(changeId).reviewer(user.id().toString()).remove();
- exception.expect(ResourceNotFoundException.class);
- gApi.changes().id(changeId).reviewer(user.id().toString()).remove();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeId).reviewer(user.id().toString()).remove());
}
@Test
@@ -2198,9 +2317,11 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).revision(r.getCommit().name()).review(ReviewInput.approve());
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(admin.id().toString()).remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer(admin.id().toString()).remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -2216,9 +2337,11 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).revision(r.getCommit().name()).submit();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer("self").remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer("self").remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -2250,9 +2373,11 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).abandon();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(admin.id().toString()).remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer(admin.id().toString()).remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -2360,24 +2485,32 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("delete vote not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(admin.id().toString()).deleteVote("Code-Review");
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChangeId())
+ .reviewer(admin.id().toString())
+ .deleteVote("Code-Review"));
+ assertThat(thrown).hasMessageThat().contains("delete vote not permitted");
}
@Test
public void nonVotingReviewerStaysAfterSubmit() throws Exception {
LabelType verified =
- category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+ label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+ String heads = "refs/heads/*";
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().put(verified.getName(), verified);
- String heads = "refs/heads/*";
- AccountGroup.UUID owners = systemGroupBackend.getGroup(CHANGE_OWNER).getUUID();
- AccountGroup.UUID registered = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- Util.allow(u.getConfig(), Permission.forLabel(verified.getName()), -1, 1, owners, heads);
- Util.allow(u.getConfig(), Permission.forLabel("Code-Review"), -2, +2, registered, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(verified.getName()).ref(heads).group(CHANGE_OWNER).range(-1, 1))
+ .add(allowLabel("Code-Review").ref(heads).group(REGISTERED_USERS).range(-2, +2))
+ .update();
// Set Code-Review+2 and Verified+1 as admin (change owner)
PushOneCommit.Result r = createChange();
@@ -2473,8 +2606,13 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void queryChangesNoLimit() throws Exception {
- allowGlobalCapabilities(
- SystemGroupBackend.REGISTERED_USERS, 0, 2, GlobalCapability.QUERY_LIMIT);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(
+ allowCapability(GlobalCapability.QUERY_LIMIT)
+ .group(SystemGroupBackend.REGISTERED_USERS)
+ .range(0, 2))
+ .update();
for (int i = 0; i < 3; i++) {
createChange();
}
@@ -2612,16 +2750,21 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
assertThat(gApi.changes().id(r.getChangeId()).topic()).isEqualTo("");
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("edit topic name not permitted");
- gApi.changes().id(r.getChangeId()).topic("mytopic");
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(r.getChangeId()).topic("mytopic"));
+ assertThat(thrown).hasMessageThat().contains("edit topic name not permitted");
}
@Test
public void editTopicWithPermissionAllowed() throws Exception {
PushOneCommit.Result r = createChange();
assertThat(gApi.changes().id(r.getChangeId()).topic()).isEqualTo("");
- grant(project, "refs/heads/master", Permission.EDIT_TOPIC_NAME, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.EDIT_TOPIC_NAME).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(r.getChangeId()).topic("mytopic");
assertThat(gApi.changes().id(r.getChangeId()).topic()).isEqualTo("mytopic");
@@ -2667,16 +2810,22 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("submit not permitted");
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit());
+ assertThat(thrown).hasMessageThat().contains("submit not permitted");
}
@Test
public void submitAllowedWithPermission() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
- grant(project, "refs/heads/master", Permission.SUBMIT, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
assertThat(gApi.changes().id(r.getChangeId()).info().status).isEqualTo(ChangeStatus.MERGED);
@@ -2692,22 +2841,24 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void commitFooters() throws Exception {
LabelType verified =
- category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+ label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
LabelType custom1 =
- category("Custom1", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
+ label("Custom1", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
LabelType custom2 =
- category("Custom2", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
+ label("Custom2", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().put(verified.getName(), verified);
u.getConfig().getLabelSections().put(custom1.getName(), custom1);
u.getConfig().getLabelSections().put(custom2.getName(), custom2);
- String heads = "refs/heads/*";
- AccountGroup.UUID anon = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- Util.allow(u.getConfig(), Permission.forLabel("Verified"), -1, 1, anon, heads);
- Util.allow(u.getConfig(), Permission.forLabel("Custom1"), -1, 1, anon, heads);
- Util.allow(u.getConfig(), Permission.forLabel("Custom2"), -1, 1, anon, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(verified.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .add(allowLabel(custom1.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .add(allowLabel(custom2.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .update();
PushOneCommit.Result r1 = createChange();
r1.assertOkStatus();
@@ -2757,18 +2908,16 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void customCommitFooters() throws Exception {
PushOneCommit.Result change = createChange();
- RegistrationHandle handle =
- changeMessageModifiers.add(
- "gerrit",
- (newCommitMessage, original, mergeTip, destination) -> {
- assertThat(original.getName()).isNotEqualTo(mergeTip.getName());
- return newCommitMessage + "Custom: " + destination.get();
- });
ChangeInfo actual;
- try {
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ (newCommitMessage, original, mergeTip, destination) -> {
+ assertThat(original.getName()).isNotEqualTo(mergeTip.getName());
+ return newCommitMessage + "Custom: " + destination.branch();
+ })) {
actual = gApi.changes().id(change.getChangeId()).get(ALL_REVISIONS, COMMIT_FOOTERS);
- } finally {
- handle.remove();
}
List<String> footers =
new ArrayList<>(
@@ -2830,10 +2979,11 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(approval._accountId).isEqualTo(user.id().get());
assertThat(approval.value).isEqualTo(0);
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.blockLabel(u.getConfig(), "Code-Review", REGISTERED_USERS, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-1, 1))
+ .update();
c = gApi.changes().id(triplet).get(DETAILED_LABELS);
codeReview = c.labels.get("Code-Review");
@@ -2879,13 +3029,13 @@ public class ChangeIT extends AbstractDaemonTest {
info = gApi.changes().id(info._number).get();
assertThat(info.changeId).isEqualTo(r.getChangeId());
-
- exception.expect(AuthException.class);
- gApi.changes().id(triplet).current().review(ReviewInput.approve());
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(triplet).current().review(ReviewInput.approve()));
}
@Test
- public void noteDbCommitsOnPatchSetCreation() throws Exception {
+ public void commitsOnPatchSetCreation() throws Exception {
PushOneCommit.Result r = createChange();
pushFactory
.create(admin.newIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "4711", r.getChangeId())
@@ -2895,7 +3045,7 @@ public class ChangeIT extends AbstractDaemonTest {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
RevCommit commitPatchSetCreation =
- rw.parseCommit(repo.exactRef(changeMetaRef(new Change.Id(c._number))).getObjectId());
+ rw.parseCommit(repo.exactRef(changeMetaRef(Change.id(c._number))).getObjectId());
assertThat(commitPatchSetCreation.getShortMessage()).isEqualTo("Create patch set 2");
PersonIdent expectedAuthor =
@@ -2938,8 +3088,7 @@ public class ChangeIT extends AbstractDaemonTest {
in.project = project.get();
in.newBranch = true;
- exception.expect(ResourceConflictException.class);
- gApi.changes().create(in).get();
+ assertThrows(ResourceConflictException.class, () -> gApi.changes().create(in).get());
}
@Test
@@ -2952,7 +3101,11 @@ public class ChangeIT extends AbstractDaemonTest {
TestRepository<InMemoryRepository> userTestRepo = cloneProject(p, user);
// Block default permission
- block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.ADD_PATCH_SET).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Create change as admin
PushOneCommit push = pushFactory.create(admin.newIdent(), adminTestRepo);
@@ -2960,12 +3113,12 @@ public class ChangeIT extends AbstractDaemonTest {
r1.assertOkStatus();
// Fetch change
- GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(userTestRepo, r1.getPatchSet().refName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
PushOneCommit.Result r2 = amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
- r2.assertErrorStatus("cannot add patch set to " + r1.getChange().getId().id + ".");
+ r2.assertErrorStatus("cannot add patch set to " + r1.getChange().getId().get() + ".");
}
@Test
@@ -2980,7 +3133,7 @@ public class ChangeIT extends AbstractDaemonTest {
r1.assertOkStatus();
// Fetch change
- GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(userTestRepo, r1.getPatchSet().refName() + ":ps");
userTestRepo.reset("ps");
// Amend change as user
@@ -2996,7 +3149,11 @@ public class ChangeIT extends AbstractDaemonTest {
TestRepository<?> adminTestRepo = cloneProject(project, admin);
// Block default permission
- block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.ADD_PATCH_SET).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Create change as admin
PushOneCommit push = pushFactory.create(admin.newIdent(), adminTestRepo);
@@ -3004,7 +3161,7 @@ public class ChangeIT extends AbstractDaemonTest {
r1.assertOkStatus();
// Fetch change
- GitUtil.fetch(adminTestRepo, r1.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(adminTestRepo, r1.getPatchSet().refName() + ":ps");
adminTestRepo.reset("ps");
// Amend change as admin
@@ -3015,20 +3172,19 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void createMergePatchSet() throws Exception {
- PushOneCommit.Result start = pushTo("refs/heads/master");
- start.assertOkStatus();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
// create a change for master
- PushOneCommit.Result r = createChange();
- r.assertOkStatus();
- String changeId = r.getChangeId();
+ String changeId = createChange().getChangeId();
- testRepo.reset(start.getCommit());
+ testRepo.reset(initialHead);
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
currentMaster.assertOkStatus();
String parent = currentMaster.getCommit().getName();
// push a commit into dev branch
- createBranch("dev");
+ testRepo.reset(initialHead);
PushOneCommit.Result changeA =
pushFactory
.create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
@@ -3049,22 +3205,57 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void createMergePatchSet_Conflict() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, "change 1", fileName, "content 1")
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, "change 2", fileName, "content 2")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
+ }
+
+ @Test
public void createMergePatchSetInheritParent() throws Exception {
- PushOneCommit.Result start = pushTo("refs/heads/master");
- start.assertOkStatus();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
// create a change for master
PushOneCommit.Result r = createChange();
- r.assertOkStatus();
String changeId = r.getChangeId();
String parent = r.getCommit().getParent(0).getName();
// advance master branch
- testRepo.reset(start.getCommit());
+ testRepo.reset(initialHead);
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
currentMaster.assertOkStatus();
// push a commit into dev branch
- createBranch("dev");
+ testRepo.reset(initialHead);
PushOneCommit.Result changeA =
pushFactory
.create(user.newIdent(), testRepo, "change A", "A.txt", "A content")
@@ -3090,7 +3281,7 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void createMergePatchSetCannotBaseOnInvisibleChange() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
createBranch("foo");
createBranch("bar");
@@ -3107,14 +3298,19 @@ public class ChangeIT extends AbstractDaemonTest {
testRepo.reset(initialHead);
String changeId = createChange().getChangeId();
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Read not permitted for " + baseChange);
- gApi.changes().id(changeId).createMergePatchSet(createMergePatchSetInput(baseChange));
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .createMergePatchSet(createMergePatchSetInput(baseChange)));
+ assertThat(thrown).hasMessageThat().contains("Read not permitted for " + baseChange);
}
@Test
public void createMergePatchSetBaseOnChange() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
createBranch("foo");
createBranch("bar");
@@ -3141,6 +3337,46 @@ public class ChangeIT extends AbstractDaemonTest {
.isEqualTo(expectedParent);
}
+ @Test
+ public void createMergePatchSetWithUnupportedMergeStrategy() throws Exception {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ createBranch("dev");
+
+ // create a change for master
+ String changeId = createChange().getChangeId();
+
+ String fileName = "shared.txt";
+ String sourceSubject = "source change";
+ String sourceContent = "source content";
+ String targetSubject = "target change";
+ String targetContent = "target content";
+ testRepo.reset(initialHead);
+ PushOneCommit.Result currentMaster =
+ pushFactory
+ .create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
+ .to("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ testRepo.reset(initialHead);
+ PushOneCommit.Result changeA =
+ pushFactory
+ .create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ mergeInput.strategy = "unsupported-strategy";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
+ assertThat(ex).hasMessageThat().isEqualTo("invalid merge strategy: " + mergeInput.strategy);
+ }
+
private MergePatchSetInput createMergePatchSetInput(String baseChange) {
MergeInput mergeInput = new MergeInput();
mergeInput.source = "foo";
@@ -3162,15 +3398,18 @@ public class ChangeIT extends AbstractDaemonTest {
// add new label and assert that it's returned for existing changes
AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- LabelType verified = Util.verified();
+ LabelType verified = TestLabels.verified();
String heads = RefNames.REFS_HEADS + "*";
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().put(verified.getName(), verified);
- Util.allow(
- u.getConfig(), Permission.forLabel(verified.getName()), -1, 1, registeredUsers, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(verified.getName()).ref(heads).group(registeredUsers).range(-1, 1))
+ .update();
change = gApi.changes().id(r.getChangeId()).get();
assertThat(change.labels.keySet()).containsExactly("Code-Review", "Verified");
@@ -3188,9 +3427,16 @@ public class ChangeIT extends AbstractDaemonTest {
// remove label and assert that it's no longer returned for existing
// changes, even if there is an approval for it
u.getConfig().getLabelSections().remove(verified.getName());
- Util.remove(u.getConfig(), Permission.forLabel(verified.getName()), registeredUsers, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(
+ permissionKey(Permission.forLabel(verified.getName()))
+ .ref(heads)
+ .group(registeredUsers))
+ .update();
change = gApi.changes().id(r.getChangeId()).get();
assertThat(change.labels.keySet()).containsExactly("Code-Review");
@@ -3217,17 +3463,20 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
assertPermitted(change, "Code-Review", 2);
- LabelType verified = Util.verified();
+ LabelType verified = TestLabels.verified();
AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
String heads = RefNames.REFS_HEADS + "*";
// add new label and assert that it's returned for existing changes
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().put(verified.getName(), verified);
- Util.allow(
- u.getConfig(), Permission.forLabel(verified.getName()), -1, 1, registeredUsers, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(verified.getName()).ref(heads).group(registeredUsers).range(-1, 1))
+ .update();
change = gApi.changes().id(r.getChangeId()).get();
assertThat(change.labels.keySet()).containsExactly("Code-Review", "Verified");
@@ -3269,9 +3518,13 @@ public class ChangeIT extends AbstractDaemonTest {
// changes, even if there is an approval for it
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().remove(verified.getName());
- Util.remove(u.getConfig(), Permission.forLabel(verified.getName()), registeredUsers, heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(verified.getName()).ref(heads).group(registeredUsers))
+ .update();
change = gApi.changes().id(r.getChangeId()).get();
assertThat(change.labels.keySet()).containsExactly("Code-Review");
@@ -3280,9 +3533,45 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
+ public void notifyConfigForDirectoryTriggersEmail() throws Exception {
+ // Configure notifications on project level.
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
+ GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
+ testRepo.reset("config");
+ PushOneCommit push =
+ pushFactory.create(
+ admin.newIdent(),
+ testRepo,
+ "Configure Notifications",
+ "project.config",
+ "[notify \"my=notify-config\"]\n"
+ + " email = foo@test.com\n"
+ + " filter = dir:\\\"foo/bar/baz\\\"");
+ push.to(RefNames.REFS_CONFIG);
+ testRepo.reset(oldHead);
+
+ // Push a change that matches the filter.
+ sender.clear();
+ push =
+ pushFactory.create(
+ admin.newIdent(), testRepo, "Test change", "foo/bar/baz/test.txt", "some content");
+ PushOneCommit.Result r = push.to("refs/for/master");
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).rcpt()).containsExactly(Address.parse("foo@test.com"));
+
+ // Comment on the change.
+ sender.clear();
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.message = "some message";
+ gApi.changes().id(r.getChangeId()).current().review(reviewInput);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).rcpt()).containsExactly(Address.parse("foo@test.com"));
+ }
+
+ @Test
public void checkLabelsForMergedChangeWithNonAuthorCodeReview() throws Exception {
// Configure Non-Author-Code-Review
- RevCommit oldHead = getRemoteHead();
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
testRepo.reset("config");
PushOneCommit push2 =
@@ -3307,20 +3596,18 @@ public class ChangeIT extends AbstractDaemonTest {
push2.to(RefNames.REFS_CONFIG);
testRepo.reset(oldHead);
- AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
String heads = RefNames.REFS_HEADS + "*";
// Allow user to approve
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(
- u.getConfig(),
- Permission.forLabel(Util.codeReview().getName()),
- -2,
- 2,
- registeredUsers,
- heads);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(TestLabels.codeReview().getName())
+ .ref(heads)
+ .group(REGISTERED_USERS)
+ .range(-2, 2))
+ .update();
PushOneCommit.Result r = createChange();
@@ -3372,16 +3659,15 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(approval.permittedVotingRange.min).isEqualTo(-1);
assertThat(approval.permittedVotingRange.max).isEqualTo(1);
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(
- u.getConfig(),
- Permission.forLabel("Code-Review"),
- minPermittedValue,
- maxPermittedValue,
- REGISTERED_USERS,
- heads);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel("Code-Review")
+ .ref(heads)
+ .group(REGISTERED_USERS)
+ .range(minPermittedValue, maxPermittedValue))
+ .update();
c = gApi.changes().id(triplet).get(DETAILED_LABELS);
codeReview = c.labels.get("Code-Review");
@@ -3395,10 +3681,11 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void maxPermittedValueBlocked() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.blockLabel(u.getConfig(), "Code-Review", REGISTERED_USERS, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-1, 1))
+ .update();
PushOneCommit.Result r = createChange();
String triplet = project.get() + "~master~" + r.getChangeId();
@@ -3435,9 +3722,9 @@ public class ChangeIT extends AbstractDaemonTest {
ReviewInput input = new ReviewInput().label("Code-Review", 3);
gApi.changes().id(changeId).current().review(input);
- Map<String, Short> votes =
- gApi.changes().id(changeId).current().reviewer(admin.email()).votes();
- assertThat(votes).isEmpty();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeId).current().reviewer(admin.email()));
}
@Test
@@ -3446,9 +3733,10 @@ public class ChangeIT extends AbstractDaemonTest {
String changeId = createChange().getChangeId();
ReviewInput in = new ReviewInput().label("Code-Style", 1);
- exception.expect(BadRequestException.class);
- exception.expectMessage("label \"Code-Style\" is not a configured label");
- gApi.changes().id(changeId).current().review(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("label \"Code-Style\" is not a configured label");
}
@Test
@@ -3457,9 +3745,10 @@ public class ChangeIT extends AbstractDaemonTest {
String changeId = createChange().getChangeId();
ReviewInput in = new ReviewInput().label("Code-Review", 3);
- exception.expect(BadRequestException.class);
- exception.expectMessage("label \"Code-Review\": 3 is not a valid value");
- gApi.changes().id(changeId).current().review(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("label \"Code-Review\": 3 is not a valid value");
}
@Test
@@ -3475,7 +3764,7 @@ public class ChangeIT extends AbstractDaemonTest {
+ "U > 0,"
+ "R = label('All-Comments-Resolved', need(_)). \n\n");
- String oldHead = getRemoteHead().name();
+ String oldHead = projectOperations.project(project).getHead("master").name();
PushOneCommit.Result result1 =
pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
testRepo.reset(oldHead);
@@ -3487,56 +3776,14 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(result1.getChangeId()).current().submit();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Failed to submit 1 change due to the following problems");
- exception.expectMessage("needs All-Comments-Resolved");
- gApi.changes().id(result2.getChangeId()).current().submit();
- }
-
- @Test
- public void pureRevertFactBlocksSubmissionOfNonReverts() throws Exception {
- addPureRevertSubmitRule();
-
- // Create a change that is not a revert of another change
- PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
- approve(r1.getChangeId());
-
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Failed to submit 1 change due to the following problems");
- exception.expectMessage("needs Is-Pure-Revert");
- gApi.changes().id(r1.getChangeId()).current().submit();
- }
-
- @Test
- public void pureRevertFactBlocksSubmissionOfNonPureReverts() throws Exception {
- PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
- merge(r1);
-
- addPureRevertSubmitRule();
-
- // Create a revert and push a content change
- String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
- amendChange(revertId);
- approve(revertId);
-
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Failed to submit 1 change due to the following problems");
- exception.expectMessage("needs Is-Pure-Revert");
- gApi.changes().id(revertId).current().submit();
- }
-
- @Test
- public void pureRevertFactAllowsSubmissionOfPureReverts() throws Exception {
- // Create a change that we can later revert
- PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
- merge(r1);
-
- addPureRevertSubmitRule();
-
- // Create a revert and submit it
- String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
- approve(revertId);
- gApi.changes().id(revertId).current().submit();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result2.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Failed to submit 1 change due to the following problems");
+ assertThat(thrown).hasMessageThat().contains("needs All-Comments-Resolved");
}
@Test
@@ -3596,25 +3843,18 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
- public void changeCommitMessageWithNoChangeIdFails() throws Exception {
- PushOneCommit.Result r = createChange();
- assertThat(getCommitMessage(r.getChangeId()))
- .isEqualTo("test commit\n\nChange-Id: " + r.getChangeId() + "\n");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("missing Change-Id footer");
- gApi.changes().id(r.getChangeId()).setMessage("modified commit\n");
- }
-
- @Test
public void changeCommitMessageNullNotAllowed() throws Exception {
PushOneCommit.Result r = createChange();
assertThat(getCommitMessage(r.getChangeId()))
.isEqualTo("test commit\n\nChange-Id: " + r.getChangeId() + "\n");
- exception.expect(BadRequestException.class);
- exception.expectMessage("NUL character");
- gApi.changes()
- .id(r.getChangeId())
- .setMessage("test\0commit\n\nChange-Id: " + r.getChangeId() + "\n");
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChangeId())
+ .setMessage("test\0commit\n\nChange-Id: " + r.getChangeId() + "\n"));
+ assertThat(thrown).hasMessageThat().contains("NUL character");
}
@Test
@@ -3623,11 +3863,15 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
assertThat(getCommitMessage(r.getChangeId()))
.isEqualTo("test commit\n\nChange-Id: " + r.getChangeId() + "\n");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("wrong Change-Id footer");
- gApi.changes()
- .id(r.getChangeId())
- .setMessage("modified commit\n\nChange-Id: " + otherChange.getChangeId() + "\n");
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChangeId())
+ .setMessage(
+ "modified commit\n\nChange-Id: " + otherChange.getChangeId() + "\n"));
+ assertThat(thrown).hasMessageThat().contains("wrong Change-Id footer");
}
@Test
@@ -3636,15 +3880,20 @@ public class ChangeIT extends AbstractDaemonTest {
Project.NameKey p = projectOperations.newProject().create();
TestRepository<InMemoryRepository> userTestRepo = cloneProject(p, user);
// Block default permission
- block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.ADD_PATCH_SET).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Create change as user
PushOneCommit push = pushFactory.create(user.newIdent(), userTestRepo);
PushOneCommit.Result r = push.to("refs/for/master");
r.assertOkStatus();
// Try to change the commit message
- exception.expect(AuthException.class);
- exception.expectMessage("modifying commit message not permitted");
- gApi.changes().id(r.getChangeId()).setMessage("foo");
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(r.getChangeId()).setMessage("foo"));
+ assertThat(thrown).hasMessageThat().contains("modifying commit message not permitted");
}
@Test
@@ -3652,9 +3901,11 @@ public class ChangeIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
assertThat(getCommitMessage(r.getChangeId()))
.isEqualTo("test commit\n\nChange-Id: " + r.getChangeId() + "\n");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("new and existing commit message are the same");
- gApi.changes().id(r.getChangeId()).setMessage(getCommitMessage(r.getChangeId()));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).setMessage(getCommitMessage(r.getChangeId())));
+ assertThat(thrown).hasMessageThat().contains("new and existing commit message are the same");
}
@Test
@@ -3694,114 +3945,13 @@ public class ChangeIT extends AbstractDaemonTest {
}
@Test
- public void pureRevertReturnsTrueForPureRevert() throws Exception {
- PushOneCommit.Result r = createChange();
- merge(r);
- String revertId = gApi.changes().id(r.getChangeId()).revert().get().id;
- // Without query parameter
- assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
- // With query parameter
- assertThat(
- gApi.changes()
- .id(revertId)
- .pureRevert(getRemoteHead().toObjectId().name())
- .isPureRevert)
- .isTrue();
- }
-
- @Test
- public void pureRevertReturnsFalseOnContentChange() throws Exception {
- PushOneCommit.Result r1 = createChange();
- merge(r1);
- // Create a revert and expect pureRevert to be true
- String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
- assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
-
- // Create a new PS and expect pureRevert to be false
- PushOneCommit.Result result = amendChange(revertId);
- result.assertOkStatus();
- assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isFalse();
- }
-
- @Test
- public void pureRevertParameterTakesPrecedence() throws Exception {
- PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
- merge(r1);
- String oldHead = getRemoteHead().toObjectId().name();
-
- PushOneCommit.Result r2 = createChange("commit message", "a.txt", "content2");
- merge(r2);
-
- String revertId = gApi.changes().id(r2.getChangeId()).revert().get().changeId;
- assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
- assertThat(gApi.changes().id(revertId).pureRevert(oldHead).isPureRevert).isFalse();
- }
-
- @Test
- public void pureRevertReturnsFalseOnInvalidInput() throws Exception {
- PushOneCommit.Result r1 = createChange();
- merge(r1);
-
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid object ID");
- gApi.changes().id(createChange().getChangeId()).pureRevert("invalid id");
- }
-
- @Test
- public void pureRevertReturnsTrueWithCleanRebase() throws Exception {
- PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
- merge(r1);
-
- PushOneCommit.Result r2 = createChange("commit message", "b.txt", "content2");
- merge(r2);
-
- String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
- // Rebase revert onto HEAD
- gApi.changes().id(revertId).rebase();
- // Check that pureRevert is true which implies that the commit can be rebased onto the original
- // commit.
- assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
- }
-
- @Test
- public void pureRevertReturnsFalseWithRebaseConflict() throws Exception {
- // Create an initial commit to serve as claimed original
- PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
- merge(r1);
- String claimedOriginal = getRemoteHead().toObjectId().name();
-
- // Change contents of the file to provoke a conflict
- merge(createChange("commit message", "a.txt", "content2"));
-
- // Create a commit that we can revert
- PushOneCommit.Result r2 = createChange("commit message", "a.txt", "content3");
- merge(r2);
-
- // Create a revert of r2
- String revertR3Id = gApi.changes().id(r2.getChangeId()).revert().id();
- // Assert that the change is a pure revert of it's 'revertOf'
- assertThat(gApi.changes().id(revertR3Id).pureRevert().isPureRevert).isTrue();
- // Assert that the change is not a pure revert of claimedOriginal because pureRevert is trying
- // to rebase this on claimed original, which fails.
- PureRevertInfo pureRevert = gApi.changes().id(revertR3Id).pureRevert(claimedOriginal);
- assertThat(pureRevert.isPureRevert).isFalse();
- }
-
- @Test
- public void pureRevertThrowsExceptionWhenChangeIsNotARevertAndNoIdProvided() throws Exception {
- exception.expect(BadRequestException.class);
- exception.expectMessage("revertOf not set");
- gApi.changes().id(createChange().getChangeId()).pureRevert();
- }
-
- @Test
public void putTopicExceedLimitFails() throws Exception {
String changeId = createChange().getChangeId();
String topic = Stream.generate(() -> "t").limit(2049).collect(joining());
- exception.expect(BadRequestException.class);
- exception.expectMessage("topic length exceeds the limit");
- gApi.changes().id(changeId).topic(topic);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.changes().id(changeId).topic(topic));
+ assertThat(thrown).hasMessageThat().contains("topic length exceeds the limit");
}
@Test
@@ -3818,13 +3968,13 @@ public class ChangeIT extends AbstractDaemonTest {
private void submittableAfterLosingPermissions(String label) throws Exception {
String codeReviewLabel = "Code-Review";
- AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.forLabel(label), -1, +1, registered, "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel(codeReviewLabel), -2, +2, registered, "refs/heads/*");
- u.save();
- }
+ AccountGroup.UUID registered = REGISTERED_USERS;
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(label).ref("refs/heads/*").group(registered).range(-1, +1))
+ .add(allowLabel(codeReviewLabel).ref("refs/heads/*").group(registered).range(-2, +2))
+ .update();
requestScopeOperations.setApiUser(user.id());
PushOneCommit.Result r = createChange();
@@ -3847,15 +3997,13 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(changeId).get().submittable).isTrue();
requestScopeOperations.setApiUser(admin.id());
- // Remove user's permission for 'Label'.
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.remove(u.getConfig(), Permission.forLabel(label), registered, "refs/heads/*");
- // Update user's permitted range for 'Code-Review' to be -1...+1.
- Util.remove(u.getConfig(), Permission.forLabel(codeReviewLabel), registered, "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel(codeReviewLabel), -1, +1, registered, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(labelPermissionKey(label).ref("refs/heads/*").group(registered))
+ .remove(labelPermissionKey(codeReviewLabel).ref("refs/heads/*").group(registered))
+ .add(allowLabel(codeReviewLabel).ref("refs/heads/*").group(registered).range(-1, +1))
+ .update();
// Verify user's new permitted range.
requestScopeOperations.setApiUser(user.id());
@@ -3939,7 +4087,7 @@ public class ChangeIT extends AbstractDaemonTest {
if (r == null) {
return ImmutableList.of();
}
- return Iterables.transform(r, a -> new Account.Id(a._accountId));
+ return Iterables.transform(r, a -> Account.id(a._accountId));
}
private ChangeResource parseResource(PushOneCommit.Result r) throws Exception {
@@ -3954,7 +4102,7 @@ public class ChangeIT extends AbstractDaemonTest {
.filter(e -> e.getValue().stream().anyMatch(a -> a._accountId == accountId.get()))
.map(Map.Entry::getKey)
.collect(toSet());
- assertThat(states.size()).named(states.toString()).isAtMost(1);
+ assertWithMessage(states.toString()).that(states.size()).isAtMost(1);
return states.stream().findFirst();
}
@@ -3980,10 +4128,7 @@ public class ChangeIT extends AbstractDaemonTest {
public boolean updateChange(ChangeContext ctx) throws Exception {
Change change = ctx.getChange();
- // Change status in database.
- change.setStatus(newStatus);
-
- // Change status in NoteDb.
+ // Change status.
PatchSet.Id currentPatchSetId = change.currentPatchSetId();
ctx.getUpdate(currentPatchSetId).setStatus(newStatus);
@@ -3991,19 +4136,6 @@ public class ChangeIT extends AbstractDaemonTest {
}
}
- private void addPureRevertSubmitRule() throws Exception {
- modifySubmitRules(
- "submit_rule(submit(R)) :- \n"
- + "gerrit:pure_revert(1), \n"
- + "!,"
- + "gerrit:uploader(U), \n"
- + "R = label('Is-Pure-Revert', ok(U)).\n"
- + "submit_rule(submit(R)) :- \n"
- + "gerrit:pure_revert(U), \n"
- + "U \\= 1,"
- + "R = label('Is-Pure-Revert', need(_)). \n\n");
- }
-
private void modifySubmitRules(String newContent) throws Exception {
try (Repository repo = repoManager.openRepository(project);
TestRepository<Repository> testRepo = new TestRepository<>(repo)) {
@@ -4043,21 +4175,25 @@ public class ChangeIT extends AbstractDaemonTest {
@Test
public void starUnstar() throws Exception {
- PushOneCommit.Result r = createChange();
- String triplet = project.get() + "~master~" + r.getChangeId();
- changeIndexedCounter.clear();
-
- gApi.accounts().self().starChange(triplet);
- ChangeInfo change = info(triplet);
- assertThat(change.starred).isTrue();
- assertThat(change.stars).contains(DEFAULT_LABEL);
- changeIndexedCounter.assertReindexOf(change);
-
- gApi.accounts().self().unstarChange(triplet);
- change = info(triplet);
- assertThat(change.starred).isNull();
- assertThat(change.stars).isNull();
- changeIndexedCounter.assertReindexOf(change);
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ changeIndexedCounter.clear();
+
+ gApi.accounts().self().starChange(triplet);
+ ChangeInfo change = info(triplet);
+ assertThat(change.starred).isTrue();
+ assertThat(change.stars).contains(DEFAULT_LABEL);
+ changeIndexedCounter.assertReindexOf(change);
+
+ gApi.accounts().self().unstarChange(triplet);
+ change = info(triplet);
+ assertThat(change.starred).isNull();
+ assertThat(change.stars).isNull();
+ changeIndexedCounter.assertReindexOf(change);
+ }
}
@Test
@@ -4117,9 +4253,9 @@ public class ChangeIT extends AbstractDaemonTest {
public void cannotIgnoreOwnChange() throws Exception {
String changeId = createChange().getChangeId();
- exception.expect(BadRequestException.class);
- exception.expectMessage("cannot ignore own change");
- gApi.changes().id(changeId).ignore(true);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.changes().id(changeId).ignore(true));
+ assertThat(thrown).hasMessageThat().contains("cannot ignore own change");
}
@Test
@@ -4130,14 +4266,17 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.accounts().self().starChange(changeId);
assertThat(gApi.changes().id(changeId).get().starred).isTrue();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "The labels "
- + StarredChangesUtil.DEFAULT_LABEL
- + " and "
- + StarredChangesUtil.IGNORE_LABEL
- + " are mutually exclusive. Only one of them can be set.");
- gApi.changes().id(changeId).ignore(true);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(changeId).ignore(true));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The labels "
+ + StarredChangesUtil.DEFAULT_LABEL
+ + " and "
+ + StarredChangesUtil.IGNORE_LABEL
+ + " are mutually exclusive. Only one of them can be set.");
}
@Test
@@ -4148,14 +4287,17 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).ignore(true);
assertThat(gApi.changes().id(changeId).ignored()).isTrue();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "The labels "
- + StarredChangesUtil.DEFAULT_LABEL
- + " and "
- + StarredChangesUtil.IGNORE_LABEL
- + " are mutually exclusive. Only one of them can be set.");
- gApi.accounts().self().starChange(changeId);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.accounts().self().starChange(changeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The labels "
+ + StarredChangesUtil.DEFAULT_LABEL
+ + " and "
+ + StarredChangesUtil.IGNORE_LABEL
+ + " are mutually exclusive. Only one of them can be set.");
}
@Test
@@ -4193,21 +4335,28 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).markAsReviewed(true);
assertThat(gApi.changes().id(changeId).get().reviewed).isTrue();
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "The labels "
- + StarredChangesUtil.REVIEWED_LABEL
- + "/"
- + 1
- + " and "
- + StarredChangesUtil.UNREVIEWED_LABEL
- + "/"
- + 1
- + " are mutually exclusive. Only one of them can be set.");
- gApi.accounts()
- .self()
- .setStars(
- changeId, new StarsInput(ImmutableSet.of(StarredChangesUtil.UNREVIEWED_LABEL + "/1")));
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .setStars(
+ changeId,
+ new StarsInput(
+ ImmutableSet.of(StarredChangesUtil.UNREVIEWED_LABEL + "/1"))));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The labels "
+ + StarredChangesUtil.REVIEWED_LABEL
+ + "/"
+ + 1
+ + " and "
+ + StarredChangesUtil.UNREVIEWED_LABEL
+ + "/"
+ + 1
+ + " are mutually exclusive. Only one of them can be set.");
}
@Test
@@ -4218,21 +4367,27 @@ public class ChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).markAsReviewed(false);
assertThat(gApi.changes().id(changeId).get().reviewed).isNull();
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "The labels "
- + StarredChangesUtil.REVIEWED_LABEL
- + "/"
- + 1
- + " and "
- + StarredChangesUtil.UNREVIEWED_LABEL
- + "/"
- + 1
- + " are mutually exclusive. Only one of them can be set.");
- gApi.accounts()
- .self()
- .setStars(
- changeId, new StarsInput(ImmutableSet.of(StarredChangesUtil.REVIEWED_LABEL + "/1")));
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .setStars(
+ changeId,
+ new StarsInput(ImmutableSet.of(StarredChangesUtil.REVIEWED_LABEL + "/1"))));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The labels "
+ + StarredChangesUtil.REVIEWED_LABEL
+ + "/"
+ + 1
+ + " and "
+ + StarredChangesUtil.UNREVIEWED_LABEL
+ + "/"
+ + 1
+ + " are mutually exclusive. Only one of them can be set.");
}
@Test
@@ -4261,9 +4416,14 @@ public class ChangeIT extends AbstractDaemonTest {
// label cannot contain whitespace
String invalidLabel = "invalid label";
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid labels: " + invalidLabel);
- gApi.accounts().self().setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel)));
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel))));
+ assertThat(thrown).hasMessageThat().contains("invalid labels: " + invalidLabel);
}
@Test
@@ -4280,7 +4440,8 @@ public class ChangeIT extends AbstractDaemonTest {
ListChangesOption.MESSAGES,
ListChangesOption.SUBMITTABLE,
ListChangesOption.WEB_LINKS,
- ListChangesOption.SKIP_MERGEABLE);
+ ListChangesOption.SKIP_MERGEABLE,
+ ListChangesOption.SKIP_DIFFSTAT);
PushOneCommit.Result change = createChange();
int number = gApi.changes().id(change.getChangeId()).get()._number;
@@ -4296,7 +4457,7 @@ public class ChangeIT extends AbstractDaemonTest {
}
private BranchApi createBranch(String branch) throws Exception {
- return createBranch(new Branch.NameKey(project, branch));
+ return createBranch(BranchNameKey.create(project, branch));
}
private ThrowableSubject assertThatQueryException(String query) throws Exception {
@@ -4307,4 +4468,9 @@ public class ChangeIT extends AbstractDaemonTest {
}
throw new AssertionError("expected BadRequestException");
}
+
+ @FunctionalInterface
+ private interface AddReviewerCaller {
+ void call(String changeId, String reviewer) throws RestApiException;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
index 4551f900c7..de73c00f82 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
@@ -15,15 +15,16 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.junit.Before;
import org.junit.Test;
@@ -54,16 +55,22 @@ public class ChangeIdIT extends AbstractDaemonTest {
@Test
public void wrongProjectInProjectChangeNumberReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: unknown~" + changeInfo._number);
- gApi.changes().id("unknown", changeInfo._number);
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id("unknown", changeInfo._number));
+ assertThat(thrown).hasMessageThat().contains("Not found: unknown~" + changeInfo._number);
}
@Test
public void wrongIdInProjectChangeNumberReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: " + project.get() + "~" + Integer.MAX_VALUE);
- gApi.changes().id(project.get(), Integer.MAX_VALUE);
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(project.get(), Integer.MAX_VALUE));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Not found: " + project.get() + "~" + Integer.MAX_VALUE);
}
@Test
@@ -74,8 +81,7 @@ public class ChangeIdIT extends AbstractDaemonTest {
@Test
public void wrongChangeNumberReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.changes().id(Integer.MAX_VALUE);
+ assertThrows(ResourceNotFoundException.class, () -> gApi.changes().id(Integer.MAX_VALUE));
}
@Test
@@ -86,25 +92,36 @@ public class ChangeIdIT extends AbstractDaemonTest {
@Test
public void wrongProjectInTripletChangeIdReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: unknown~" + changeInfo.branch + "~" + changeInfo.changeId);
- gApi.changes().id("unknown", changeInfo.branch, changeInfo.changeId);
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id("unknown", changeInfo.branch, changeInfo.changeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Not found: unknown~" + changeInfo.branch + "~" + changeInfo.changeId);
}
@Test
public void wrongBranchInTripletChangeIdReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: " + project.get() + "~unknown~" + changeInfo.changeId);
- gApi.changes().id(project.get(), "unknown", changeInfo.changeId);
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(project.get(), "unknown", changeInfo.changeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Not found: " + project.get() + "~unknown~" + changeInfo.changeId);
}
@Test
public void wrongIdInTripletChangeIdReturnsNotFound() throws Exception {
String unknownId = "I1234567890";
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage(
- "Not found: " + project.get() + "~" + changeInfo.branch + "~" + unknownId);
- gApi.changes().id(project.get(), changeInfo.branch, unknownId);
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(project.get(), changeInfo.branch, unknownId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Not found: " + project.get() + "~" + changeInfo.branch + "~" + unknownId);
}
@Test
@@ -119,7 +136,6 @@ public class ChangeIdIT extends AbstractDaemonTest {
@Test
public void wrongChangeIdReturnsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.changes().id("I1234567890");
+ assertThrows(ResourceNotFoundException.class, () -> gApi.changes().id("I1234567890"));
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
index 48c46d2976..fcc33c9e82 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
@@ -23,19 +23,19 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.SubmitRequirementInfo;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Optional;
import org.junit.Test;
public class ChangeSubmitRequirementIT extends AbstractDaemonTest {
@@ -100,24 +100,117 @@ public class ChangeSubmitRequirementIT extends AbstractDaemonTest {
assertThat(result.get(0).requirements).containsExactly(reqInfo);
}
+ @Test
+ public void submittableQueryRuleNotReady() throws Exception {
+ ChangeApi change = newChangeApi();
+
+ // Satisfy the default rule.
+ approveChange(change);
+
+ // The custom rule is NOT_READY.
+ rule.block(true);
+ change.index();
+
+ assertThat(queryIsSubmittable()).isEmpty();
+ }
+
+ @Test
+ public void submittableQueryRuleError() throws Exception {
+ ChangeApi change = newChangeApi();
+
+ // Satisfy the default rule.
+ approveChange(change);
+
+ rule.status(Optional.of(SubmitRecord.Status.RULE_ERROR));
+ change.index();
+
+ assertThat(queryIsSubmittable()).isEmpty();
+ }
+
+ @Test
+ public void submittableQueryDefaultRejected() throws Exception {
+ ChangeApi change = newChangeApi();
+
+ // CodeReview:-2 the change, causing the default rule to fail.
+ rejectChange(change);
+
+ rule.status(Optional.of(SubmitRecord.Status.OK));
+ change.index();
+
+ assertThat(queryIsSubmittable()).isEmpty();
+ }
+
+ @Test
+ public void submittableQueryRuleOk() throws Exception {
+ ChangeApi change = newChangeApi();
+
+ // Satisfy the default rule.
+ approveChange(change);
+
+ rule.status(Optional.of(SubmitRecord.Status.OK));
+ change.index();
+
+ List<ChangeInfo> result = queryIsSubmittable();
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).changeId).isEqualTo(change.info().changeId);
+ }
+
+ @Test
+ public void submittableQueryRuleNoRecord() throws Exception {
+ ChangeApi change = newChangeApi();
+
+ // Satisfy the default rule.
+ approveChange(change);
+
+ // Our custom rule isn't providing any submit records.
+ rule.status(Optional.empty());
+ change.index();
+
+ // is:submittable should return the change, since it was approved and the custom rule is not
+ // blocking it.
+ List<ChangeInfo> result = queryIsSubmittable();
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).changeId).isEqualTo(change.info().changeId);
+ }
+
+ private List<ChangeInfo> queryIsSubmittable() throws Exception {
+ return gApi.changes().query("is:submittable project:" + project.get()).get();
+ }
+
+ private ChangeApi newChangeApi() throws Exception {
+ return gApi.changes().id(createChange().getChangeId());
+ }
+
+ private void approveChange(ChangeApi changeApi) throws Exception {
+ changeApi.current().review(ReviewInput.approve());
+ }
+
+ private void rejectChange(ChangeApi changeApi) throws Exception {
+ changeApi.current().review(ReviewInput.reject());
+ }
+
@Singleton
private static class CustomSubmitRule implements SubmitRule {
- private final AtomicBoolean block = new AtomicBoolean(true);
+ private Optional<SubmitRecord.Status> recordStatus = Optional.empty();
public void block(boolean block) {
- this.block.set(block);
+ this.status(block ? Optional.of(SubmitRecord.Status.NOT_READY) : Optional.empty());
+ }
+
+ public void status(Optional<SubmitRecord.Status> status) {
+ this.recordStatus = status;
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData changeData, SubmitRuleOptions options) {
- if (block.get()) {
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
+ if (this.recordStatus.isPresent()) {
SubmitRecord record = new SubmitRecord();
record.labels = new ArrayList<>();
- record.status = SubmitRecord.Status.NOT_READY;
+ record.status = this.recordStatus.get();
record.requirements = ImmutableList.of(req);
- return ImmutableList.of(record);
+ return Optional.of(record);
}
- return ImmutableList.of();
+ return Optional.empty();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java b/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java
index ae88afdab2..42d62bd12f 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java
@@ -15,25 +15,24 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public class DisablePrivateChangesIT extends AbstractDaemonTest {
-
@Test
@GerritConfig(name = "change.disablePrivateChanges", value = "true")
public void createPrivateChangeWithDisablePrivateChangesTrue() throws Exception {
ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
input.isPrivate = true;
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("private changes are disabled");
- gApi.changes().create(input);
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> gApi.changes().create(input));
+ assertThat(thrown).hasMessageThat().contains("private changes are disabled");
}
@Test
@@ -59,20 +58,6 @@ public class DisablePrivateChangesIT extends AbstractDaemonTest {
}
@Test
- @GerritConfig(name = "change.allowDrafts", value = "true")
- @GerritConfig(name = "change.disablePrivateChanges", value = "true")
- public void pushDraftsWithDisablePrivateChangesTrue() throws Exception {
- RevCommit initialHead = getRemoteHead();
- PushOneCommit.Result result =
- pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%draft");
- result.assertErrorStatus();
-
- testRepo.reset(initialHead);
- result = pushFactory.create(admin.newIdent(), testRepo).to("refs/drafts/master");
- result.assertErrorStatus();
- }
-
- @Test
@GerritConfig(name = "change.disablePrivateChanges", value = "true")
public void pushWithDisablePrivateChangesTrue() throws Exception {
PushOneCommit.Result result =
@@ -82,34 +67,15 @@ public class DisablePrivateChangesIT extends AbstractDaemonTest {
}
@Test
- @GerritConfig(name = "change.allowDrafts", value = "true")
- public void pushPrivatesWithDisablePrivateChangesFalse() throws Exception {
- PushOneCommit.Result result =
- pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%private");
- assertThat(result.getChange().change().isPrivate()).isTrue();
- }
-
- @Test
- @GerritConfig(name = "change.allowDrafts", value = "true")
- public void pushDraftsWithDisablePrivateChangesFalse() throws Exception {
- RevCommit initialHead = getRemoteHead();
- PushOneCommit.Result result =
- pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%draft");
- assertThat(result.getChange().change().isPrivate()).isTrue();
-
- testRepo.reset(initialHead);
- result = pushFactory.create(admin.newIdent(), testRepo).to("refs/drafts/master");
- assertThat(result.getChange().change().isPrivate()).isTrue();
- }
-
- @Test
@GerritConfig(name = "change.disablePrivateChanges", value = "true")
public void setPrivateWithDisablePrivateChangesTrue() throws Exception {
PushOneCommit.Result result = createChange();
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("private changes are disabled");
- gApi.changes().id(result.getChangeId()).setPrivate(true, "set private");
+ MethodNotAllowedException thrown =
+ assertThrows(
+ MethodNotAllowedException.class,
+ () -> gApi.changes().id(result.getChangeId()).setPrivate(true, "set private"));
+ assertThat(thrown).hasMessageThat().contains("private changes are disabled");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/api/change/MergeListIT.java b/javatests/com/google/gerrit/acceptance/api/change/MergeListIT.java
index a08d417455..7354ca412d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/MergeListIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/MergeListIT.java
@@ -15,7 +15,9 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
+import static com.google.gerrit.entities.Patch.MERGE_LIST;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
@@ -152,18 +154,26 @@ public class MergeListIT extends AbstractDaemonTest {
public void editMergeList() throws Exception {
gApi.changes().id(changeId).edit().create();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Invalid path: " + MERGE_LIST);
- gApi.changes().id(changeId).edit().modifyFile(MERGE_LIST, RawInputUtil.create("new content"));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyFile(MERGE_LIST, RawInputUtil.create("new content")));
+ assertThat(thrown).hasMessageThat().contains("Invalid path: " + MERGE_LIST);
}
@Test
public void deleteMergeList() throws Exception {
gApi.changes().id(changeId).edit().create();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("no changes were made");
- gApi.changes().id(changeId).edit().deleteFile(MERGE_LIST);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).edit().deleteFile(MERGE_LIST));
+ assertThat(thrown).hasMessageThat().contains("no changes were made");
}
private String getMergeListContent(RevCommit... commits) {
@@ -171,7 +181,7 @@ public class MergeListIT extends AbstractDaemonTest {
for (RevCommit c : commits) {
mergeList
.append("* ")
- .append(c.abbreviate(8).name())
+ .append(abbreviateName(c, 8))
.append(" ")
.append(c.getShortMessage())
.append("\n");
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
new file mode 100644
index 0000000000..7156c8d1b8
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
@@ -0,0 +1,290 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.server.restapi.change.PostReview;
+import com.google.gerrit.server.update.CommentsRejectedException;
+import com.google.gerrit.testing.TestCommentHelper;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import java.sql.Timestamp;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+
+/** Tests for comment validation in {@link PostReview}. */
+public class PostReviewIT extends AbstractDaemonTest {
+ @Inject private CommentValidator mockCommentValidator;
+ @Inject private TestCommentHelper testCommentHelper;
+
+ private static final String COMMENT_TEXT = "The comment text";
+
+ @Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
+
+ @Override
+ public Module createModule() {
+ return new FactoryModule() {
+ @Override
+ public void configure() {
+ CommentValidator mockCommentValidator = mock(CommentValidator.class);
+ bind(CommentValidator.class)
+ .annotatedWith(Exports.named(mockCommentValidator.getClass()))
+ .toInstance(mockCommentValidator);
+ bind(CommentValidator.class).toInstance(mockCommentValidator);
+ }
+ };
+ }
+
+ @Before
+ public void resetMock() {
+ initMocks(this);
+ clearInvocations(mockCommentValidator);
+ }
+
+ @Test
+ public void validateCommentsInInput_commentOK() throws Exception {
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of());
+
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput input = new ReviewInput();
+ CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
+ comment.updated = new Timestamp(0);
+ input.comments = ImmutableMap.of(comment.path, ImmutableList.of(comment));
+
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+ gApi.changes().id(r.getChangeId()).current().review(input);
+
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(1);
+ }
+
+ @Test
+ public void validateCommentsInInput_commentRejected() throws Exception {
+ CommentForValidation commentForValidation =
+ CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput input = new ReviewInput();
+ CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
+ comment.updated = new Timestamp(0);
+ input.comments = ImmutableMap.of(comment.path, ImmutableList.of(comment));
+
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+ BadRequestException badRequestException =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().review(input));
+ assertThat(badRequestException.getCause()).isInstanceOf(CommentsRejectedException.class);
+ assertThat(
+ Iterables.getOnlyElement(
+ ((CommentsRejectedException) badRequestException.getCause())
+ .getCommentValidationFailures())
+ .getComment()
+ .getText())
+ .isEqualTo(COMMENT_TEXT);
+ assertThat(badRequestException.getCause()).hasMessageThat().contains("Oh no!");
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+ }
+
+ @Test
+ public void validateCommentsInInput_commentCleanedUp() throws Exception {
+ PushOneCommit.Result r = createChange();
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+
+ // posting a comment which is empty after trim is a no-op, as the empty comment is dropped
+ // during comment cleanup
+ ReviewInput input = new ReviewInput();
+ CommentInput comment =
+ TestCommentHelper.populate(
+ new CommentInput(), r.getChange().currentFilePaths().get(0), " ");
+ comment.updated = new Timestamp(0);
+ input.comments = ImmutableMap.of(comment.path, ImmutableList.of(comment));
+ gApi.changes().id(r.getChangeId()).current().review(input);
+
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+ }
+
+ @Test
+ public void validateDrafts_draftOK() throws Exception {
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of());
+
+ PushOneCommit.Result r = createChange();
+
+ DraftInput draft =
+ testCommentHelper.newDraft(
+ r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft).get();
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+
+ ReviewInput input = new ReviewInput();
+ input.drafts = DraftHandling.PUBLISH;
+
+ gApi.changes().id(r.getChangeId()).current().review(input);
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(1);
+ }
+
+ @Test
+ public void validateDrafts_draftRejected() throws Exception {
+ CommentForValidation commentForValidation =
+ CommentForValidation.create(CommentType.INLINE_COMMENT, COMMENT_TEXT);
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ PushOneCommit.Result r = createChange();
+
+ DraftInput draft =
+ testCommentHelper.newDraft(
+ r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
+ testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draft);
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+
+ ReviewInput input = new ReviewInput();
+ input.drafts = DraftHandling.PUBLISH;
+ BadRequestException badRequestException =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().review(input));
+ assertThat(badRequestException.getCause()).isInstanceOf(CommentsRejectedException.class);
+ assertThat(
+ Iterables.getOnlyElement(
+ ((CommentsRejectedException) badRequestException.getCause())
+ .getCommentValidationFailures())
+ .getComment()
+ .getText())
+ .isEqualTo(draft.message);
+ assertThat(badRequestException.getCause()).hasMessageThat().contains("Oh no!");
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+ }
+
+ @Test
+ public void validateDrafts_inlineVsFileComments_allOK() throws Exception {
+ PushOneCommit.Result r = createChange();
+ DraftInput draftInline =
+ testCommentHelper.newDraft(
+ r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
+ testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftInline);
+ DraftInput draftFile = testCommentHelper.newDraft(COMMENT_TEXT);
+ testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftFile);
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
+
+ when(mockCommentValidator.validateComments(capture.capture())).thenReturn(ImmutableList.of());
+
+ ReviewInput input = new ReviewInput();
+ input.drafts = DraftHandling.PUBLISH;
+ gApi.changes().id(r.getChangeId()).current().review(input);
+ assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(2);
+
+ assertThat(capture.getAllValues()).hasSize(1);
+ assertThat(capture.getValue())
+ .containsExactly(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
+ CommentForValidation.create(
+ CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
+ }
+
+ @Test
+ public void validateCommentsInChangeMessage_messageOK() throws Exception {
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of());
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
+ int numMessages = gApi.changes().id(r.getChangeId()).get().messages.size();
+ gApi.changes().id(r.getChangeId()).current().review(input);
+ assertThat(gApi.changes().id(r.getChangeId()).get().messages).hasSize(numMessages + 1);
+ ChangeMessageInfo message =
+ Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages);
+ assertThat(message.message).contains(COMMENT_TEXT);
+ }
+
+ @Test
+ public void validateCommentsInChangeMessage_messageRejected() throws Exception {
+ CommentForValidation commentForValidation =
+ CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT);
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
+ assertThat(gApi.changes().id(r.getChangeId()).get().messages)
+ .hasSize(1); // From the initial commit.
+ BadRequestException badRequestException =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().review(input));
+ assertThat(badRequestException.getCause()).isInstanceOf(CommentsRejectedException.class);
+ assertThat(
+ Iterables.getOnlyElement(
+ ((CommentsRejectedException) badRequestException.getCause())
+ .getCommentValidationFailures())
+ .getComment()
+ .getText())
+ .isEqualTo(COMMENT_TEXT);
+ assertThat(badRequestException.getCause()).hasMessageThat().contains("Oh no!");
+ assertThat(gApi.changes().id(r.getChangeId()).get().messages)
+ .hasSize(1); // Unchanged from before.
+ ChangeMessageInfo message =
+ Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages);
+ assertThat(message.message).doesNotContain(COMMENT_TEXT);
+ }
+
+ private static CommentInput newComment(String path) {
+ return TestCommentHelper.populate(new CommentInput(), path, PostReviewIT.COMMENT_TEXT);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PrivateChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/PrivateChangeIT.java
index bfcb1a8358..f043c9b3c9 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/PrivateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/PrivateChangeIT.java
@@ -15,19 +15,22 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdate;
@@ -41,6 +44,7 @@ import org.junit.Test;
public class PrivateChangeIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
@@ -89,9 +93,9 @@ public class PrivateChangeIT extends AbstractDaemonTest {
String changeId = result.getChangeId();
assertThat(gApi.changes().id(changeId).get().isPrivate).isNull();
- exception.expect(BadRequestException.class);
- exception.expectMessage("cannot set merged change to private");
- gApi.changes().id(changeId).setPrivate(true);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.changes().id(changeId).setPrivate(true));
+ assertThat(thrown).hasMessageThat().contains("cannot set merged change to private");
}
@Test
@@ -102,9 +106,9 @@ public class PrivateChangeIT extends AbstractDaemonTest {
gApi.changes().id(changeId).abandon();
assertThat(gApi.changes().id(changeId).get().isPrivate).isNull();
- exception.expect(BadRequestException.class);
- exception.expectMessage("cannot set abandoned change to private");
- gApi.changes().id(changeId).setPrivate(true);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.changes().id(changeId).setPrivate(true));
+ assertThat(thrown).hasMessageThat().contains("cannot set abandoned change to private");
}
@Test
@@ -126,9 +130,11 @@ public class PrivateChangeIT extends AbstractDaemonTest {
public void cannotSetOtherUsersChangePrivate() throws Exception {
PushOneCommit.Result result = createChange();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to mark private");
- gApi.changes().id(result.getChangeId()).setPrivate(true, null);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(result.getChangeId()).setPrivate(true, null));
+ assertThat(thrown).hasMessageThat().contains("not allowed to mark private");
}
@Test
@@ -153,9 +159,10 @@ public class PrivateChangeIT extends AbstractDaemonTest {
gApi.changes().id(result.getChangeId()).reviewer(admin.id().toString()).remove();
// This change should not be visible for admin anymore.
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: " + result.getChangeId());
- gApi.changes().id(result.getChangeId());
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(result.getChangeId()));
+ assertThat(thrown).hasMessageThat().contains("Not found: " + result.getChangeId());
}
@Test
@@ -163,7 +170,11 @@ public class PrivateChangeIT extends AbstractDaemonTest {
PushOneCommit.Result result = createChange();
gApi.changes().id(result.getChangeId()).setPrivate(true, null);
- allow("refs/*", Permission.VIEW_PRIVATE_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertThat(gApi.changes().id(result.getChangeId()).get().isPrivate).isTrue();
}
@@ -173,7 +184,7 @@ public class PrivateChangeIT extends AbstractDaemonTest {
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
merge(result);
- markMergedChangePrivate(new Change.Id(gApi.changes().id(changeId).get()._number));
+ markMergedChangePrivate(Change.id(gApi.changes().id(changeId).get()._number));
gApi.changes().id(changeId).setPrivate(false, null);
assertThat(gApi.changes().id(changeId).get().isPrivate).isNull();
@@ -191,9 +202,9 @@ public class PrivateChangeIT extends AbstractDaemonTest {
merge(result);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to mark private");
- gApi.changes().id(changeId).setPrivate(true, null);
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.changes().id(changeId).setPrivate(true, null));
+ assertThat(thrown).hasMessageThat().contains("not allowed to mark private");
}
@Test
@@ -218,7 +229,7 @@ public class PrivateChangeIT extends AbstractDaemonTest {
String changeId = result.getChangeId();
gApi.changes().id(changeId).addReviewer(admin.id().toString());
merge(result);
- markMergedChangePrivate(new Change.Id(gApi.changes().id(changeId).get()._number));
+ markMergedChangePrivate(Change.id(gApi.changes().id(changeId).get()._number));
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId).setPrivate(false, null);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
index 843527acfd..6839b967fc 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
@@ -15,22 +15,32 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.restapi.change.QueryChanges;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.util.Arrays;
import java.util.List;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
import org.junit.Test;
@NoHttpd
public class QueryChangesIT extends AbstractDaemonTest {
@Inject private Provider<QueryChanges> queryChangesProvider;
+ @Inject private RequestScopeOperations requestScopeOperations;
@Test
@SuppressWarnings("unchecked")
@@ -48,7 +58,7 @@ public class QueryChangesIT extends AbstractDaemonTest {
queryChanges.addQuery("is:wip repo:" + project.get());
List<List<ChangeInfo>> result =
- (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE);
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
assertThat(result).hasSize(2);
assertThat(result.get(0)).hasSize(2);
assertThat(result.get(1)).hasSize(1);
@@ -75,7 +85,7 @@ public class QueryChangesIT extends AbstractDaemonTest {
queryChanges.addQuery(queryWithMoreChanges);
queryChanges.addQuery(queryWithNoMoreChanges);
List<List<ChangeInfo>> result =
- (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE);
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
assertThat(result).hasSize(2);
assertThat(result.get(0)).hasSize(1);
assertThat(result.get(1)).hasSize(3);
@@ -88,7 +98,7 @@ public class QueryChangesIT extends AbstractDaemonTest {
queryChanges2.addQuery(queryWithNoMoreChanges);
queryChanges2.addQuery(queryWithMoreChanges);
List<List<ChangeInfo>> result2 =
- (List<List<ChangeInfo>>) queryChanges2.apply(TopLevelResource.INSTANCE);
+ (List<List<ChangeInfo>>) queryChanges2.apply(TopLevelResource.INSTANCE).value();
assertThat(result2).hasSize(2);
assertThat(result2.get(0)).hasSize(3);
assertThat(result2.get(1)).hasSize(1);
@@ -97,9 +107,87 @@ public class QueryChangesIT extends AbstractDaemonTest {
assertThat(result2.get(1).get(0)._moreChanges).isTrue();
}
+ @Test
+ public void skipVisibility_rejectedForNonAdmin() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ final QueryChanges queryChanges = queryChangesProvider.get();
+ String query = "is:open repo:" + project.get();
+ queryChanges.addQuery(query);
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> queryChanges.skipVisibility(true));
+ assertThat(thrown).hasMessageThat().isEqualTo("administrate server not permitted");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void skipVisibility_noReadPermission() throws Exception {
+ createChange().getChangeId();
+ requestScopeOperations.setApiUser(admin.id());
+ QueryChanges queryChanges = queryChangesProvider.get();
+
+ queryChanges.addQuery("is:open repo:" + project.get());
+ List<List<ChangeInfo>> result =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result).hasSize(1);
+
+ try (ProjectConfigUpdate u = updateProject(allProjects)) {
+ ProjectConfig cfg = u.getConfig();
+ removeAllBranchPermissions(cfg, Permission.READ);
+ u.save();
+ }
+
+ queryChanges = queryChangesProvider.get();
+ queryChanges.addQuery("is:open repo:" + project.get());
+ List<List<ChangeInfo>> result2 =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result2).hasSize(0);
+
+ queryChanges = queryChangesProvider.get();
+ queryChanges.addQuery("is:open repo:" + project.get());
+ queryChanges.skipVisibility(true);
+ List<List<ChangeInfo>> result3 =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result3).hasSize(1);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void skipVisibility_privateChange() throws Exception {
+ TestRepository<InMemoryRepository> userRepo = cloneProject(project, user);
+ PushOneCommit.Result result =
+ pushFactory.create(user.newIdent(), userRepo).to("refs/for/master");
+ requestScopeOperations.setApiUser(user.id());
+ gApi.changes().id(result.getChangeId()).setPrivate(true);
+
+ requestScopeOperations.setApiUser(admin.id());
+ QueryChanges queryChanges = queryChangesProvider.get();
+
+ queryChanges.addQuery("is:open repo:" + project.get());
+ List<List<ChangeInfo>> result2 =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result2).hasSize(0);
+
+ queryChanges = queryChangesProvider.get();
+ queryChanges.addQuery("is:open repo:" + project.get());
+ queryChanges.skipVisibility(true);
+ List<List<ChangeInfo>> result3 =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result3).hasSize(1);
+ }
+
private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
for (ChangeInfo info : results) {
assertThat(info._moreChanges).isNull();
}
}
+
+ private static void removeAllBranchPermissions(ProjectConfig cfg, String... permissions) {
+ cfg.getAccessSections().stream()
+ .filter(
+ s ->
+ s.getName().startsWith("refs/heads/")
+ || s.getName().startsWith("refs/for/")
+ || s.getName().equals("refs/*"))
+ .forEach(s -> Arrays.stream(permissions).forEach(s::removePermission));
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
new file mode 100644
index 0000000000..0607a3ce36
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -0,0 +1,454 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.util.stream.Collectors.toList;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RevertInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.PureRevertInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.permissions.PermissionDeniedException;
+import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+public class RevertIT extends AbstractDaemonTest {
+
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+
+ @Test
+ public void pureRevertFactBlocksSubmissionOfNonReverts() throws Exception {
+ addPureRevertSubmitRule();
+
+ // Create a change that is not a revert of another change
+ PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
+ approve(r1.getChangeId());
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r1.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Failed to submit 1 change due to the following problems");
+ assertThat(thrown).hasMessageThat().contains("needs Is-Pure-Revert");
+ }
+
+ @Test
+ public void pureRevertFactBlocksSubmissionOfNonPureReverts() throws Exception {
+ PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
+ merge(r1);
+
+ addPureRevertSubmitRule();
+
+ // Create a revert and push a content change
+ String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
+ amendChange(revertId);
+ approve(revertId);
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(revertId).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Failed to submit 1 change due to the following problems");
+ assertThat(thrown).hasMessageThat().contains("needs Is-Pure-Revert");
+ }
+
+ @Test
+ public void pureRevertFactAllowsSubmissionOfPureReverts() throws Exception {
+ // Create a change that we can later revert
+ PushOneCommit.Result r1 = pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
+ merge(r1);
+
+ addPureRevertSubmitRule();
+
+ // Create a revert and submit it
+ String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
+ approve(revertId);
+ gApi.changes().id(revertId).current().submit();
+ }
+
+ @Test
+ public void pureRevertReturnsTrueForPureRevert() throws Exception {
+ PushOneCommit.Result r = createChange();
+ merge(r);
+ String revertId = gApi.changes().id(r.getChangeId()).revert().get().id;
+ // Without query parameter
+ assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
+ // With query parameter
+ assertThat(
+ gApi.changes()
+ .id(revertId)
+ .pureRevert(
+ projectOperations.project(project).getHead("master").toObjectId().name())
+ .isPureRevert)
+ .isTrue();
+ }
+
+ @Test
+ public void pureRevertReturnsFalseOnContentChange() throws Exception {
+ PushOneCommit.Result r1 = createChange();
+ merge(r1);
+ // Create a revert and expect pureRevert to be true
+ String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
+ assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
+
+ // Create a new PS and expect pureRevert to be false
+ PushOneCommit.Result result = amendChange(revertId);
+ result.assertOkStatus();
+ assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isFalse();
+ }
+
+ @Test
+ public void pureRevertParameterTakesPrecedence() throws Exception {
+ PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
+ merge(r1);
+ String oldHead = projectOperations.project(project).getHead("master").toObjectId().name();
+
+ PushOneCommit.Result r2 = createChange("commit message", "a.txt", "content2");
+ merge(r2);
+
+ String revertId = gApi.changes().id(r2.getChangeId()).revert().get().changeId;
+ assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
+ assertThat(gApi.changes().id(revertId).pureRevert(oldHead).isPureRevert).isFalse();
+ }
+
+ @Test
+ public void pureRevertReturnsFalseOnInvalidInput() throws Exception {
+ PushOneCommit.Result r1 = createChange();
+ merge(r1);
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(createChange().getChangeId()).pureRevert("invalid id"));
+ assertThat(thrown).hasMessageThat().contains("invalid object ID");
+ }
+
+ @Test
+ public void pureRevertReturnsTrueWithCleanRebase() throws Exception {
+ PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
+ merge(r1);
+
+ PushOneCommit.Result r2 = createChange("commit message", "b.txt", "content2");
+ merge(r2);
+
+ String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
+ // Rebase revert onto HEAD
+ gApi.changes().id(revertId).rebase();
+ // Check that pureRevert is true which implies that the commit can be rebased onto the original
+ // commit.
+ assertThat(gApi.changes().id(revertId).pureRevert().isPureRevert).isTrue();
+ }
+
+ @Test
+ public void pureRevertReturnsFalseWithRebaseConflict() throws Exception {
+ // Create an initial commit to serve as claimed original
+ PushOneCommit.Result r1 = createChange("commit message", "a.txt", "content1");
+ merge(r1);
+ String claimedOriginal =
+ projectOperations.project(project).getHead("master").toObjectId().name();
+
+ // Change contents of the file to provoke a conflict
+ merge(createChange("commit message", "a.txt", "content2"));
+
+ // Create a commit that we can revert
+ PushOneCommit.Result r2 = createChange("commit message", "a.txt", "content3");
+ merge(r2);
+
+ // Create a revert of r2
+ String revertR3Id = gApi.changes().id(r2.getChangeId()).revert().id();
+ // Assert that the change is a pure revert of it's 'revertOf'
+ assertThat(gApi.changes().id(revertR3Id).pureRevert().isPureRevert).isTrue();
+ // Assert that the change is not a pure revert of claimedOriginal because pureRevert is trying
+ // to rebase this on claimed original, which fails.
+ PureRevertInfo pureRevert = gApi.changes().id(revertR3Id).pureRevert(claimedOriginal);
+ assertThat(pureRevert.isPureRevert).isFalse();
+ }
+
+ @Test
+ public void pureRevertThrowsExceptionWhenChangeIsNotARevertAndNoIdProvided() throws Exception {
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(createChange().getChangeId()).pureRevert());
+ assertThat(thrown).hasMessageThat().contains("revertOf not set");
+ }
+
+ @Test
+ public void revert() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+ ChangeInfo revertChange = gApi.changes().id(r.getChangeId()).revert().get();
+
+ // expected messages on source change:
+ // 1. Uploaded patch set 1.
+ // 2. Patch Set 1: Code-Review+2
+ // 3. Change has been successfully merged by Administrator
+ // 4. Patch Set 1: Reverted
+ List<ChangeMessageInfo> sourceMessages =
+ new ArrayList<>(gApi.changes().id(r.getChangeId()).get().messages);
+ assertThat(sourceMessages).hasSize(4);
+ String expectedMessage =
+ String.format("Created a revert of this change as %s", revertChange.changeId);
+ assertThat(sourceMessages.get(3).message).isEqualTo(expectedMessage);
+
+ assertThat(revertChange.messages).hasSize(1);
+ assertThat(revertChange.messages.iterator().next().message).isEqualTo("Uploaded patch set 1.");
+ assertThat(revertChange.revertOf).isEqualTo(gApi.changes().id(r.getChangeId()).get()._number);
+ }
+
+ @Test
+ public void revertWithDefaultTopic() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).topic("topic");
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
+ RevertInput revertInput = new RevertInput();
+ assertThat(gApi.changes().id(result.getChangeId()).revert(revertInput).topic())
+ .isEqualTo("topic");
+ }
+
+ @Test
+ public void revertWithSetTopic() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).topic("topic");
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
+ RevertInput revertInput = new RevertInput();
+ revertInput.topic = "reverted-not-default";
+ assertThat(gApi.changes().id(result.getChangeId()).revert(revertInput).topic())
+ .isEqualTo(revertInput.topic);
+ }
+
+ @Test
+ public void revertWithSetMessage() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
+ RevertInput revertInput = new RevertInput();
+ revertInput.message = "Message from input";
+ assertThat(gApi.changes().id(result.getChangeId()).revert(revertInput).get().subject)
+ .isEqualTo(revertInput.message);
+ }
+
+ @Test
+ public void revertNotifications() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).addReviewer(user.email());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ sender.clear();
+ ChangeInfo revertChange = gApi.changes().id(r.getChangeId()).revert().get();
+
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(2);
+ assertThat(sender.getMessages(revertChange.changeId, "newchange")).hasSize(1);
+ assertThat(sender.getMessages(r.getChangeId(), "revert")).hasSize(1);
+ }
+
+ @Test
+ public void suppressRevertNotifications() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).addReviewer(user.email());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ RevertInput revertInput = new RevertInput();
+ revertInput.notify = NotifyHandling.NONE;
+
+ sender.clear();
+ gApi.changes().id(r.getChangeId()).revert(revertInput).get();
+ assertThat(sender.getMessages()).isEmpty();
+ }
+
+ @Test
+ public void revertPreservesReviewersAndCcs() throws Exception {
+ PushOneCommit.Result r = createChange();
+
+ ReviewInput in = ReviewInput.approve();
+ in.reviewer(user.email());
+ in.reviewer(accountCreator.user2().email(), ReviewerState.CC, true);
+ // Add user as reviewer that will create the revert
+ in.reviewer(accountCreator.admin2().email());
+
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ // expect both the original reviewers and CCs to be preserved
+ // original owner should be added as reviewer, user requesting the revert (new owner) removed
+ requestScopeOperations.setApiUser(accountCreator.admin2().id());
+ Map<ReviewerState, Collection<AccountInfo>> result =
+ gApi.changes().id(r.getChangeId()).revert().get().reviewers;
+ assertThat(result).containsKey(ReviewerState.REVIEWER);
+
+ List<Integer> reviewers =
+ result.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
+ assertThat(result).containsKey(ReviewerState.CC);
+ List<Integer> ccs =
+ result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
+ assertThat(ccs).containsExactly(accountCreator.user2().id().get());
+ assertThat(reviewers).containsExactly(user.id().get(), admin.id().get());
+ }
+
+ @Test
+ @TestProjectInput(createEmptyCommit = false)
+ public void revertInitialCommit() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(r.getChangeId()).revert());
+ assertThat(thrown).hasMessageThat().contains("Cannot revert initial commit");
+ }
+
+ @Test
+ public void cantRevertNonMergedCommit() throws Exception {
+ PushOneCommit.Result result = createChange();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result.getChangeId()).revert());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("change is " + ChangeUtil.status(result.getChange().change()));
+ }
+
+ @Test
+ public void cantCreateRevertWithoutProjectWritePermission() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+ projectCache.checkedGet(project).getProject().setState(ProjectState.READ_ONLY);
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(r.getChangeId()).revert());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("project state " + ProjectState.READ_ONLY + " does not permit write");
+ }
+
+ @Test
+ public void cantCreateRevertWithoutCreateChangePermission() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
+
+ PermissionDeniedException thrown =
+ assertThrows(
+ PermissionDeniedException.class, () -> gApi.changes().id(r.getChangeId()).revert());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("not permitted: create change on refs/heads/master");
+ }
+
+ @Test
+ public void cantCreateRevertWithoutReadPermission() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
+
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(r.getChangeId()).revert());
+ assertThat(thrown).hasMessageThat().contains("Not found: " + r.getChangeId());
+ }
+
+ @Override
+ protected PushOneCommit.Result createChange() throws Exception {
+ return createChange("refs/for/master");
+ }
+
+ @Override
+ protected PushOneCommit.Result createChange(String ref) throws Exception {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result result = push.to(ref);
+ result.assertOkStatus();
+ return result;
+ }
+
+ private void addPureRevertSubmitRule() throws Exception {
+ modifySubmitRules(
+ "submit_rule(submit(R)) :- \n"
+ + "gerrit:pure_revert(1), \n"
+ + "!,"
+ + "gerrit:uploader(U), \n"
+ + "R = label('Is-Pure-Revert', ok(U)).\n"
+ + "submit_rule(submit(R)) :- \n"
+ + "gerrit:pure_revert(U), \n"
+ + "U \\= 1,"
+ + "R = label('Is-Pure-Revert', need(_)). \n\n");
+ }
+
+ private void modifySubmitRules(String newContent) throws Exception {
+ try (Repository repo = repoManager.openRepository(project);
+ TestRepository<Repository> testRepo = new TestRepository<>(repo)) {
+ testRepo
+ .branch(RefNames.REFS_CONFIG)
+ .commit()
+ .author(admin.newIdent())
+ .committer(admin.newIdent())
+ .add("rules.pl", newContent)
+ .message("Modify rules.pl")
+ .create();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index d53b305db3..7e692518d8 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.client.ChangeKind.MERGE_FIRST_PARENT_UPDATE;
import static com.google.gerrit.extensions.client.ChangeKind.NO_CHANGE;
import static com.google.gerrit.extensions.client.ChangeKind.NO_CODE_CHANGE;
@@ -24,19 +26,21 @@ import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMM
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
import static org.eclipse.jgit.lib.Constants.HEAD;
+import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
@@ -44,12 +48,14 @@ import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.inject.Inject;
+import com.google.inject.name.Named;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
@@ -60,15 +66,20 @@ import org.junit.Test;
@NoHttpd
public class StickyApprovalsIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject
+ @Named("change_kind")
+ private Cache<ChangeKindCacheImpl.Key, ChangeKind> changeKindCache;
+
@Before
public void setup() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
// Overwrite "Code-Review" label that is inherited from All-Projects.
// This way changes to the "Code Review" label don't affect other tests.
LabelType codeReview =
- category(
+ label(
"Code-Review",
value(2, "Looks good to me, approved"),
value(1, "Looks good to me, but someone else must approve"),
@@ -79,28 +90,26 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
u.getConfig().getLabelSections().put(codeReview.getName(), codeReview);
LabelType verified =
- category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+ label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
verified.setCopyAllScoresIfNoChange(false);
u.getConfig().getLabelSections().put(verified.getName(), verified);
- AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- String heads = RefNames.REFS_HEADS + "*";
- Util.allow(
- u.getConfig(),
- Permission.forLabel(Util.codeReview().getName()),
- -2,
- 2,
- registeredUsers,
- heads);
- Util.allow(
- u.getConfig(),
- Permission.forLabel(Util.verified().getName()),
- -1,
- 1,
- registeredUsers,
- heads);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(TestLabels.codeReview().getName())
+ .ref(RefNames.REFS_HEADS + "*")
+ .group(REGISTERED_USERS)
+ .range(-2, 2))
+ .add(
+ allowLabel(TestLabels.verified().getName())
+ .ref(RefNames.REFS_HEADS + "*")
+ .group(REGISTERED_USERS)
+ .range(-1, 1))
+ .update();
}
@Test
@@ -110,6 +119,28 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
}
@Test
+ public void stickyOnAnyScore() throws Exception {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig().getLabelSections().get("Code-Review").setCopyAnyScore(true);
+ u.save();
+ }
+
+ for (ChangeKind changeKind :
+ EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) {
+ testRepo.reset(projectOperations.project(project).getHead("master"));
+
+ String changeId = createChange(changeKind);
+ vote(admin, changeId, 2, 1);
+ vote(user, changeId, 1, -1);
+
+ updateChange(changeId, changeKind);
+ ChangeInfo c = detailedChange(changeId);
+ assertVotes(c, admin, 2, 0, changeKind);
+ assertVotes(c, user, 1, 0, changeKind);
+ }
+ }
+
+ @Test
public void stickyOnMinScore() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().get("Code-Review").setCopyMinScore(true);
@@ -118,7 +149,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
for (ChangeKind changeKind :
EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) {
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
String changeId = createChange(changeKind);
vote(admin, changeId, -1, 1);
@@ -140,7 +171,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
for (ChangeKind changeKind :
EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) {
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
String changeId = createChange(changeKind);
vote(admin, changeId, 2, 1);
@@ -177,7 +208,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
assertNotSticky(EnumSet.of(REWORK, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE));
// check that votes are sticky when trivial rebase is done by cherry-pick
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
changeId = createChange().getChangeId();
vote(admin, changeId, 2, 1);
vote(user, changeId, -2, -1);
@@ -188,7 +219,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
assertVotes(c, user, -2, 0);
// check that votes are not sticky when rework is done by cherry-pick
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
changeId = createChange().getChangeId();
vote(admin, changeId, 2, 1);
vote(user, changeId, -2, -1);
@@ -277,7 +308,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
for (ChangeKind changeKind :
EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) {
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
String changeId = createChange(changeKind);
vote(admin, changeId, 2, 1);
@@ -320,6 +351,42 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
}
@Test
+ public void stickyAcrossMultiplePatchSetsDoNotRegressPerformance() throws Exception {
+ // The purpose of this test is to make sure that we compute change kind only against the parent
+ // patch set. Change kind is a heavy operation. In prior version of Gerrit, we computed the
+ // change kind against all prior patch sets. This is a regression that made Gerrit do expensive
+ // work in O(num-patch-sets). This test ensures that we aren't regressing.
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
+ u.getConfig().getLabelSections().get("Verified").setCopyAllScoresIfNoCodeChange(true);
+ u.save();
+ }
+
+ String changeId = createChange(REWORK);
+ vote(admin, changeId, 2, 1);
+ updateChange(changeId, NO_CODE_CHANGE);
+ updateChange(changeId, NO_CODE_CHANGE);
+ updateChange(changeId, NO_CODE_CHANGE);
+
+ Map<Integer, ObjectId> revisions = new HashMap<>();
+ gApi.changes()
+ .id(changeId)
+ .get()
+ .revisions
+ .forEach(
+ (revId, revisionInfo) ->
+ revisions.put(revisionInfo._number, ObjectId.fromString(revId)));
+ assertThat(revisions.size()).isEqualTo(4);
+ assertChangeKindCacheContains(revisions.get(3), revisions.get(4));
+ assertChangeKindCacheContains(revisions.get(2), revisions.get(3));
+ assertChangeKindCacheContains(revisions.get(1), revisions.get(2));
+
+ assertChangeKindCacheDoesNotContain(revisions.get(1), revisions.get(4));
+ assertChangeKindCacheDoesNotContain(revisions.get(2), revisions.get(4));
+ assertChangeKindCacheDoesNotContain(revisions.get(1), revisions.get(3));
+ }
+
+ @Test
public void copyMinMaxAcrossMultiplePatchSets() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
u.getConfig().getLabelSections().get("Code-Review").setCopyMaxScore(true);
@@ -379,13 +446,25 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
assertVotes(detailedChange(changeId), admin, label, 0, REWORK);
}
+ private void assertChangeKindCacheContains(ObjectId prior, ObjectId next) {
+ ChangeKind kind =
+ changeKindCache.getIfPresent(ChangeKindCacheImpl.Key.create(prior, next, "recursive"));
+ assertThat(kind).isNotNull();
+ }
+
+ private void assertChangeKindCacheDoesNotContain(ObjectId prior, ObjectId next) {
+ ChangeKind kind =
+ changeKindCache.getIfPresent(ChangeKindCacheImpl.Key.create(prior, next, "recursive"));
+ assertThat(kind).isNull();
+ }
+
private ChangeInfo detailedChange(String changeId) throws Exception {
return gApi.changes().id(changeId).get(DETAILED_LABELS, CURRENT_REVISION, CURRENT_COMMIT);
}
private void assertNotSticky(Set<ChangeKind> changeKinds) throws Exception {
for (ChangeKind changeKind : changeKinds) {
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
String changeId = createChange(changeKind);
vote(admin, changeId, +2, 1);
@@ -430,7 +509,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
noChange(changeId);
return;
default:
- fail("unexpected change kind: " + changeKind);
+ assertWithMessage("unexpected change kind: " + changeKind).fail();
}
}
@@ -476,7 +555,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
private void trivialRebase(String changeId) throws Exception {
requestScopeOperations.setApiUser(admin.id());
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
PushOneCommit push =
pushFactory.create(
admin.newIdent(),
@@ -557,10 +636,10 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
case NO_CHANGE:
case MERGE_FIRST_PARENT_UPDATE:
default:
- fail("unexpected change kind: " + changeKind);
+ assertWithMessage("unexpected change kind: " + changeKind).fail();
}
- testRepo.reset(getRemoteHead());
+ testRepo.reset(projectOperations.project(project).getHead("master"));
PushOneCommit.Result r =
pushFactory
.create(
@@ -581,7 +660,7 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
CherryPickInput in = new CherryPickInput();
in.destination = "master";
in.message = String.format("%s\n\nChange-Id: %s", subject, changeId);
- ChangeInfo c = gApi.changes().id(changeId).revision("current").cherryPick(in).get();
+ ChangeInfo c = gApi.changes().id(changeId).current().cherryPick(in).get();
return c.changeId;
}
@@ -634,6 +713,6 @@ public class StickyApprovalsIT extends AbstractDaemonTest {
if (changeKind != null) {
name += "; changeKind = " + changeKind.name();
}
- assertThat(vote).named(name).isEqualTo(expectedVote);
+ assertWithMessage(name).that(vote).isEqualTo(expectedVote);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
index 6c0e9ab51b..dab2d00de8 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
@@ -21,11 +21,14 @@ import static com.google.gerrit.extensions.client.SubmitType.MERGE_ALWAYS;
import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
import static com.google.gerrit.extensions.client.SubmitType.REBASE_ALWAYS;
import static com.google.gerrit.extensions.client.SubmitType.REBASE_IF_NECESSARY;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.SubmitType;
@@ -33,8 +36,6 @@ import com.google.gerrit.extensions.common.TestSubmitRuleInfo;
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.testing.ConfigSuite;
@@ -237,22 +238,21 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
gApi.changes().id(r1.getChangeId()).current().review(ReviewInput.approve());
gApi.changes().id(r2.getChangeId()).current().review(ReviewInput.approve());
- try {
- gApi.changes().id(r2.getChangeId()).current().submit();
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- "Failed to submit 2 changes due to the following problems:\n"
- + "Change "
- + r1.getChange().getId()
- + ": Change has submit type "
- + "CHERRY_PICK, but previously chose submit type MERGE_IF_NECESSARY "
- + "from change "
- + r2.getChange().getId()
- + " in the same batch");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r2.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Failed to submit 2 changes due to the following problems:\n"
+ + "Change "
+ + r1.getChange().getId()
+ + ": Change has submit type "
+ + "CHERRY_PICK, but previously chose submit type MERGE_IF_NECESSARY "
+ + "from change "
+ + r2.getChange().getId()
+ + " in the same batch");
}
@Test
@@ -262,8 +262,8 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
TestSubmitRuleInput in = new TestSubmitRuleInput();
in.rule = "invalid prolog rule";
// We have no rules.pl by default. The fact that the default rules are showing up here is a bug.
- List<TestSubmitRuleInfo> response = gApi.changes().id(changeId).current().testSubmitRule(in);
- assertThat(response).containsExactly(invalidPrologRuleInfo());
+ TestSubmitRuleInfo response = gApi.changes().id(changeId).current().testSubmitRule(in);
+ assertThat(response).isEqualTo(invalidPrologRuleInfo());
}
@Test
@@ -274,8 +274,8 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
TestSubmitRuleInput in = new TestSubmitRuleInput();
in.rule = "invalid prolog rule";
- List<TestSubmitRuleInfo> response = gApi.changes().id(changeId).current().testSubmitRule(in);
- assertThat(response).containsExactly(invalidPrologRuleInfo());
+ TestSubmitRuleInfo response = gApi.changes().id(changeId).current().testSubmitRule(in);
+ assertThat(response).isEqualTo(invalidPrologRuleInfo());
}
private static TestSubmitRuleInfo invalidPrologRuleInfo() {
diff --git a/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
index fd08838e24..70aa557438 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/DiffPreferencesIT.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.api.config;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -37,7 +37,7 @@ public class DiffPreferencesIT extends AbstractDaemonTest {
DiffPreferencesInfo update = new DiffPreferencesInfo();
update.lineLength = newLineLength;
DiffPreferencesInfo result = gApi.config().server().setDefaultDiffPreferences(update);
- assertThat(result.lineLength).named("lineLength").isEqualTo(newLineLength);
+ assertWithMessage("lineLength").that(result.lineLength).isEqualTo(newLineLength);
result = gApi.config().server().getDefaultDiffPreferences();
DiffPreferencesInfo expected = DiffPreferencesInfo.defaults();
diff --git a/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
index e89aa3dec2..02f1ec355c 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/EditPreferencesIT.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.api.config;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -37,7 +37,7 @@ public class EditPreferencesIT extends AbstractDaemonTest {
EditPreferencesInfo update = new EditPreferencesInfo();
update.lineLength = newLineLength;
EditPreferencesInfo result = gApi.config().server().setDefaultEditPreferences(update);
- assertThat(result.lineLength).named("lineLength").isEqualTo(newLineLength);
+ assertWithMessage("lineLength").that(result.lineLength).isEqualTo(newLineLength);
result = gApi.config().server().getDefaultEditPreferences();
EditPreferencesInfo expected = EditPreferencesInfo.defaults();
diff --git a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
index c6069824c6..221e171959 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
@@ -14,7 +14,7 @@
package com.google.gerrit.acceptance.api.config;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -36,7 +36,7 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
GeneralPreferencesInfo update = new GeneralPreferencesInfo();
update.signedOffBy = newSignedOffBy;
GeneralPreferencesInfo result = gApi.config().server().setDefaultPreferences(update);
- assertThat(result.signedOffBy).named("signedOffBy").isEqualTo(newSignedOffBy);
+ assertWithMessage("signedOffBy").that(result.signedOffBy).isEqualTo(newSignedOffBy);
result = gApi.config().server().getDefaultPreferences();
GeneralPreferencesInfo expected = GeneralPreferencesInfo.defaults();
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
index d7d311ba39..41ae3701a5 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
@@ -20,11 +20,11 @@ import static com.google.gerrit.truth.ListSubject.assertThat;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
@@ -57,7 +57,7 @@ public class GroupIndexerIT {
@Test
public void indexingUpdatesTheIndex() throws Exception {
AccountGroup.UUID groupUuid = createGroup("users");
- AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("contributors");
+ AccountGroup.UUID subgroupUuid = AccountGroup.uuid("contributors");
updateGroupWithoutCacheOrIndex(
groupUuid,
newGroupUpdate()
@@ -74,7 +74,7 @@ public class GroupIndexerIT {
public void indexCannotBeCorruptedByStaleCache() throws Exception {
AccountGroup.UUID groupUuid = createGroup("verifiers");
loadGroupToCache(groupUuid);
- AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("contributors");
+ AccountGroup.UUID subgroupUuid = AccountGroup.uuid("contributors");
updateGroupWithoutCacheOrIndex(
groupUuid,
newGroupUpdate()
@@ -102,7 +102,7 @@ public class GroupIndexerIT {
@Test
public void reindexingStaleGroupUpdatesTheIndex() throws Exception {
AccountGroup.UUID groupUuid = createGroup("users");
- AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("contributors");
+ AccountGroup.UUID subgroupUuid = AccountGroup.uuid("contributors");
updateGroupWithoutCacheOrIndex(
groupUuid,
newGroupUpdate()
@@ -139,7 +139,7 @@ public class GroupIndexerIT {
private AccountGroup.UUID createGroup(String name) throws RestApiException {
GroupInfo group = gApi.groups().create(name).get();
- return new AccountGroup.UUID(group.id);
+ return AccountGroup.uuid(group.id);
}
private void reloadGroupToCache(AccountGroup.UUID groupUuid) {
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsConsistencyIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsConsistencyIT.java
index e269e682f5..e6c3919e99 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsConsistencyIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsConsistencyIT.java
@@ -15,19 +15,22 @@
package com.google.gerrit.acceptance.api.group;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.group.db.GroupConfig;
import com.google.gerrit.server.group.db.GroupNameNotes;
import com.google.gerrit.server.group.db.testing.GroupTestUtil;
@@ -51,6 +54,7 @@ import org.junit.Test;
public class GroupsConsistencyIT extends AbstractDaemonTest {
@Inject protected GroupOperations groupOperations;
+ @Inject private ProjectOperations projectOperations;
private GroupInfo gAdmin;
private GroupInfo g1;
private GroupInfo g2;
@@ -59,7 +63,10 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
@Before
public void basicSetup() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
String name1 = groupOperations.newGroup().name("g1").create().get();
String name2 = groupOperations.newGroup().name("g2").create().get();
@@ -94,7 +101,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
public void missingGroupRef() throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
- RefUpdate ru = repo.updateRef(RefNames.refsGroups(new AccountGroup.UUID(g1.id)));
+ RefUpdate ru = repo.updateRef(RefNames.refsGroups(AccountGroup.uuid(g1.id)));
ru.setForceUpdate(true);
RefUpdate.Result result = ru.delete();
assertThat(result).isEqualTo(Result.FORCED);
@@ -109,7 +116,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
try (Repository repo = repoManager.openRepository(allUsers)) {
RefRename ru =
repo.renameRef(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)), RefNames.REFS_GROUPS + BOGUS_UUID);
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)), RefNames.REFS_GROUPS + BOGUS_UUID);
RefUpdate.Result result = ru.rename();
assertThat(result).isEqualTo(Result.RENAMED);
}
@@ -123,8 +130,8 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
try (Repository repo = repoManager.openRepository(allUsers)) {
RefRename ru =
repo.renameRef(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)),
- RefNames.refsGroups(new AccountGroup.UUID(BOGUS_UUID)));
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)),
+ RefNames.refsGroups(AccountGroup.uuid(BOGUS_UUID)));
RefUpdate.Result result = ru.rename();
assertThat(result).isEqualTo(Result.RENAMED);
}
@@ -135,7 +142,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
@Test
public void groupRefDoesNotParse() throws Exception {
updateGroupFile(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)),
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)),
GroupConfig.GROUP_CONFIG_FILE,
"[this is not valid\n");
assertError("does not parse");
@@ -145,7 +152,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
public void nameRefDoesNotParse() throws Exception {
updateGroupFile(
RefNames.REFS_GROUPNAMES,
- GroupNameNotes.getNoteKey(new AccountGroup.NameKey(g1.name)).getName(),
+ GroupNameNotes.getNoteKey(AccountGroup.nameKey(g1.name)).getName(),
"[this is not valid\n");
assertError("does not parse");
}
@@ -158,9 +165,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
cfg.setString("group", null, "ownerGroupUuid", gAdmin.id);
updateGroupFile(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)),
- GroupConfig.GROUP_CONFIG_FILE,
- cfg.toText());
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)), GroupConfig.GROUP_CONFIG_FILE, cfg.toText());
assertError("inconsistent name");
}
@@ -172,9 +177,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
cfg.setString("group", null, "ownerGroupUuid", gAdmin.id);
updateGroupFile(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)),
- GroupConfig.GROUP_CONFIG_FILE,
- cfg.toText());
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)), GroupConfig.GROUP_CONFIG_FILE, cfg.toText());
assertError("shared group id");
}
@@ -186,9 +189,7 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
cfg.setString("group", null, "ownerGroupUuid", BOGUS_UUID);
updateGroupFile(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)),
- GroupConfig.GROUP_CONFIG_FILE,
- cfg.toText());
+ RefNames.refsGroups(AccountGroup.uuid(g1.id)), GroupConfig.GROUP_CONFIG_FILE, cfg.toText());
assertError("nonexistent owner group");
}
@@ -201,27 +202,26 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
updateGroupFile(
RefNames.REFS_GROUPNAMES,
- GroupNameNotes.getNoteKey(new AccountGroup.NameKey(bogusName)).getName(),
+ GroupNameNotes.getNoteKey(AccountGroup.nameKey(bogusName)).getName(),
config.toText());
assertError("entry missing as group ref");
}
@Test
public void nonexistentMember() throws Exception {
- updateGroupFile(RefNames.refsGroups(new AccountGroup.UUID(g1.id)), "members", "314159265\n");
+ updateGroupFile(RefNames.refsGroups(AccountGroup.uuid(g1.id)), "members", "314159265\n");
assertError("nonexistent member 314159265");
}
@Test
public void nonexistentSubgroup() throws Exception {
- updateGroupFile(
- RefNames.refsGroups(new AccountGroup.UUID(g1.id)), "subgroups", BOGUS_UUID + "\n");
+ updateGroupFile(RefNames.refsGroups(AccountGroup.uuid(g1.id)), "subgroups", BOGUS_UUID + "\n");
assertError("has nonexistent subgroup");
}
@Test
public void cyclicSubgroup() throws Exception {
- updateGroupFile(RefNames.refsGroups(new AccountGroup.UUID(g1.id)), "subgroups", g1.id + "\n");
+ updateGroupFile(RefNames.refsGroups(AccountGroup.uuid(g1.id)), "subgroups", g1.id + "\n");
assertWarning("cycle");
}
@@ -252,7 +252,8 @@ public class GroupsConsistencyIT extends AbstractDaemonTest {
}
}
- fail(String.format("could not find %s substring '%s' in %s", want, msg, problems));
+ assertWithMessage(String.format("could not find %s substring '%s' in %s", want, msg, problems))
+ .fail();
}
private void updateGroupFile(String refName, String fileName, String content) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index b86fb29795..b53681b782 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -21,19 +21,25 @@ import static com.google.gerrit.acceptance.GitUtil.deleteRef;
import static com.google.gerrit.acceptance.GitUtil.fetch;
import static com.google.gerrit.acceptance.api.group.GroupAssert.assertGroupInfo;
import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAccountInfos;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.truth.Correspondence;
-import com.google.common.truth.Correspondence.BinaryPredicate;
import com.google.common.util.concurrent.AtomicLongMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
@@ -41,13 +47,19 @@ import com.google.gerrit.acceptance.ProjectResetter;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseClockStep;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -60,18 +72,12 @@ import com.google.gerrit.extensions.common.GroupAuditEventInfo.UserMemberAuditEv
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.GroupOptionsInfo;
import com.google.gerrit.extensions.events.GroupIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.group.InternalGroup;
@@ -85,11 +91,9 @@ import com.google.gerrit.server.group.db.InternalGroupUpdate;
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.index.group.StalenessChecker;
import com.google.gerrit.server.notedb.Sequences;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.TestTimeUtil;
+import com.google.gerrit.testing.GerritJUnit.ThrowingRunnable;
import com.google.inject.Inject;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -100,7 +104,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
@@ -117,33 +120,24 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
+@UseClockStep
public class GroupsIT extends AbstractDaemonTest {
@Inject @ServerInitiated private GroupsUpdate groupsUpdate;
@Inject private AccountOperations accountOperations;
- @Inject private DynamicSet<GroupIndexedListener> groupIndexedListeners;
@Inject private GroupIncludeCache groupIncludeCache;
@Inject private GroupIndexer groupIndexer;
@Inject private GroupOperations groupOperations;
@Inject private Groups groups;
@Inject private GroupsConsistencyChecker consistencyChecker;
@Inject private PeriodicGroupIndexer slaveGroupIndexer;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private Sequences seq;
@Inject private StalenessChecker stalenessChecker;
-
- @Before
- public void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- }
+ @Inject private ExtensionRegistry extensionRegistry;
@After
public void consistencyCheck() throws Exception {
@@ -168,14 +162,16 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void addToNonExistingGroup_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id("non-existing").addMembers("admin");
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.groups().id("non-existing").addMembers("admin"));
}
@Test
public void removeFromNonExistingGroup_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id("non-existing").removeMembers("admin");
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.groups().id("non-existing").removeMembers("admin"));
}
@Test
@@ -215,7 +211,7 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void cachedGroupByNameIsUpdatedOnCreation() throws Exception {
String newGroupName = name("newGroup");
- AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newGroupName);
+ AccountGroup.NameKey nameKey = AccountGroup.nameKey(newGroupName);
assertThat(groupCache.get(nameKey)).isEmpty();
gApi.groups().create(newGroupName);
assertThat(groupCache.get(nameKey)).isPresent();
@@ -231,8 +227,9 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void addNonExistingMember_UnprocessableEntity() throws Exception {
- exception.expect(UnprocessableEntityException.class);
- gApi.groups().id("Administrators").addMembers("non-existing");
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.groups().id("Administrators").addMembers("non-existing"));
}
@Test
@@ -351,9 +348,9 @@ public class GroupsIT extends AbstractDaemonTest {
public void createDuplicateInternalGroupCaseSensitiveName_Conflict() throws Exception {
String dupGroupName = name("dupGroup");
gApi.groups().create(dupGroupName);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group '" + dupGroupName + "' already exists");
- gApi.groups().create(dupGroupName);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(dupGroupName));
+ assertThat(thrown).hasMessageThat().contains("group '" + dupGroupName + "' already exists");
}
@Test
@@ -369,33 +366,71 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void createDuplicateSystemGroupCaseSensitiveName_Conflict() throws Exception {
String newGroupName = "Registered Users";
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group 'Registered Users' already exists");
- gApi.groups().create(newGroupName);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(newGroupName));
+ assertThat(thrown).hasMessageThat().contains("group 'Registered Users' already exists");
}
@Test
public void createDuplicateSystemGroupCaseInsensitiveName_Conflict() throws Exception {
String newGroupName = "registered users";
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group 'Registered Users' already exists");
- gApi.groups().create(newGroupName);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(newGroupName));
+ assertThat(thrown).hasMessageThat().contains("group 'Registered Users' already exists");
}
@Test
@GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users")
public void createGroupWithConfiguredNameOfSystemGroup_Conflict() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group 'All Users' already exists");
- gApi.groups().create("all users");
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create("all users"));
+ assertThat(thrown).hasMessageThat().contains("group 'All Users' already exists");
}
@Test
@GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users")
public void createGroupWithDefaultNameOfSystemGroup_Conflict() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group name 'Anonymous Users' is reserved");
- gApi.groups().create("anonymous users");
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.groups().create("anonymous users"));
+ assertThat(thrown).hasMessageThat().contains("group name 'Anonymous Users' is reserved");
+ }
+
+ @Test
+ public void createGroupWithUuid() throws Exception {
+ AccountGroup.UUID uuid = AccountGroup.UUID.parse("4eb25d1cca562f53b9356117f33840706a36a349");
+ GroupInput input = new GroupInput();
+ input.uuid = uuid.get();
+ input.name = name("new-group");
+ GroupInfo info = gApi.groups().create(input).get();
+ assertThat(info.name).isEqualTo(input.name);
+ assertThat(info.id).isEqualTo(input.uuid);
+ }
+
+ @Test
+ public void createGroupWithExistingUuid_Conflict() throws Exception {
+ GroupInfo existingGroup = gApi.groups().create(name("new-group")).get();
+ GroupInput input = new GroupInput();
+ input.uuid = existingGroup.id;
+ input.name = name("another-new-group");
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(input).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("group with UUID '%s' already exists", input.uuid));
+ }
+
+ @Test
+ public void createGroupWithInvalidUuid_BadRequest() throws Exception {
+ AccountGroup.UUID uuid = AccountGroup.UUID.parse("foo:bar");
+ GroupInput input = new GroupInput();
+ input.uuid = uuid.get();
+ input.name = name("new-group");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.groups().create(input).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("invalid group UUID '%s'", input.uuid));
}
@Test
@@ -414,8 +449,7 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void createGroupWithoutCapability_Forbidden() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.groups().create(name("newGroup"));
+ assertThrows(AuthException.class, () -> gApi.groups().create(name("newGroup")));
}
@Test
@@ -441,7 +475,7 @@ public class GroupsIT extends AbstractDaemonTest {
GroupInfo group = gApi.groups().create(groupInput).get();
Collection<AccountGroup.UUID> groups = groupIncludeCache.getGroupsWithMember(accountId);
- assertThat(groups).containsExactly(new AccountGroup.UUID(group.id));
+ assertThat(groups).containsExactly(AccountGroup.uuid(group.id));
}
@Test
@@ -481,8 +515,7 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
@GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users")
public void getSystemGroupByDefaultName_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id("Anonymous-Users").get();
+ assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id("Anonymous-Users").get());
}
@Test
@@ -498,8 +531,7 @@ public class GroupsIT extends AbstractDaemonTest {
String name = name("Users");
gApi.groups().create(name).get();
- exception.expect(ResourceConflictException.class);
- gApi.groups().create(name);
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(name));
}
@Test
@@ -528,9 +560,7 @@ public class GroupsIT extends AbstractDaemonTest {
String name2 = name("Name2");
gApi.groups().create(name2);
-
- exception.expect(ResourceConflictException.class);
- gApi.groups().id(group1.id).name(name2);
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().id(group1.id).name(name2));
}
@Test
@@ -554,8 +584,7 @@ public class GroupsIT extends AbstractDaemonTest {
gApi.groups().id(group.id).name(newName);
assertGroupDoesNotExist(name);
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id(name).get();
+ assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id(name).get());
}
@Test
@@ -626,14 +655,15 @@ public class GroupsIT extends AbstractDaemonTest {
assertThat(Url.decode(gApi.groups().id(name).owner().id)).isEqualTo(adminUUID);
// set non existing owner
- exception.expect(UnprocessableEntityException.class);
- gApi.groups().id(name).owner("Non-Existing Group");
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.groups().id(name).owner("Non-Existing Group"));
}
@Test
public void listNonExistingGroupIncludes_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id("non-existing").includedGroups();
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.groups().id("non-existing").includedGroups());
}
@Test
@@ -645,8 +675,9 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void includeNonExistingGroup() throws Exception {
AccountGroup.UUID gx = groupOperations.newGroup().create();
- exception.expect(UnprocessableEntityException.class);
- gApi.groups().id(gx.get()).addGroups("non-existing");
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.groups().id(gx.get()).addGroups("non-existing"));
}
@Test
@@ -675,8 +706,7 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void listNonExistingGroupMembers_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.groups().id("non-existing").members();
+ assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id("non-existing").members());
}
@Test
@@ -806,13 +836,13 @@ public class GroupsIT extends AbstractDaemonTest {
// By UUID
List<GroupInfo> owned = gApi.groups().list().withOwnedBy(parent.get()).get();
- assertThat(owned.stream().map(g -> new AccountGroup.UUID(g.id)).collect(toList()))
+ assertThat(owned.stream().map(g -> AccountGroup.uuid(g.id)).collect(toList()))
.containsExactlyElementsIn(children);
// By name
String parentName = groupOperations.group(parent).get().name();
owned = gApi.groups().list().withOwnedBy(parentName).get();
- assertThat(owned.stream().map(g -> new AccountGroup.UUID(g.id)).collect(toList()))
+ assertThat(owned.stream().map(g -> AccountGroup.uuid(g.id)).collect(toList()))
.containsExactlyElementsIn(children);
// By group that does not own any others
@@ -820,9 +850,11 @@ public class GroupsIT extends AbstractDaemonTest {
assertThat(owned).isEmpty();
// By non-existing group
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Group Not Found: does-not-exist");
- gApi.groups().list().withOwnedBy("does-not-exist").get();
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.groups().list().withOwnedBy("does-not-exist").get());
+ assertThat(thrown).hasMessageThat().contains("Group Not Found: does-not-exist");
}
@Test
@@ -984,7 +1016,7 @@ public class GroupsIT extends AbstractDaemonTest {
}
private void deleteGroupRef(String groupId) throws Exception {
- AccountGroup.UUID uuid = new AccountGroup.UUID(groupId);
+ AccountGroup.UUID uuid = AccountGroup.uuid(groupId);
try (Repository repo = repoManager.openRepository(allUsers)) {
RefUpdate ru = repo.updateRef(RefNames.refsGroups(uuid));
ru.setForceUpdate(true);
@@ -996,11 +1028,7 @@ public class GroupsIT extends AbstractDaemonTest {
gApi.groups().id(uuid.get()).index();
// Verify "sub-group" has been deleted.
- try {
- gApi.groups().id(uuid.get()).get();
- fail("expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- }
+ assertThrows(ResourceNotFoundException.class, () -> gApi.groups().id(uuid.get()).get());
}
// reindex is tested by {@link AbstractQueryGroupsTest#reindex}
@@ -1023,9 +1051,9 @@ public class GroupsIT extends AbstractDaemonTest {
// user cannot reindex any group
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to index group");
- gApi.groups().id(group.id).index();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.groups().id(group.id).index());
+ assertThat(thrown).hasMessageThat().contains("not allowed to index group");
}
@Test
@@ -1037,10 +1065,12 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void pushToDeletedGroupBranchIsRejectedForAllUsersRepo() throws Exception {
// refs/deleted-groups is only visible with ACCESS_DATABASE
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
String groupRef =
- RefNames.refsDeletedGroups(
- new AccountGroup.UUID(gApi.groups().create(name("foo")).get().id));
+ RefNames.refsDeletedGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id));
createBranch(allUsers, groupRef);
assertPushToGroupBranch(allUsers, groupRef, "group update not allowed");
}
@@ -1048,7 +1078,10 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void pushToGroupNamesBranchIsRejectedForAllUsersRepo() throws Exception {
// refs/meta/group-names isn't usually available for fetch, so grant ACCESS_DATABASE
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
assertPushToGroupBranch(allUsers, RefNames.REFS_GROUPNAMES, "group update not allowed");
}
@@ -1056,7 +1089,7 @@ public class GroupsIT extends AbstractDaemonTest {
public void pushToGroupsBranchForNonAllUsersRepo() throws Exception {
assertCreateGroupBranch(project);
String groupRef =
- RefNames.refsGroups(new AccountGroup.UUID(gApi.groups().create(name("foo")).get().id));
+ RefNames.refsGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id));
createBranch(project, groupRef);
assertPushToGroupBranch(project, groupRef, null);
}
@@ -1065,8 +1098,7 @@ public class GroupsIT extends AbstractDaemonTest {
public void pushToDeletedGroupsBranchForNonAllUsersRepo() throws Exception {
assertCreateGroupBranch(project);
String groupRef =
- RefNames.refsDeletedGroups(
- new AccountGroup.UUID(gApi.groups().create(name("foo")).get().id));
+ RefNames.refsDeletedGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id));
createBranch(project, groupRef);
assertPushToGroupBranch(project, groupRef, null);
}
@@ -1079,15 +1111,18 @@ public class GroupsIT extends AbstractDaemonTest {
private void assertPushToGroupBranch(
Project.NameKey project, String groupRefName, String expectedErrorOnUpdate) throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- ProjectConfig cfg = u.getConfig();
- Util.allow(cfg, Permission.CREATE, REGISTERED_USERS, RefNames.REFS_GROUPS + "*");
- Util.allow(cfg, Permission.PUSH, REGISTERED_USERS, RefNames.REFS_GROUPS + "*");
- Util.allow(cfg, Permission.CREATE, REGISTERED_USERS, RefNames.REFS_DELETED_GROUPS + "*");
- Util.allow(cfg, Permission.PUSH, REGISTERED_USERS, RefNames.REFS_DELETED_GROUPS + "*");
- Util.allow(cfg, Permission.PUSH, REGISTERED_USERS, RefNames.REFS_GROUPNAMES);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .add(
+ allow(Permission.CREATE)
+ .ref(RefNames.REFS_DELETED_GROUPS + "*")
+ .group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_DELETED_GROUPS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPNAMES).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(project);
@@ -1106,12 +1141,12 @@ public class GroupsIT extends AbstractDaemonTest {
}
private void assertCreateGroupBranch(Project.NameKey project) throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- ProjectConfig cfg = u.getConfig();
- Util.allow(cfg, Permission.CREATE, REGISTERED_USERS, RefNames.REFS_GROUPS + "*");
- Util.allow(cfg, Permission.PUSH, REGISTERED_USERS, RefNames.REFS_GROUPS + "*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(project);
PushOneCommit.Result r =
pushFactory
@@ -1122,13 +1157,13 @@ public class GroupsIT extends AbstractDaemonTest {
}
@Test
- public void pushToGroupBranchForReviewForAllUsersRepoIsRejectedOnSubmit() throws Exception {
+ public void pushToGroupBranchForReviewForAllUsersRepoIsRejectedOnSubmit() throws Throwable {
pushToGroupBranchForReviewAndSubmit(
allUsers, RefNames.refsGroups(adminGroupUuid()), "group update not allowed");
}
@Test
- public void pushToGroupBranchForReviewForNonAllUsersRepoAndSubmit() throws Exception {
+ public void pushToGroupBranchForReviewForNonAllUsersRepoAndSubmit() throws Throwable {
String groupRef = RefNames.refsGroups(adminGroupUuid());
createBranch(project, groupRef);
pushToGroupBranchForReviewAndSubmit(project, groupRef, null);
@@ -1162,14 +1197,14 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void cannotCreateGroupBranch() throws Exception {
testCannotCreateGroupBranch(
- RefNames.REFS_GROUPS + "*", RefNames.refsGroups(new AccountGroup.UUID(name("foo"))));
+ RefNames.REFS_GROUPS + "*", RefNames.refsGroups(AccountGroup.uuid(name("foo"))));
}
@Test
public void cannotCreateDeletedGroupBranch() throws Exception {
testCannotCreateGroupBranch(
RefNames.REFS_DELETED_GROUPS + "*",
- RefNames.refsDeletedGroups(new AccountGroup.UUID(name("foo"))));
+ RefNames.refsDeletedGroups(AccountGroup.uuid(name("foo"))));
}
@Test
@@ -1192,15 +1227,22 @@ public class GroupsIT extends AbstractDaemonTest {
}
// refs/meta/group-names is only visible with ACCESS_DATABASE
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
testCannotCreateGroupBranch(RefNames.REFS_GROUPNAMES, RefNames.REFS_GROUPNAMES);
}
}
private void testCannotCreateGroupBranch(String refPattern, String groupRef) throws Exception {
- grant(allUsers, refPattern, Permission.CREATE);
- grant(allUsers, refPattern, Permission.PUSH);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(refPattern).group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(refPattern).group(adminGroupUuid()))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(groupRef);
@@ -1220,7 +1262,10 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void cannotDeleteDeletedGroupBranch() throws Exception {
// refs/deleted-groups is only visible with ACCESS_DATABASE
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
String groupRef = RefNames.refsDeletedGroups(AccountGroup.uuid(name("foo")));
createBranch(allUsers, groupRef);
testCannotDeleteGroupBranch(RefNames.REFS_DELETED_GROUPS + "*", groupRef);
@@ -1229,13 +1274,20 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void cannotDeleteGroupNamesBranch() throws Exception {
// refs/meta/group-names is only visible with ACCESS_DATABASE
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
testCannotDeleteGroupBranch(RefNames.REFS_GROUPNAMES, RefNames.REFS_GROUPNAMES);
}
private void testCannotDeleteGroupBranch(String refPattern, String groupRef) throws Exception {
- grant(allUsers, refPattern, Permission.DELETE, true, REGISTERED_USERS);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.DELETE).ref(refPattern).group(REGISTERED_USERS).force(true))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
PushResult r = deleteRef(allUsersRepo, groupRef);
@@ -1259,7 +1311,7 @@ public class GroupsIT extends AbstractDaemonTest {
public void stalenessChecker() throws Exception {
// Newly created group is not stale
GroupInfo groupInfo = gApi.groups().create(name("foo")).get();
- AccountGroup.UUID groupUuid = new AccountGroup.UUID(groupInfo.id);
+ AccountGroup.UUID groupUuid = AccountGroup.uuid(groupInfo.id);
assertThat(stalenessChecker.isStale(groupUuid)).isFalse();
// Manual update makes index document stale
@@ -1329,9 +1381,7 @@ public class GroupsIT extends AbstractDaemonTest {
restartAsSlave();
GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter();
- RegistrationHandle groupIndexEventCounterHandle =
- groupIndexedListeners.add("gerrit", groupIndexedCounter);
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(groupIndexedCounter)) {
// Running the reindexer right after startup should not need to reindex any group since
// reindexing was already done on startup.
slaveGroupIndexer.run();
@@ -1340,12 +1390,12 @@ public class GroupsIT extends AbstractDaemonTest {
// Create a group without updating the cache or index,
// then run the reindexer -> only the new group is reindexed.
String groupName = "foo";
- AccountGroup.UUID groupUuid = new AccountGroup.UUID(groupName + "-UUID");
+ AccountGroup.UUID groupUuid = AccountGroup.uuid(groupName + "-UUID");
groupsUpdate.createGroupInNoteDb(
InternalGroupCreation.builder()
.setGroupUUID(groupUuid)
- .setNameKey(new AccountGroup.NameKey(groupName))
- .setId(new AccountGroup.Id(seq.nextGroupId()))
+ .setNameKey(AccountGroup.nameKey(groupName))
+ .setId(AccountGroup.id(seq.nextGroupId()))
.build(),
InternalGroupUpdate.builder().build());
slaveGroupIndexer.run();
@@ -1367,8 +1417,6 @@ public class GroupsIT extends AbstractDaemonTest {
}
slaveGroupIndexer.run();
groupIndexedCounter.assertReindexOf(groupUuid);
- } finally {
- groupIndexEventCounterHandle.remove();
}
}
@@ -1386,25 +1434,18 @@ public class GroupsIT extends AbstractDaemonTest {
restartAsSlave();
GroupIndexedCounter groupIndexedCounter = new GroupIndexedCounter();
- RegistrationHandle groupIndexEventCounterHandle =
- groupIndexedListeners.add("gerrit", groupIndexedCounter);
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(groupIndexedCounter)) {
// No group indexing happened on startup. All groups should be reindexed now.
slaveGroupIndexer.run();
groupIndexedCounter.assertReindexOf(expectedGroups);
- } finally {
- groupIndexEventCounterHandle.remove();
}
}
private static Correspondence<AccountInfo, String> getAccountToUsernameCorrespondence() {
return Correspondence.from(
- new BinaryPredicate<AccountInfo, String>() {
- @Override
- public boolean apply(AccountInfo actualAccount, String expectedName) {
- String username = actualAccount == null ? null : actualAccount.username;
- return Objects.equals(username, expectedName);
- }
+ (actualAccount, expectedName) -> {
+ String username = actualAccount == null ? null : actualAccount.username;
+ return Objects.equals(username, expectedName);
},
"has username");
}
@@ -1420,10 +1461,17 @@ public class GroupsIT extends AbstractDaemonTest {
}
private void pushToGroupBranchForReviewAndSubmit(
- Project.NameKey project, String groupRef, String expectedError) throws Exception {
- grantLabel(
- "Code-Review", -2, 2, project, RefNames.REFS_GROUPS + "*", false, REGISTERED_USERS, false);
- grant(project, RefNames.REFS_GROUPS + "*", Permission.SUBMIT, false, REGISTERED_USERS);
+ Project.NameKey project, String groupRef, String expectedError) throws Throwable {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel("Code-Review")
+ .ref(RefNames.REFS_GROUPS + "*")
+ .group(REGISTERED_USERS)
+ .range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(project);
fetch(repo, groupRef + ":groupRef");
@@ -1434,14 +1482,16 @@ public class GroupsIT extends AbstractDaemonTest {
.create(admin.newIdent(), repo, "Update group config", "group.config", "some content")
.to(MagicBranch.NEW_CHANGE + groupRef);
r.assertOkStatus();
- assertThat(r.getChange().change().getDest().get()).isEqualTo(groupRef);
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(groupRef);
gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ ThrowingRunnable submit = () -> gApi.changes().id(r.getChangeId()).current().submit();
if (expectedError != null) {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("group update not allowed");
+ Throwable thrown = assertThrows(ResourceConflictException.class, submit);
+ assertThat(thrown).hasMessageThat().contains("group update not allowed");
+ } else {
+ submit.run();
}
- gApi.changes().id(r.getChangeId()).current().submit();
}
private void createBranch(Project.NameKey project, String ref) throws IOException {
@@ -1518,16 +1568,11 @@ public class GroupsIT extends AbstractDaemonTest {
private static void assertIncludes(List<GroupInfo> includes, String... expectedNames) {
List<String> names = includes.stream().map(i -> i.name).collect(toImmutableList());
assertThat(names).containsExactlyElementsIn(Arrays.asList(expectedNames));
- assertThat(names).isOrdered();
+ assertThat(names).isInOrder();
}
private void assertBadRequest(ListRequest req) throws Exception {
- try {
- req.get();
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- }
+ assertThrows(BadRequestException.class, () -> req.get());
}
@Target({METHOD})
@@ -1547,24 +1592,18 @@ public class GroupsIT extends AbstractDaemonTest {
countsByGroup.clear();
}
- long getCount(AccountGroup.UUID groupUuid) {
- return countsByGroup.get(groupUuid.get());
- }
-
void assertReindexOf(AccountGroup.UUID groupUuid) {
assertReindexOf(ImmutableList.of(groupUuid));
}
void assertReindexOf(List<AccountGroup.UUID> groupUuids) {
- for (AccountGroup.UUID groupUuid : groupUuids) {
- assertThat(getCount(groupUuid)).named(groupUuid.get()).isEqualTo(1);
- }
- assertThat(countsByGroup).hasSize(groupUuids.size());
+ Map<String, Long> expected = groupUuids.stream().collect(toMap(u -> u.get(), u -> 1L));
+ assertThat(countsByGroup.asMap()).containsExactlyEntriesIn(expected);
clear();
}
void assertNoReindex() {
- assertThat(countsByGroup).isEmpty();
+ assertThat(countsByGroup.asMap()).isEmpty();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
index 5e143c0d8b..6fcca8c68c 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsUpdateIT.java
@@ -15,13 +15,14 @@
package com.google.gerrit.acceptance.api.group;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.group.db.Groups;
import com.google.gerrit.server.group.db.GroupsUpdate;
@@ -36,12 +37,9 @@ import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
public class GroupsUpdateIT {
@Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment();
- @Rule public ExpectedException expectedException = ExpectedException.none();
-
@Inject @ServerInitiated private Provider<GroupsUpdate> groupsUpdateProvider;
@Inject private Groups groups;
@@ -56,7 +54,7 @@ public class GroupsUpdateIT {
createGroup(groupCreation, groupUpdate);
Stream<String> allGroupNames = getAllGroupNames();
- assertThat(allGroupNames).containsAllOf("users", "verifiers");
+ assertThat(allGroupNames).containsAtLeast("users", "verifiers");
}
@Test
@@ -65,23 +63,23 @@ public class GroupsUpdateIT {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("contributors"))
+ .setName(AccountGroup.nameKey("contributors"))
.setMemberModification(
new CreateAnotherGroupOnceAsSideEffectOfMemberModification("verifiers"))
.build();
- updateGroup(new AccountGroup.UUID("users-UUID"), groupUpdate);
+ updateGroup(AccountGroup.uuid("users-UUID"), groupUpdate);
Stream<String> allGroupNames = getAllGroupNames();
- assertThat(allGroupNames).containsAllOf("contributors", "verifiers");
+ assertThat(allGroupNames).containsAtLeast("contributors", "verifiers");
}
@Test
public void groupUpdateFailsWithExceptionForNotExistingGroup() throws Exception {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setDescription("A description for the group").build();
-
- expectedException.expect(NoSuchGroupException.class);
- updateGroup(new AccountGroup.UUID("nonexistent-group-UUID"), groupUpdate);
+ assertThrows(
+ NoSuchGroupException.class,
+ () -> updateGroup(AccountGroup.uuid("nonexistent-group-UUID"), groupUpdate));
}
private void createGroup(String groupName, String groupUuid) throws Exception {
@@ -107,9 +105,9 @@ public class GroupsUpdateIT {
private static InternalGroupCreation getGroupCreation(String groupName, String groupUuid) {
return InternalGroupCreation.builder()
- .setGroupUUID(new AccountGroup.UUID(groupUuid))
- .setNameKey(new AccountGroup.NameKey(groupName))
- .setId(new AccountGroup.Id(Math.abs(groupName.hashCode())))
+ .setGroupUUID(AccountGroup.uuid(groupUuid))
+ .setNameKey(AccountGroup.nameKey(groupName))
+ .setId(AccountGroup.id(Math.abs(groupName.hashCode())))
.build();
}
diff --git a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index 2e455528c3..67da084fc6 100644
--- a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.api.plugin;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
@@ -34,6 +35,7 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.plugins.MandatoryPluginsCollection;
import com.google.inject.Inject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -64,6 +66,7 @@ public class PluginIT extends AbstractDaemonTest {
"plugin_e.js");
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private MandatoryPluginsCollection mandatoryPluginsCollection;
@Test
@GerritConfig(name = "plugins.allowRemoteAdmin", value = "true")
@@ -111,7 +114,13 @@ public class PluginIT extends AbstractDaemonTest {
assertBadRequest(list().regex(".*in-b").prefix("a"));
assertBadRequest(list().substring(".*in-b").prefix("a"));
- // Disable
+ // Disable mandatory
+ mandatoryPluginsCollection.add("plugin_e");
+ assertThrows(MethodNotAllowedException.class, () -> gApi.plugins().name("plugin_e").disable());
+ api = gApi.plugins().name("plugin_e");
+ assertThat(api.get().disabled).isNull();
+
+ // Disable non-mandatory
api = gApi.plugins().name("plugin-a");
api.disable();
api = gApi.plugins().name("plugin-a");
@@ -130,12 +139,7 @@ public class PluginIT extends AbstractDaemonTest {
// Non-admin cannot disable
requestScopeOperations.setApiUser(user.id());
- try {
- gApi.plugins().name("plugin-a").disable();
- fail("Expected AuthException");
- } catch (AuthException expected) {
- // Expected
- }
+ assertThrows(AuthException.class, () -> gApi.plugins().name("plugin-a").disable());
}
@SuppressWarnings("deprecation")
@@ -149,15 +153,16 @@ public class PluginIT extends AbstractDaemonTest {
@Test
public void installNotAllowed() throws Exception {
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("remote plugin administration is disabled");
- gApi.plugins().install("test.js", new InstallPluginInput());
+ MethodNotAllowedException thrown =
+ assertThrows(
+ MethodNotAllowedException.class,
+ () -> gApi.plugins().install("test.js", new InstallPluginInput()));
+ assertThat(thrown).hasMessageThat().contains("remote plugin administration is disabled");
}
@Test
public void getNonExistingThrowsNotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.plugins().name("does-not-exist");
+ assertThrows(ResourceNotFoundException.class, () -> gApi.plugins().name("does-not-exist"));
}
private ListRequest list() throws RestApiException {
@@ -223,11 +228,6 @@ public class PluginIT extends AbstractDaemonTest {
}
private void assertBadRequest(ListRequest req) throws Exception {
- try {
- req.get();
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- }
+ assertThrows(BadRequestException.class, () -> req.get());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/plugin/PluginLoaderIT.java b/javatests/com/google/gerrit/acceptance/api/plugin/PluginLoaderIT.java
new file mode 100644
index 0000000000..7eb3680659
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/plugin/PluginLoaderIT.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.plugin;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.server.plugins.MissingMandatoryPluginsException;
+import org.junit.Test;
+import org.junit.runner.Description;
+
+@NoHttpd
+public class PluginLoaderIT extends AbstractDaemonTest {
+
+ Description testDescription;
+
+ @Override
+ protected void beforeTest(Description description) throws Exception {
+ this.testDescription = description;
+ }
+
+ @Override
+ protected void afterTest() throws Exception {}
+
+ @Test(expected = MissingMandatoryPluginsException.class)
+ @GerritConfig(name = "plugins.mandatory", value = "my-mandatory-plugin")
+ public void shouldFailToStartGerritWhenMandatoryPluginsAreMissing() throws Exception {
+ super.beforeTest(testDescription);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
index 8cdd2f66e2..b8c1818c1a 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
@@ -15,6 +15,11 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -23,17 +28,15 @@ import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
import com.google.gerrit.extensions.api.config.AccessCheckInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.lib.RefUpdate;
@@ -62,36 +65,41 @@ public class CheckAccessIT extends AbstractDaemonTest {
privilegedUser = accountCreator.create("privilegedUser", "snowden@nsa.gov", "Ed Snowden");
groupOperations.group(privilegedGroupUuid).forUpdate().addMember(privilegedUser.id()).update();
- try (ProjectConfigUpdate u = updateProject(secretProject)) {
- ProjectConfig cfg = u.getConfig();
- Util.allow(cfg, Permission.READ, privilegedGroupUuid, "refs/*");
- Util.block(cfg, Permission.READ, SystemGroupBackend.REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(secretProject)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(privilegedGroupUuid))
+ .add(block(Permission.READ).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
- try (ProjectConfigUpdate u = updateProject(secretRefProject)) {
- ProjectConfig cfg = u.getConfig();
- Util.deny(cfg, Permission.READ, SystemGroupBackend.ANONYMOUS_USERS, "refs/*");
- Util.allow(cfg, Permission.READ, privilegedGroupUuid, "refs/heads/secret/*");
- Util.block(cfg, Permission.READ, SystemGroupBackend.REGISTERED_USERS, "refs/heads/secret/*");
- Util.allow(cfg, Permission.READ, SystemGroupBackend.REGISTERED_USERS, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(secretRefProject)
+ .forUpdate()
+ .add(deny(Permission.READ).ref("refs/*").group(SystemGroupBackend.ANONYMOUS_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/secret/*").group(privilegedGroupUuid))
+ .add(
+ block(Permission.READ)
+ .ref("refs/heads/secret/*")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/*").group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
// Ref permission
- try (ProjectConfigUpdate u = updateProject(normalProject)) {
- ProjectConfig cfg = u.getConfig();
- Util.allow(cfg, Permission.VIEW_PRIVATE_CHANGES, privilegedGroupUuid, "refs/*");
- Util.allow(cfg, Permission.FORGE_SERVER, privilegedGroupUuid, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(normalProject)
+ .forUpdate()
+ .add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(privilegedGroupUuid))
+ .add(allow(Permission.FORGE_SERVER).ref("refs/*").group(privilegedGroupUuid))
+ .update();
}
@Test
public void emptyInput() throws Exception {
- exception.expect(BadRequestException.class);
- exception.expectMessage("input requires 'account'");
- gApi.projects().name(normalProject.get()).checkAccess(new AccessCheckInput());
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(normalProject.get()).checkAccess(new AccessCheckInput()));
+ assertThat(thrown).hasMessageThat().contains("input requires 'account'");
}
@Test
@@ -101,9 +109,11 @@ public class CheckAccessIT extends AbstractDaemonTest {
in.permission = "notapermission";
in.ref = "refs/heads/master";
- exception.expect(BadRequestException.class);
- exception.expectMessage("not recognized");
- gApi.projects().name(normalProject.get()).checkAccess(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(normalProject.get()).checkAccess(in));
+ assertThat(thrown).hasMessageThat().contains("not recognized");
}
@Test
@@ -112,9 +122,11 @@ public class CheckAccessIT extends AbstractDaemonTest {
in.account = user.email();
in.permission = "forge_author";
- exception.expect(BadRequestException.class);
- exception.expectMessage("must set 'ref'");
- gApi.projects().name(normalProject.get()).checkAccess(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(normalProject.get()).checkAccess(in));
+ assertThat(thrown).hasMessageThat().contains("must set 'ref'");
}
@Test
@@ -124,9 +136,11 @@ public class CheckAccessIT extends AbstractDaemonTest {
in.permission = "rebase";
in.ref = "refs/heads/master";
- exception.expect(BadRequestException.class);
- exception.expectMessage("recognized as ref permission");
- gApi.projects().name(normalProject.get()).checkAccess(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(normalProject.get()).checkAccess(in));
+ assertThat(thrown).hasMessageThat().contains("recognized as ref permission");
}
@Test
@@ -136,9 +150,11 @@ public class CheckAccessIT extends AbstractDaemonTest {
in.permission = "rebase";
in.ref = "refs/heads/master";
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Account 'doesnotexist@invalid.com' not found");
- gApi.projects().name(normalProject.get()).checkAccess(in);
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.projects().name(normalProject.get()).checkAccess(in));
+ assertThat(thrown).hasMessageThat().contains("Account 'doesnotexist@invalid.com' not found");
}
private static class TestCase {
@@ -231,13 +247,16 @@ public class CheckAccessIT extends AbstractDaemonTest {
try {
info = gApi.projects().name(tc.project).checkAccess(tc.input);
} catch (RestApiException e) {
- fail(String.format("check.access(%s, %s): exception %s", tc.project, in, e));
+ assertWithMessage(String.format("check.access(%s, %s): exception %s", tc.project, in, e))
+ .fail();
}
int want = tc.want;
if (want != info.status) {
- fail(
- String.format("check.access(%s, %s) = %d, want %d", tc.project, in, info.status, want));
+ assertWithMessage(
+ String.format(
+ "check.access(%s, %s) = %d, want %d", tc.project, in, info.status, want))
+ .fail();
}
switch (want) {
@@ -253,7 +272,7 @@ public class CheckAccessIT extends AbstractDaemonTest {
assertThat(info.message).isNull();
break;
default:
- fail(String.format("unknown code %d", want));
+ assertWithMessage(String.format("unknown code %d", want)).fail();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
index 388ea30a1d..27dd16afe9 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckProjectIT.java
@@ -16,6 +16,7 @@ package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -50,7 +51,7 @@ public class CheckProjectIT extends AbstractDaemonTest {
@Test
public void noProblem() throws Exception {
PushOneCommit.Result r = createChange("refs/for/master");
- String branch = r.getChange().change().getDest().get();
+ String branch = r.getChange().change().getDest().branch();
ChangeInfo info = gApi.changes().id(r.getChange().getId().get()).info();
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
@@ -115,7 +116,7 @@ public class CheckProjectIT extends AbstractDaemonTest {
@Test
public void detectAutoCloseableChangeByChangeId() throws Exception {
PushOneCommit.Result r = createChange("refs/for/master");
- String branch = r.getChange().change().getDest().get();
+ String branch = r.getChange().change().getDest().branch();
RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
serverSideTestRepo.branch(branch).update(amendedCommit);
@@ -138,7 +139,7 @@ public class CheckProjectIT extends AbstractDaemonTest {
@Test
public void fixAutoCloseableChangeByChangeId() throws Exception {
PushOneCommit.Result r = createChange("refs/for/master");
- String branch = r.getChange().change().getDest().get();
+ String branch = r.getChange().change().getDest().branch();
RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
serverSideTestRepo.branch(branch).update(amendedCommit);
@@ -162,7 +163,7 @@ public class CheckProjectIT extends AbstractDaemonTest {
@Test
public void maxCommits() throws Exception {
PushOneCommit.Result r = createChange("refs/for/master");
- String branch = r.getChange().change().getDest().get();
+ String branch = r.getChange().change().getDest().branch();
RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
serverSideTestRepo.branch(branch).update(amendedCommit);
@@ -196,7 +197,7 @@ public class CheckProjectIT extends AbstractDaemonTest {
@Test
public void skipCommits() throws Exception {
PushOneCommit.Result r = createChange("refs/for/master");
- String branch = r.getChange().change().getDest().get();
+ String branch = r.getChange().change().getDest().branch();
RevCommit amendedCommit = serverSideTestRepo.amend(r.getCommit()).create();
serverSideTestRepo.branch(branch).update(amendedCommit);
@@ -232,18 +233,21 @@ public class CheckProjectIT extends AbstractDaemonTest {
CheckProjectInput input = new CheckProjectInput();
input.autoCloseableChangesCheck = new AutoCloseableChangesCheckInput();
- exception.expect(BadRequestException.class);
- exception.expectMessage("branch is required");
- gApi.projects().name(project.get()).check(input);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.projects().name(project.get()).check(input));
+ assertThat(thrown).hasMessageThat().contains("branch is required");
}
@Test
public void nonExistingBranch() throws Exception {
CheckProjectInput input = checkProjectInputForAutoCloseableCheck("non-existing");
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("branch 'non-existing' not found");
- gApi.projects().name(project.get()).check(input);
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.projects().name(project.get()).check(input));
+ assertThat(thrown).hasMessageThat().contains("branch 'non-existing' not found");
}
@Test
@@ -266,11 +270,14 @@ public class CheckProjectIT extends AbstractDaemonTest {
input.autoCloseableChangesCheck.maxCommits =
ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT + 1;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "max commits can at most be set to "
- + ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT);
- gApi.projects().name(project.get()).check(input);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.projects().name(project.get()).check(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "max commits can at most be set to "
+ + ProjectsConsistencyChecker.AUTO_CLOSE_MAX_COMMITS_LIMIT);
}
private RevCommit pushCommitWithoutChangeIdForReview() throws Exception {
@@ -280,10 +287,12 @@ public class CheckProjectIT extends AbstractDaemonTest {
.branch("HEAD")
.commit()
.message("A change")
+ .insertChangeId()
.author(admin.newIdent())
.committer(new PersonIdent(admin.newIdent(), testRepo.getDate()))
.create();
pushHead(testRepo, "refs/for/master");
+
return commit;
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
index a341b3c64c..c7955a56ad 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
@@ -15,6 +15,10 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -22,7 +26,10 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -33,7 +40,7 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.inject.Inject;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
@@ -42,6 +49,8 @@ import org.junit.Test;
@NoHttpd
public class CommitIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
@Test
public void getCommitInfo() throws Exception {
Result result = createChange();
@@ -75,22 +84,93 @@ public class CommitIT extends AbstractDaemonTest {
assertThat(getIncludedIn(result.getCommit().getId()).branches).containsExactly("master");
assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
- grant(project, R_TAGS + "*", Permission.CREATE_TAG);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE_TAG).ref(R_TAGS + "*").group(adminGroupUuid()))
+ .update();
gApi.projects().name(result.getChange().project().get()).tag("test-tag").create(new TagInput());
assertThat(getIncludedIn(result.getCommit().getId()).tags).containsExactly("test-tag");
- createBranch(new Branch.NameKey(project.get(), "test-branch"));
+ createBranch(BranchNameKey.create(project, "test-branch"));
assertThat(getIncludedIn(result.getCommit().getId()).branches)
.containsExactly("master", "test-branch");
}
@Test
+ public void includedInMergedChange_filtersOutNonVisibleBranches() throws Exception {
+ Result baseChange = createAndSubmitChange("refs/for/master");
+
+ createBranch(BranchNameKey.create(project, "test-branch-1"));
+ createBranch(BranchNameKey.create(project, "test-branch-2"));
+ createAndSubmitChange("refs/for/test-branch-1");
+ createAndSubmitChange("refs/for/test-branch-2");
+
+ assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+ .containsExactly("master", "test-branch-1", "test-branch-2");
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+ .update();
+
+ assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+ .containsExactly("master", "test-branch-2");
+ }
+
+ @Test
+ public void includedInMergedChange_filtersOutNonVisibleTags() throws Exception {
+ String tagBase = "tag_base";
+ String tagBranch1 = "tag_1";
+
+ Result baseChange = createAndSubmitChange("refs/for/master");
+ createLightWeightTag(tagBase);
+ assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+
+ createBranch(BranchNameKey.create(project, "test-branch-1"));
+ createAndSubmitChange("refs/for/test-branch-1");
+ createLightWeightTag(tagBranch1);
+ assertThat(getIncludedIn(baseChange.getCommit().getId()).tags)
+ .containsExactly(tagBase, tagBranch1);
+
+ projectOperations
+ .project(project)
+ .forUpdate()
+ // Tag permissions are controlled by read permissions on branches. Blocking read permission
+ // on test-branch-1 so that tagBranch1 becomes non-visible
+ .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+ .update();
+ assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+ }
+
+ @Test
+ public void cherryPickWithoutMessage() throws Exception {
+ String branch = "foo";
+
+ // Create change to cherry-pick
+ RevCommit revCommit = createChange().getCommit();
+
+ // Create target branch to cherry-pick to.
+ gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+
+ // Cherry-pick without message.
+ CherryPickInput input = new CherryPickInput();
+ input.destination = branch;
+ String changeId =
+ gApi.projects().name(project.get()).commit(revCommit.name()).cherryPick(input).get().id;
+
+ // Expect that the message of the cherry-picked commit was used for the cherry-pick change.
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ RevisionInfo revInfo = changeInfo.revisions.get(changeInfo.currentRevision);
+ assertThat(revInfo).isNotNull();
+ assertThat(revInfo.commit.message).isEqualTo(revCommit.getFullMessage());
+ }
+
+ @Test
public void cherryPickCommitWithoutChangeId() throws Exception {
- // This test is a little superfluous, since the current cherry-pick code ignores
- // the commit message of the to-be-cherry-picked change, using the one in
- // CherryPickInput instead.
CherryPickInput input = new CherryPickInput();
input.destination = "foo";
input.message = "it goes to foo branch";
@@ -148,4 +228,15 @@ public class CommitIT extends AbstractDaemonTest {
assertThat(actual.email).isEqualTo(expected.email());
assertThat(actual.name).isEqualTo(expected.fullName());
}
+
+ private Result createAndSubmitChange(String branch) throws Exception {
+ Result r = createChange(branch);
+ approve(r.getChangeId());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ return r;
+ }
+
+ private void createLightWeightTag(String tagName) throws Exception {
+ pushHead(testRepo, RefNames.REFS_TAGS + tagName, false, false);
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java b/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
index f597392a5a..6442645563 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/DashboardIT.java
@@ -15,12 +15,15 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
@@ -31,6 +34,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.restapi.project.DashboardsCollection;
+import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
@@ -40,21 +44,25 @@ import org.junit.Test;
@NoHttpd
public class DashboardIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
@Before
public void setup() throws Exception {
- allow("refs/meta/dashboards/*", Permission.CREATE, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref("refs/meta/dashboards/*").group(REGISTERED_USERS))
+ .update();
}
@Test
public void defaultDashboardDoesNotExist() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- project().defaultDashboard().get();
+ assertThrows(ResourceNotFoundException.class, () -> project().defaultDashboard().get());
}
@Test
public void dashboardDoesNotExist() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- project().dashboard("my:dashboard").get();
+ assertThrows(ResourceNotFoundException.class, () -> project().dashboard("my:dashboard").get());
}
@Test
@@ -110,8 +118,7 @@ public class DashboardIT extends AbstractDaemonTest {
project().removeDefaultDashboard();
assertThat(project().dashboard(info.id).get().isDefault).isNull();
- exception.expect(ResourceNotFoundException.class);
- project().defaultDashboard().get();
+ assertThrows(ResourceNotFoundException.class, () -> project().defaultDashboard().get());
}
@Test
@@ -133,9 +140,9 @@ public class DashboardIT extends AbstractDaemonTest {
@Test
public void cannotGetDashboardWithInheritedForNonDefault() throws Exception {
DashboardInfo info = createTestDashboard();
- exception.expect(BadRequestException.class);
- exception.expectMessage("inherited flag can only be used with default");
- project().dashboard(info.id).get(true);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> project().dashboard(info.id).get(true));
+ assertThat(thrown).hasMessageThat().contains("inherited flag can only be used with default");
}
private void assertDashboardInfo(DashboardInfo actual, DashboardInfo expected) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 766f8537b1..7f00930b74 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -15,17 +15,24 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_GLOBAL;
import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_PARENT;
import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_GLOBAL;
import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_PARENT;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toSet;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.AtomicLongMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
@@ -33,6 +40,9 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
@@ -45,15 +55,12 @@ import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.CommentLinkInfoImpl;
@@ -66,16 +73,10 @@ import java.util.Map;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
@@ -87,12 +88,9 @@ public class ProjectIT extends AbstractDaemonTest {
private static final String JIRA_LINK = "http://jira.example.com/?id=$2";
private static final String JIRA_MATCH = "(jira\\\\s+#?)(\\\\d+)";
- @Inject private DynamicSet<ProjectIndexedListener> projectIndexedListeners;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
-
- private ProjectIndexedCounter projectIndexedCounter;
- private RegistrationHandle projectIndexedCounterHandle;
+ @Inject private ExtensionRegistry extensionRegistry;
@Override
public Module createModule() {
@@ -106,53 +104,49 @@ public class ProjectIT extends AbstractDaemonTest {
};
}
- @Before
- public void addProjectIndexedCounter() {
- projectIndexedCounter = new ProjectIndexedCounter();
- projectIndexedCounterHandle = projectIndexedListeners.add("gerrit", projectIndexedCounter);
- }
-
- @After
- public void removeProjectIndexedCounter() {
- if (projectIndexedCounterHandle != null) {
- projectIndexedCounterHandle.remove();
- }
- }
-
@Test
public void createProject() throws Exception {
- String name = name("foo");
- assertThat(gApi.projects().create(name).get().name).isEqualTo(name);
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+ String name = name("foo");
+ assertThat(gApi.projects().create(name).get().name).isEqualTo(name);
- RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
- eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
+ RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
+ eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
- eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", new String[] {});
- projectIndexedCounter.assertReindexOf(name);
+ eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", new String[] {});
+ projectIndexedCounter.assertReindexOf(name);
+ }
}
@Test
public void createProjectWithInitialBranches() throws Exception {
- String name = name("foo");
- ProjectInput input = new ProjectInput();
- input.name = name;
- input.createEmptyCommit = true;
- input.branches = ImmutableList.of("master", "foo");
- assertThat(gApi.projects().create(input).get().name).isEqualTo(name);
- assertThat(
- gApi.projects().name(name).branches().get().stream().map(b -> b.ref).collect(toSet()))
- .containsExactly("refs/heads/foo", "refs/heads/master", "HEAD", RefNames.REFS_CONFIG);
-
- RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
- eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
-
- head = getRemoteHead(name, "refs/heads/foo");
- eventRecorder.assertRefUpdatedEvents(name, "refs/heads/foo", null, head);
-
- head = getRemoteHead(name, "refs/heads/master");
- eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", null, head);
-
- projectIndexedCounter.assertReindexOf(name);
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+
+ String name = name("foo");
+ ProjectInput input = new ProjectInput();
+ input.name = name;
+ input.createEmptyCommit = true;
+ input.branches = ImmutableList.of("master", "foo");
+ assertThat(gApi.projects().create(input).get().name).isEqualTo(name);
+ assertThat(
+ gApi.projects().name(name).branches().get().stream().map(b -> b.ref).collect(toSet()))
+ .containsExactly("refs/heads/foo", "refs/heads/master", "HEAD", RefNames.REFS_CONFIG);
+
+ RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
+ eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
+
+ head = getRemoteHead(name, "refs/heads/foo");
+ eventRecorder.assertRefUpdatedEvents(name, "refs/heads/foo", null, head);
+
+ head = getRemoteHead(name, "refs/heads/master");
+ eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", null, head);
+
+ projectIndexedCounter.assertReindexOf(name);
+ }
}
@Test
@@ -196,17 +190,17 @@ public class ProjectIT extends AbstractDaemonTest {
public void createProjectWithMismatchedInput() throws Exception {
ProjectInput in = new ProjectInput();
in.name = name("foo");
- exception.expect(BadRequestException.class);
- exception.expectMessage("name must match input.name");
- gApi.projects().name("bar").create(in);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.projects().name("bar").create(in));
+ assertThat(thrown).hasMessageThat().contains("name must match input.name");
}
@Test
public void createProjectNoNameInInput() throws Exception {
ProjectInput in = new ProjectInput();
- exception.expect(BadRequestException.class);
- exception.expectMessage("input.name is required");
- gApi.projects().create(in);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.projects().create(in));
+ assertThat(thrown).hasMessageThat().contains("input.name is required");
}
@Test
@@ -214,9 +208,9 @@ public class ProjectIT extends AbstractDaemonTest {
ProjectInput in = new ProjectInput();
in.name = name("baz");
gApi.projects().create(in);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Project already exists");
- gApi.projects().create(in);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.projects().create(in));
+ assertThat(thrown).hasMessageThat().contains("Project already exists");
}
@Test
@@ -225,9 +219,9 @@ public class ProjectIT extends AbstractDaemonTest {
in.name = name("baz");
in.parent = "non-existing";
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Project Not Found: " + in.parent);
- gApi.projects().create(in);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> gApi.projects().create(in));
+ assertThat(thrown).hasMessageThat().contains("Project Not Found: " + in.parent);
}
@Test
@@ -236,9 +230,9 @@ public class ProjectIT extends AbstractDaemonTest {
in.name = name("baz");
in.parent = in.name;
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("Project Not Found: " + in.parent);
- gApi.projects().create(in);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> gApi.projects().create(in));
+ assertThat(thrown).hasMessageThat().contains("Project Not Found: " + in.parent);
}
@Test
@@ -246,52 +240,67 @@ public class ProjectIT extends AbstractDaemonTest {
ProjectInput in = new ProjectInput();
in.name = name("foo");
in.parent = allUsers.get();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(String.format("Cannot inherit from '%s' project", allUsers.get()));
- gApi.projects().create(in);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.projects().create(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("Cannot inherit from '%s' project", allUsers.get()));
}
@Test
public void createAndDeleteBranch() throws Exception {
- assertThat(hasHead(project, "foo")).isFalse();
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+
+ assertThat(hasHead(project, "foo")).isFalse();
- gApi.projects().name(project.get()).branch("foo").create(new BranchInput());
- assertThat(getRemoteHead(project.get(), "foo")).isNotNull();
- projectIndexedCounter.assertNoReindex();
+ gApi.projects().name(project.get()).branch("foo").create(new BranchInput());
+ assertThat(getRemoteHead(project.get(), "foo")).isNotNull();
+ projectIndexedCounter.assertNoReindex();
- gApi.projects().name(project.get()).branch("foo").delete();
- assertThat(hasHead(project, "foo")).isFalse();
- projectIndexedCounter.assertNoReindex();
+ gApi.projects().name(project.get()).branch("foo").delete();
+ assertThat(hasHead(project, "foo")).isFalse();
+ projectIndexedCounter.assertNoReindex();
+ }
}
@Test
public void createAndDeleteBranchByPush() throws Exception {
- grant(project, "refs/*", Permission.PUSH, true);
- projectIndexedCounter.clear();
-
- assertThat(hasHead(project, "foo")).isFalse();
-
- PushOneCommit.Result r = pushTo("refs/heads/foo");
- r.assertOkStatus();
- assertThat(getRemoteHead(project.get(), "foo")).isEqualTo(r.getCommit());
- projectIndexedCounter.assertNoReindex();
-
- PushResult r2 = GitUtil.pushOne(testRepo, null, "refs/heads/foo", false, true, null);
- assertThat(r2.getRemoteUpdate("refs/heads/foo").getStatus()).isEqualTo(Status.OK);
- assertThat(hasHead(project, "foo")).isFalse();
- projectIndexedCounter.assertNoReindex();
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(adminGroupUuid()).force(true))
+ .update();
+ projectIndexedCounter.clear();
+
+ assertThat(hasHead(project, "foo")).isFalse();
+
+ PushOneCommit.Result r = pushTo("refs/heads/foo");
+ r.assertOkStatus();
+ assertThat(getRemoteHead(project.get(), "foo")).isEqualTo(r.getCommit());
+ projectIndexedCounter.assertNoReindex();
+
+ PushResult r2 = GitUtil.pushOne(testRepo, null, "refs/heads/foo", false, true, null);
+ assertThat(r2.getRemoteUpdate("refs/heads/foo").getStatus()).isEqualTo(Status.OK);
+ assertThat(hasHead(project, "foo")).isFalse();
+ projectIndexedCounter.assertNoReindex();
+ }
}
@Test
public void descriptionChangeCausesRefUpdate() throws Exception {
- RevCommit initialHead = getRemoteHead(project, RefNames.REFS_CONFIG);
+ RevCommit initialHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
assertThat(gApi.projects().name(project.get()).description()).isEmpty();
DescriptionInput in = new DescriptionInput();
in.description = "new project description";
gApi.projects().name(project.get()).description(in);
assertThat(gApi.projects().name(project.get()).description()).isEqualTo(in.description);
- RevCommit updatedHead = getRemoteHead(project, RefNames.REFS_CONFIG);
+ RevCommit updatedHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(
project.get(), RefNames.REFS_CONFIG, initialHead, updatedHead);
}
@@ -310,7 +319,7 @@ public class ProjectIT extends AbstractDaemonTest {
@Test
public void configChangeCausesRefUpdate() throws Exception {
- RevCommit initialHead = getRemoteHead(project, RefNames.REFS_CONFIG);
+ RevCommit initialHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
ConfigInfo info = gApi.projects().name(project.get()).config();
assertThat(info.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
@@ -321,7 +330,7 @@ public class ProjectIT extends AbstractDaemonTest {
info = gApi.projects().name(project.get()).config();
assertThat(info.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
- RevCommit updatedHead = getRemoteHead(project, RefNames.REFS_CONFIG);
+ RevCommit updatedHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(
project.get(), RefNames.REFS_CONFIG, initialHead, updatedHead);
}
@@ -385,9 +394,9 @@ public class ProjectIT extends AbstractDaemonTest {
public void nonOwnerCannotSetConfig() throws Exception {
ConfigInput input = createTestConfigInput();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("write refs/meta/config not permitted");
- gApi.projects().name(project.get()).config(input);
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).config(input));
+ assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
}
@Test
@@ -403,8 +412,9 @@ public class ProjectIT extends AbstractDaemonTest {
@Test
public void setHeadToNonexistentBranch() throws Exception {
- exception.expect(UnprocessableEntityException.class);
- gApi.projects().name(project.get()).head("does-not-exist");
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.projects().name(project.get()).head("does-not-exist"));
}
@Test
@@ -420,9 +430,9 @@ public class ProjectIT extends AbstractDaemonTest {
public void setHeadNotAllowed() throws Exception {
gApi.projects().name(project.get()).branch("test").create(new BranchInput());
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not permitted: set HEAD on refs/heads/test");
- gApi.projects().name(project.get()).head("test");
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).head("test"));
+ assertThat(thrown).hasMessageThat().contains("not permitted: set HEAD on refs/heads/test");
}
@Test
@@ -452,8 +462,18 @@ public class ProjectIT extends AbstractDaemonTest {
assertThat(gApi.projects().name(project.get()).config().state).isEqualTo(ProjectState.HIDDEN);
// Revoke OWNER permission for admin and block them from reading the project's refs
- block(project, RefNames.REFS + "*", Permission.OWNER, SystemGroupBackend.REGISTERED_USERS);
- block(project, RefNames.REFS + "*", Permission.READ, SystemGroupBackend.REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ block(Permission.OWNER)
+ .ref(RefNames.REFS + "*")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .add(
+ block(Permission.READ)
+ .ref(RefNames.REFS + "*")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
// HIDDEN => ACTIVE
ConfigInput ci2 = new ConfigInput();
@@ -465,22 +485,49 @@ public class ProjectIT extends AbstractDaemonTest {
@Test
public void reindexProject() throws Exception {
- projectOperations.newProject().parent(project).create();
- projectIndexedCounter.clear();
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+
+ projectOperations.newProject().parent(project).create();
+ projectIndexedCounter.clear();
- gApi.projects().name(allProjects.get()).index(false);
- projectIndexedCounter.assertReindexOf(allProjects.get());
+ gApi.projects().name(allProjects.get()).index(false);
+ projectIndexedCounter.assertReindexOf(allProjects.get());
+ }
}
@Test
public void reindexProjectWithChildren() throws Exception {
- Project.NameKey middle = projectOperations.newProject().parent(project).create();
- Project.NameKey leave = projectOperations.newProject().parent(middle).create();
- projectIndexedCounter.clear();
+ ProjectIndexedCounter projectIndexedCounter = new ProjectIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectIndexedCounter)) {
+
+ Project.NameKey middle = projectOperations.newProject().parent(project).create();
+ Project.NameKey leave = projectOperations.newProject().parent(middle).create();
+ projectIndexedCounter.clear();
- gApi.projects().name(project.get()).index(true);
- projectIndexedCounter.assertReindexExactly(
- ImmutableMap.of(project.get(), 1L, middle.get(), 1L, leave.get(), 1L));
+ gApi.projects().name(project.get()).index(true);
+ projectIndexedCounter.assertReindexExactly(
+ ImmutableMap.of(project.get(), 1L, middle.get(), 1L, leave.get(), 1L));
+ }
+ }
+
+ @Test
+ public void reindexChangesOfProject() throws Exception {
+ Change.Id changeId1 = createChange().getChange().getId();
+ Change.Id changeId2 = createChange().getChange().getId();
+
+ ChangeIndexedListener changeIndexedListener = mock(ChangeIndexedListener.class);
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedListener)) {
+ gApi.projects().name(project.get()).indexChanges();
+
+ verify(changeIndexedListener, times(1))
+ .onChangeScheduledForIndexing(project.get(), changeId1.get());
+ verify(changeIndexedListener, times(1))
+ .onChangeScheduledForIndexing(project.get(), changeId2.get());
+ }
}
@Test
@@ -645,9 +692,9 @@ public class ProjectIT extends AbstractDaemonTest {
@Test
public void invalidMaxObjectSizeIsRejected() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("100 foo");
- setMaxObjectSize("100 foo");
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> setMaxObjectSize("100 foo"));
+ assertThat(thrown).hasMessageThat().contains("100 foo");
}
@Test
@@ -669,7 +716,8 @@ public class ProjectIT extends AbstractDaemonTest {
@Test
public void cannotPushLabelDefinitionWithDuplicateValues() throws Exception {
- Config cfg = readAllProjectsConfig();
+ Config cfg = new Config();
+ cfg.fromText(projectOperations.project(allProjects).getConfig().toText());
cfg.setStringList(
"label",
"Code-Review",
@@ -692,7 +740,8 @@ public class ProjectIT extends AbstractDaemonTest {
// Update the definition of the Code-Review label so that it has the value "+1 LGTM" twice.
// This update bypasses all validation checks so that the duplicate label value doesn't get
// rejected.
- Config cfg = readAllProjectsConfig();
+ Config cfg = new Config();
+ cfg.fromText(projectOperations.project(allProjects).getConfig().toText());
cfg.setStringList(
"label",
"Code-Review",
@@ -717,20 +766,6 @@ public class ProjectIT extends AbstractDaemonTest {
.containsExactly("+1", "LGTM", " 0", "No Value", "-1", "Looks Bad");
}
- private Config readAllProjectsConfig() throws Exception {
- try (TestRepository<Repository> repo =
- new TestRepository<>(repoManager.openRepository(allProjects))) {
- RevWalk rw = repo.getRevWalk();
- RevTree tree = rw.parseTree(repo.getRepository().resolve("HEAD"));
- RevObject obj = rw.parseAny(repo.get(tree, "project.config"));
- ObjectLoader loader = rw.getObjectReader().open(obj);
- String text = new String(loader.getCachedBytes(), UTF_8);
- Config cfg = new Config();
- cfg.fromText(text);
- return cfg;
- }
- }
-
private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
return new CommentLinkInfoImpl(name, match, link, null /*html*/, null /*enabled*/);
}
@@ -800,22 +835,17 @@ public class ProjectIT extends AbstractDaemonTest {
countsByProject.clear();
}
- long getCount(String projectName) {
- return countsByProject.get(projectName);
- }
-
void assertReindexOf(String projectName) {
assertReindexOf(projectName, 1);
}
- void assertReindexOf(String projectName, int expectedCount) {
- assertThat(getCount(projectName)).isEqualTo(expectedCount);
- assertThat(countsByProject).hasSize(1);
+ void assertReindexOf(String projectName, long expectedCount) {
+ assertThat(countsByProject.asMap()).containsExactly(projectName, expectedCount);
clear();
}
void assertNoReindex() {
- assertThat(countsByProject).isEmpty();
+ assertThat(countsByProject.asMap()).isEmpty();
}
void assertReindexExactly(ImmutableMap<String, Long> expected) {
@@ -825,7 +855,7 @@ public class ProjectIT extends AbstractDaemonTest {
}
protected RevCommit getRemoteHead(String project, String branch) throws Exception {
- return getRemoteHead(new Project.NameKey(project), branch);
+ return projectOperations.project(Project.nameKey(project)).getHead(branch);
}
boolean hasHead(Project.NameKey k, String b) {
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
index 6b511f66e6..019df0e0b4 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -15,11 +15,14 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.RefState;
@@ -28,7 +31,6 @@ import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.project.ProjectIndexer;
import com.google.gerrit.index.query.FieldBundle;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.index.project.StalenessChecker;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.inject.Inject;
@@ -60,7 +62,7 @@ public class ProjectIndexerIT extends AbstractDaemonTest {
Optional<FieldBundle> result =
i.getRaw(project, QueryOptions.create(indexConfig, 0, 1, FIELDS));
- assertThat(result.isPresent()).isTrue();
+ assertThat(result).isPresent();
Iterable<byte[]> refState = result.get().getValue(ProjectField.REF_STATE);
assertThat(refState).isNotEmpty();
@@ -117,15 +119,15 @@ public class ProjectIndexerIT extends AbstractDaemonTest {
private void updateProjectConfigWithoutIndexUpdate(
Project.NameKey project, Consumer<ProjectConfig> update) throws Exception {
- try (AutoCloseable ignored = disableProjectIndex()) {
- try (ProjectConfigUpdate u = updateProject(project)) {
- update.accept(u.getConfig());
- u.save();
- }
- } catch (UnsupportedOperationException e) {
- // Drop, as we just wanted to drop the index update
- return;
- }
- fail("should have a UnsupportedOperationException");
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> {
+ try (AutoCloseable ignored = disableProjectIndex()) {
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ update.accept(u.getConfig());
+ u.save();
+ }
+ }
+ });
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
index 3c1428d3ae..cf7aab4798 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
@@ -22,11 +24,11 @@ import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.inject.Inject;
@@ -34,7 +36,6 @@ import org.junit.Test;
@NoHttpd
public class SetParentIT extends AbstractDaemonTest {
-
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@@ -42,8 +43,7 @@ public class SetParentIT extends AbstractDaemonTest {
public void setParentNotAllowed() throws Exception {
String parent = projectOperations.newProject().create().get();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.projects().name(project.get()).parent(parent);
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).parent(parent));
}
@Test
@@ -51,8 +51,7 @@ public class SetParentIT extends AbstractDaemonTest {
public void setParentNotAllowedForNonOwners() throws Exception {
String parent = projectOperations.newProject().create().get();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.projects().name(project.get()).parent(parent);
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).parent(parent));
}
@Test
@@ -75,7 +74,11 @@ public class SetParentIT extends AbstractDaemonTest {
public void setParentAllowedForOwners() throws Exception {
String parent = projectOperations.newProject().create().get();
requestScopeOperations.setApiUser(user.id());
- grant(project, "refs/*", Permission.OWNER, false, SystemGroupBackend.REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
gApi.projects().name(project.get()).parent(parent);
assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
}
@@ -96,47 +99,63 @@ public class SetParentIT extends AbstractDaemonTest {
@Test
public void setParentForAllProjectsNotAllowed() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cannot set parent of " + AllProjectsNameProvider.DEFAULT);
- gApi.projects().name(allProjects.get()).parent(project.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(allProjects.get()).parent(project.get()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("cannot set parent of " + AllProjectsNameProvider.DEFAULT);
}
@Test
public void setParentToSelfNotAllowed() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cannot set parent to self");
- gApi.projects().name(project.get()).parent(project.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).parent(project.get()));
+ assertThat(thrown).hasMessageThat().contains("cannot set parent to self");
}
@Test
public void setParentToOwnChildNotAllowed() throws Exception {
String child = projectOperations.newProject().parent(project).create().get();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cycle exists between");
- gApi.projects().name(project.get()).parent(child);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).parent(child));
+ assertThat(thrown).hasMessageThat().contains("cycle exists between");
}
@Test
public void setParentToGrandchildNotAllowed() throws Exception {
Project.NameKey child = projectOperations.newProject().parent(project).create();
String grandchild = projectOperations.newProject().parent(child).create().get();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("cycle exists between");
- gApi.projects().name(project.get()).parent(grandchild);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).parent(grandchild));
+ assertThat(thrown).hasMessageThat().contains("cycle exists between");
}
@Test
public void setParentToNonexistentProject() throws Exception {
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("not found");
- gApi.projects().name(project.get()).parent("non-existing");
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.projects().name(project.get()).parent("non-existing"));
+ assertThat(thrown).hasMessageThat().contains("not found");
}
@Test
public void setParentToAllUsersNotAllowed() throws Exception {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(String.format("Cannot inherit from '%s' project", allUsers.get()));
- gApi.projects().name(project.get()).parent(allUsers.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).parent(allUsers.get()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("Cannot inherit from '%s' project", allUsers.get()));
}
@Test
@@ -145,8 +164,9 @@ public class SetParentIT extends AbstractDaemonTest {
String parent = projectOperations.newProject().create().get();
- exception.expect(BadRequestException.class);
- exception.expectMessage("All-Users must inherit from All-Projects");
- gApi.projects().name(allUsers.get()).parent(parent);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.projects().name(allUsers.get()).parent(parent));
+ assertThat(thrown).hasMessageThat().contains("All-Users must inherit from All-Projects");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 38195a9849..c1162ff019 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -16,10 +16,11 @@ package com.google.gerrit.acceptance.api.revision;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.entities.Patch.COMMIT_MSG;
+import static com.google.gerrit.entities.Patch.MERGE_LIST;
import static com.google.gerrit.extensions.common.testing.DiffInfoSubject.assertThat;
import static com.google.gerrit.extensions.common.testing.FileInfoSubject.assertThat;
-import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
-import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
@@ -2756,7 +2757,7 @@ public class RevisionDiffIT extends AbstractDaemonTest {
RevCommit parentCommit = c.getParents()[0];
String parentCommitId =
- testRepo.getRevWalk().getObjectReader().abbreviate(parentCommit.getId(), 8).name();
+ abbreviateName(parentCommit, 8, testRepo.getRevWalk().getObjectReader());
headers.add("Parent: " + parentCommitId + " (" + parentCommit.getShortMessage() + ")");
SimpleDateFormat dtfmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US);
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 237560767b..32941ffcdc 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -21,10 +21,12 @@ import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.entities.Patch.COMMIT_MSG;
+import static com.google.gerrit.entities.Patch.MERGE_LIST;
import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
-import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
-import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -38,12 +40,20 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
@@ -72,8 +82,6 @@ import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -83,11 +91,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.restapi.change.GetRevisionActions;
@@ -115,11 +118,10 @@ import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
public class RevisionIT extends AbstractDaemonTest {
-
- @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
- @Inject private DynamicSet<PatchSetWebLink> patchSetLinks;
@Inject private GetRevisionActions getRevisionActions;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
@Test
public void reviewTriplet() throws Exception {
@@ -182,14 +184,13 @@ public class RevisionIT extends AbstractDaemonTest {
assertThat(approval.postSubmit).isNull();
// Reducing vote is not allowed.
- try {
- gApi.changes().id(changeId).current().review(ReviewInput.dislike());
- fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo("Cannot reduce vote on labels for closed change: Code-Review");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).current().review(ReviewInput.dislike()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Cannot reduce vote on labels for closed change: Code-Review");
approval = getApproval(changeId, label);
assertThat(approval.value).isEqualTo(1);
assertThat(approval.postSubmit).isNull();
@@ -202,14 +203,13 @@ public class RevisionIT extends AbstractDaemonTest {
assertPermitted(gApi.changes().id(changeId).get(DETAILED_LABELS), "Code-Review", 2);
// Decreasing to previous post-submit vote is still not allowed.
- try {
- gApi.changes().id(changeId).current().review(ReviewInput.dislike());
- fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo("Cannot reduce vote on labels for closed change: Code-Review");
- }
+ thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).current().review(ReviewInput.dislike()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Cannot reduce vote on labels for closed change: Code-Review");
approval = getApproval(changeId, label);
assertThat(approval.value).isEqualTo(2);
assertThat(approval.postSubmit).isTrue();
@@ -261,9 +261,11 @@ public class RevisionIT extends AbstractDaemonTest {
ReviewInput in = new ReviewInput();
in.label("Code-Review", 0);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Cannot reduce vote on labels for closed change: Code-Review");
- revision(r).review(in);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> revision(r).review(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cannot reduce vote on labels for closed change: Code-Review");
}
@TestProjectInput(submitType = SubmitType.CHERRY_PICK)
@@ -279,28 +281,36 @@ public class RevisionIT extends AbstractDaemonTest {
PatchSetApproval psa =
Iterators.getOnlyElement(
cd.currentApprovals().stream().filter(a -> !a.isLegacySubmit()).iterator());
- assertThat(psa.getPatchSetId().get()).isEqualTo(2);
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getValue()).isEqualTo(2);
- assertThat(psa.isPostSubmit()).isFalse();
+ assertThat(psa.patchSetId().get()).isEqualTo(2);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.value()).isEqualTo(2);
+ assertThat(psa.postSubmit()).isFalse();
}
@Test
public void voteOnAbandonedChange() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes().id(r.getChangeId()).abandon();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("change is closed");
- gApi.changes().id(r.getChangeId()).current().review(ReviewInput.reject());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().review(ReviewInput.reject()));
+ assertThat(thrown).hasMessageThat().contains("change is closed");
}
@Test
public void voteNotAllowedWithoutPermission() throws Exception {
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("is restricted");
- gApi.changes().id(r.getChange().getId().get()).current().review(ReviewInput.approve());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChange().getId().get())
+ .current()
+ .review(ReviewInput.approve()));
+ assertThat(thrown).hasMessageThat().contains("is restricted");
}
@Test
@@ -331,6 +341,34 @@ public class RevisionIT extends AbstractDaemonTest {
}
@Test
+ public void cherryPickWithoutMessage() throws Exception {
+ String branch = "foo";
+
+ // Create change to cherry-pick
+ PushOneCommit.Result change = createChange();
+ RevCommit revCommit = change.getCommit();
+
+ // Create target branch to cherry-pick to.
+ gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
+
+ // Cherry-pick without message.
+ CherryPickInput input = new CherryPickInput();
+ input.destination = branch;
+ String changeId =
+ gApi.changes()
+ .id(change.getChangeId())
+ .revision(revCommit.name())
+ .cherryPickAsInfo(input)
+ .id;
+
+ // Expect that the message of the cherry-picked commit was used for the cherry-pick change.
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ RevisionInfo revInfo = changeInfo.revisions.get(changeInfo.currentRevision);
+ assertThat(revInfo).isNotNull();
+ assertThat(revInfo.commit.message).isEqualTo(revCommit.getFullMessage());
+ }
+
+ @Test
public void cherryPickSetChangeId() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master");
CherryPickInput in = new CherryPickInput();
@@ -456,9 +494,11 @@ public class RevisionIT extends AbstractDaemonTest {
cherry.current().review(ReviewInput.approve());
cherry.current().submit();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Cherry pick failed: identical tree");
- orig.revision(r.getCommit().name()).cherryPick(in);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> orig.revision(r.getCommit().name()).cherryPick(in));
+ assertThat(thrown).hasMessageThat().contains("Cherry pick failed: identical tree");
}
@Test
@@ -482,9 +522,11 @@ public class RevisionIT extends AbstractDaemonTest {
ChangeApi orig = gApi.changes().id(triplet);
assertThat(orig.get().messages).hasSize(1);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Cherry pick failed: merge conflict");
- orig.revision(r.getCommit().name()).cherryPick(in);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> orig.revision(r.getCommit().name()).cherryPick(in));
+ assertThat(thrown).hasMessageThat().contains("Cherry pick failed: merge conflict");
}
@Test
@@ -522,12 +564,11 @@ public class RevisionIT extends AbstractDaemonTest {
CherryPickInput in = new CherryPickInput();
in.destination = destBranch;
in.message = "Cherry-Pick";
- try {
- changeApi.revision(r.getCommit().name()).cherryPickAsInfo(in);
- fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage()).isEqualTo("Cherry pick failed: merge conflict");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> changeApi.revision(r.getCommit().name()).cherryPickAsInfo(in));
+ assertThat(thrown).hasMessageThat().isEqualTo("Cherry pick failed: merge conflict");
// Cherry-pick with auto merge should succeed.
in.allowConflicts = true;
@@ -551,8 +592,8 @@ public class RevisionIT extends AbstractDaemonTest {
ByteArrayOutputStream os = new ByteArrayOutputStream();
bin.writeTo(os);
String fileContent = new String(os.toByteArray(), UTF_8);
- String destSha1 = getRemoteHead(project, destBranch).abbreviate(6).name();
- String changeSha1 = r.getCommit().abbreviate(6).name();
+ String destSha1 = abbreviateName(projectOperations.project(project).getHead(destBranch), 6);
+ String changeSha1 = abbreviateName(r.getCommit(), 6);
assertThat(fileContent)
.isEqualTo(
"<<<<<<< HEAD ("
@@ -604,16 +645,15 @@ public class RevisionIT extends AbstractDaemonTest {
CherryPickInput in = new CherryPickInput();
in.destination = "foo";
in.message = r1.getCommit().getFullMessage();
- try {
- gApi.changes().id(t1).current().cherryPick(in);
- fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e.getMessage())
- .isEqualTo(
- "Cannot create new patch set of change "
- + info(t2)._number
- + " because it is abandoned");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(t1).current().cherryPick(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Cannot create new patch set of change "
+ + info(t2)._number
+ + " because it is abandoned");
gApi.changes().id(t2).restore();
gApi.changes().id(t1).current().cherryPick(in);
@@ -629,7 +669,7 @@ public class RevisionIT extends AbstractDaemonTest {
createCherryPickableMerge(parent1FileName, parent2FileName);
String cherryPickBranchName = "branch_for_cherry_pick";
- createBranch(new Branch.NameKey(project, cherryPickBranchName));
+ createBranch(BranchNameKey.create(project, cherryPickBranchName));
CherryPickInput cherryPickInput = new CherryPickInput();
cherryPickInput.destination = cherryPickBranchName;
@@ -656,7 +696,7 @@ public class RevisionIT extends AbstractDaemonTest {
createCherryPickableMerge(parent1FileName, parent2FileName);
String cherryPickBranchName = "branch_for_cherry_pick";
- createBranch(new Branch.NameKey(project, cherryPickBranchName));
+ createBranch(BranchNameKey.create(project, cherryPickBranchName));
CherryPickInput cherryPickInput = new CherryPickInput();
cherryPickInput.destination = cherryPickBranchName;
@@ -684,17 +724,24 @@ public class RevisionIT extends AbstractDaemonTest {
createCherryPickableMerge(parent1FileName, parent2FileName);
String cherryPickBranchName = "branch_for_cherry_pick";
- createBranch(new Branch.NameKey(project, cherryPickBranchName));
+ createBranch(BranchNameKey.create(project, cherryPickBranchName));
CherryPickInput cherryPickInput = new CherryPickInput();
cherryPickInput.destination = cherryPickBranchName;
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
cherryPickInput.parent = 0;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "Cherry Pick: Parent 0 does not exist. Please specify a parent in range [1, 2].");
- gApi.changes().id(mergeChangeResult.getChangeId()).current().cherryPick(cherryPickInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.changes()
+ .id(mergeChangeResult.getChangeId())
+ .current()
+ .cherryPick(cherryPickInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cherry Pick: Parent 0 does not exist. Please specify a parent in range [1, 2].");
}
@Test
@@ -705,24 +752,31 @@ public class RevisionIT extends AbstractDaemonTest {
createCherryPickableMerge(parent1FileName, parent2FileName);
String cherryPickBranchName = "branch_for_cherry_pick";
- createBranch(new Branch.NameKey(project, cherryPickBranchName));
+ createBranch(BranchNameKey.create(project, cherryPickBranchName));
CherryPickInput cherryPickInput = new CherryPickInput();
cherryPickInput.destination = cherryPickBranchName;
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
cherryPickInput.parent = 3;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- "Cherry Pick: Parent 3 does not exist. Please specify a parent in range [1, 2].");
- gApi.changes().id(mergeChangeResult.getChangeId()).current().cherryPick(cherryPickInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.changes()
+ .id(mergeChangeResult.getChangeId())
+ .current()
+ .cherryPick(cherryPickInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cherry Pick: Parent 3 does not exist. Please specify a parent in range [1, 2].");
}
@Test
public void cherryPickNotify() throws Exception {
- createBranch(new Branch.NameKey(project, "branch-1"));
- createBranch(new Branch.NameKey(project, "branch-2"));
- createBranch(new Branch.NameKey(project, "branch-3"));
+ createBranch(BranchNameKey.create(project, "branch-1"));
+ createBranch(BranchNameKey.create(project, "branch-2"));
+ createBranch(BranchNameKey.create(project, "branch-3"));
// Creates a change for 'admin'.
PushOneCommit.Result result = createChange();
@@ -761,7 +815,7 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void cherryPickKeepReviewers() throws Exception {
- createBranch(new Branch.NameKey(project, "stable"));
+ createBranch(BranchNameKey.create(project, "stable"));
// Change is created by 'admin'.
PushOneCommit.Result r = createChange();
@@ -795,7 +849,7 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void cherryPickToMergedChangeRevision() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
+ createBranch(BranchNameKey.create(project, "foo"));
PushOneCommit.Result dstChange = createChange(testRepo, "foo", SUBJECT, "b.txt", "b", "t");
dstChange.assertOkStatus();
@@ -819,7 +873,7 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void cherryPickToOpenChangeRevision() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
+ createBranch(BranchNameKey.create(project, "foo"));
PushOneCommit.Result dstChange = createChange(testRepo, "foo", SUBJECT, "b.txt", "b", "t");
dstChange.assertOkStatus();
@@ -837,7 +891,7 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void cherryPickToNonVisibleChangeFails() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
+ createBranch(BranchNameKey.create(project, "foo"));
PushOneCommit.Result dstChange = createChange(testRepo, "foo", SUBJECT, "b.txt", "b", "t");
dstChange.assertOkStatus();
@@ -852,10 +906,13 @@ public class RevisionIT extends AbstractDaemonTest {
input.message = srcChange.getCommit().getFullMessage();
requestScopeOperations.setApiUser(user.id());
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage(
- String.format("Commit %s does not exist on branch refs/heads/foo", input.base));
- gApi.changes().id(srcChange.getChangeId()).current().cherryPick(input).get();
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.changes().id(srcChange.getChangeId()).current().cherryPick(input).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("Commit %s does not exist on branch refs/heads/foo", input.base));
}
@Test
@@ -869,12 +926,16 @@ public class RevisionIT extends AbstractDaemonTest {
input.base = change2.getCommit().name();
input.message = change1.getCommit().getFullMessage();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format(
- "Change %s with commit %s is abandoned",
- change2.getChange().getId().get(), input.base));
- gApi.changes().id(change1.getChangeId()).current().cherryPick(input);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(change1.getChangeId()).current().cherryPick(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "Change %s with commit %s is abandoned",
+ change2.getChange().getId().get(), input.base));
}
@Test
@@ -886,9 +947,13 @@ public class RevisionIT extends AbstractDaemonTest {
input.base = "invalid-sha1";
input.message = change1.getCommit().getFullMessage();
- exception.expect(BadRequestException.class);
- exception.expectMessage(String.format("Base %s doesn't represent a valid SHA-1", input.base));
- gApi.changes().id(change1.getChangeId()).current().cherryPick(input);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(change1.getChangeId()).current().cherryPick(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("Base %s doesn't represent a valid SHA-1", input.base));
}
@Test
@@ -929,7 +994,7 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void cherryPickToNonExistingBaseCommit() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
+ createBranch(BranchNameKey.create(project, "foo"));
PushOneCommit.Result result = createChange();
CherryPickInput input = new CherryPickInput();
@@ -1050,25 +1115,22 @@ public class RevisionIT extends AbstractDaemonTest {
// Using the API returns the correct value, and reindexes as well.
CountDownLatch reindexed = new CountDownLatch(1);
- RegistrationHandle handle =
- changeIndexedListeners.add(
- "gerrit",
- new ChangeIndexedListener() {
- @Override
- public void onChangeIndexed(String projectName, int id) {
- if (id == id2.get()) {
- reindexed.countDown();
- }
- }
-
- @Override
- public void onChangeDeleted(int id) {}
- });
- try {
+ ChangeIndexedListener listener =
+ new ChangeIndexedListener() {
+ @Override
+ public void onChangeIndexed(String projectName, int id) {
+ if (id == id2.get()) {
+ reindexed.countDown();
+ }
+ }
+
+ @Override
+ public void onChangeDeleted(int id) {}
+ };
+
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
assertMergeable(r2.getChangeId(), true);
reindexed.await();
- } finally {
- handle.remove();
}
List<ChangeInfo> changes = search.call();
@@ -1228,16 +1290,26 @@ public class RevisionIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
assertDescription(r, "");
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("edit description not permitted");
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("test");
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().name())
+ .description("test"));
+ assertThat(thrown).hasMessageThat().contains("edit description not permitted");
}
@Test
public void setDescriptionAllowedWithPermission() throws Exception {
PushOneCommit.Result r = createChange();
assertDescription(r, "");
- grant(project, "refs/heads/master", Permission.OWNER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("test");
assertDescription(r, "test");
@@ -1276,10 +1348,14 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void commit() throws Exception {
WebLinkInfo expectedWebLinkInfo = new WebLinkInfo("foo", "imageUrl", "url");
- RegistrationHandle handle =
- patchSetLinks.add("gerrit", (projectName, commit) -> expectedWebLinkInfo);
-
- try {
+ PatchSetWebLink link =
+ new PatchSetWebLink() {
+ @Override
+ public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
+ return expectedWebLinkInfo;
+ }
+ };
+ try (Registration registration = extensionRegistry.newRegistration().add(link)) {
PushOneCommit.Result r = createChange();
RevCommit c = r.getCommit();
@@ -1301,8 +1377,6 @@ public class RevisionIT extends AbstractDaemonTest {
assertThat(webLinkInfo.imageUrl).isEqualTo(expectedWebLinkInfo.imageUrl);
assertThat(webLinkInfo.url).isEqualTo(expectedWebLinkInfo.url);
assertThat(webLinkInfo.target).isEqualTo(expectedWebLinkInfo.target);
- } finally {
- handle.remove();
}
}
@@ -1413,8 +1487,8 @@ public class RevisionIT extends AbstractDaemonTest {
@Test
public void commentOnNonExistingFile() throws Exception {
- PushOneCommit.Result r = createChange();
- r = updateChange(r, "new content");
+ PushOneCommit.Result r1 = createChange();
+ PushOneCommit.Result r2 = updateChange(r1, "new content");
CommentInput in = new CommentInput();
in.line = 1;
in.message = "nit: trailing whitespace";
@@ -1425,10 +1499,14 @@ public class RevisionIT extends AbstractDaemonTest {
reviewInput.comments = comments;
reviewInput.message = "comment test";
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format("not found in revision %d,1", r.getChange().change().getId().id));
- gApi.changes().id(r.getChangeId()).revision(1).review(reviewInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(r2.getChangeId()).revision(1).review(reviewInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format("not found in revision %d,1", r2.getChange().change().getId().get()));
}
@Test
@@ -1456,9 +1534,11 @@ public class RevisionIT extends AbstractDaemonTest {
String res = new String(os.toByteArray(), UTF_8);
assertThat(res).isEqualTo(PATCH_FILE_ONLY);
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("File not found: nonexistent-file.");
- changeApi.revision(r.getCommit().name()).patch("nonexistent-file");
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> changeApi.revision(r.getCommit().name()).patch("nonexistent-file"));
+ assertThat(thrown).hasMessageThat().contains("File not found: nonexistent-file.");
}
@Test
@@ -1506,13 +1586,16 @@ public class RevisionIT extends AbstractDaemonTest {
// check if it's blocked to delete a vote on a non-current patch set.
requestScopeOperations.setApiUser(admin.id());
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("Cannot access on non-current patch set");
- gApi.changes()
- .id(r.getChangeId())
- .revision(r.getCommit().getName())
- .reviewer(user.id().toString())
- .deleteVote("Code-Review");
+ MethodNotAllowedException thrown =
+ assertThrows(
+ MethodNotAllowedException.class,
+ () ->
+ gApi.changes()
+ .id(r.getChangeId())
+ .revision(r.getCommit().getName())
+ .reviewer(user.id().toString())
+ .deleteVote("Code-Review"));
+ assertThat(thrown).hasMessageThat().contains("Cannot access on non-current patch set");
}
@Test
@@ -1622,9 +1705,9 @@ public class RevisionIT extends AbstractDaemonTest {
RevCommit initialCommit = getHead(repo(), "HEAD");
String branchAName = "branchA";
- createBranch(new Branch.NameKey(project, branchAName));
+ createBranch(BranchNameKey.create(project, branchAName));
String branchBName = "branchB";
- createBranch(new Branch.NameKey(project, branchBName));
+ createBranch(BranchNameKey.create(project, branchBName));
PushOneCommit.Result changeAResult =
pushFactory
@@ -1659,6 +1742,6 @@ public class RevisionIT extends AbstractDaemonTest {
}
private static Iterable<Account.Id> getReviewers(Collection<AccountInfo> r) {
- return Iterables.transform(r, a -> new Account.Id(a._accountId));
+ return Iterables.transform(r, a -> Account.id(a._accountId));
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index d58210a6bc..486b157733 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assertThat;
import static com.google.gerrit.extensions.common.testing.RobotCommentInfoSubject.assertThatList;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
@@ -164,9 +165,10 @@ public class RobotCommentsIT extends AbstractDaemonTest {
int sizeOfRest = 451;
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest + 1);
- exception.expect(BadRequestException.class);
- exception.expectMessage("limit");
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown).hasMessageThat().contains("limit");
}
@Test
@@ -187,9 +189,10 @@ public class RobotCommentsIT extends AbstractDaemonTest {
int sizeLimit = 10 * 1024;
fixReplacementInfo.replacement = getStringFor(sizeLimit);
- exception.expect(BadRequestException.class);
- exception.expectMessage("limit");
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown).hasMessageThat().contains("limit");
}
@Test
@@ -254,12 +257,15 @@ public class RobotCommentsIT extends AbstractDaemonTest {
public void descriptionOfFixSuggestionIsMandatory() throws Exception {
fixSuggestionInfo.description = null;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format(
- "A description is required for the suggested fix of the robot comment on %s",
- withFixRobotCommentInput.path));
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "A description is required for the suggested fix of the robot comment on %s",
+ withFixRobotCommentInput.path));
}
@Test
@@ -278,13 +284,16 @@ public class RobotCommentsIT extends AbstractDaemonTest {
public void fixReplacementsAreMandatory() throws Exception {
fixSuggestionInfo.replacements = Collections.emptyList();
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format(
- "At least one replacement is required"
- + " for the suggested fix of the robot comment on %s",
- withFixRobotCommentInput.path));
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "At least one replacement is required"
+ + " for the suggested fix of the robot comment on %s",
+ withFixRobotCommentInput.path));
}
@Test
@@ -305,12 +314,15 @@ public class RobotCommentsIT extends AbstractDaemonTest {
public void pathOfFixReplacementIsMandatory() throws Exception {
fixReplacementInfo.path = null;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format(
- "A file path must be given for the replacement of the robot comment on %s",
- withFixRobotCommentInput.path));
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "A file path must be given for the replacement of the robot comment on %s",
+ withFixRobotCommentInput.path));
}
@Test
@@ -331,20 +343,24 @@ public class RobotCommentsIT extends AbstractDaemonTest {
public void rangeOfFixReplacementIsMandatory() throws Exception {
fixReplacementInfo.range = null;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format(
- "A range must be given for the replacement of the robot comment on %s",
- withFixRobotCommentInput.path));
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "A range must be given for the replacement of the robot comment on %s",
+ withFixRobotCommentInput.path));
}
@Test
public void rangeOfFixReplacementNeedsToBeValid() throws Exception {
fixReplacementInfo.range = createRange(13, 9, 5, 10);
- exception.expect(BadRequestException.class);
- exception.expectMessage("Range (13:9 - 5:10)");
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown).hasMessageThat().contains("Range (13:9 - 5:10)");
}
@Test
@@ -364,9 +380,10 @@ public class RobotCommentsIT extends AbstractDaemonTest {
createFixSuggestionInfo(fixReplacementInfo1, fixReplacementInfo2);
withFixRobotCommentInput.fixSuggestions = ImmutableList.of(fixSuggestionInfo);
- exception.expect(BadRequestException.class);
- exception.expectMessage("overlap");
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown).hasMessageThat().contains("overlap");
}
@Test
@@ -461,13 +478,16 @@ public class RobotCommentsIT extends AbstractDaemonTest {
public void replacementStringOfFixReplacementIsMandatory() throws Exception {
fixReplacementInfo.replacement = null;
- exception.expect(BadRequestException.class);
- exception.expectMessage(
- String.format(
- "A content for replacement must be "
- + "indicated for the replacement of the robot comment on %s",
- withFixRobotCommentInput.path));
- addRobotComment(changeId, withFixRobotCommentInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "A content for replacement must be "
+ + "indicated for the replacement of the robot comment on %s",
+ withFixRobotCommentInput.path));
}
@Test
@@ -602,9 +622,11 @@ public class RobotCommentsIT extends AbstractDaemonTest {
List<String> fixIds = getFixIds(robotCommentInfos);
gApi.changes().id(changeId).current().applyFix(fixIds.get(0));
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("merge");
- gApi.changes().id(changeId).current().applyFix(fixIds.get(1));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).current().applyFix(fixIds.get(1)));
+ assertThat(thrown).hasMessageThat().contains("merge");
}
@Test
@@ -708,8 +730,9 @@ public class RobotCommentsIT extends AbstractDaemonTest {
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
- exception.expect(ResourceNotFoundException.class);
- gApi.changes().id(changeId).current().applyFix(fixId);
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeId).current().applyFix(fixId));
}
@Test
@@ -728,9 +751,11 @@ public class RobotCommentsIT extends AbstractDaemonTest {
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("current");
- gApi.changes().id(changeId).revision(previousRevision).applyFix(fixId);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).revision(previousRevision).applyFix(fixId));
+ assertThat(thrown).hasMessageThat().contains("current");
}
@Test
@@ -783,9 +808,11 @@ public class RobotCommentsIT extends AbstractDaemonTest {
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("based");
- gApi.changes().id(changeId).current().applyFix(fixId);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).current().applyFix(fixId));
+ assertThat(thrown).hasMessageThat().contains("based");
}
@Test
@@ -846,8 +873,9 @@ public class RobotCommentsIT extends AbstractDaemonTest {
String fixId = Iterables.getOnlyElement(fixIds);
String nonExistentFixId = fixId + "_non-existent";
- exception.expect(ResourceNotFoundException.class);
- gApi.changes().id(changeId).current().applyFix(nonExistentFixId);
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeId).current().applyFix(nonExistentFixId));
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 0f2018d2ed..73542ca840 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -16,17 +16,17 @@ package com.google.gerrit.acceptance.edit;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.entities.Patch.COMMIT_MSG;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assertThat;
import static com.google.gerrit.extensions.restapi.testing.BinaryResultSubject.assertThat;
-import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
@@ -36,19 +36,25 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.UseClockStep;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ChangeEditDetailOption;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.FileInfo;
@@ -56,15 +62,11 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.restapi.change.ChangeEdits.EditMessage;
import com.google.gerrit.server.restapi.change.ChangeEdits.Post;
import com.google.gerrit.server.restapi.change.ChangeEdits.Put;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.inject.Inject;
@@ -80,11 +82,10 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
+@UseClockStep
public class ChangeEditIT extends AbstractDaemonTest {
private static final String FILE_NAME = "foo";
@@ -102,16 +103,6 @@ public class ChangeEditIT extends AbstractDaemonTest {
private String changeId2;
private PatchSet ps;
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
-
@Before
public void setUp() throws Exception {
changeId = newChange(admin.newIdent());
@@ -196,7 +187,7 @@ public class ChangeEditIT extends AbstractDaemonTest {
adminRestSession.post(urlPublish(changeId)).assertNoContent();
assertThat(getEdit(changeId)).isAbsent();
PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId);
- assertThat(newCurrentPatchSet.getId()).isNotEqualTo(oldCurrentPatchSet.getId());
+ assertThat(newCurrentPatchSet.id()).isNotEqualTo(oldCurrentPatchSet.id());
assertChangeMessages(
changeId,
ImmutableList.of(
@@ -249,13 +240,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
PatchSet currentPatchSet = getCurrentPatchSet(changeId2);
Optional<EditInfo> originalEdit = getEdit(changeId2);
- assertThat(originalEdit).value().baseRevision().isEqualTo(previousPatchSet.getRevision().get());
+ assertThat(originalEdit).value().baseRevision().isEqualTo(previousPatchSet.commitId().name());
Timestamp beforeRebase = originalEdit.get().commit.committer.date;
gApi.changes().id(changeId2).edit().rebase();
ensureSameBytes(getFileContentOfEdit(changeId2, FILE_NAME), CONTENT_NEW);
ensureSameBytes(getFileContentOfEdit(changeId2, FILE_NAME2), CONTENT_NEW2);
Optional<EditInfo> rebasedEdit = getEdit(changeId2);
- assertThat(rebasedEdit).value().baseRevision().isEqualTo(currentPatchSet.getRevision().get());
+ assertThat(rebasedEdit).value().baseRevision().isEqualTo(currentPatchSet.commitId().name());
assertThat(rebasedEdit).value().commit().committer().date().isNotEqualTo(beforeRebase);
}
@@ -268,13 +259,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
PatchSet currentPatchSet = getCurrentPatchSet(changeId2);
Optional<EditInfo> originalEdit = getEdit(changeId2);
- assertThat(originalEdit).value().baseRevision().isEqualTo(previousPatchSet.getRevision().get());
+ assertThat(originalEdit).value().baseRevision().isEqualTo(previousPatchSet.commitId().name());
Timestamp beforeRebase = originalEdit.get().commit.committer.date;
adminRestSession.post(urlRebase(changeId2)).assertNoContent();
ensureSameBytes(getFileContentOfEdit(changeId2, FILE_NAME), CONTENT_NEW);
ensureSameBytes(getFileContentOfEdit(changeId2, FILE_NAME2), CONTENT_NEW2);
Optional<EditInfo> rebasedEdit = getEdit(changeId2);
- assertThat(rebasedEdit).value().baseRevision().isEqualTo(currentPatchSet.getRevision().get());
+ assertThat(rebasedEdit).value().baseRevision().isEqualTo(currentPatchSet.commitId().name());
assertThat(rebasedEdit).value().commit().committer().date().isNotEqualTo(beforeRebase);
}
@@ -284,7 +275,7 @@ public class ChangeEditIT extends AbstractDaemonTest {
createEmptyEditFor(changeId2);
gApi.changes().id(changeId2).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW));
Optional<EditInfo> edit = getEdit(changeId2);
- assertThat(edit).value().baseRevision().isEqualTo(currentPatchSet.getRevision().get());
+ assertThat(edit).value().baseRevision().isEqualTo(currentPatchSet.commitId().name());
PushOneCommit push =
pushFactory.create(
admin.newIdent(),
@@ -341,12 +332,15 @@ public class ChangeEditIT extends AbstractDaemonTest {
createEmptyEditFor(changeId);
String commitMessage = gApi.changes().id(changeId).edit().getCommitMessage();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("wrong Change-Id footer");
- gApi.changes()
- .id(changeId)
- .edit()
- .modifyCommitMessage(commitMessage.replaceAll(changeId, changeId2));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyCommitMessage(commitMessage.replaceAll(changeId, changeId2)));
+ assertThat(thrown).hasMessageThat().isEqualTo("wrong Change-Id footer");
}
@Test
@@ -355,12 +349,15 @@ public class ChangeEditIT extends AbstractDaemonTest {
createEmptyEditFor(changeId);
String commitMessage = gApi.changes().id(changeId).edit().getCommitMessage();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("missing Change-Id footer");
- gApi.changes()
- .id(changeId)
- .edit()
- .modifyCommitMessage(commitMessage.replaceAll("(Change-Id:).*", ""));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyCommitMessage(commitMessage.replaceAll("(Change-Id:).*", "")));
+ assertThat(thrown).hasMessageThat().isEqualTo("missing Change-Id footer");
}
@Test
@@ -368,9 +365,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
createEmptyEditFor(changeId);
String commitMessage = gApi.changes().id(changeId).edit().getCommitMessage();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("New commit message cannot be same as existing commit message");
- gApi.changes().id(changeId).edit().modifyCommitMessage(commitMessage);
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).edit().modifyCommitMessage(commitMessage));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("New commit message cannot be same as existing commit message");
}
@Test
@@ -378,9 +379,13 @@ public class ChangeEditIT extends AbstractDaemonTest {
createEmptyEditFor(changeId);
String commitMessage = gApi.changes().id(changeId).edit().getCommitMessage();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("New commit message cannot be same as existing commit message");
- gApi.changes().id(changeId).edit().modifyCommitMessage(commitMessage + "\n\n");
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(changeId).edit().modifyCommitMessage(commitMessage + "\n\n"));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("New commit message cannot be same as existing commit message");
}
@Test
@@ -431,7 +436,7 @@ public class ChangeEditIT extends AbstractDaemonTest {
r = adminRestSession.getJsonAccept(urlEditMessage(changeId, true));
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
- RevCommit commit = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+ RevCommit commit = rw.parseCommit(ObjectId.fromString(ps.commitId().name()));
assertThat(readContentFromJson(r)).isEqualTo(commit.getFullMessage());
}
@@ -635,16 +640,22 @@ public class ChangeEditIT extends AbstractDaemonTest {
@Test
public void writeNoChanges() throws Exception {
createEmptyEditFor(changeId);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("no changes were made");
- gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_OLD));
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_OLD)));
+ assertThat(thrown).hasMessageThat().contains("no changes were made");
}
@Test
public void editCommitMessageCopiesLabelScores() throws Exception {
String cr = "Code-Review";
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType codeReview = Util.codeReview();
+ LabelType codeReview = TestLabels.codeReview();
codeReview.setCopyAllScoresIfNoCodeChange(true);
u.getConfig().getLabelSections().put(cr, codeReview);
u.save();
@@ -737,7 +748,11 @@ public class ChangeEditIT extends AbstractDaemonTest {
TestRepository<InMemoryRepository> userTestRepo = cloneProject(p, user);
// Block default permission
- block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.ADD_PATCH_SET).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
// Create change as user
PushOneCommit push = pushFactory.create(user.newIdent(), userTestRepo);
@@ -745,8 +760,7 @@ public class ChangeEditIT extends AbstractDaemonTest {
r1.assertOkStatus();
// Try to create edit as admin
- exception.expect(AuthException.class);
- createEmptyEditFor(r1.getChangeId());
+ assertThrows(AuthException.class, () -> createEmptyEditFor(r1.getChangeId()));
}
@Test
@@ -755,9 +769,11 @@ public class ChangeEditIT extends AbstractDaemonTest {
gApi.changes().id(changeId).current().review(ReviewInput.approve());
gApi.changes().id(changeId).current().submit();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(String.format("change %s is merged", change._number));
- createArbitraryEditFor(changeId);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> createArbitraryEditFor(changeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("change %s is merged", change._number));
}
@Test
@@ -765,9 +781,32 @@ public class ChangeEditIT extends AbstractDaemonTest {
ChangeInfo change = gApi.changes().id(changeId).get();
gApi.changes().id(changeId).abandon();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(String.format("change %s is abandoned", change._number));
- createArbitraryEditFor(changeId);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> createArbitraryEditFor(changeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("change %s is abandoned", change._number));
+ }
+
+ @Test
+ public void sha1sOfTwoChangesWithSameContentAfterEditDiffer() throws Exception {
+ ChangeInput changeInput = new ChangeInput();
+ changeInput.project = project.get();
+ changeInput.branch = "master";
+ changeInput.subject = "Empty change";
+ changeInput.status = ChangeStatus.NEW;
+
+ ChangeInfo info1 = gApi.changes().create(changeInput).get();
+ gApi.changes().id(info1._number).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW));
+ gApi.changes().id(info1._number).edit().publish(new PublishChangeEditInput());
+ info1 = gApi.changes().id(info1._number).get();
+
+ ChangeInfo info2 = gApi.changes().create(changeInput).get();
+ gApi.changes().id(info2._number).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW));
+ gApi.changes().id(info2._number).edit().publish(new PublishChangeEditInput());
+ info2 = gApi.changes().id(info2._number).get();
+
+ assertThat(info1.currentRevision).isNotEqualTo(info2.currentRevision);
}
private void createArbitraryEditFor(String changeId) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractForcePush.java b/javatests/com/google/gerrit/acceptance/git/AbstractForcePush.java
index 8ebb2bdaaf..3b80312a35 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractForcePush.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractForcePush.java
@@ -16,14 +16,17 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.OK;
import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.transport.PushResult;
@@ -31,6 +34,7 @@ import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.Test;
public abstract class AbstractForcePush extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Test
public void forcePushNotAllowed() throws Exception {
@@ -55,7 +59,11 @@ public abstract class AbstractForcePush extends AbstractDaemonTest {
@Test
public void forcePushAllowed() throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
- grant(project, "refs/*", Permission.PUSH, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(adminGroupUuid()).force(true))
+ .update();
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to("refs/heads/master");
@@ -80,19 +88,31 @@ public abstract class AbstractForcePush extends AbstractDaemonTest {
@Test
public void deleteNotAllowedWithOnlyPushPermission() throws Exception {
- grant(project, "refs/*", Permission.PUSH, false);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(adminGroupUuid()))
+ .update();
assertDeleteRef(REJECTED_OTHER_REASON);
}
@Test
public void deleteAllowedWithForcePushPermission() throws Exception {
- grant(project, "refs/*", Permission.PUSH, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(adminGroupUuid()).force(true))
+ .update();
assertDeleteRef(OK);
}
@Test
public void deleteAllowedWithDeletePermission() throws Exception {
- grant(project, "refs/*", Permission.DELETE, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE).ref("refs/*").group(adminGroupUuid()).force(true))
+ .update();
assertDeleteRef(OK);
}
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java b/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
index cdd047092a..35f82700c4 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractGitOverHttpServlet.java
@@ -19,8 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.FakeGroupAuditService;
import com.google.gerrit.acceptance.Sandboxed;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.pgm.http.jetty.JettyServer;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.inject.Inject;
import java.util.Optional;
@@ -73,16 +73,15 @@ public class AbstractGitOverHttpServlet extends AbstractPushForReview {
assertThat(receivePack.what).endsWith("/git-receive-pack");
assertThat(receivePack.params).isEmpty();
assertThat(receivePack.httpStatus).isEqualTo(HttpServletResponse.SC_OK);
+ assertThat(jettyServer.numActiveSessions()).isEqualTo(0);
}
@Test
- @Sandboxed
public void anonymousUploadPackAuditEventLog() throws Exception {
uploadPackAuditEventLog(Constants.DEFAULT_REMOTE_NAME, Optional.empty());
}
@Test
- @Sandboxed
public void authenticatedUploadPackAuditEventLog() throws Exception {
String remote = "authenticated";
Config cfg = testRepo.git().getRepository().getConfig();
@@ -108,7 +107,6 @@ public class AbstractGitOverHttpServlet extends AbstractPushForReview {
assertThat(auditEvents).hasSize(2);
HttpAuditEvent lsRemote = auditEvents.get(0);
- // Repo URL doesn't include /a, so fetching doesn't cause authentication.
assertThat(lsRemote.who.toString())
.isEqualTo(
accountId.map(id -> "IdentifiedUser[account " + id.get() + "]").orElse("ANONYMOUS"));
@@ -117,7 +115,7 @@ public class AbstractGitOverHttpServlet extends AbstractPushForReview {
assertThat(lsRemote.httpStatus).isEqualTo(HttpServletResponse.SC_OK);
HttpAuditEvent uploadPack = auditEvents.get(1);
- assertThat(lsRemote.who.toString())
+ assertThat(uploadPack.who.toString())
.isEqualTo(
accountId.map(id -> "IdentifiedUser[account " + id.get() + "]").orElse("ANONYMOUS"));
assertThat(uploadPack.what).endsWith("/git-upload-pack");
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 216677d271..ea82013877 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -17,12 +17,17 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.assertPushRejected;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.GitUtil.pushOne;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
@@ -33,10 +38,9 @@ import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assert
import static com.google.gerrit.server.git.receive.ReceiveConstants.PUSH_OPTION_SKIP_VALIDATION;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
import static java.util.Comparator.comparing;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
@@ -46,17 +50,27 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.SkipProjectClone;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -77,30 +91,18 @@ import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.extensions.common.testing.EditInfoSubject;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.receive.NoteDbPushOption;
import com.google.gerrit.server.git.receive.ReceiveConstants;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
-import com.google.gerrit.server.git.validators.CommitValidators.ChangeIdValidator;
import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
@@ -117,6 +119,7 @@ import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@@ -128,52 +131,51 @@ import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
@SkipProjectClone
+@UseClockStep
public abstract class AbstractPushForReview extends AbstractDaemonTest {
protected enum Protocol {
- // TODO(dborowitz): TEST.
+ // Only test protocols which are actually served by the Gerrit server, since each separate test
+ // class is large and slow.
+ //
+ // This list excludes the test InProcessProtocol, which is used by large numbers of other
+ // acceptance tests. Small tests of InProcessProtocol are still possible, without incurring a
+ // new large slow test.
SSH,
HTTP
}
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
private static String NEW_CHANGE_INDICATOR = " [NEW]";
private LabelType patchSetLock;
- @Inject private DynamicSet<CommitValidationListener> commitValidators;
-
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
-
@Before
public void setUpPatchSetLock() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
- patchSetLock = Util.patchSetLock();
+ patchSetLock = TestLabels.patchSetLock();
u.getConfig().getLabelSections().put(patchSetLock.getName(), patchSetLock);
- AccountGroup.UUID anonymousUsers = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- Util.allow(
- u.getConfig(),
- Permission.forLabel(patchSetLock.getName()),
- 0,
- 1,
- anonymousUsers,
- "refs/heads/*");
u.save();
}
- grant(project, "refs/heads/*", Permission.LABEL + "Patch-Set-Lock");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(patchSetLock.getName())
+ .ref("refs/heads/*")
+ .group(ANONYMOUS_USERS)
+ .range(0, 1))
+ .add(
+ allowLabel(patchSetLock.getName())
+ .ref("refs/heads/*")
+ .group(adminGroupUuid())
+ .range(0, 1))
+ .update();
}
@After
@@ -859,7 +861,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
assertThat(r.getChange().change().isWorkInProgress()).isTrue();
// Admin user trying to move from WIP to ready should succeed.
- GitUtil.fetch(testRepo, r.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(testRepo, r.getPatchSet().refName() + ":ps");
testRepo.reset("ps");
r = amendChange(r.getChangeId(), "refs/for/master%ready", user, testRepo);
r.assertOkStatus();
@@ -875,7 +877,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
assertThat(r.getChange().change().isWorkInProgress()).isFalse();
// Admin user trying to move from ready to WIP should succeed.
- GitUtil.fetch(testRepo, r.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(testRepo, r.getPatchSet().refName() + ":ps");
testRepo.reset("ps");
r = amendChange(r.getChangeId(), "refs/for/master%wip", admin, testRepo);
r.assertOkStatus();
@@ -886,16 +888,38 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
// Non owner, non admin and non project owner cannot flip wip bit:
TestAccount user2 = accountCreator.user2();
- grant(
- project, "refs/*", Permission.FORGE_COMMITTER, false, SystemGroupBackend.REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allow(Permission.FORGE_COMMITTER)
+ .ref("refs/*")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
TestRepository<?> user2Repo = cloneProject(project, user2);
- GitUtil.fetch(user2Repo, r.getPatchSet().getRefName() + ":ps");
+ GitUtil.fetch(user2Repo, r.getPatchSet().refName() + ":ps");
user2Repo.reset("ps");
r = amendChange(r.getChangeId(), "refs/for/master%ready", user2, user2Repo);
- r.assertErrorStatus(ReceiveConstants.ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP);
+ r.assertErrorStatus(ReceiveConstants.ONLY_USERS_WITH_TOGGLE_WIP_STATE_PERM_CAN_MODIFY_WIP);
+
+ // Non owner, non admin and non project owner with toggleWipState should succeed.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allow(Permission.TOGGLE_WORK_IN_PROGRESS_STATE)
+ .ref(RefNames.REFS_HEADS + "*")
+ .group(REGISTERED_USERS))
+ .update();
+ r = amendChange(r.getChangeId(), "refs/for/master%ready", user2, user2Repo);
+ r.assertOkStatus();
// Project owner trying to move from WIP to ready should succeed.
- allow("refs/*", Permission.OWNER, SystemGroupBackend.REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
r = amendChange(r.getChangeId(), "refs/for/master%ready", user2, user2Repo);
r.assertOkStatus();
}
@@ -1196,14 +1220,17 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void pushWithMultipleApprovals() throws Exception {
LabelType Q =
- category("Custom-Label", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
- AccountGroup.UUID anon = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
+ label("Custom-Label", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
String heads = "refs/heads/*";
try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.forLabel("Custom-Label"), -1, 1, anon, heads);
u.getConfig().getLabelSections().put(Q.getName(), Q);
u.save();
}
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Custom-Label").ref(heads).group(ANONYMOUS_USERS).range(-1, 1))
+ .update();
RevCommit c =
commitBuilder()
@@ -1225,40 +1252,6 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
}
@Test
- @GerritConfig(name = "receive.allowPushToRefsChanges", value = "true")
- public void pushToRefsChangesAllowed() throws Exception {
- PushOneCommit.Result r = pushOneCommitToRefsChanges();
- r.assertOkStatus();
- }
-
- @Test
- public void pushNewPatchsetToRefsChanges() throws Exception {
- PushOneCommit.Result r = pushOneCommitToRefsChanges();
- r.assertErrorStatus("upload to refs/changes not allowed");
- }
-
- @Test
- @GerritConfig(name = "receive.allowPushToRefsChanges", value = "false")
- public void pushToRefsChangesNotAllowed() throws Exception {
- PushOneCommit.Result r = pushOneCommitToRefsChanges();
- r.assertErrorStatus("upload to refs/changes not allowed");
- }
-
- private PushOneCommit.Result pushOneCommitToRefsChanges() throws Exception {
- PushOneCommit.Result r = pushTo("refs/for/master");
- r.assertOkStatus();
- PushOneCommit push =
- pushFactory.create(
- admin.newIdent(),
- testRepo,
- PushOneCommit.SUBJECT,
- "b.txt",
- "anotherContent",
- r.getChangeId());
- return push.to("refs/changes/" + r.getChange().change().getId().get());
- }
-
- @Test
public void pushNewPatchsetToPatchSetLockedChange() throws Exception {
PushOneCommit.Result r = pushTo("refs/for/master");
r.assertOkStatus();
@@ -1364,7 +1357,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
r.assertOkStatus();
setUseSignedOffBy(InheritableBoolean.TRUE);
- block(project, "refs/heads/master", Permission.FORGE_COMMITTER, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.FORGE_COMMITTER).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
push =
pushFactory.create(
@@ -1420,7 +1417,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
// create a second change as user (depends on the change from admin)
TestRepository<?> userRepo = cloneProject(project, user);
- GitUtil.fetch(userRepo, r.getPatchSet().getRefName() + ":change");
+ GitUtil.fetch(userRepo, r.getPatchSet().refName() + ":change");
userRepo.reset("change");
push =
pushFactory.create(
@@ -1434,7 +1431,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void pushSameCommitTwiceUsingMagicBranchBaseOption() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result rBase = pushTo("refs/heads/master");
rBase.assertOkStatus();
@@ -1530,8 +1531,8 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
// Check that a change was created for each.
for (RevCommit c : commits) {
- assertThat(byCommit(c).change().getSubject())
- .named("change for " + c.name())
+ assertWithMessage("change for " + c.name())
+ .that(byCommit(c).change().getSubject())
.isEqualTo(c.getShortMessage());
}
@@ -1543,9 +1544,9 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
RevCommit c2 = commits2.get(i);
String name = "change for " + c2.name();
ChangeData cd = byCommit(c);
- assertThat(cd.change().getSubject()).named(name).isEqualTo(c2.getShortMessage());
- assertThat(getPatchSetRevisions(cd))
- .named(name)
+ assertWithMessage(name).that(cd.change().getSubject()).isEqualTo(c2.getShortMessage());
+ assertWithMessage(name)
+ .that(getPatchSetRevisions(cd))
.containsExactlyEntriesIn(ImmutableMap.of(1, c.name(), 2, c2.name()));
}
@@ -1608,37 +1609,13 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
RemoteRefUpdate refUpdate = r.getRemoteUpdate(ref);
assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
String reason =
- String.format(
- "commit %s: missing Change-Id in message footer", c.toObjectId().abbreviate(7).name());
+ String.format("commit %s: missing Change-Id in message footer", abbreviateName(c));
assertThat(refUpdate.getMessage()).isEqualTo(reason);
assertThat(r.getMessages()).contains("\nERROR: " + reason);
}
@Test
- @GerritConfig(name = "receive.allowPushToRefsChanges", value = "true")
- public void testPushWithChangedChangeId() throws Exception {
- PushOneCommit.Result r = pushTo("refs/for/master");
- r.assertOkStatus();
- PushOneCommit push =
- pushFactory.create(
- admin.newIdent(),
- testRepo,
- PushOneCommit.SUBJECT
- + "\n\n"
- + "Change-Id: I55eab7c7a76e95005fa9cc469aa8f9fc16da9eba\n",
- "b.txt",
- "anotherContent",
- r.getChangeId());
- r = push.to("refs/changes/" + r.getChange().change().getId().get());
- r.assertErrorStatus(
- String.format(
- "commit %s: %s",
- r.getCommit().abbreviate(RevId.ABBREV_LEN).name(),
- ChangeIdValidator.CHANGE_ID_MISMATCH_MSG));
- }
-
- @Test
public void pushWithMultipleChangeIds() throws Exception {
testPushWithMultipleChangeIds();
}
@@ -1814,30 +1791,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
}
@Test
- @GerritConfig(name = "receive.allowPushToRefsChanges", value = "true")
- public void accidentallyPushNewPatchSetDirectlyToBranchAndRecoverByPushingToRefsChanges()
- throws Exception {
- Change.Id id = accidentallyPushNewPatchSetDirectlyToBranch();
- ChangeData cd = byChangeId(id);
- String ps1Rev = Iterables.getOnlyElement(cd.patchSets()).getRevision().get();
-
- String r = "refs/changes/" + id;
- assertPushOk(pushHead(testRepo, r, false), r);
-
- // Added a new patch set and auto-closed the change.
- cd = byChangeId(id);
- assertThat(cd.change().isMerged()).isTrue();
- assertThat(getPatchSetRevisions(cd))
- .containsExactlyEntriesIn(
- ImmutableMap.of(1, ps1Rev, 2, testRepo.getRepository().resolve("HEAD").name()));
- }
-
- @Test
public void accidentallyPushNewPatchSetDirectlyToBranchAndCantRecoverByPushingToRefsFor()
throws Exception {
Change.Id id = accidentallyPushNewPatchSetDirectlyToBranch();
ChangeData cd = byChangeId(id);
- String ps1Rev = Iterables.getOnlyElement(cd.patchSets()).getRevision().get();
+ String ps1Rev = Iterables.getOnlyElement(cd.patchSets()).commitId().name();
String r = "refs/for/master";
assertPushRejected(pushHead(testRepo, r, false), r, "no new changes");
@@ -1850,7 +1808,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void forcePushAbandonedChange() throws Exception {
- grant(project, "refs/*", Permission.PUSH, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/*").group(adminGroupUuid()).force(true))
+ .update();
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r = push1.to("refs/for/master");
@@ -1923,7 +1885,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void pushNewPatchsetOverridingStickyLabel() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType codeReview = Util.codeReview();
+ LabelType codeReview = TestLabels.codeReview();
codeReview.setCopyMaxScore(true);
u.getConfig().getLabelSections().put(codeReview.getName(), codeReview);
u.save();
@@ -1946,7 +1908,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void createChangeForMergedCommit() throws Exception {
String master = "refs/heads/master";
- grant(project, master, Permission.PUSH, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(master).group(adminGroupUuid()).force(true))
+ .update();
// Update master with a direct push.
RevCommit c1 = testRepo.commit().message("Non-change 1").create();
@@ -2045,7 +2011,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void mergedOptionWithExistingChangeInsertsPatchSet() throws Exception {
String master = "refs/heads/master";
- grant(project, master, Permission.PUSH, true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(master).group(adminGroupUuid()).force(true))
+ .update();
PushOneCommit.Result r = pushTo("refs/for/master");
r.assertOkStatus();
@@ -2238,53 +2208,6 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
amendChanges(unChanged.toObjectId(), commits, "refs/for/master%publish-comments");
}
- @Test
- public void pushWithDraftOptionIsDisabledPerDefault() throws Exception {
- for (String ref : ImmutableSet.of("refs/drafts/master", "refs/for/master%draft")) {
- PushOneCommit.Result r = pushTo(ref);
- r.assertErrorStatus();
- r.assertMessage("draft workflow is disabled");
- }
- }
-
- @GerritConfig(name = "change.allowDrafts", value = "true")
- @Test
- public void pushDraftGetsPrivateChange() throws Exception {
- String changeId1 = createChange("refs/drafts/master").getChangeId();
- String changeId2 = createChange("refs/for/master%draft").getChangeId();
-
- ChangeInfo info1 = gApi.changes().id(changeId1).get();
- ChangeInfo info2 = gApi.changes().id(changeId2).get();
-
- assertThat(info1.status).isEqualTo(ChangeStatus.NEW);
- assertThat(info2.status).isEqualTo(ChangeStatus.NEW);
- assertThat(info1.isPrivate).isTrue();
- assertThat(info2.isPrivate).isTrue();
- assertThat(info1.revisions).hasSize(1);
- assertThat(info2.revisions).hasSize(1);
- }
-
- @GerritConfig(name = "change.allowDrafts", value = "true")
- @Sandboxed
- @Test
- public void pushWithDraftOptionToExistingNewChangeGetsChangeEdit() throws Exception {
- String changeId = createChange().getChangeId();
- EditInfoSubject.assertThat(getEdit(changeId)).isAbsent();
-
- ChangeInfo changeInfo = gApi.changes().id(changeId).get();
- ChangeStatus originalChangeStatus = changeInfo.status;
-
- PushOneCommit.Result result = amendChange(changeId, "refs/drafts/master");
- result.assertOkStatus();
-
- changeInfo = gApi.changes().id(changeId).get();
- assertThat(changeInfo.status).isEqualTo(originalChangeStatus);
- assertThat(changeInfo.isPrivate).isNull();
- assertThat(changeInfo.revisions).hasSize(1);
-
- EditInfoSubject.assertThat(getEdit(changeId)).isPresent();
- }
-
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
@Test
public void maxBatchCommits() throws Exception {
@@ -2294,24 +2217,16 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
@Test
public void maxBatchCommitsWithDefaultValidator() throws Exception {
- TestValidator validator = new TestValidator();
- RegistrationHandle handle = commitValidators.add("test-validator", validator);
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(new TestValidator())) {
testMaxBatchCommits();
- } finally {
- handle.remove();
}
}
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
@Test
public void maxBatchCommitsWithValidateAllCommitsValidator() throws Exception {
- TestValidator validator = new TestValidator(true);
- RegistrationHandle handle = commitValidators.add("test-validator", validator);
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(new TestValidator())) {
testMaxBatchCommits();
- } finally {
- handle.remove();
}
}
@@ -2369,10 +2284,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
public void skipValidation() throws Exception {
String master = "refs/heads/master";
TestValidator validator = new TestValidator();
- RegistrationHandle handle = commitValidators.add("test-validator", validator);
- RegistrationHandle handle2 = null;
-
- try {
+ try (Registration registration = extensionRegistry.newRegistration().add(validator)) {
// Validation listener is called on normal push
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
@@ -2401,20 +2313,16 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
// Validation listener that needs to validate all commits gets called even
// when the skip option is used.
TestValidator validator2 = new TestValidator(true);
- handle2 = commitValidators.add("test-validator-2", validator2);
- PushOneCommit push4 =
- pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
- push4.setPushOptions(ImmutableList.of(PUSH_OPTION_SKIP_VALIDATION));
- r = push4.to(master);
- r.assertOkStatus();
- // First listener was not called; its count remains the same.
- assertThat(validator.count()).isEqualTo(1);
- // Second listener was called.
- assertThat(validator2.count()).isEqualTo(1);
- } finally {
- handle.remove();
- if (handle2 != null) {
- handle2.remove();
+ try (Registration registration2 = extensionRegistry.newRegistration().add(validator2)) {
+ PushOneCommit push4 =
+ pushFactory.create(admin.newIdent(), testRepo, "change2", "b.txt", "content");
+ push4.setPushOptions(ImmutableList.of(PUSH_OPTION_SKIP_VALIDATION));
+ r = push4.to(master);
+ r.assertOkStatus();
+ // First listener was not called; its count remains the same.
+ assertThat(validator.count()).isEqualTo(1);
+ // Second listener was called.
+ assertThat(validator2.count()).isEqualTo(1);
}
}
}
@@ -2435,12 +2343,19 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
pr = pushOne(testRepo, c.name(), ref, false, false, opts);
assertPushRejected(pr, ref, "NoteDb update requires access database permission");
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
pr = pushOne(testRepo, c.name(), ref, false, false, opts);
assertPushRejected(pr, ref, "prohibited by Gerrit: not permitted: create");
- grant(project, "refs/changes/*", Permission.CREATE);
- grant(project, "refs/changes/*", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref("refs/changes/*").group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref("refs/changes/*").group(adminGroupUuid()))
+ .update();
grantSkipValidation(project, "refs/changes/*", REGISTERED_USERS);
pr = pushOne(testRepo, c.name(), ref, false, false, opts);
assertPushOk(pr, ref);
@@ -2489,9 +2404,9 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
assertThat(pr.getMessages())
.contains(
"warning: no changes between prior commit "
- + c.abbreviate(7).name()
+ + abbreviateName(c)
+ " and new commit "
- + amended.abbreviate(7).name());
+ + abbreviateName(amended));
}
@Test
@@ -2517,8 +2432,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
pr = pushHead(testRepo, r, false);
assertPushOk(pr, r);
assertThat(pr.getMessages())
- .contains(
- "warning: " + amended.abbreviate(7).name() + ": no files changed, message updated");
+ .contains("warning: " + abbreviateName(amended) + ": no files changed, message updated");
}
@Test
@@ -2542,8 +2456,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
pr = pushHead(testRepo, r, false);
assertPushOk(pr, r);
assertThat(pr.getMessages())
- .contains(
- "warning: " + amended.abbreviate(7).name() + ": no files changed, author changed");
+ .contains("warning: " + abbreviateName(amended) + ": no files changed, author changed");
}
@Test
@@ -2570,7 +2483,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
pr = pushHead(testRepo, r, false);
assertPushOk(pr, r);
assertThat(pr.getMessages())
- .contains("warning: " + amended.abbreviate(7).name() + ": no files changed, was rebased");
+ .contains("warning: " + abbreviateName(amended) + ": no files changed, was rebased");
}
@Test
@@ -2641,6 +2554,76 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
+ "\n");
}
+ @Test
+ public void cannotPushTheSameCommitTwiceForReviewToTheSameBranch() throws Exception {
+ testCannotPushTheSameCommitTwiceForReviewToTheSameBranch();
+ }
+
+ @Test
+ public void cannotPushTheSameCommitTwiceForReviewToTheSameBranchCreateNewChangeForAllNotInTarget()
+ throws Exception {
+ enableCreateNewChangeForAllNotInTarget();
+ testCannotPushTheSameCommitTwiceForReviewToTheSameBranch();
+ }
+
+ private void testCannotPushTheSameCommitTwiceForReviewToTheSameBranch() throws Exception {
+ setRequireChangeId(InheritableBoolean.FALSE);
+
+ // create a commit without Change-Id
+ testRepo
+ .branch("HEAD")
+ .commit()
+ .author(user.newIdent())
+ .committer(user.newIdent())
+ .add(PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT)
+ .message(PushOneCommit.SUBJECT)
+ .create();
+
+ // push the commit for review to create a change
+ PushResult r = pushHead(testRepo, "refs/for/master");
+ assertPushOk(r, "refs/for/master");
+
+ // try to push the same commit for review again to create another change on the same branch,
+ // it's expected that this is rejected with "no new changes"
+ r = pushHead(testRepo, "refs/for/master");
+ assertPushRejected(r, "refs/for/master", "no new changes");
+ }
+
+ @Test
+ public void pushTheSameCommitTwiceForReviewToDifferentBranches() throws Exception {
+ setRequireChangeId(InheritableBoolean.FALSE);
+
+ // create a commit without Change-Id
+ testRepo
+ .branch("HEAD")
+ .commit()
+ .author(user.newIdent())
+ .committer(user.newIdent())
+ .add(PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT)
+ .message(PushOneCommit.SUBJECT)
+ .create();
+
+ // push the commit for review to create a change
+ PushResult r = pushHead(testRepo, "refs/for/master");
+ assertPushOk(r, "refs/for/master");
+
+ // create another branch
+ gApi.projects().name(project.get()).branch("otherBranch").create(new BranchInput());
+
+ // try to push the same commit for review again to create a change on another branch,
+ // it's expected that this is rejected with "no new changes" since
+ // CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET is false
+ r = pushHead(testRepo, "refs/for/otherBranch");
+ assertPushRejected(r, "refs/for/otherBranch", "no new changes");
+
+ enableCreateNewChangeForAllNotInTarget();
+
+ // try to push the same commit for review again to create a change on another branch,
+ // now it should succeed since CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET is true
+ r = pushHead(testRepo, "refs/for/otherBranch");
+ assertPushOk(r, "refs/for/otherBranch");
+ }
+
private DraftInput newDraft(String path, int line, String message) {
DraftInput d = new DraftInput();
d.path = path;
@@ -2685,11 +2668,11 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
ChangeData cd = byCommit(c);
String name = "reviewers for " + (i + 1);
if (expectedReviewer != null) {
- assertThat(cd.reviewers().all()).named(name).containsExactly(expectedReviewer.id());
+ assertWithMessage(name).that(cd.reviewers().all()).containsExactly(expectedReviewer.id());
// Remove reviewer from PS1 so we can test adding this same reviewer on PS2 below.
gApi.changes().id(cd.getId().get()).reviewer(expectedReviewer.id().toString()).remove();
}
- assertThat(byCommit(c).reviewers().all()).named(name).isEmpty();
+ assertWithMessage(name).that(byCommit(c).reviewers().all()).isEmpty();
}
List<RevCommit> commits2 = amendChanges(initialHead, commits, r);
@@ -2698,9 +2681,9 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
ChangeData cd = byCommit(c);
String name = "reviewers for " + (i + 1);
if (expectedReviewer != null) {
- assertThat(cd.reviewers().all()).named(name).containsExactly(expectedReviewer.id());
+ assertWithMessage(name).that(cd.reviewers().all()).containsExactly(expectedReviewer.id());
} else {
- assertThat(byCommit(c).reviewers().all()).named(name).isEmpty();
+ assertWithMessage(name).that(byCommit(c).reviewers().all()).isEmpty();
}
}
}
@@ -2767,20 +2750,20 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
private static Map<Integer, String> getPatchSetRevisions(ChangeData cd) throws Exception {
Map<Integer, String> revisions = new HashMap<>();
for (PatchSet ps : cd.patchSets()) {
- revisions.put(ps.getPatchSetId(), ps.getRevision().get());
+ revisions.put(ps.number(), ps.commitId().name());
}
return revisions;
}
private ChangeData byCommit(ObjectId id) throws Exception {
List<ChangeData> cds = queryProvider.get().byCommit(id);
- assertThat(cds).named("change for " + id.name()).hasSize(1);
+ assertWithMessage("change for " + id.name()).that(cds).hasSize(1);
return cds.get(0);
}
private ChangeData byChangeId(Change.Id id) throws Exception {
List<ChangeData> cds = queryProvider.get().byLegacyChangeId(id);
- assertThat(cds).named("change " + id).hasSize(1);
+ assertWithMessage("change " + id).that(cds).hasSize(1);
return cds.get(0);
}
@@ -2808,13 +2791,14 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
private void grantSkipValidation(Project.NameKey project, String ref, AccountGroup.UUID groupUuid)
throws Exception {
// See SKIP_VALIDATION implementation in default permission backend.
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.FORGE_AUTHOR, groupUuid, ref);
- Util.allow(u.getConfig(), Permission.FORGE_COMMITTER, groupUuid, ref);
- Util.allow(u.getConfig(), Permission.FORGE_SERVER, groupUuid, ref);
- Util.allow(u.getConfig(), Permission.PUSH_MERGE, groupUuid, "refs/for/" + ref);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.FORGE_AUTHOR).ref(ref).group(groupUuid))
+ .add(allow(Permission.FORGE_COMMITTER).ref(ref).group(groupUuid))
+ .add(allow(Permission.FORGE_SERVER).ref(ref).group(groupUuid))
+ .add(allow(Permission.PUSH_MERGE).ref("refs/for/" + ref).group(groupUuid))
+ .update();
}
private PushOneCommit.Result amendChange(String changeId, String ref) throws Exception {
@@ -2833,4 +2817,8 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
? infos.stream().map(a -> a.email).collect(toImmutableList())
: ImmutableList.of();
}
+
+ private String abbreviateName(AnyObjectId id) throws Exception {
+ return ObjectIds.abbreviateName(id, testRepo.getRevWalk().getObjectReader());
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmitOnPush.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmitOnPush.java
index c02a5edc87..7cfb0f2855 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmitOnPush.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmitOnPush.java
@@ -17,21 +17,30 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.events.ChangeMergedEvent;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -48,6 +57,7 @@ import org.junit.Test;
public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
@Inject private ApprovalsUtil approvalsUtil;
+ @Inject private ProjectOperations projectOperations;
@Before
public void blockAnonymous() throws Exception {
@@ -56,7 +66,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
@Test
public void submitOnPush() throws Exception {
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r = pushTo("refs/for/master%submit");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
@@ -66,7 +80,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
@Test
public void submitOnPushToRefsMetaConfig() throws Exception {
- grant(project, "refs/for/refs/meta/config", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/meta/config").group(adminGroupUuid()))
+ .update();
git().fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config")).call();
testRepo.reset(RefNames.REFS_CONFIG);
@@ -84,7 +102,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
push("refs/heads/master", "one change", "a.txt", "some content");
testRepo.reset(objectId);
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r =
push("refs/for/master%submit", "other change", "a.txt", "other content");
r.assertErrorStatus();
@@ -100,7 +122,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
push(master, "one change", "a.txt", "some content");
testRepo.reset(objectId);
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r =
push("refs/for/master%submit", "other change", "b.txt", "other content");
r.assertOkStatus();
@@ -113,7 +139,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
r =
push(
"refs/for/master%submit",
@@ -153,7 +183,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
@Test
public void mergeOnPushToBranch() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
r.assertOkStatus();
@@ -162,28 +196,32 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
assertCommit(project, "refs/heads/master");
ChangeData cd =
- Iterables.getOnlyElement(queryProvider.get().byKey(new Change.Key(r.getChangeId())));
+ Iterables.getOnlyElement(queryProvider.get().byKey(Change.key(r.getChangeId())));
RevCommit c = r.getCommit();
- PatchSet.Id psId = cd.currentPatchSet().getId();
+ PatchSet.Id psId = cd.currentPatchSet().id();
assertThat(psId.get()).isEqualTo(1);
assertThat(cd.change().isMerged()).isTrue();
assertSubmitApproval(psId);
assertThat(cd.patchSets()).hasSize(1);
- assertThat(cd.patchSet(psId).getRevision().get()).isEqualTo(c.name());
+ assertThat(cd.patchSet(psId).commitId()).isEqualTo(c);
}
@Test
public void correctNewRevOnMergeByPushToBranch() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
push("refs/for/master", PushOneCommit.SUBJECT, "one.txt", "One");
PushOneCommit.Result r = push("refs/for/master", PushOneCommit.SUBJECT, "two.txt", "Two");
startEventRecorder();
git().push().setRefSpecs(new RefSpec(r.getCommit().name() + ":refs/heads/master")).call();
List<ChangeMergedEvent> changeMergedEvents =
eventRecorder.getChangeMergedEvents(project.get(), "refs/heads/master", 2);
- assertThat(changeMergedEvents.get(0).newRev).isEqualTo(r.getPatchSet().getRevision().get());
- assertThat(changeMergedEvents.get(1).newRev).isEqualTo(r.getPatchSet().getRevision().get());
+ assertThat(changeMergedEvents.get(0).newRev).isEqualTo(r.getPatchSet().commitId().name());
+ assertThat(changeMergedEvents.get(1).newRev).isEqualTo(r.getPatchSet().commitId().name());
}
@Test
@@ -191,10 +229,14 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
enableCreateNewChangeForAllNotInTarget();
String master = "refs/heads/master";
String other = "refs/heads/other";
- grant(project, master, Permission.PUSH);
- grant(project, other, Permission.CREATE);
- grant(project, other, Permission.PUSH);
- RevCommit masterRev = getRemoteHead();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(master).group(adminGroupUuid()))
+ .add(allow(Permission.CREATE).ref(other).group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(other).group(adminGroupUuid()))
+ .update();
+ RevCommit masterRev = projectOperations.project(project).getHead("master");
pushCommitTo(masterRev, other);
PushOneCommit.Result r = createChange();
r.assertOkStatus();
@@ -202,7 +244,7 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
pushCommitTo(commit, master);
assertCommit(project, master);
ChangeData cd =
- Iterables.getOnlyElement(queryProvider.get().byKey(new Change.Key(r.getChangeId())));
+ Iterables.getOnlyElement(queryProvider.get().byKey(Change.key(r.getChangeId())));
assertThat(cd.change().isMerged()).isTrue();
RemoteRefUpdate.Status status = pushCommitTo(commit, "refs/for/other");
@@ -211,8 +253,8 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
pushCommitTo(commit, other);
assertCommit(project, other);
- for (ChangeData c : queryProvider.get().byKey(new Change.Key(r.getChangeId()))) {
- if (c.change().getDest().get().equals(other)) {
+ for (ChangeData c : queryProvider.get().byKey(Change.key(r.getChangeId()))) {
+ if (c.change().getDest().branch().equals(other)) {
assertThat(c.change().isMerged()).isTrue();
}
}
@@ -228,7 +270,11 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
@Test
public void mergeOnPushToBranchWithNewPatchset() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r = pushTo("refs/for/master");
r.assertOkStatus();
RevCommit c1 = r.getCommit();
@@ -256,13 +302,17 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
assertSubmitApproval(psId2);
assertThat(cd.patchSets()).hasSize(2);
- assertThat(cd.patchSet(psId1).getRevision().get()).isEqualTo(c1.name());
- assertThat(cd.patchSet(psId2).getRevision().get()).isEqualTo(c2.name());
+ assertThat(cd.patchSet(psId1).commitId()).isEqualTo(c1);
+ assertThat(cd.patchSet(psId2).commitId()).isEqualTo(c2);
}
@Test
public void mergeOnPushToBranchWithOldPatchset() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
PushOneCommit.Result r = pushTo("refs/for/master");
r.assertOkStatus();
RevCommit c1 = r.getCommit();
@@ -273,26 +323,30 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
r = amendChange(changeId);
ChangeData cd = r.getChange();
PatchSet.Id psId2 = cd.change().currentPatchSetId();
- assertThat(psId2.getParentKey()).isEqualTo(psId1.getParentKey());
+ assertThat(psId2.changeId()).isEqualTo(psId1.changeId());
assertThat(psId2.get()).isEqualTo(2);
testRepo.reset(c1);
assertPushOk(pushHead(testRepo, "refs/heads/master", false), "refs/heads/master");
- cd = changeDataFactory.create(project, psId1.getParentKey());
+ cd = changeDataFactory.create(project, psId1.changeId());
Change c = cd.change();
assertThat(c.isMerged()).isTrue();
assertThat(c.currentPatchSetId()).isEqualTo(psId1);
- assertThat(cd.patchSets().stream().map(PatchSet::getId).collect(toList()))
+ assertThat(cd.patchSets().stream().map(PatchSet::id).collect(toList()))
.containsExactly(psId1, psId2);
}
@Test
public void mergeMultipleOnPushToBranchWithNewPatchset() throws Exception {
- grant(project, "refs/heads/master", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
+ .update();
// Create 2 changes.
- ObjectId initialHead = getRemoteHead();
+ ObjectId initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result r1 = createChange("Change 1", "a", "a");
r1.assertOkStatus();
PushOneCommit.Result r2 = createChange("Change 2", "b", "b");
@@ -323,27 +377,96 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
assertThat(cd2.change().isMerged()).isTrue();
PatchSet.Id psId2_2 = cd2.change().currentPatchSetId();
assertThat(psId2_2.get()).isEqualTo(2);
- assertThat(cd2.patchSet(psId2_1).getRevision().get()).isEqualTo(c2_1.name());
- assertThat(cd2.patchSet(psId2_2).getRevision().get()).isEqualTo(c2_2.name());
+ assertThat(cd2.patchSet(psId2_1).commitId()).isEqualTo(c2_1);
+ assertThat(cd2.patchSet(psId2_2).commitId()).isEqualTo(c2_2);
ChangeData cd1 = r1.getChange();
assertThat(cd1.change().isMerged()).isTrue();
PatchSet.Id psId1_2 = cd1.change().currentPatchSetId();
assertThat(psId1_2.get()).isEqualTo(2);
- assertThat(cd1.patchSet(psId1_1).getRevision().get()).isEqualTo(c1_1.name());
- assertThat(cd1.patchSet(psId1_2).getRevision().get()).isEqualTo(c1_2.name());
+ assertThat(cd1.patchSet(psId1_1).commitId()).isEqualTo(c1_1);
+ assertThat(cd1.patchSet(psId1_2).commitId()).isEqualTo(c1_2);
+ }
+
+ @Test
+ public void pushForSubmitWithNotifyOption() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
+
+ TestAccount user = accountCreator.user();
+ String pushSpec = "refs/for/master%reviewer=" + user.email();
+ sender.clear();
+
+ PushOneCommit.Result result = pushTo(pushSpec + ",submit,notify=" + NotifyHandling.NONE);
+ result.assertOkStatus();
+ assertThat(sender.getMessages()).isEmpty();
+
+ sender.clear();
+ result = pushTo(pushSpec + ",submit,notify=" + NotifyHandling.OWNER);
+ result.assertOkStatus();
+ assertThat(sender.getMessages()).isEmpty();
+
+ sender.clear();
+ result = pushTo(pushSpec + ",submit,notify=" + NotifyHandling.OWNER_REVIEWERS);
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user, null);
+
+ sender.clear();
+ result = pushTo(pushSpec + ",submit,notify=" + NotifyHandling.ALL);
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user, null);
+
+ sender.clear();
+ result = pushTo(pushSpec + ",submit"); // default is notify = ALL
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user, null);
+ }
+
+ @Test
+ public void pushForSubmitWithNotifyingUsersExplicitly() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(adminGroupUuid()))
+ .update();
+
+ TestAccount user = accountCreator.user();
+ String pushSpec = "refs/for/master%reviewer=" + user.email() + ",cc=" + user.email();
+
+ TestAccount user2 = accountCreator.user2();
+
+ sender.clear();
+ PushOneCommit.Result result =
+ pushTo(pushSpec + ",submit,notify=" + NotifyHandling.NONE + ",notify-to=" + user2.email());
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user2, RecipientType.TO);
+
+ sender.clear();
+ result =
+ pushTo(pushSpec + ",submit,notify=" + NotifyHandling.NONE + ",notify-cc=" + user2.email());
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user2, RecipientType.CC);
+
+ sender.clear();
+ result =
+ pushTo(pushSpec + ",submit,notify=" + NotifyHandling.NONE + ",notify-bcc=" + user2.email());
+ result.assertOkStatus();
+ assertThatEmailsForChangeCreationAndSubmitWereSent(user2, RecipientType.BCC);
}
private PatchSetApproval getSubmitter(PatchSet.Id patchSetId) throws Exception {
- ChangeNotes notes = notesFactory.createChecked(project, patchSetId.getParentKey()).load();
+ ChangeNotes notes = notesFactory.createChecked(project, patchSetId.changeId()).load();
return approvalsUtil.getSubmitter(notes, patchSetId);
}
private void assertSubmitApproval(PatchSet.Id patchSetId) throws Exception {
PatchSetApproval a = getSubmitter(patchSetId);
assertThat(a.isLegacySubmit()).isTrue();
- assertThat(a.getValue()).isEqualTo((short) 1);
- assertThat(a.getAccountId()).isEqualTo(admin.id());
+ assertThat(a.value()).isEqualTo((short) 1);
+ assertThat(a.accountId()).isEqualTo(admin.id());
}
private void assertCommit(Project.NameKey project, String branch) throws Exception {
@@ -381,4 +504,45 @@ public abstract class AbstractSubmitOnPush extends AbstractDaemonTest {
pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content, changeId);
return push.to(ref);
}
+
+ /**
+ * Makes sure that two emails are sent: one for the change creation, and one for the submit.
+ *
+ * @param expected The account expected to receive message.
+ * @param expectedRecipientType The notification's type: To/Cc/Bcc. if {@code null} then it is not
+ * needed to check the recipientType. It is meant for -notify without other flags like
+ * notify-cc, notify-to, and notify-bcc. With the -notify flag, the message can sometimes be
+ * sent as "To" and sometimes can be sent as "Cc".
+ */
+ private void assertThatEmailsForChangeCreationAndSubmitWereSent(
+ TestAccount expected, @Nullable RecipientType expectedRecipientType) {
+ String expectedEmail = expected.email();
+ String expectedFullName = expected.fullName();
+ Address expectedAddress = new Address(expectedFullName, expectedEmail);
+ assertThat(sender.getMessages()).hasSize(2);
+ Message message = sender.getMessages().get(0);
+ assertThat(message.body().contains("review")).isTrue();
+ assertAddress(message, expectedAddress, expectedRecipientType);
+ message = sender.getMessages().get(1);
+ assertThat(message.rcpt()).containsExactly(expectedAddress);
+ assertAddress(message, expectedAddress, expectedRecipientType);
+ assertThat(message.body().contains("submitted")).isTrue();
+ }
+
+ private void assertAddress(
+ Message message, Address expectedAddress, @Nullable RecipientType expectedRecipientType) {
+ assertThat(message.rcpt()).containsExactly(expectedAddress);
+ if (expectedRecipientType != null
+ && expectedRecipientType
+ != RecipientType.BCC) { // When Bcc, it does not appear in the header.
+ String expectedRecipientTypeString = "To";
+ if (expectedRecipientType == RecipientType.CC) {
+ expectedRecipientTypeString = "Cc";
+ }
+ assertThat(
+ ((EmailHeader.AddressList) message.headers().get(expectedRecipientTypeString))
+ .getAddressList())
+ .containsExactly(expectedAddress);
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index d1349d073a..01323a08a9 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.Iterables;
@@ -22,8 +24,8 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.SubscribeSection;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.inject.Inject;
@@ -58,11 +60,12 @@ import org.junit.Before;
public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
protected TestRepository<?> superRepo;
protected Project.NameKey superKey;
protected TestRepository<?> subRepo;
protected Project.NameKey subKey;
- @Inject protected ProjectOperations projectOperations;
protected SubmitType getSubmitType() {
return cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
@@ -104,8 +107,12 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
}
protected void grantPush(Project.NameKey project) throws Exception {
- grant(project, "refs/heads/*", Permission.PUSH);
- grant(project, "refs/for/refs/heads/*", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(adminGroupUuid()))
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(adminGroupUuid()))
+ .update();
}
protected Project.NameKey createProjectForPush(SubmitType submitType) throws Exception {
@@ -472,7 +479,7 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
ObjectInserter ins = serverRepo.newObjectInserter();
RevWalk rw = new RevWalk(serverRepo)) {
Ref ref = serverRepo.exactRef(refName);
- assertThat(ref).named(refName).isNotNull();
+ assertWithMessage(refName).that(ref).isNotNull();
ObjectId oldCommitId = ref.getObjectId();
DirCache dc = DirCache.newInCore();
diff --git a/javatests/com/google/gerrit/acceptance/git/BUILD b/javatests/com/google/gerrit/acceptance/git/BUILD
index 24a83e0b33..ef54c92639 100644
--- a/javatests/com/google/gerrit/acceptance/git/BUILD
+++ b/javatests/com/google/gerrit/acceptance/git/BUILD
@@ -8,6 +8,8 @@ load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
deps = [
":push_for_review",
":submodule_util",
+ "//java/com/google/gerrit/git",
+ "//java/com/google/gerrit/server/git/receive/testing",
"//lib/commons:lang",
],
) for f in glob(["*IT.java"])]
@@ -18,6 +20,7 @@ java_library(
srcs = glob(["Abstract*.java"]),
deps = [
"//java/com/google/gerrit/acceptance:lib",
+ "//java/com/google/gerrit/git",
"//java/com/google/gerrit/mail",
],
)
diff --git a/javatests/com/google/gerrit/acceptance/git/GitmodulesIT.java b/javatests/com/google/gerrit/acceptance/git/GitmodulesIT.java
index ac0cbd83e3..e2aa666275 100644
--- a/javatests/com/google/gerrit/acceptance/git/GitmodulesIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/GitmodulesIT.java
@@ -14,6 +14,9 @@
package com.google.gerrit.acceptance.git;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
import com.google.gerrit.acceptance.AbstractDaemonTest;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.junit.TestRepository;
@@ -50,8 +53,15 @@ public class GitmodulesIT extends AbstractDaemonTest {
.add(".gitmodules", config.toText())
.create();
- exception.expectMessage(expectedErrorMessage);
- exception.expect(TransportException.class);
- repo.git().push().setRemote("origin").setRefSpecs(new RefSpec("HEAD:refs/for/master")).call();
+ TransportException thrown =
+ assertThrows(
+ TransportException.class,
+ () ->
+ repo.git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/for/master"))
+ .call());
+ assertThat(thrown).hasMessageThat().contains(expectedErrorMessage);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java b/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
index 6516b32eb5..b51263e596 100644
--- a/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/ImplicitMergeCheckIT.java
@@ -19,8 +19,9 @@ import static com.google.gerrit.acceptance.GitUtil.pushHead;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
+import com.google.gerrit.git.ObjectIds;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -76,8 +77,9 @@ public class ImplicitMergeCheckIT extends AbstractDaemonTest {
assertThat(c.getMessage().toLowerCase()).doesNotContain(implicitMergeOf(m.getCommit()));
}
- private static String implicitMergeOf(ObjectId commit) {
- return "implicit merge of " + commit.abbreviate(7).name();
+ private String implicitMergeOf(ObjectId commit) throws Exception {
+ return "implicit merge of "
+ + ObjectIds.abbreviateName(commit, testRepo.getRevWalk().getObjectReader());
}
private void setRejectImplicitMerges() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
new file mode 100644
index 0000000000..2f677e2fd0
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
@@ -0,0 +1,778 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.ServerInitiated;
+import com.google.gerrit.server.account.AccountProperties;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.account.ProjectWatches;
+import com.google.gerrit.server.account.ProjectWatches.NotifyType;
+import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.util.MagicBranch;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.util.EnumSet;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Test;
+
+/** Tests account behavior when users push to accounts refs. */
+public class PushAccountIT extends AbstractDaemonTest {
+
+ @ConfigSuite.Default
+ public static Config enableSignedPushConfig() {
+ return defaultConfig();
+ }
+
+ @ConfigSuite.Config
+ public static Config disableInMemoryRefCache() {
+ // Run these tests for both enabled and disabled in-memory ref caches. This is an implementation
+ // detail of ReceiveCommits that makes the logic either base it's computation on previously
+ // advertised refs or a make it query a ref database.
+ Config cfg = defaultConfig();
+ cfg.setBoolean("receive", null, "enableInMemoryRefCache", false);
+ return cfg;
+ }
+
+ private static Config defaultConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("receive", null, "enableSignedPush", true);
+
+ // Disable the staleness checker so that tests that verify the number of expected index events
+ // are stable.
+ cfg.setBoolean("index", null, "autoReindexIfStale", false);
+
+ return cfg;
+ }
+
+ @Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private Sequences seq;
+
+ @Test
+ public void pushToUserBranch() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+ PushOneCommit push = pushFactory.create(admin.newIdent(), allUsersRepo);
+ push.to(RefNames.refsUsers(admin.id())).assertOkStatus();
+ accountIndexedCounter.assertReindexOf(admin);
+
+ push = pushFactory.create(admin.newIdent(), allUsersRepo);
+ push.to(RefNames.REFS_USERS_SELF).assertOkStatus();
+ accountIndexedCounter.assertReindexOf(admin);
+ }
+ }
+
+ @Test
+ public void pushToUserBranchForReview() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String userRefName = RefNames.refsUsers(admin.id());
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRefName + ":userRef");
+ allUsersRepo.reset("userRef");
+ PushOneCommit push = pushFactory.create(admin.newIdent(), allUsersRepo);
+ PushOneCommit.Result r = push.to(MagicBranch.NEW_CHANGE + userRefName);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRefName);
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ accountIndexedCounter.assertReindexOf(admin);
+
+ push = pushFactory.create(admin.newIdent(), allUsersRepo);
+ r = push.to(MagicBranch.NEW_CHANGE + RefNames.REFS_USERS_SELF);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRefName);
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ accountIndexedCounter.assertReindexOf(admin);
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchForReviewAndSubmit() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String userRef = RefNames.refsUsers(admin.id());
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, "out-of-office");
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ accountIndexedCounter.assertReindexOf(admin);
+
+ AccountInfo info = gApi.accounts().self().get();
+ assertThat(info.email).isEqualTo(admin.email());
+ assertThat(info.name).isEqualTo(admin.fullName());
+ assertThat(info.status).isEqualTo("out-of-office");
+ }
+ }
+
+ @Test
+ public void pushAccountConfigWithPrefEmailThatDoesNotExistAsExtIdToUserBranchForReviewAndSubmit()
+ throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
+ String userRef = RefNames.refsUsers(foo.id());
+ accountIndexedCounter.clear();
+
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ String email = "some.email@example.com";
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, email);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ foo.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ requestScopeOperations.setApiUser(foo.id());
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+
+ accountIndexedCounter.assertReindexOf(foo);
+
+ AccountInfo info = gApi.accounts().self().get();
+ assertThat(info.email).isEqualTo(email);
+ assertThat(info.name).isEqualTo(foo.fullName());
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfConfigIsInvalid()
+ throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String userRef = RefNames.refsUsers(admin.id());
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ "invalid config")
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "invalid account configuration: commit '%s' has an invalid '%s' file for account"
+ + " '%s': Invalid config file %s in commit %s",
+ r.getCommit().name(),
+ AccountProperties.ACCOUNT_CONFIG,
+ admin.id(),
+ AccountProperties.ACCOUNT_CONFIG,
+ r.getCommit().name()));
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfPreferredEmailIsInvalid()
+ throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String userRef = RefNames.refsUsers(admin.id());
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ String noEmail = "no.email";
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, noEmail);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format(
+ "invalid account configuration: invalid preferred email '%s' for account '%s'",
+ noEmail, admin.id()));
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchForReviewIsRejectedOnSubmitIfOwnAccountIsDeactivated()
+ throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ String userRef = RefNames.refsUsers(admin.id());
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r.getChangeId()).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("invalid account configuration: cannot deactivate own account");
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchForReviewDeactivateOtherAccount() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+
+ TestAccount foo = accountCreator.create(name("foo"));
+ assertThat(gApi.accounts().id(foo.id().get()).getActive()).isTrue();
+ String userRef = RefNames.refsUsers(foo.id());
+ accountIndexedCounter.clear();
+
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(userRef).group(adminGroupUuid()))
+ .add(allowLabel("Code-Review").ref(userRef).group(adminGroupUuid()).range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref(userRef).group(adminGroupUuid()))
+ .update();
+
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(MagicBranch.NEW_CHANGE + userRef);
+ r.assertOkStatus();
+ accountIndexedCounter.assertNoReindex();
+ assertThat(r.getChange().change().getDest().branch()).isEqualTo(userRef);
+
+ gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).current().submit();
+ accountIndexedCounter.assertReindexOf(foo);
+
+ assertThat(gApi.accounts().id(foo.id().get()).getActive()).isFalse();
+ }
+ }
+
+ @Test
+ public void pushWatchConfigToUserBranch() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config wc = new Config();
+ wc.setString(
+ ProjectWatches.PROJECT,
+ project.get(),
+ ProjectWatches.KEY_NOTIFY,
+ ProjectWatches.NotifyValue.create(null, EnumSet.of(NotifyType.ALL_COMMENTS)).toString());
+ PushOneCommit push =
+ pushFactory.create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Add project watch",
+ ProjectWatches.WATCH_CONFIG,
+ wc.toText());
+ push.to(RefNames.REFS_USERS_SELF).assertOkStatus();
+ accountIndexedCounter.assertReindexOf(admin);
+
+ String invalidNotifyValue = "]invalid[";
+ wc.setString(
+ ProjectWatches.PROJECT, project.get(), ProjectWatches.KEY_NOTIFY, invalidNotifyValue);
+ push =
+ pushFactory.create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Add invalid project watch",
+ ProjectWatches.WATCH_CONFIG,
+ wc.toText());
+ PushOneCommit.Result r = push.to(RefNames.REFS_USERS_SELF);
+ r.assertErrorStatus("invalid account configuration");
+ r.assertMessage(
+ String.format(
+ "%s: Invalid project watch of account %d for project %s: %s",
+ ProjectWatches.WATCH_CONFIG, admin.id().get(), project.get(), invalidNotifyValue));
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranch() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount oooUser = accountCreator.create("away", "away@mail.invalid", "Ambrose Way");
+ requestScopeOperations.setApiUser(oooUser.id());
+
+ // Must clone as oooUser to ensure the push is allowed.
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, oooUser);
+ fetch(allUsersRepo, RefNames.refsUsers(oooUser.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, "out-of-office");
+
+ accountIndexedCounter.clear();
+ pushFactory
+ .create(
+ oooUser.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(RefNames.refsUsers(oooUser.id()))
+ .assertOkStatus();
+
+ accountIndexedCounter.assertReindexOf(oooUser);
+
+ AccountInfo info = gApi.accounts().self().get();
+ assertThat(info.email).isEqualTo(oooUser.email());
+ assertThat(info.name).isEqualTo(oooUser.fullName());
+ assertThat(info.status).isEqualTo("out-of-office");
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchIsRejectedIfConfigIsInvalid() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ "invalid config")
+ .to(RefNames.REFS_USERS_SELF);
+ r.assertErrorStatus("invalid account configuration");
+ r.assertMessage(
+ String.format(
+ "commit '%s' has an invalid '%s' file for account '%s':"
+ + " Invalid config file %s in commit %s",
+ r.getCommit().name(),
+ AccountProperties.ACCOUNT_CONFIG,
+ admin.id(),
+ AccountProperties.ACCOUNT_CONFIG,
+ r.getCommit().name()));
+ accountIndexedCounter.assertNoReindex();
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchIsRejectedIfPreferredEmailIsInvalid() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ String noEmail = "no.email";
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, noEmail);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(RefNames.REFS_USERS_SELF);
+ r.assertErrorStatus("invalid account configuration");
+ r.assertMessage(
+ String.format("invalid preferred email '%s' for account '%s'", noEmail, admin.id()));
+ accountIndexedCounter.assertNoReindex();
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchInvalidPreferredEmailButNotChanged() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
+ String userRef = RefNames.refsUsers(foo.id());
+
+ String noEmail = "no.email";
+ accountsUpdateProvider
+ .get()
+ .update("Set Preferred Email", foo.id(), u -> u.setPreferredEmail(noEmail));
+ accountIndexedCounter.clear();
+
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(userRef).group(REGISTERED_USERS))
+ .update();
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ String status = "in vacation";
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_STATUS, status);
+
+ pushFactory
+ .create(
+ foo.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(userRef)
+ .assertOkStatus();
+ accountIndexedCounter.assertReindexOf(foo);
+
+ AccountInfo info = gApi.accounts().id(foo.id().get()).get();
+ assertThat(info.email).isEqualTo(noEmail);
+ assertThat(info.name).isEqualTo(foo.fullName());
+ assertThat(info.status).isEqualTo(status);
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchIfPreferredEmailDoesNotExistAsExtId() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestAccount foo = accountCreator.create(name("foo"), name("foo") + "@example.com", "Foo");
+ String userRef = RefNames.refsUsers(foo.id());
+ accountIndexedCounter.clear();
+
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(userRef).group(adminGroupUuid()))
+ .update();
+
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, foo);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ String email = "some.email@example.com";
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setString(AccountProperties.ACCOUNT, null, AccountProperties.KEY_PREFERRED_EMAIL, email);
+
+ pushFactory
+ .create(
+ foo.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(userRef)
+ .assertOkStatus();
+ accountIndexedCounter.assertReindexOf(foo);
+
+ AccountInfo info = gApi.accounts().id(foo.id().get()).get();
+ assertThat(info.email).isEqualTo(email);
+ assertThat(info.name).isEqualTo(foo.fullName());
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchIsRejectedIfOwnAccountIsDeactivated() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
+
+ PushOneCommit.Result r =
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(RefNames.REFS_USERS_SELF);
+ r.assertErrorStatus("invalid account configuration");
+ r.assertMessage("cannot deactivate own account");
+ accountIndexedCounter.assertNoReindex();
+ }
+ }
+
+ @Test
+ public void pushAccountConfigToUserBranchDeactivateOtherAccount() throws Exception {
+ AccountIndexedCounter accountIndexedCounter = new AccountIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(accountIndexedCounter)) {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+
+ TestAccount foo = accountCreator.create(name("foo"));
+ assertThat(gApi.accounts().id(foo.id().get()).getActive()).isTrue();
+ String userRef = RefNames.refsUsers(foo.id());
+ accountIndexedCounter.clear();
+
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref(userRef).group(adminGroupUuid()))
+ .update();
+
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ fetch(allUsersRepo, userRef + ":userRef");
+ allUsersRepo.reset("userRef");
+
+ Config ac = getAccountConfig(allUsersRepo);
+ ac.setBoolean(AccountProperties.ACCOUNT, null, AccountProperties.KEY_ACTIVE, false);
+
+ pushFactory
+ .create(
+ admin.newIdent(),
+ allUsersRepo,
+ "Update account config",
+ AccountProperties.ACCOUNT_CONFIG,
+ ac.toText())
+ .to(userRef)
+ .assertOkStatus();
+ accountIndexedCounter.assertReindexOf(foo);
+
+ assertThat(gApi.accounts().id(foo.id().get()).getActive()).isFalse();
+ }
+ }
+
+ @Test
+ public void cannotCreateNonUserBranchUnderRefsUsersWithAccessDatabaseCapability()
+ throws Exception {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .update();
+
+ String userRef = RefNames.REFS_USERS + "foo";
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef);
+ r.assertErrorStatus();
+ assertThat(r.getMessage()).contains("Not allowed to create non-user branch under refs/users/.");
+
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ assertThat(repo.exactRef(userRef)).isNull();
+ }
+ }
+
+ @Test
+ public void cannotCreateUserBranch() throws Exception {
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .update();
+
+ String userRef = RefNames.refsUsers(Account.id(seq.nextAccountId()));
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ PushOneCommit.Result r = pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef);
+ r.assertErrorStatus();
+ assertThat(r.getMessage()).contains("Not allowed to create user branch.");
+
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ assertThat(repo.exactRef(userRef)).isNull();
+ }
+ }
+
+ @Test
+ public void createUserBranchWithAccessDatabaseCapability() throws Exception {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS + "*").group(adminGroupUuid()))
+ .update();
+
+ String userRef = RefNames.refsUsers(Account.id(seq.nextAccountId()));
+ TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
+ pushFactory.create(admin.newIdent(), allUsersRepo).to(userRef).assertOkStatus();
+
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ assertThat(repo.exactRef(userRef)).isNotNull();
+ }
+ }
+
+ private Config getAccountConfig(TestRepository<?> allUsersRepo) throws Exception {
+ Config ac = new Config();
+ try (TreeWalk tw =
+ TreeWalk.forPath(
+ allUsersRepo.getRepository(),
+ AccountProperties.ACCOUNT_CONFIG,
+ getHead(allUsersRepo.getRepository(), "HEAD").getTree())) {
+ assertThat(tw).isNotNull();
+ ac.fromText(
+ new String(
+ allUsersRepo
+ .getRevWalk()
+ .getObjectReader()
+ .open(tw.getObjectId(0), OBJ_BLOB)
+ .getBytes(),
+ UTF_8));
+ }
+ return ac;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
index cc19ad2a65..f9c751fe7c 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
@@ -16,7 +16,7 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.git.testing.PushResultSubject.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.util.stream.Collectors.toList;
@@ -24,19 +24,17 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -54,14 +52,13 @@ import org.junit.Before;
import org.junit.Test;
public class PushPermissionsIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
public void setUp() throws Exception {
try (ProjectConfigUpdate u = updateProject(allProjects)) {
ProjectConfig cfg = u.getConfig();
- cfg.getProject()
- .setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, InheritableBoolean.FALSE);
// Remove push-related permissions, so they can be added back individually by test methods.
removeAllBranchPermissions(
@@ -73,13 +70,15 @@ public class PushPermissionsIT extends AbstractDaemonTest {
Permission.PUSH_MERGE,
Permission.SUBMIT);
removeAllGlobalCapabilities(cfg, GlobalCapability.ADMINISTRATE_SERVER);
-
- // Include some auxiliary permissions.
- Util.allow(cfg, Permission.FORGE_AUTHOR, REGISTERED_USERS, "refs/*");
- Util.allow(cfg, Permission.FORGE_COMMITTER, REGISTERED_USERS, "refs/*");
-
u.save();
}
+
+ // Include some auxiliary permissions.
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allow(Permission.FORGE_AUTHOR).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.FORGE_COMMITTER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
}
@Test
@@ -94,17 +93,6 @@ public class PushPermissionsIT extends AbstractDaemonTest {
}
@Test
- public void mixingDirectChangesAndRegularPush() throws Exception {
- testRepo.branch("HEAD").commit().create();
- PushResult r = push("HEAD:refs/heads/master", "HEAD:refs/changes/01/101");
-
- String msg = "cannot combine normal pushes and magic pushes";
- assertThat(r.getRemoteUpdate("refs/heads/master")).isNotEqualTo(Status.OK);
- assertThat(r.getRemoteUpdate("refs/changes/01/101")).isNotEqualTo(Status.OK);
- assertThat(r.getRemoteUpdate("refs/heads/master").getMessage()).isEqualTo(msg);
- }
-
- @Test
public void fastForwardUpdateDenied() throws Exception {
testRepo.branch("HEAD").commit().create();
PushResult r = push("HEAD:refs/heads/master");
@@ -202,7 +190,11 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void refsMetaConfigUpdateRequiresProjectOwner() throws Exception {
- grant(project, "refs/meta/config", Permission.PUSH, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/meta/config").group(REGISTERED_USERS))
+ .update();
forceFetch("refs/meta/config");
ObjectId commit = testRepo.branch("refs/meta/config").commit().create();
@@ -222,7 +214,11 @@ public class PushPermissionsIT extends AbstractDaemonTest {
"Contact an administrator to fix the permissions");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
- grant(project, "refs/*", Permission.OWNER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Re-fetch refs/meta/config from the server because the grant changed it, and we want a
// fast-forward.
@@ -249,9 +245,14 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void updateBySubmitDenied() throws Exception {
- grant(project, "refs/for/refs/heads/*", Permission.PUSH, false, REGISTERED_USERS);
-
- ObjectId commit = testRepo.branch("HEAD").commit().create();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/for/refs/heads/*").group(REGISTERED_USERS))
+ .update();
+
+ ObjectId commit =
+ testRepo.branch("HEAD").commit().message("test commit").insertChangeId().create();
assertThat(push("HEAD:refs/for/master")).onlyRef("refs/for/master").isOk();
gApi.changes().id(commit.name()).current().review(ReviewInput.approve());
@@ -267,18 +268,23 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void addPatchSetDenied() throws Exception {
- grant(project, "refs/for/refs/heads/*", Permission.PUSH, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/for/refs/heads/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
ChangeInput ci = new ChangeInput();
ci.project = project.get();
ci.branch = "master";
ci.subject = "A change";
- Change.Id id = new Change.Id(gApi.changes().create(ci).get()._number);
+ Change.Id id = Change.id(gApi.changes().create(ci).get()._number);
requestScopeOperations.setApiUser(admin.id());
- ObjectId ps1Id = forceFetch(new PatchSet.Id(id, 1).toRefName());
+ ObjectId ps1Id = forceFetch(PatchSet.id(id, 1).toRefName());
ObjectId ps2Id = testRepo.amend(ps1Id).add("file", "content").create();
PushResult r = push(ps2Id.name() + ":refs/for/master");
+ // Admin had ADD_PATCH_SET removed in setup.
assertThat(r)
.onlyRef("refs/for/master")
.isRejected("cannot add patch set to " + id.get() + ".");
@@ -288,7 +294,11 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void skipValidationDenied() throws Exception {
- grant(project, "refs/heads/*", Permission.PUSH, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
testRepo.branch("HEAD").commit().create();
PushResult r =
@@ -305,7 +315,11 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void accessDatabaseForNoteDbDenied() throws Exception {
- grant(project, "refs/heads/*", Permission.PUSH, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
testRepo.branch("HEAD").commit().create();
PushResult r =
@@ -322,8 +336,12 @@ public class PushPermissionsIT extends AbstractDaemonTest {
@Test
public void administrateServerForUpdateParentDenied() throws Exception {
- grant(project, "refs/meta/config", Permission.PUSH, false, REGISTERED_USERS);
- grant(project, "refs/*", Permission.OWNER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/meta/config").group(REGISTERED_USERS))
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
String project2 = name("project2");
gApi.projects().create(project2);
@@ -398,7 +416,7 @@ public class PushPermissionsIT extends AbstractDaemonTest {
case REJECTED_OTHER_REASON:
case RENAMED:
default:
- assert_().fail("fetch failed to update local %s: %s", ref, u.getResult());
+ assertWithMessage("fetch failed to update local %s: %s", ref, u.getResult()).fail();
break;
}
return u.getNewObjectId();
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index 5bed77c41f..ea9f48eb2c 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -18,11 +18,14 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
-import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -30,28 +33,29 @@ import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHook;
+import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHookChain;
+import com.google.gerrit.server.git.receive.testing.TestRefAdvertiser;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
@@ -71,6 +75,9 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.ReceivePack;
import org.junit.Before;
import org.junit.Test;
@@ -79,11 +86,15 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Inject private AllUsersName allUsersName;
@Inject private ChangeNoteUtil noteUtil;
@Inject private PermissionBackend permissionBackend;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
private AccountGroup.UUID admins;
private AccountGroup.UUID nonInteractiveUsers;
+ private RevCommit rcMaster;
+ private RevCommit rcBranch;
+
private ChangeData cd1;
private String psRef1;
private String metaRef1;
@@ -122,9 +133,12 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
for (AccessSection sec : u.getConfig().getAccessSections()) {
sec.removePermission(Permission.READ);
}
- Util.allow(u.getConfig(), Permission.READ, admins, "refs/*");
u.save();
}
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(admins))
+ .update();
// Remove all read permissions on All-Users.
try (ProjectConfigUpdate u = updateProject(allUsers)) {
@@ -135,60 +149,96 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
}
+ // Building the following:
+ // rcMaster (c1 master master-tag) <-- rcBranch (c2 branch branch-tag)
+ // \ \
+ // (c3_open) (c4_open)
+ //
private void setUpChanges() throws Exception {
gApi.projects().name(project.get()).branch("branch").create(new BranchInput());
// First 2 changes are merged, which means the tags pointing to them are
// visible.
- allow("refs/for/refs/heads/*", Permission.SUBMIT, admins);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(admins))
+ .update();
+
+ // rcMaster (c1 master)
PushOneCommit.Result mr =
pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%submit");
mr.assertOkStatus();
cd1 = mr.getChange();
- psRef1 = cd1.currentPatchSet().getId().toRefName();
+ rcMaster = mr.getCommit();
+ psRef1 = cd1.currentPatchSet().id().toRefName();
metaRef1 = RefNames.changeMetaRef(cd1.getId());
+
+ // rcMaster (c1 master) <-- rcBranch (c2 branch)
PushOneCommit.Result br =
pushFactory.create(admin.newIdent(), testRepo).to("refs/for/branch%submit");
br.assertOkStatus();
cd2 = br.getChange();
- psRef2 = cd2.currentPatchSet().getId().toRefName();
+ rcBranch = br.getCommit();
+ psRef2 = cd2.currentPatchSet().id().toRefName();
metaRef2 = RefNames.changeMetaRef(cd2.getId());
// Second 2 changes are unmerged.
+ // rcMaster (c1 master) <-- rcBranch (c2 branch)
+ // \
+ // (c3_open)
+ //
mr = pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master");
mr.assertOkStatus();
cd3 = mr.getChange();
- psRef3 = cd3.currentPatchSet().getId().toRefName();
+ psRef3 = cd3.currentPatchSet().id().toRefName();
metaRef3 = RefNames.changeMetaRef(cd3.getId());
+
+ // rcMaster (c1 master) <-- rcBranch (c2 branch)
+ // \ \
+ // (c3_open) (c4_open)
br = pushFactory.create(admin.newIdent(), testRepo).to("refs/for/branch");
br.assertOkStatus();
cd4 = br.getChange();
- psRef4 = cd4.currentPatchSet().getId().toRefName();
+ psRef4 = cd4.currentPatchSet().id().toRefName();
metaRef4 = RefNames.changeMetaRef(cd4.getId());
try (Repository repo = repoManager.openRepository(project)) {
- // master-tag -> master
+ // rcMaster (c1 master master-tag) <-- rcBranch (c2 branch)
+ // \ \
+ // (c3_open) (c4_open)
RefUpdate mtu = repo.updateRef("refs/tags/master-tag");
mtu.setExpectedOldObjectId(ObjectId.zeroId());
mtu.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId());
assertThat(mtu.update()).isEqualTo(RefUpdate.Result.NEW);
- // branch-tag -> branch
+ // rcMaster (c1 master master-tag) <-- rcBranch (c2 branch branch-tag)
+ // \ \
+ // (c3_open) (c4_open)
RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
btu.setExpectedOldObjectId(ObjectId.zeroId());
btu.setNewObjectId(repo.exactRef("refs/heads/branch").getObjectId());
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+
+ // Create a tag for the tree of the commit on 'master'
+ // tree-tag -> master.tree
+ RefUpdate ttu = repo.updateRef("refs/tags/tree-tag");
+ ttu.setExpectedOldObjectId(ObjectId.zeroId());
+ ttu.setNewObjectId(rcMaster.getTree().toObjectId());
+ assertThat(ttu.update()).isEqualTo(RefUpdate.Result.NEW);
}
}
@Test
+ @GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "false")
public void uploadPackAllRefsVisibleNoRefsMetaConfig() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- Util.allow(u.getConfig(), Permission.READ, admins, RefNames.REFS_CONFIG);
- Util.doNotInherit(u.getConfig(), Permission.READ, RefNames.REFS_CONFIG);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(admins))
+ .setExclusiveGroup(permissionKey(Permission.READ).ref(RefNames.REFS_CONFIG), true)
+ .update();
requestScopeOperations.setApiUser(user.id());
assertUploadPackRefs(
@@ -205,12 +255,46 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
+ }
+
+ @Test
+ @GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "true")
+ public void uploadPackAllRefsVisibleNoRefsMetaConfigSkipFullRefEval() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(admins))
+ .setExclusiveGroup(permissionKey(Permission.READ).ref(RefNames.REFS_CONFIG), true)
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ assertUploadPackRefs(
+ "HEAD",
+ psRef1,
+ metaRef1,
+ psRef2,
+ metaRef2,
+ psRef3,
+ metaRef3,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/heads/master",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag",
+ "refs/tags/tree-tag");
}
@Test
public void uploadPackAllRefsVisibleWithRefsMetaConfig() throws Exception {
- allow("refs/*", Permission.READ, REGISTERED_USERS);
- allow(RefNames.REFS_CONFIG, Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
assertUploadPackRefs(
"HEAD",
@@ -226,23 +310,46 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/heads/master",
RefNames.REFS_CONFIG,
"refs/tags/branch-tag",
- "refs/tags/master-tag");
+ "refs/tags/master-tag",
+ "refs/tags/tree-tag");
+ }
+
+ @Test
+ public void grantReadOnRefsTagsIsNoOp() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ assertUploadPackRefs(); // We expect no refs returned
}
@Test
public void uploadPackSubsetOfBranchesVisibleIncludingHead() throws Exception {
- allow("refs/heads/master", Permission.READ, REGISTERED_USERS);
- deny("refs/heads/branch", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(deny(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertUploadPackRefs(
"HEAD", psRef1, metaRef1, psRef3, metaRef3, "refs/heads/master", "refs/tags/master-tag");
+ // tree-tag is not visible because we don't look at trees reachable from
+ // refs
}
@Test
public void uploadPackSubsetOfBranchesVisibleNotIncludingHead() throws Exception {
- deny("refs/heads/master", Permission.READ, REGISTERED_USERS);
- allow("refs/heads/branch", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(deny(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertUploadPackRefs(
@@ -255,11 +362,16 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
// master branch is not visible but master-tag is reachable from branch
// (since PushOneCommit always bases changes on each other).
"refs/tags/master-tag");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
}
@Test
public void uploadPackSubsetOfBranchesVisibleWithEdit() throws Exception {
- allow("refs/heads/master", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
// Admin's edit is not visible.
requestScopeOperations.setApiUser(admin.id());
@@ -278,12 +390,17 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/heads/master",
"refs/tags/master-tag",
"refs/users/01/1000001/edit-" + cd3.getId() + "/1");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
}
@Test
public void uploadPackSubsetOfBranchesAndEditsVisibleWithViewPrivateChanges() throws Exception {
- allow("refs/heads/master", Permission.READ, REGISTERED_USERS);
- allow("refs/*", Permission.VIEW_PRIVATE_CHANGES, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Admin's edit on change3 is visible.
requestScopeOperations.setApiUser(admin.id());
@@ -306,13 +423,21 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/tags/master-tag",
"refs/users/00/1000000/edit-" + cd3.getId() + "/1",
"refs/users/01/1000001/edit-" + cd3.getId() + "/1");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
}
@Test
public void uploadPackSubsetOfRefsVisibleWithAccessDatabase() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
- deny("refs/heads/master", Permission.READ, REGISTERED_USERS);
- allow("refs/heads/branch", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(deny(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(admin.id());
gApi.changes().id(cd3.getId().get()).edit().create();
@@ -335,6 +460,7 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/tags/master-tag",
// All edits are visible due to accessDatabase capability.
"refs/users/00/1000000/edit-" + cd3.getId() + "/1");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
}
@Test
@@ -349,7 +475,11 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
private void uploadPackNoSearchingChangeCacheImpl() throws Exception {
- allow("refs/heads/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertRefs(
@@ -370,21 +500,29 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
+ // tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
}
@Test
public void uploadPackSequencesWithAccessDatabase() throws Exception {
assertRefs(allProjects, user, true);
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
assertRefs(allProjects, user, true, "refs/sequences/changes");
}
@Test
public void uploadPackAllRefsAreVisibleOrphanedTag() throws Exception {
- allow("refs/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Delete the pending change on 'branch' and 'branch' itself so that the tag gets orphaned
- gApi.changes().id(cd4.getId().id).delete();
+ gApi.changes().id(cd4.getId().get()).delete();
gApi.projects().name(project.get()).branch("refs/heads/branch").delete();
requestScopeOperations.setApiUser(user.id());
@@ -399,28 +537,517 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
metaRef3,
"refs/heads/master",
"refs/tags/branch-tag",
+ "refs/tags/master-tag",
+ "refs/tags/tree-tag");
+ }
+
+ @Test
+ public void uploadPackSubsetRefsVisibleOrphanedTagInvisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
+ // Create a tag for the pending change on 'branch' so that the tag is orphaned
+ try (Repository repo = repoManager.openRepository(project)) {
+ // change4-tag -> psRef4
+ RefUpdate ctu = repo.updateRef("refs/tags/change4-tag");
+ ctu.setExpectedOldObjectId(ObjectId.zeroId());
+ ctu.setNewObjectId(repo.exactRef(psRef4).getObjectId());
+ assertThat(ctu.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
+ requestScopeOperations.setApiUser(user.id());
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcMaster (c1 master)
+ // second ls-remote: rcMaster (c1 master) <- newchange1 (master-newtag)
+ @Test
+ public void uploadPackNewCommitOrphanTagInvisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ // rcMaster (c1 master)
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ PushOneCommit.Result r =
+ pushFactory.create(admin.newIdent(), testRepo).setParent(rcMaster).to("refs/for/master");
+ r.assertOkStatus();
+
+ // rcMaster (c1 master) <- newchange1 (master-newtag)
+ RefUpdate btu = repo.updateRef("refs/tags/master-newtag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(r.getCommit());
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
+ // second ls-remote: rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
+ @Test
+ public void uploadPackNewReachableTagVisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // c2 <- newcommit1 (branch)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/heads/branch");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ // c2 <- newcommit1 <- newcommit2 (branch)
+ r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
+ r.assertOkStatus();
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+
+ // c2 <- newcommit1 (branch-newtag) <- newcommit2 (branch)
+ RefUpdate btu = repo.updateRef("refs/tags/branch-newtag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(tagRc);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/branch-newtag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2) <- newcommit1 (branch)
+ // second ls-remote: rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
+ // third ls-remote: rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
+ @Test
+ public void uploadPackBranchFFNewTagOldBranchVisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (c2) <- newcommit1 (branch)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/heads/branch");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+
+ // rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
+ r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
+ r.assertOkStatus();
+
+ // rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
+ RefUpdate btu = repo.updateRef("refs/tags/branch-newtag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(tagRc);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/branch-newtag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2) <- newcommit1 (branch-oldtag) <- newcommit2 (branch)
+ // second ls-remote: rcBranch (c2 branch) <- newcommit1 (branch-oldtag)
+ @Test
+ public void uploadPackBranchRewindMakeTagUnreachableInVisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (c2) <- newcommit1 (branch)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/heads/branch");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ // rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
+ r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
+ r.assertOkStatus();
+ RevCommit bRc = r.getCommit();
+
+ // rcBranch (c2) <- newcommit1 (branch-oldtag) <- newcommit2 (branch)
+ RefUpdate btu = repo.updateRef("refs/tags/branch-oldtag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(tagRc);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/branch-oldtag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+
+ // rcBranch (c2 branch) <- newcommit1 (branch-oldtag) <- newcommit2
+ btu = repo.updateRef("refs/heads/branch");
+ btu.setExpectedOldObjectId(bRc);
+ btu.setNewObjectId(rcBranch);
+ btu.setForceUpdate(true);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.FORCED);
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ // See comment in subsetOfBranchesVisibleNotIncludingHead.
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag)
+ // second ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
+ @Test
+ public void uploadPackCreateBranchTagReachableVisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/new-branch").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (c2 branch) <- newcommit1 (branch-newtag)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/tags/new-tag");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ assertUploadPackRefs();
+
+ // rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
+ r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(tagRc)
+ .to("refs/heads/new-branch");
+ r.assertOkStatus();
+ }
+
+ assertUploadPackRefs(
+ "refs/heads/new-branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag",
+ "refs/tags/new-tag");
+ }
+
+ // first ls-remote: rcBranch (c2 branch) <- newcommit1 (updated-tag)
+ // second ls-remote: rcBranch (c2 branch updated-tag)
+ @Test
+ public void uploadPackTagUpdatedReachableVisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (c2 branch) <- newcommit1 (updated-tag)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/tags/updated-tag");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+
+ // rcBranch (c2 branch updated-tag)
+ RefUpdate btu = repo.updateRef("refs/tags/updated-tag");
+ btu.setExpectedOldObjectId(tagRc);
+ btu.setNewObjectId(rcBranch);
+ btu.setForceUpdate(true);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.FORCED);
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag",
+ "refs/tags/updated-tag");
+ }
+
+ // first ls-remote: rcBranch (c2 branch updated-tag)
+ // second ls-remote: rcBranch (c2 branch) <- newcommit1 (updated-tag)
+ @Test
+ public void uploadPackTagUpdatedUnreachableInvisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (c2 branch updated-tag)
+ RefUpdate btu = repo.updateRef("refs/tags/updated-tag");
+ btu.setExpectedOldObjectId(ObjectId.zeroId());
+ btu.setNewObjectId(rcBranch);
+ assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag",
+ "refs/tags/updated-tag");
+
+ // rcBranch (c2 branch) <- newcommit1 (updated-tag)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/tags/updated-tag");
+ r.assertOkStatus();
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2 branch branch-tag)
+ // second ls-remote: rcBranch (c2 branch)
+ @Test
+ public void uploadPackTagDeleted() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .add(allow(Permission.DELETE).ref("refs/tags/branch-tag").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/tags/branch-tag").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ // rcBranch (c2 branch branch-tag)
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/tags/master-tag");
+
+ // rcBranch (c2 branch)
+ try (Repository repo = repoManager.openRepository(project)) {
+ RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
+ btu.setExpectedOldObjectId(rcBranch);
+ btu.setNewObjectId(ObjectId.zeroId());
+ btu.setForceUpdate(true);
+ assertThat(btu.delete()).isEqualTo(RefUpdate.Result.FORCED);
+ }
+
+ assertUploadPackRefs(
+ psRef2, metaRef2, psRef4, metaRef4, "refs/heads/branch", "refs/tags/master-tag");
+ }
+
+ // first ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
+ // second ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag)
+ @Test
+ public void uploadPackBranchDeleteTagUnreachableInvisible() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .add(allow(Permission.READ).ref("refs/heads/new-branch").group(REGISTERED_USERS))
+ .add(allow(Permission.DELETE).ref("refs/heads/new-branch").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // rcBranch (branch) <- newcommit1 (new-tag)
+ PushOneCommit.Result r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(rcBranch)
+ .to("refs/tags/new-tag");
+ r.assertOkStatus();
+ RevCommit tagRc = r.getCommit();
+
+ // rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
+ r =
+ pushFactory
+ .create(admin.newIdent(), testRepo)
+ .setParent(tagRc)
+ .to("refs/heads/new-branch");
+ r.assertOkStatus();
+ }
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
+ "refs/heads/new-branch",
+ "refs/tags/new-tag",
+ "refs/tags/master-tag");
+
+ // rcBranch (c2 branch) <- newcommit1 (new-tag)
+ gApi.projects().name(project.get()).branch("refs/heads/new-branch").delete();
+
+ assertUploadPackRefs(
+ psRef2,
+ metaRef2,
+ psRef4,
+ metaRef4,
+ "refs/heads/branch",
+ "refs/tags/branch-tag",
"refs/tags/master-tag");
}
@Test
public void receivePackListsOpenChangesAsAdditionalHaves() throws Exception {
- ReceiveCommitsAdvertiseRefsHook.Result r = getReceivePackRefs();
+ TestRefAdvertiser.Result r = getReceivePackRefs();
assertThat(r.allRefs().keySet())
.containsExactly(
// meta refs are excluded
- "HEAD",
"refs/heads/branch",
"refs/heads/master",
"refs/meta/config",
"refs/tags/branch-tag",
- "refs/tags/master-tag");
+ "refs/tags/master-tag",
+ "refs/tags/tree-tag");
assertThat(r.additionalHaves()).containsExactly(obj(cd3, 1), obj(cd4, 1));
}
@Test
public void receivePackRespectsVisibilityOfOpenChanges() throws Exception {
- allow("refs/heads/master", Permission.READ, REGISTERED_USERS);
- deny("refs/heads/branch", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .add(deny(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd3, 1));
@@ -442,7 +1069,7 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
TestRepository<Repository> tr = new TestRepository<>(repo)) {
String subject = "Subject for missing commit";
Change c = new Change(cd3.change());
- PatchSet.Id psId = new PatchSet.Id(cd3.getId(), 2);
+ PatchSet.Id psId = PatchSet.id(cd3.getId(), 2);
c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
PersonIdent committer = serverIdent.get();
@@ -483,7 +1110,11 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitUserBranchesOfOtherUsers() throws Exception {
- allow(allUsersName, RefNames.REFS_USERS + "*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
try (Git git = userTestRepository.git()) {
assertThat(getUserRefs(git))
@@ -493,7 +1124,10 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesIncludeAllUserBranchesWithAccessDatabase() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
try (Git git = userTestRepository.git()) {
assertThat(getUserRefs(git))
@@ -515,7 +1149,11 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitGroupBranchesOfNonOwnedGroups() throws Exception {
- allow(allUsersName, RefNames.REFS_GROUPS + "*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
AccountGroup.UUID users = createGroup("Users", admins, user);
AccountGroup.UUID foos = createGroup("Foos", users);
AccountGroup.UUID bars = createSelfOwnedGroup("Bars", user);
@@ -528,7 +1166,10 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesIncludeAllGroupBranchesWithAccessDatabase() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
AccountGroup.UUID users = createGroup("Users", admins);
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
try (Git git = userTestRepository.git()) {
@@ -542,8 +1183,15 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesIncludeAllGroupBranchesForAdmins() throws Exception {
- allow(allUsersName, RefNames.REFS_GROUPS + "*", Permission.READ, REGISTERED_USERS);
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ADMINISTRATE_SERVER);
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .update();
AccountGroup.UUID users = createGroup("Users", admins);
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
try (Git git = userTestRepository.git()) {
@@ -557,7 +1205,11 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitNoteDbNotesBranches() throws Exception {
- allow(allUsersName, RefNames.REFS + "*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS + "*").group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
try (Git git = userTestRepository.git()) {
assertThat(getRefs(git)).containsNoneOf(RefNames.REFS_EXTERNAL_IDS, RefNames.REFS_GROUPNAMES);
@@ -566,11 +1218,15 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitPrivateChangesOfOtherUsers() throws Exception {
- allow("refs/heads/master", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(project, user);
try (Git git = userTestRepository.git()) {
- String change3RefName = cd3.currentPatchSet().getRefName();
+ String change3RefName = cd3.currentPatchSet().refName();
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
@@ -583,11 +1239,15 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
assume()
.that(baseConfig.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true))
.isTrue();
- allow("refs/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(project, user);
try (Git git = userTestRepository.git()) {
- String change3RefName = cd3.currentPatchSet().getRefName();
+ String change3RefName = cd3.currentPatchSet().refName();
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
@@ -599,11 +1259,15 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "false")
public void advertisedReferencesOmitPrivateChangesOfOtherUsersWhenShortcutDisabled()
throws Exception {
- allow("refs/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
TestRepository<?> userTestRepository = cloneProject(project, user);
try (Git git = userTestRepository.git()) {
- String change3RefName = cd3.currentPatchSet().getRefName();
+ String change3RefName = cd3.currentPatchSet().refName();
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
@@ -613,8 +1277,16 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitDraftCommentRefsOfOtherUsers() throws Exception {
- allow(project, "refs/*", Permission.READ, REGISTERED_USERS);
- allow(allUsersName, "refs/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
DraftInput draftInput = new DraftInput();
@@ -633,8 +1305,16 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void advertisedReferencesOmitStarredChangesRefsOfOtherUsers() throws Exception {
- allow(project, "refs/*", Permission.READ, REGISTERED_USERS);
- allow(allUsersName, "refs/*", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.accounts().self().starChange(cd3.getId().toString());
@@ -649,7 +1329,10 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
@Test
public void hideMetadata() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
// create change
TestRepository<?> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_USERS_SELF + ":userRef");
@@ -662,7 +1345,6 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
List<String> expectedNonMetaRefs =
ImmutableList.of(
- RefNames.REFS_USERS_SELF,
RefNames.refsUsers(admin.id()),
RefNames.refsUsers(user.id()),
RefNames.REFS_EXTERNAL_IDS,
@@ -719,7 +1401,7 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
private List<String> getRefs(Git git) throws Exception {
- return getRefs(git, Predicates.alwaysTrue());
+ return getRefs(git, x -> true);
}
private List<String> getUserRefs(Git git) throws Exception {
@@ -760,11 +1442,16 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
}
- private ReceiveCommitsAdvertiseRefsHook.Result getReceivePackRefs() throws Exception {
- ReceiveCommitsAdvertiseRefsHook hook =
- new ReceiveCommitsAdvertiseRefsHook(queryProvider, project);
+ private TestRefAdvertiser.Result getReceivePackRefs() throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
- return hook.advertiseRefs(getAllRefs(repo));
+ AdvertiseRefsHook adv =
+ ReceiveCommitsAdvertiseRefsHookChain.createForTest(
+ queryProvider, project, identifiedUserFactory.create(admin.id()));
+ ReceivePack rp = new ReceivePack(repo);
+ rp.setAdvertiseRefsHook(adv);
+ TestRefAdvertiser advertiser = new TestRefAdvertiser(repo);
+ rp.sendAdvertisedRefs(advertiser);
+ return advertiser.result();
}
}
@@ -773,10 +1460,10 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
}
private static ObjectId obj(ChangeData cd, int psNum) throws Exception {
- PatchSet.Id psId = new PatchSet.Id(cd.getId(), psNum);
+ PatchSet.Id psId = PatchSet.id(cd.getId(), psNum);
PatchSet ps = cd.patchSet(psId);
assertWithMessage("%s not found in %s", psId, cd.patchSets()).that(ps).isNotNull();
- return ObjectId.fromString(ps.getRevision().get());
+ return ps.commitId();
}
private AccountGroup.UUID createSelfOwnedGroup(String name, TestAccount... members)
@@ -792,7 +1479,7 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
groupInput.ownerId = ownerGroup != null ? ownerGroup.get() : null;
groupInput.members =
Arrays.stream(members).map(m -> String.valueOf(m.id().get())).collect(toList());
- return new AccountGroup.UUID(gApi.groups().create(groupInput).get().id);
+ return AccountGroup.uuid(gApi.groups().create(groupInput).get().id);
}
private static Map<String, Ref> getAllRefs(Repository repo) throws IOException {
diff --git a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
index 9b823b7ad2..876e34264b 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefOperationValidationIT.java
@@ -15,8 +15,10 @@
package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
@@ -24,11 +26,12 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.events.RefReceivedEvent;
import com.google.gerrit.server.git.validators.RefOperationValidationListener;
@@ -47,17 +50,16 @@ import org.junit.Test;
public class RefOperationValidationIT extends AbstractDaemonTest {
private static final String TEST_REF = "refs/heads/protected";
- @Inject DynamicSet<RefOperationValidationListener> validators;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
- private class TestRefValidator implements RefOperationValidationListener, AutoCloseable {
+ private static class TestRefValidator implements RefOperationValidationListener {
private final ReceiveCommand.Type rejectType;
private final String rejectRef;
- private final RegistrationHandle handle;
public TestRefValidator(ReceiveCommand.Type rejectType) {
this.rejectType = rejectType;
this.rejectRef = TEST_REF;
- this.handle = validators.add("test-" + rejectType.name(), this);
}
@Override
@@ -69,27 +71,35 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
}
return Collections.emptyList();
}
+ }
- @Override
- public void close() throws Exception {
- handle.remove();
- }
+ private Registration testValidator(ReceiveCommand.Type rejectType) {
+ return extensionRegistry.newRegistration().add(new TestRefValidator(rejectType));
}
@Test
public void rejectRefCreation() throws Exception {
- try (TestRefValidator validator = new TestRefValidator(CREATE)) {
- gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- assert_().fail("expected exception");
- } catch (RestApiException expected) {
+ try (Registration registration = testValidator(CREATE)) {
+ RestApiException expected =
+ assertThrows(
+ RestApiException.class,
+ () -> gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput()));
assertThat(expected).hasMessageThat().contains(CREATE.name());
}
}
+ private void grant(String permission) {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(permission).ref("refs/*").group(REGISTERED_USERS).force(true))
+ .update();
+ }
+
@Test
public void rejectRefCreationByPush() throws Exception {
- try (TestRefValidator validator = new TestRefValidator(CREATE)) {
- grant(project, "refs/*", Permission.PUSH, true);
+ try (Registration registration = testValidator(CREATE)) {
+ grant(Permission.PUSH);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to("refs/heads/master");
@@ -102,10 +112,11 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
@Test
public void rejectRefDeletion() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- try (TestRefValidator validator = new TestRefValidator(DELETE)) {
- gApi.projects().name(project.get()).branch(TEST_REF).delete();
- assert_().fail("expected exception");
- } catch (RestApiException expected) {
+ try (Registration registration = testValidator(DELETE)) {
+ RestApiException expected =
+ assertThrows(
+ RestApiException.class,
+ () -> gApi.projects().name(project.get()).branch(TEST_REF).delete());
assertThat(expected).hasMessageThat().contains(DELETE.name());
}
}
@@ -113,8 +124,8 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
@Test
public void rejectRefDeletionByPush() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- grant(project, "refs/*", Permission.DELETE, true);
- try (TestRefValidator validator = new TestRefValidator(DELETE)) {
+ grant(Permission.DELETE);
+ try (Registration registration = testValidator(DELETE)) {
PushResult result = deleteRef(testRepo, TEST_REF);
RemoteRefUpdate refUpdate = result.getRemoteUpdate(TEST_REF);
assertThat(refUpdate.getMessage()).contains(DELETE.name());
@@ -124,8 +135,8 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
@Test
public void rejectRefUpdateFastForward() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- try (TestRefValidator validator = new TestRefValidator(UPDATE)) {
- grant(project, "refs/*", Permission.PUSH, true);
+ try (Registration registration = testValidator(UPDATE)) {
+ grant(Permission.PUSH);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to(TEST_REF);
@@ -136,9 +147,9 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
@Test
public void rejectRefUpdateNonFastForward() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- try (TestRefValidator validator = new TestRefValidator(UPDATE_NONFASTFORWARD)) {
+ try (Registration registration = testValidator(UPDATE_NONFASTFORWARD)) {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
- grant(project, "refs/*", Permission.PUSH, true);
+ grant(Permission.PUSH);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to(TEST_REF);
@@ -161,8 +172,8 @@ public class RefOperationValidationIT extends AbstractDaemonTest {
public void rejectRefUpdateNonFastForwardToExistingCommit() throws Exception {
gApi.projects().name(project.get()).branch(TEST_REF).create(new BranchInput());
- try (TestRefValidator validator = new TestRefValidator(UPDATE_NONFASTFORWARD)) {
- grant(project, "refs/*", Permission.PUSH, true);
+ try (Registration registration = testValidator(UPDATE_NONFASTFORWARD)) {
+ grant(Permission.PUSH);
PushOneCommit push1 =
pushFactory.create(admin.newIdent(), testRepo, "change1", "a.txt", "content");
PushOneCommit.Result r1 = push1.to("refs/heads/master");
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index e090fae228..09da6284c7 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -16,15 +16,16 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
-import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.TestTimeUtil;
+import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
@@ -45,6 +46,8 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
return submitWholeTopicEnabledConfig();
}
+ @Inject private ProjectOperations projectOperations;
+
@Test
@GerritConfig(name = "submodule.enableSuperProjectSubscriptions", value = "false")
public void testSubscriptionWithoutGlobalServerSetting() throws Exception {
@@ -108,7 +111,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
public void subscriptionWildcardACLForMissingProject() throws Exception {
allowMatchingSubmoduleSubscription(
- subKey, "refs/heads/*", new Project.NameKey("not-existing-super-project"), "refs/heads/*");
+ subKey, "refs/heads/*", Project.nameKey("not-existing-super-project"), "refs/heads/*");
pushChangeTo(subRepo, "master");
}
@@ -379,10 +382,7 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
@Test
public void subscriptionFailOnWrongProjectACL() throws Exception {
allowMatchingSubmoduleSubscription(
- subKey,
- "refs/heads/master",
- new Project.NameKey("wrong-super-project"),
- "refs/heads/master");
+ subKey, "refs/heads/master", Project.nameKey("wrong-super-project"), "refs/heads/master");
pushChangeTo(subRepo, "master");
createSubmoduleSubscription(superRepo, "master", subKey, "master");
@@ -479,127 +479,107 @@ public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
}
@Test
+ @UseClockStep
public void superRepoCommitHasSameAuthorAsSubmoduleCommit() throws Exception {
// Make sure that the commit is created at an earlier timestamp than the submit timestamp.
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- try {
- allowMatchingSubmoduleSubscription(
- subKey, "refs/heads/master", superKey, "refs/heads/master");
- createSubmoduleSubscription(superRepo, "master", subKey, "master");
-
- PushOneCommit.Result pushResult =
- createChange(subRepo, "refs/heads/master", "Change", "a.txt", "some content", null);
- approve(pushResult.getChangeId());
- gApi.changes().id(pushResult.getChangeId()).current().submit();
-
- // Expect that the author name/email is preserved for the superRepo commit, but a new author
- // timestamp is used.
- PersonIdent authorIdent = getAuthor(superRepo, "master");
- assertThat(authorIdent.getName()).isEqualTo(admin.fullName());
- assertThat(authorIdent.getEmailAddress()).isEqualTo(admin.email());
- assertThat(authorIdent.getWhen())
- .isGreaterThan(pushResult.getCommit().getAuthorIdent().getWhen());
- } finally {
- TestTimeUtil.useSystemTime();
- }
+ allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
+ createSubmoduleSubscription(superRepo, "master", subKey, "master");
+
+ PushOneCommit.Result pushResult =
+ createChange(subRepo, "refs/heads/master", "Change", "a.txt", "some content", null);
+ approve(pushResult.getChangeId());
+ gApi.changes().id(pushResult.getChangeId()).current().submit();
+
+ // Expect that the author name/email is preserved for the superRepo commit, but a new author
+ // timestamp is used.
+ PersonIdent authorIdent = getAuthor(superRepo, "master");
+ assertThat(authorIdent.getName()).isEqualTo(admin.fullName());
+ assertThat(authorIdent.getEmailAddress()).isEqualTo(admin.email());
+ assertThat(authorIdent.getWhen())
+ .isGreaterThan(pushResult.getCommit().getAuthorIdent().getWhen());
}
@Test
+ @UseClockStep
public void superRepoCommitHasSameAuthorAsSubmoduleCommits() throws Exception {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
- // Make sure that the commits are created at different timestamps and that the submit timestamp
- // is afterwards.
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- try {
-
- Project.NameKey proj2 = createProjectForPush(getSubmitType());
-
- TestRepository<?> subRepo2 = cloneProject(proj2);
- allowMatchingSubmoduleSubscription(
- subKey, "refs/heads/master", superKey, "refs/heads/master");
- allowMatchingSubmoduleSubscription(proj2, "refs/heads/master", superKey, "refs/heads/master");
-
- Config config = new Config();
- prepareSubmoduleConfigEntry(config, subKey, subKey, "master");
- prepareSubmoduleConfigEntry(config, proj2, proj2, "master");
- pushSubmoduleConfig(superRepo, "master", config);
-
- String topic = "foo";
-
- PushOneCommit.Result pushResult1 =
- createChange(subRepo, "refs/heads/master", "Change 1", "a.txt", "some content", topic);
- approve(pushResult1.getChangeId());
-
- PushOneCommit.Result pushResult2 =
- createChange(subRepo2, "refs/heads/master", "Change 2", "b.txt", "other content", topic);
- approve(pushResult2.getChangeId());
-
- // Submit the topic, 2 changes with the same author.
- gApi.changes().id(pushResult1.getChangeId()).current().submit();
-
- // Expect that the author name/email is preserved for the superRepo commit, but a new author
- // timestamp is used.
- PersonIdent authorIdent = getAuthor(superRepo, "master");
- assertThat(authorIdent.getName()).isEqualTo(admin.fullName());
- assertThat(authorIdent.getEmailAddress()).isEqualTo(admin.email());
- assertThat(authorIdent.getWhen())
- .isGreaterThan(pushResult1.getCommit().getAuthorIdent().getWhen());
- assertThat(authorIdent.getWhen())
- .isGreaterThan(pushResult2.getCommit().getAuthorIdent().getWhen());
- } finally {
- TestTimeUtil.useSystemTime();
- }
+ Project.NameKey proj2 = createProjectForPush(getSubmitType());
+
+ TestRepository<?> subRepo2 = cloneProject(proj2);
+ allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
+ allowMatchingSubmoduleSubscription(proj2, "refs/heads/master", superKey, "refs/heads/master");
+
+ Config config = new Config();
+ prepareSubmoduleConfigEntry(config, subKey, subKey, "master");
+ prepareSubmoduleConfigEntry(config, proj2, proj2, "master");
+ pushSubmoduleConfig(superRepo, "master", config);
+
+ String topic = "foo";
+
+ PushOneCommit.Result pushResult1 =
+ createChange(subRepo, "refs/heads/master", "Change 1", "a.txt", "some content", topic);
+ approve(pushResult1.getChangeId());
+
+ PushOneCommit.Result pushResult2 =
+ createChange(subRepo2, "refs/heads/master", "Change 2", "b.txt", "other content", topic);
+ approve(pushResult2.getChangeId());
+
+ // Submit the topic, 2 changes with the same author.
+ gApi.changes().id(pushResult1.getChangeId()).current().submit();
+
+ // Expect that the author name/email is preserved for the superRepo commit, but a new author
+ // timestamp is used.
+ PersonIdent authorIdent = getAuthor(superRepo, "master");
+ assertThat(authorIdent.getName()).isEqualTo(admin.fullName());
+ assertThat(authorIdent.getEmailAddress()).isEqualTo(admin.email());
+ assertThat(authorIdent.getWhen())
+ .isGreaterThan(pushResult1.getCommit().getAuthorIdent().getWhen());
+ assertThat(authorIdent.getWhen())
+ .isGreaterThan(pushResult2.getCommit().getAuthorIdent().getWhen());
}
@Test
+ @UseClockStep
public void superRepoCommitHasGerritAsAuthorIfAuthorsOfSubmoduleCommitsDiffer() throws Exception {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
- // Make sure that the commits are created at different timestamps and that the submit timestamp
- // is afterwards.
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- try {
- Project.NameKey proj2 = createProjectForPush(getSubmitType());
- TestRepository<InMemoryRepository> repo2 = cloneProject(proj2, user);
-
- allowMatchingSubmoduleSubscription(
- subKey, "refs/heads/master", superKey, "refs/heads/master");
- allowMatchingSubmoduleSubscription(proj2, "refs/heads/master", superKey, "refs/heads/master");
-
- Config config = new Config();
- prepareSubmoduleConfigEntry(config, subKey, subKey, "master");
- prepareSubmoduleConfigEntry(config, proj2, proj2, "master");
- pushSubmoduleConfig(superRepo, "master", config);
-
- String topic = "foo";
-
- // Create change as admin.
- PushOneCommit.Result pushResult1 =
- createChange(subRepo, "refs/heads/master", "Change 1", "a.txt", "some content", topic);
- approve(pushResult1.getChangeId());
-
- // Create change as user.
- PushOneCommit push =
- pushFactory.create(user.newIdent(), repo2, "Change 2", "b.txt", "other content");
- PushOneCommit.Result pushResult2 = push.to("refs/for/master/" + name(topic));
- approve(pushResult2.getChangeId());
-
- // Submit the topic, 2 changes with the different author.
- gApi.changes().id(pushResult1.getChangeId()).current().submit();
-
- // Expect that the Gerrit server identity is chosen as author for the superRepo commit and a
- // new author timestamp is used.
- PersonIdent authorIdent = getAuthor(superRepo, "master");
- assertThat(authorIdent.getName()).isEqualTo(serverIdent.get().getName());
- assertThat(authorIdent.getEmailAddress()).isEqualTo(serverIdent.get().getEmailAddress());
- assertThat(authorIdent.getWhen())
- .isGreaterThan(pushResult1.getCommit().getAuthorIdent().getWhen());
- assertThat(authorIdent.getWhen())
- .isGreaterThan(pushResult2.getCommit().getAuthorIdent().getWhen());
- } finally {
- TestTimeUtil.useSystemTime();
- }
+ Project.NameKey proj2 = createProjectForPush(getSubmitType());
+ TestRepository<InMemoryRepository> repo2 = cloneProject(proj2, user);
+
+ allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
+ allowMatchingSubmoduleSubscription(proj2, "refs/heads/master", superKey, "refs/heads/master");
+
+ Config config = new Config();
+ prepareSubmoduleConfigEntry(config, subKey, subKey, "master");
+ prepareSubmoduleConfigEntry(config, proj2, proj2, "master");
+ pushSubmoduleConfig(superRepo, "master", config);
+
+ String topic = "foo";
+
+ // Create change as admin.
+ PushOneCommit.Result pushResult1 =
+ createChange(subRepo, "refs/heads/master", "Change 1", "a.txt", "some content", topic);
+ approve(pushResult1.getChangeId());
+
+ // Create change as user.
+ PushOneCommit push =
+ pushFactory.create(user.newIdent(), repo2, "Change 2", "b.txt", "other content");
+ PushOneCommit.Result pushResult2 = push.to("refs/for/master/" + name(topic));
+ approve(pushResult2.getChangeId());
+
+ // Submit the topic, 2 changes with the different author.
+ gApi.changes().id(pushResult1.getChangeId()).current().submit();
+
+ // Expect that the Gerrit server identity is chosen as author for the superRepo commit and a
+ // new author timestamp is used.
+ PersonIdent authorIdent = getAuthor(superRepo, "master");
+ assertThat(authorIdent.getName()).isEqualTo(serverIdent.get().getName());
+ assertThat(authorIdent.getEmailAddress()).isEqualTo(serverIdent.get().getEmailAddress());
+ assertThat(authorIdent.getWhen())
+ .isGreaterThan(pushResult1.getCommit().getAuthorIdent().getWhen());
+ assertThat(authorIdent.getWhen())
+ .isGreaterThan(pushResult2.getCommit().getAuthorIdent().getWhen());
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index dc84d13fc6..283c95fd6c 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -17,17 +17,23 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.change.TestSubmitInput;
import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
import java.util.ArrayDeque;
import java.util.Map;
import org.apache.commons.lang.RandomStringUtils;
@@ -67,6 +73,8 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
return submitByRebaseIfNecessaryConfig();
}
+ @Inject private ProjectOperations projectOperations;
+
@Test
public void subscriptionUpdateOfManyChanges() throws Exception {
allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
@@ -136,7 +144,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
gApi.changes().id(id2).current().review(ReviewInput.approve());
gApi.changes().id(id3).current().review(ReviewInput.approve());
- Map<Branch.NameKey, ObjectId> preview = fetchFromSubmitPreview(id1);
+ Map<BranchNameKey, ObjectId> preview = fetchFromSubmitPreview(id1);
gApi.changes().id(id1).current().submit();
ObjectId subRepoId =
subRepo
@@ -152,8 +160,8 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
// As the submodules have changed commits, the superproject tree will be
// different, so we cannot directly compare the trees here, so make
// assumptions only about the changed branches:
- assertThat(preview).containsKey(new Branch.NameKey(superKey, "refs/heads/master"));
- assertThat(preview).containsKey(new Branch.NameKey(subKey, "refs/heads/master"));
+ assertThat(preview).containsKey(BranchNameKey.create(superKey, "refs/heads/master"));
+ assertThat(preview).containsKey(BranchNameKey.create(subKey, "refs/heads/master"));
if ((getSubmitType() == SubmitType.CHERRY_PICK)
|| (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
@@ -282,8 +290,12 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
.name(prefix + "sub" + i)
.submitType(getSubmitType())
.create();
- grant(subKey[i], "refs/heads/*", Permission.PUSH);
- grant(subKey[i], "refs/for/refs/heads/*", Permission.SUBMIT);
+ projectOperations
+ .project(subKey[i])
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(adminGroupUuid()))
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(adminGroupUuid()))
+ .update();
sub[i] = cloneProject(subKey[i]);
}
@@ -393,7 +405,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
gApi.changes().id(subChangeId).current().submit();
expectToHaveSubmoduleState(superRepo, "master", subKey, subRepo, "master");
- RevCommit superHead = getRemoteHead(superKey, "master");
+ RevCommit superHead = projectOperations.project(superKey).getHead("master");
assertThat(superHead.getShortMessage()).contains("some message");
assertThat(superHead.getId()).isNotEqualTo(superId);
}
@@ -427,7 +439,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
gApi.changes().id(subChangeId).current().submit();
- RevCommit superHead = getRemoteHead(superKey, "master");
+ RevCommit superHead = projectOperations.project(superKey).getHead("master");
assertThat(superHead.getShortMessage()).isEqualTo("some message");
assertThat(superHead.getId()).isEqualTo(superId);
}
@@ -614,7 +626,7 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
expectToHaveSubmoduleState(topRepo, "master", botKey, bottomRepo, "master");
}
- private String prepareBranchCircularSubscription() throws Exception {
+ private void testBranchCircularSubscription(ThrowingConsumer<String> apiCall) throws Exception {
Project.NameKey topKey = createProjectForPush(getSubmitType());
Project.NameKey midKey = createProjectForPush(getSubmitType());
Project.NameKey botKey = createProjectForPush(getSubmitType());
@@ -634,23 +646,24 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
String changeId = getChangeId(bottomRepo, bottomMasterHead).get();
approve(changeId);
- exception.expectMessage("Branch level circular subscriptions detected");
- exception.expectMessage(topKey.get() + ",refs/heads/master");
- exception.expectMessage(midKey.get() + ",refs/heads/master");
- exception.expectMessage(botKey.get() + ",refs/heads/master");
- return changeId;
+
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> apiCall.accept(changeId));
+ assertThat(thrown).hasMessageThat().contains("Branch level circular subscriptions detected");
+ assertThat(thrown).hasMessageThat().contains(topKey.get() + ",refs/heads/master");
+ assertThat(thrown).hasMessageThat().contains(midKey.get() + ",refs/heads/master");
+ assertThat(thrown).hasMessageThat().contains(botKey.get() + ",refs/heads/master");
}
@Test
public void branchCircularSubscription() throws Exception {
- String changeId = prepareBranchCircularSubscription();
- gApi.changes().id(changeId).current().submit();
+ testBranchCircularSubscription(changeId -> gApi.changes().id(changeId).current().submit());
}
@Test
public void branchCircularSubscriptionPreview() throws Exception {
- String changeId = prepareBranchCircularSubscription();
- gApi.changes().id(changeId).current().submitPreview();
+ testBranchCircularSubscription(
+ changeId -> gApi.changes().id(changeId).current().submitPreview());
}
@Test
@@ -672,10 +685,13 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
approve(getChangeId(subRepo, subMasterHead).get());
approve(getChangeId(superRepo, superDevHead).get());
- exception.expectMessage("Project level circular subscriptions detected");
- exception.expectMessage(subKey.get());
- exception.expectMessage(superKey.get());
- gApi.changes().id(getChangeId(subRepo, subMasterHead).get()).current().submit();
+ Throwable thrown =
+ assertThrows(
+ Throwable.class,
+ () -> gApi.changes().id(getChangeId(subRepo, subMasterHead).get()).current().submit());
+ assertThat(thrown).hasMessageThat().contains("Project level circular subscriptions detected");
+ assertThat(thrown).hasMessageThat().contains(subKey.get());
+ assertThat(thrown).hasMessageThat().contains(superKey.get());
}
@Test
@@ -739,13 +755,13 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
approve(getChangeId(repoB, bDevHead).get());
gApi.changes().id(getChangeId(repoA, aDevHead).get()).current().submit();
- assertThat(getRemoteHead(keyA, "refs/heads/master").getShortMessage())
+ assertThat(projectOperations.project(keyA).getHead("refs/heads/master").getShortMessage())
.contains("some message in a master.txt");
- assertThat(getRemoteHead(keyA, "refs/heads/dev").getShortMessage())
+ assertThat(projectOperations.project(keyA).getHead("refs/heads/dev").getShortMessage())
.contains("some message in a dev.txt");
- assertThat(getRemoteHead(keyB, "refs/heads/master").getShortMessage())
+ assertThat(projectOperations.project(keyB).getHead("refs/heads/master").getShortMessage())
.contains("some message in b master.txt");
- assertThat(getRemoteHead(keyB, "refs/heads/dev").getShortMessage())
+ assertThat(projectOperations.project(keyB).getHead("refs/heads/dev").getShortMessage())
.contains("some message in b dev.txt");
}
@@ -838,13 +854,13 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
sub1.git().fetch().call();
RevWalk rw1 = sub1.getRevWalk();
- RevCommit master1 = rw1.parseCommit(getRemoteHead(subKey1, "master"));
+ RevCommit master1 = rw1.parseCommit(projectOperations.project(subKey1).getHead("master"));
RevCommit change1Ps = parseCurrentRevision(rw1, changeId1);
assertThat(rw1.isMergedInto(change1Ps, master1)).isTrue();
sub2.git().fetch().call();
RevWalk rw2 = sub2.getRevWalk();
- RevCommit master2 = rw2.parseCommit(getRemoteHead(subKey2, "master"));
+ RevCommit master2 = rw2.parseCommit(projectOperations.project(subKey2).getHead("master"));
RevCommit change2Ps = parseCurrentRevision(rw2, changeId2);
assertThat(rw2.isMergedInto(change2Ps, master2)).isTrue();
@@ -898,6 +914,6 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT extends AbstractSubmoduleSu
}
private Project.NameKey nameKey(String s) {
- return new Project.NameKey(name(s));
+ return Project.nameKey(name(s));
}
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
index c07d512946..cad0b83942 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/AbstractReindexTests.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.pgm;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static com.google.common.truth.StreamSubject.streams;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
@@ -26,11 +27,11 @@ import com.google.common.io.RecursiveDeleteOption;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.StandaloneSiteTest;
import com.google.gerrit.acceptance.pgm.IndexUpgradeController.UpgradeAttempt;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.index.GerritIndexStatus;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
@@ -73,13 +74,13 @@ public abstract class AbstractReindexTests extends StandaloneSiteTest {
.containsExactly(changeId);
// Query account index
assertThat(gApi.accounts().query("admin").get().stream().map(a -> a._accountId))
- .containsExactly(adminId.get());
+ .containsExactly(admin.id().get());
// Query group index
assertThat(
gApi.groups().query("Group").withOption(MEMBERS).get().stream()
.flatMap(g -> g.members.stream())
.map(a -> a._accountId))
- .containsExactly(adminId.get());
+ .containsExactly(admin.id().get());
// Query project index
assertThat(gApi.projects().query(project.get()).get().stream().map(p -> p.name))
.containsExactly(project.get());
@@ -195,7 +196,7 @@ public abstract class AbstractReindexTests extends StandaloneSiteTest {
// Updating and searching old schema version works.
Provider<InternalChangeQuery> queryProvider =
ctx.getInjector().getProvider(InternalChangeQuery.class);
- assertThat(queryProvider.get().byKey(new Change.Key(changeId))).hasSize(1);
+ assertThat(queryProvider.get().byKey(Change.key(changeId))).hasSize(1);
assertThat(queryProvider.get().byTopicOpen("topic1")).isEmpty();
GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
@@ -223,7 +224,7 @@ public abstract class AbstractReindexTests extends StandaloneSiteTest {
}
private void setUpChange() throws Exception {
- project = new Project.NameKey("reindex-project-test");
+ project = Project.nameKey("reindex-project-test");
try (ServerContext ctx = startServer()) {
configureIndex(ctx.getInjector());
GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
@@ -240,7 +241,7 @@ public abstract class AbstractReindexTests extends StandaloneSiteTest {
}
private void enableSlaveMode() throws Exception {
- updateConfig(config -> config.setBoolean("container", null, "slave", true));
+ updateConfig(config -> config.setBoolean("container", null, "replica", true));
}
private void updateConfig(Consumer<Config> configConsumer) throws Exception {
@@ -255,30 +256,31 @@ public abstract class AbstractReindexTests extends StandaloneSiteTest {
}
private void assertSearchVersion(ServerContext ctx, int expected) {
- assertThat(
+ assertWithMessage("search version")
+ .that(
ctx.getInjector()
.getInstance(ChangeIndexCollection.class)
.getSearchIndex()
.getSchema()
.getVersion())
- .named("search version")
.isEqualTo(expected);
}
private void assertWriteVersions(ServerContext ctx, Integer... expected) {
- assertThat(
+ assertWithMessage("write versions")
+ .about(streams())
+ .that(
ctx.getInjector().getInstance(ChangeIndexCollection.class).getWriteIndexes().stream()
.map(i -> i.getSchema().getVersion()))
- .named("write versions")
.containsExactlyElementsIn(ImmutableSet.copyOf(expected));
}
private void assertReady(int expectedReady) throws Exception {
Set<Integer> allVersions = ChangeSchemaDefinitions.INSTANCE.getSchemas().keySet();
GerritIndexStatus status = new GerritIndexStatus(sitePaths);
- assertThat(
+ assertWithMessage("ready state for index versions")
+ .that(
allVersions.stream().collect(toImmutableMap(v -> v, v -> status.getReady(CHANGES, v))))
- .named("ready state for index versions")
.isEqualTo(allVersions.stream().collect(toImmutableMap(v -> v, v -> v == expectedReady)));
}
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index 7b99a55e57..f23cc10cb8 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -25,11 +25,6 @@ import org.eclipse.jgit.lib.Config;
public class ElasticReindexIT extends AbstractReindexTests {
@ConfigSuite.Default
- public static Config elasticsearchV6() {
- return getConfig(ElasticVersion.V6_8);
- }
-
- @ConfigSuite.Config
public static Config elasticsearchV7() {
return getConfig(ElasticVersion.V7_8);
}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/InitIT.java b/javatests/com/google/gerrit/acceptance/pgm/InitIT.java
index 7cc47c3fa8..4caee6482c 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/InitIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/InitIT.java
@@ -14,16 +14,16 @@
package com.google.gerrit.acceptance.pgm;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndexCollection;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import java.io.IOException;
@@ -52,9 +52,9 @@ public class InitIT extends StandaloneSiteTest {
QueryOptions opts =
QueryOptions.create(IndexConfig.createDefault(), 0, 1, ImmutableSet.of("name"));
Optional<ProjectData> allProjectsData = projectIndex.getSearchIndex().get(allProjects, opts);
- assertThat(allProjectsData.isPresent()).isTrue();
+ assertThat(allProjectsData).isPresent();
Optional<ProjectData> allUsersData = projectIndex.getSearchIndex().get(allUsers, opts);
- assertThat(allUsersData.isPresent()).isTrue();
+ assertThat(allUsersData).isPresent();
}
}
@@ -65,7 +65,7 @@ public class InitIT extends StandaloneSiteTest {
// Simulate a projects indexes files modified in the past by 3 seconds
Optional<Instant> projectsLastModified =
getProjectsIndexLastModified(sitePaths.index_dir).map(t -> t.minusSeconds(3));
- assertThat((projectsLastModified).isPresent()).isTrue();
+ assertThat(projectsLastModified).isPresent();
setProjectsIndexLastModifiedInThePast(sitePaths.index_dir, projectsLastModified.get());
initSite();
diff --git a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
new file mode 100644
index 0000000000..bc454605ab
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.apache.http.HttpStatus.SC_OK;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import org.apache.http.message.BasicHeader;
+import org.junit.Test;
+
+public class RestApiServletIT extends AbstractDaemonTest {
+ private static String ANY_REST_API = "/accounts/self/capabilities";
+ private static BasicHeader ACCEPT_STAR_HEADER = new BasicHeader("Accept", "*/*");
+ private static Pattern ANY_SPACE = Pattern.compile("\\s");
+
+ @Test
+ public void restResponseBodyShouldBeCompactWithoutSpaces() throws Exception {
+ RestResponse response = adminRestSession.getWithHeader(ANY_REST_API, ACCEPT_STAR_HEADER);
+ assertThat(response.getStatusCode()).isEqualTo(SC_OK);
+
+ assertThat(contentWithoutMagicJson(response)).doesNotContainMatch(ANY_SPACE);
+ }
+
+ @Test
+ public void restResponseBodyShouldBeCompactWithoutSpacesWhenPPIsZero() throws Exception {
+ assertThat(contentWithoutMagicJson(prettyJsonRestResponse("prettyPrint", 0)))
+ .doesNotContainMatch(ANY_SPACE);
+ }
+
+ @Test
+ public void restResponseBodyShouldBeCompactWithoutSpacesWhenPrerryPrintIsZero() throws Exception {
+ assertThat(contentWithoutMagicJson(prettyJsonRestResponse("pp", 0)))
+ .doesNotContainMatch(ANY_SPACE);
+ }
+
+ @Test
+ public void restResponseBodyShouldBePrettyfiedWhenPPIsOne() throws Exception {
+ assertThat(contentWithoutMagicJson(prettyJsonRestResponse("pp", 1))).containsMatch(ANY_SPACE);
+ }
+
+ @Test
+ public void restResponseBodyShouldBePrettyfiedWhenPrettyPrintIsOne() throws Exception {
+ assertThat(contentWithoutMagicJson(prettyJsonRestResponse("prettyPrint", 1)))
+ .containsMatch(ANY_SPACE);
+ }
+
+ private RestResponse prettyJsonRestResponse(String ppArgument, int ppValue) throws Exception {
+ RestResponse response =
+ adminRestSession.getWithHeader(
+ ANY_REST_API + "?" + ppArgument + "=" + ppValue, ACCEPT_STAR_HEADER);
+ assertThat(response.getStatusCode()).isEqualTo(SC_OK);
+
+ return response;
+ }
+
+ private String contentWithoutMagicJson(RestResponse response) throws IOException {
+ return response.getEntityContent().substring(RestApiServlet.JSON_MAGIC.length);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
index b30dc41290..52de5adf43 100644
--- a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -16,215 +16,320 @@ package com.google.gerrit.acceptance.rest;
import static com.google.common.truth.Truth.assertThat;
import static org.apache.http.HttpStatus.SC_CREATED;
+import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+import static org.apache.http.HttpStatus.SC_OK;
+import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.truth.Expect;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.httpd.restapi.ParameterParser;
import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.ExceptionHook;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.rules.SubmitRule;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.SortedMap;
import java.util.SortedSet;
import org.apache.http.message.BasicHeader;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+/**
+ * This test tests the tracing of requests.
+ *
+ * <p>To verify that tracing is working we do:
+ *
+ * <ul>
+ * <li>Register a plugin extension that we know is invoked when the request is done. Within the
+ * implementation of this plugin extension we access the status of the thread local state in
+ * the {@link LoggingContext} and store it locally in the plugin extension class.
+ * <li>Do a request (e.g. REST) that triggers the plugin extension.
+ * <li>When the plugin extension is invoked it records the current logging context.
+ * <li>After the request is done the test verifies that logging context that was recorded by the
+ * plugin extension has the expected state.
+ * </ul>
+ */
public class TraceIT extends AbstractDaemonTest {
@Rule public final Expect expect = Expect.create();
- @Inject private DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
- @Inject private DynamicSet<CommitValidationListener> commitValidationListeners;
+ @Inject private ExtensionRegistry extensionRegistry;
@Inject private WorkQueue workQueue;
- private TraceValidatingProjectCreationValidationListener projectCreationListener;
- private RegistrationHandle projectCreationListenerRegistrationHandle;
- private TraceValidatingCommitValidationListener commitValidationListener;
- private RegistrationHandle commitValidationRegistrationHandle;
-
- @Before
- public void setup() {
- projectCreationListener = new TraceValidatingProjectCreationValidationListener();
- projectCreationListenerRegistrationHandle =
- projectCreationValidationListeners.add("gerrit", projectCreationListener);
- commitValidationListener = new TraceValidatingCommitValidationListener();
- commitValidationRegistrationHandle =
- commitValidationListeners.add("gerrit", commitValidationListener);
- }
+ @Test
+ public void restCallWithoutTrace() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new1");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
- @After
- public void cleanup() {
- projectCreationListenerRegistrationHandle.remove();
- commitValidationRegistrationHandle.remove();
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new1");
+ }
}
@Test
- public void restCallWithoutTrace() throws Exception {
- RestResponse response = adminRestSession.put("/projects/new1");
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
- assertThat(projectCreationListener.traceId).isNull();
- assertThat(projectCreationListener.isLoggingForced).isFalse();
+ public void restCallForChangeSetsProjectTag() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ TraceChangeIndexedListener changeIndexedListener = new TraceChangeIndexedListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedListener)) {
+ RestResponse response =
+ adminRestSession.post(
+ "/changes/" + changeId + "/revisions/current/review", ReviewInput.approve());
+ assertThat(response.getStatusCode()).isEqualTo(SC_OK);
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(changeIndexedListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void restCallWithTraceRequestParam() throws Exception {
- RestResponse response =
- adminRestSession.put("/projects/new2?" + ParameterParser.TRACE_PARAMETER);
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
- assertThat(projectCreationListener.traceId).isNotNull();
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response =
+ adminRestSession.put("/projects/new2?" + ParameterParser.TRACE_PARAMETER);
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new2");
+ }
}
@Test
public void restCallWithTraceRequestParamAndProvidedTraceId() throws Exception {
- RestResponse response =
- adminRestSession.put("/projects/new3?" + ParameterParser.TRACE_PARAMETER + "=issue/123");
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response =
+ adminRestSession.put("/projects/new3?" + ParameterParser.TRACE_PARAMETER + "=issue/123");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new3");
+ }
}
@Test
public void restCallWithTraceHeader() throws Exception {
- RestResponse response =
- adminRestSession.putWithHeader(
- "/projects/new4", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
- assertThat(projectCreationListener.traceId).isNotNull();
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new4", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNotNull();
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new4");
+ }
}
@Test
public void restCallWithTraceHeaderAndProvidedTraceId() throws Exception {
- RestResponse response =
- adminRestSession.putWithHeader(
- "/projects/new5", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new5", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new5");
+ }
}
@Test
public void restCallWithTraceRequestParamAndTraceHeader() throws Exception {
- // trace ID only specified by trace header
- RestResponse response =
- adminRestSession.putWithHeader(
- "/projects/new6?trace", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
-
- // trace ID only specified by trace request parameter
- response =
- adminRestSession.putWithHeader(
- "/projects/new7?trace=issue/123", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
-
- // same trace ID specified by trace header and trace request parameter
- response =
- adminRestSession.putWithHeader(
- "/projects/new8?trace=issue/123",
- new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
-
- // different trace IDs specified by trace header and trace request parameter
- response =
- adminRestSession.putWithHeader(
- "/projects/new9?trace=issue/123",
- new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/456"));
- assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
- assertThat(response.getHeaders(RestApiServlet.X_GERRIT_TRACE))
- .containsExactly("issue/123", "issue/456");
- assertThat(projectCreationListener.traceIds).containsExactly("issue/123", "issue/456");
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ // trace ID only specified by trace header
+ RestResponse response =
+ adminRestSession.putWithHeader(
+ "/projects/new6?trace", new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new6");
+
+ // trace ID only specified by trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new7?trace=issue/123",
+ new BasicHeader(RestApiServlet.X_GERRIT_TRACE, null));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new7");
+
+ // same trace ID specified by trace header and trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new8?trace=issue/123",
+ new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/123"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isEqualTo("issue/123");
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new8");
+
+ // different trace IDs specified by trace header and trace request parameter
+ response =
+ adminRestSession.putWithHeader(
+ "/projects/new9?trace=issue/123",
+ new BasicHeader(RestApiServlet.X_GERRIT_TRACE, "issue/456"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeaders(RestApiServlet.X_GERRIT_TRACE))
+ .containsExactly("issue/123", "issue/456");
+ assertThat(projectCreationListener.traceIds).containsExactly("issue/123", "issue/456");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new9");
+ }
}
@Test
public void pushWithoutTrace() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- PushOneCommit.Result r = push.to("refs/heads/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isNull();
- assertThat(commitValidationListener.isLoggingForced).isFalse();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNull();
+ assertThat(commitValidationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void pushWithTrace() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- push.setPushOptions(ImmutableList.of("trace"));
- PushOneCommit.Result r = push.to("refs/heads/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isNotNull();
- assertThat(commitValidationListener.isLoggingForced).isTrue();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace"));
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNotNull();
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void pushWithTraceAndProvidedTraceId() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- push.setPushOptions(ImmutableList.of("trace=issue/123"));
- PushOneCommit.Result r = push.to("refs/heads/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
- assertThat(commitValidationListener.isLoggingForced).isTrue();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace=issue/123"));
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void pushForReviewWithoutTrace() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- PushOneCommit.Result r = push.to("refs/for/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isNull();
- assertThat(commitValidationListener.isLoggingForced).isFalse();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNull();
+ assertThat(commitValidationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void pushForReviewWithTrace() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- push.setPushOptions(ImmutableList.of("trace"));
- PushOneCommit.Result r = push.to("refs/for/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isNotNull();
- assertThat(commitValidationListener.isLoggingForced).isTrue();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace"));
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isNotNull();
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
public void pushForReviewWithTraceAndProvidedTraceId() throws Exception {
- PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
- push.setPushOptions(ImmutableList.of("trace=issue/123"));
- PushOneCommit.Result r = push.to("refs/for/master");
- r.assertOkStatus();
- assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
- assertThat(commitValidationListener.isLoggingForced).isTrue();
+ TraceValidatingCommitValidationListener commitValidationListener =
+ new TraceValidatingCommitValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ push.setPushOptions(ImmutableList.of("trace=issue/123"));
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+ assertThat(commitValidationListener.traceId).isEqualTo("issue/123");
+ assertThat(commitValidationListener.isLoggingForced).isTrue();
+ assertThat(commitValidationListener.tags.get("project")).containsExactly(project.get());
+ }
}
@Test
@@ -263,6 +368,409 @@ public class TraceIT extends AbstractDaemonTest {
assertForceLogging(false);
}
+ @Test
+ public void performanceLoggingForRestCall() throws Exception {
+ TestPerformanceLogger testPerformanceLogger = new TestPerformanceLogger();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(testPerformanceLogger)) {
+ RestResponse response = adminRestSession.put("/projects/new10");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+
+ // This assertion assumes that the server invokes the PerformanceLogger plugins before it
+ // sends
+ // the response to the client. If this assertion gets flaky it's likely that this got changed
+ // on
+ // server-side.
+ assertThat(testPerformanceLogger.logEntries()).isNotEmpty();
+ }
+ }
+
+ @Test
+ public void performanceLoggingForPush() throws Exception {
+ TestPerformanceLogger testPerformanceLogger = new TestPerformanceLogger();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(testPerformanceLogger)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+ assertThat(testPerformanceLogger.logEntries()).isNotEmpty();
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.performanceLogging", value = "false")
+ public void noPerformanceLoggingIfDisabled() throws Exception {
+ TestPerformanceLogger testPerformanceLogger = new TestPerformanceLogger();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(testPerformanceLogger)) {
+ RestResponse response = adminRestSession.put("/projects/new11");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertOkStatus();
+
+ assertThat(testPerformanceLogger.logEntries()).isEmpty();
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "new12")
+ public void traceProject() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new12");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new12");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "new.*")
+ public void traceProjectMatchRegEx() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new13");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new13");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "foo.*")
+ public void traceProjectNoMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new13");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new13");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "][")
+ public void traceProjectInvalidRegEx() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new14");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new14");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "1000000")
+ public void traceAccount() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new15");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new15");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "1000001")
+ public void traceAccountNoMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new16");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new16");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "999")
+ public void traceAccountNotFound() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new17");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new17");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "invalid")
+ public void traceAccountInvalidId() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new18");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new18");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestType", value = "REST")
+ public void traceRequestType() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new19");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new19");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestType", value = "SSH")
+ public void traceRequestTypeNoMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new20");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new20");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestType", value = "FOO")
+ public void traceProjectInvalidRequestType() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new21");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new21");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "1000000")
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "new.*")
+ public void traceProjectForAccount() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new22");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new22");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "1000000")
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "foo.*")
+ public void traceProjectForAccountNoProjectMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new23");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new23");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.account", value = "1000001")
+ @GerritConfig(name = "tracing.issue123.projectPattern", value = "new.*")
+ public void traceProjectForAccountNoAccountMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new24");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new24");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "/projects/.*")
+ public void traceRequestUri() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new23");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new23");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "/projects/.*/foo")
+ public void traceRequestUriNoMatch() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new23");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new23");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "][")
+ public void traceRequestUriInvalidRegEx() throws Exception {
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/new24");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new24");
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "retry.retryWithTraceOnFailure", value = "true")
+ public void autoRetryWithTrace() throws Exception {
+ String changeId = createChange().getChangeId();
+ approve(changeId);
+
+ TraceSubmitRule traceSubmitRule = new TraceSubmitRule();
+ traceSubmitRule.failAlways = true;
+ try (Registration registration = extensionRegistry.newRegistration().add(traceSubmitRule)) {
+ RestResponse response = adminRestSession.post("/changes/" + changeId + "/submit");
+ assertThat(response.getStatusCode()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).startsWith("retry-on-failure-");
+ assertThat(traceSubmitRule.traceId).startsWith("retry-on-failure-");
+ assertThat(traceSubmitRule.isLoggingForced).isTrue();
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "retry.retryWithTraceOnFailure", value = "true")
+ public void noAutoRetryIfExceptionCausesNormalRetrying() throws Exception {
+ String changeId = createChange().getChangeId();
+ approve(changeId);
+
+ TraceSubmitRule traceSubmitRule = new TraceSubmitRule();
+ traceSubmitRule.failAlways = true;
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(traceSubmitRule)
+ .add(
+ new ExceptionHook() {
+ @Override
+ public boolean shouldRetry(Throwable t) {
+ return true;
+ }
+ })) {
+ RestResponse response = adminRestSession.post("/changes/" + changeId + "/submit");
+ assertThat(response.getStatusCode()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(traceSubmitRule.traceId).isNull();
+ assertThat(traceSubmitRule.isLoggingForced).isFalse();
+ }
+ }
+
+ @Test
+ public void noAutoRetryWithTraceIfDisabled() throws Exception {
+ String changeId = createChange().getChangeId();
+ approve(changeId);
+
+ TraceSubmitRule traceSubmitRule = new TraceSubmitRule();
+ traceSubmitRule.failOnce = true;
+ try (Registration registration = extensionRegistry.newRegistration().add(traceSubmitRule)) {
+ RestResponse response = adminRestSession.post("/changes/" + changeId + "/submit");
+ assertThat(response.getStatusCode()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(traceSubmitRule.traceId).isNull();
+ assertThat(traceSubmitRule.isLoggingForced).isFalse();
+ }
+ }
+
private void assertForceLogging(boolean expected) {
assertThat(LoggingContext.getInstance().shouldForceLogging(null, null, false))
.isEqualTo(expected);
@@ -273,6 +781,7 @@ public class TraceIT extends AbstractDaemonTest {
String traceId;
ImmutableSet<String> traceIds;
Boolean isLoggingForced;
+ ImmutableSetMultimap<String, String> tags;
@Override
public void validateNewProject(CreateProjectArgs args) throws ValidationException {
@@ -280,12 +789,14 @@ public class TraceIT extends AbstractDaemonTest {
Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
this.traceIds = LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID");
this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+ this.tags = LoggingContext.getInstance().getTagsAsMap();
}
}
private static class TraceValidatingCommitValidationListener implements CommitValidationListener {
String traceId;
Boolean isLoggingForced;
+ ImmutableSetMultimap<String, String> tags;
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
@@ -293,7 +804,67 @@ public class TraceIT extends AbstractDaemonTest {
this.traceId =
Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+ this.tags = LoggingContext.getInstance().getTagsAsMap();
return ImmutableList.of();
}
}
+
+ private static class TraceChangeIndexedListener implements ChangeIndexedListener {
+ ImmutableSetMultimap<String, String> tags;
+
+ @Override
+ public void onChangeIndexed(String projectName, int id) {
+ this.tags = LoggingContext.getInstance().getTagsAsMap();
+ }
+
+ @Override
+ public void onChangeDeleted(int id) {}
+ }
+
+ private static class TraceSubmitRule implements SubmitRule {
+ String traceId;
+ Boolean isLoggingForced;
+ boolean failOnce;
+ boolean failAlways;
+
+ @Override
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
+ this.traceId =
+ Iterables.getFirst(LoggingContext.getInstance().getTagsAsMap().get("TRACE_ID"), null);
+ this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
+
+ if (failOnce || failAlways) {
+ failOnce = false;
+ throw new IllegalStateException("forced failure from test");
+ }
+
+ SubmitRecord submitRecord = new SubmitRecord();
+ submitRecord.status = SubmitRecord.Status.OK;
+ return Optional.of(submitRecord);
+ }
+ }
+
+ private static class TestPerformanceLogger implements PerformanceLogger {
+ private List<PerformanceLogEntry> logEntries = new ArrayList<>();
+
+ @Override
+ public void log(String operation, long durationMs, Metadata metadata) {
+ logEntries.add(PerformanceLogEntry.create(operation, metadata));
+ }
+
+ ImmutableList<PerformanceLogEntry> logEntries() {
+ return ImmutableList.copyOf(logEntries);
+ }
+ }
+
+ @AutoValue
+ abstract static class PerformanceLogEntry {
+ static PerformanceLogEntry create(String operation, Metadata metadata) {
+ return new AutoValue_TraceIT_PerformanceLogEntry(operation, metadata);
+ }
+
+ abstract String operation();
+
+ abstract Metadata metadata();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
index 5e652c0c18..03202f2002 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
@@ -18,21 +18,33 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.reviewdb.client.Account;
import java.util.List;
public class AccountAssert {
-
- public static void assertAccountInfo(TestAccount a, AccountInfo ai) {
- assertThat(a.id().get()).isEqualTo(ai._accountId);
- assertThat(a.fullName()).isEqualTo(ai.name);
- assertThat(a.email()).isEqualTo(ai.email);
+ /**
+ * Asserts an AccountInfo for an active account.
+ *
+ * @param testAccount the TestAccount which the provided AccountInfo is expected to match
+ * @param accountInfo the AccountInfo that should be asserted
+ */
+ public static void assertAccountInfo(TestAccount testAccount, AccountInfo accountInfo) {
+ assertThat(accountInfo._accountId).isEqualTo(testAccount.id().get());
+ assertThat(accountInfo.name).isEqualTo(testAccount.fullName());
+ assertThat(accountInfo.email).isEqualTo(testAccount.email());
+ assertThat(accountInfo.inactive).isNull();
}
+ /**
+ * Asserts an AccountInfos for active accounts.
+ *
+ * @param expected the TestAccounts which the provided AccountInfos are expected to match
+ * @param actual the AccountInfos that should be asserted
+ */
public static void assertAccountInfos(List<TestAccount> expected, List<AccountInfo> actual) {
Iterable<Account.Id> expectedIds = TestAccount.ids(expected);
- Iterable<Account.Id> actualIds = Iterables.transform(actual, a -> new Account.Id(a._accountId));
+ Iterable<Account.Id> actualIds = Iterables.transform(actual, a -> Account.id(a._accountId));
assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder();
for (int i = 0; i < expected.size(); i++) {
AccountAssert.assertAccountInfo(expected.get(i), actual.get(i));
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/BUILD b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
index 66ea6f33c3..a00a8b2886 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
@@ -5,7 +5,10 @@ acceptance_tests(
srcs = glob(["*IT.java"]),
group = "rest_account",
labels = ["rest"],
- deps = [":util"],
+ deps = [
+ ":util",
+ "//java/com/google/gerrit/server/account/externalids/testing",
+ ],
)
java_library(
@@ -18,7 +21,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/acceptance:lib",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//lib:junit",
],
)
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java b/javatests/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
index e7ce43f69c..be4fde02c2 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
@@ -14,8 +14,11 @@
package com.google.gerrit.acceptance.rest.account;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.common.data.GlobalCapability.ACCESS_DATABASE;
import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.BATCH_CHANGES_LIMIT;
@@ -26,24 +29,30 @@ import static com.google.gerrit.common.data.GlobalCapability.QUERY_LIMIT;
import static com.google.gerrit.common.data.GlobalCapability.RUN_AS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
import org.junit.Test;
public class CapabilitiesIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
@Test
public void capabilitiesUser() throws Exception {
- Iterable<String> all =
- Iterables.filter(
- GlobalCapability.getAllNames(),
- c -> !ADMINISTRATE_SERVER.equals(c) && !PRIORITY.equals(c));
-
- allowGlobalCapabilities(REGISTERED_USERS, all);
+ ImmutableList<String> all =
+ GlobalCapability.getAllNames().stream()
+ .filter(c -> !ADMINISTRATE_SERVER.equals(c) && !PRIORITY.equals(c))
+ .collect(toImmutableList());
+ TestProjectUpdate.Builder allowBuilder = projectOperations.allProjectsForUpdate();
+ all.forEach(c -> allowBuilder.add(allowCapability(c).group(REGISTERED_USERS)));
+ allowBuilder.update();
try {
RestResponse r = userRestSession.get("/accounts/self/capabilities");
r.assertOK();
@@ -67,7 +76,9 @@ public class CapabilitiesIT extends AbstractDaemonTest {
}
}
} finally {
- removeGlobalCapabilities(REGISTERED_USERS, all);
+ TestProjectUpdate.Builder removeBuilder = projectOperations.allProjectsForUpdate();
+ all.forEach(c -> removeBuilder.remove(capabilityKey(c).group(REGISTERED_USERS)));
+ removeBuilder.update();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/CreateAccountIT.java b/javatests/com/google/gerrit/acceptance/rest/account/CreateAccountIT.java
index aca6c4cbac..1b83d92828 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/CreateAccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/CreateAccountIT.java
@@ -17,6 +17,7 @@ package com.google.gerrit.acceptance.rest.account;
import static com.google.common.truth.Truth8.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import org.junit.Test;
@@ -31,4 +32,44 @@ public class CreateAccountIT extends AbstractDaemonTest {
r.assertCreated();
assertThat(accountCache.getByUsername(input.username)).isPresent();
}
+
+ @Test
+ @GerritConfig(name = "auth.userNameToLowerCase", value = "false")
+ public void createAccountRestApiUserNameToLowerCaseFalse() throws Exception {
+ AccountInput input = new AccountInput();
+ input.username = "JohnDoe";
+ assertThat(accountCache.getByUsername(input.username)).isEmpty();
+ RestResponse r = adminRestSession.put("/accounts/" + input.username, input);
+ r.assertCreated();
+ assertThat(accountCache.getByUsername(input.username)).isPresent();
+ }
+
+ @Test
+ @GerritConfig(name = "auth.userNameToLowerCase", value = "true")
+ public void createAccountRestApiUserNameToLowerCaseTrue() throws Exception {
+ testUserNameToLowerCase("John1", "John1", "john1");
+ assertThat(accountCache.getByUsername("John1")).isEmpty();
+
+ testUserNameToLowerCase("john2", "John2", "john2");
+ assertThat(accountCache.getByUsername("John2")).isEmpty();
+
+ testUserNameToLowerCase("John3", "john3", "john3");
+ assertThat(accountCache.getByUsername("John3")).isEmpty();
+
+ testUserNameToLowerCase("John4", "johN4", "john4");
+ assertThat(accountCache.getByUsername("John4")).isEmpty();
+ assertThat(accountCache.getByUsername("johN4")).isEmpty();
+
+ testUserNameToLowerCase("john5", "john5", "john5");
+ }
+
+ private void testUserNameToLowerCase(String usernameUrl, String usernameInput, String usernameDb)
+ throws Exception {
+ AccountInput input = new AccountInput();
+ input.username = usernameInput;
+ assertThat(accountCache.getByUsername(usernameDb)).isEmpty();
+ RestResponse r = adminRestSession.put("/accounts/" + usernameUrl, input);
+ r.assertCreated();
+ assertThat(accountCache.getByUsername(usernameDb)).isPresent();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java b/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
index 84f218d091..aaeed0214b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/EmailIT.java
@@ -16,6 +16,7 @@ package com.google.gerrit.acceptance.rest.account;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableMultimap;
@@ -24,13 +25,13 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.accounts.EmailApi;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate;
@@ -149,16 +150,20 @@ public class EmailIT extends AbstractDaemonTest {
@Test
public void setPreferredEmailToNonExistingEmail() throws Exception {
String email = "non-existing@example.com";
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: " + email);
- gApi.accounts().self().email(email).setPreferred();
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.accounts().self().email(email).setPreferred());
+ assertThat(thrown).hasMessageThat().contains("Not found: " + email);
}
@Test
public void setPreferredEmailToEmailOfOtherAccount() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("Not found: " + user.email());
- gApi.accounts().self().email(user.email()).setPreferred();
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.accounts().self().email(user.email()).setPreferred());
+ assertThat(thrown).hasMessageThat().contains("Not found: " + user.email());
}
@Test
@@ -201,9 +206,11 @@ public class EmailIT extends AbstractDaemonTest {
Context oldCtx =
createContextWithCustomRealm(new RealmWithAdditionalEmails(admin.id(), user.email()));
try {
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("email in use by another account");
- gApi.accounts().self().email(user.email()).setPreferred();
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.accounts().self().email(user.email()).setPreferred());
+ assertThat(thrown).hasMessageThat().contains("email in use by another account");
} finally {
atrScope.set(oldCtx);
}
@@ -248,9 +255,7 @@ public class EmailIT extends AbstractDaemonTest {
// Now the email is no longer found
requestScopeOperations.resetCurrentApiUser();
- emailApi = gApi.accounts().self().email(email);
- exception.expect(ResourceNotFoundException.class);
- emailApi.get();
+ assertThrows(ResourceNotFoundException.class, () -> gApi.accounts().self().email(email).get());
}
private Set<String> getEmails() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index f08fa447b0..6e164356f5 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -16,16 +16,21 @@ package com.google.gerrit.acceptance.rest.account;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.fetch;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_UUID;
+import static com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil.insertExternalIdWithEmptyNote;
+import static com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil.insertExternalIdWithInvalidConfig;
+import static com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil.insertExternalIdWithKeyThatDoesntMatchNoteId;
+import static com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil.insertExternalIdWithoutAccountId;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -33,9 +38,12 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
@@ -44,8 +52,6 @@ import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccoun
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -54,6 +60,7 @@ import com.google.gerrit.server.account.externalids.ExternalIdReader;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.testing.ConfigSuite;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -70,13 +77,8 @@ import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -91,11 +93,26 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Inject private ExternalIds externalIds;
@Inject private ExternalIdReader externalIdReader;
@Inject private ExternalIdNotes.Factory externalIdNotesFactory;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @ConfigSuite.Default
+ public static Config partialCacheReloadingEnabled() {
+ Config cfg = new Config();
+ cfg.setBoolean("cache", "external_ids_map", "enablePartialReloads", true);
+ return cfg;
+ }
+
+ @ConfigSuite.Config
+ public static Config partialCacheReloadingDisabled() {
+ Config cfg = new Config();
+ cfg.setBoolean("cache", "external_ids_map", "enablePartialReloads", false);
+ return cfg;
+ }
+
@Test
public void getExternalIds() throws Exception {
- Collection<ExternalId> expectedIds = getAccountState(user.id()).getExternalIds();
+ Collection<ExternalId> expectedIds = getAccountState(user.id()).externalIds();
List<AccountExternalIdInfo> expectedIdInfos = toExternalIdInfos(expectedIds);
RestResponse response = userRestSession.get("/accounts/self/external.ids");
@@ -112,16 +129,20 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void getExternalIdsOfOtherUserNotAllowed() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("access database not permitted");
- gApi.accounts().id(admin.id().get()).getExternalIds();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.accounts().id(admin.id().get()).getExternalIds());
+ assertThat(thrown).hasMessageThat().contains("access database not permitted");
}
@Test
public void getExternalIdsOfOtherUserWithAccessDatabase() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
- Collection<ExternalId> expectedIds = getAccountState(admin.id()).getExternalIds();
+ Collection<ExternalId> expectedIds = getAccountState(admin.id()).externalIds();
List<AccountExternalIdInfo> expectedIdInfos = toExternalIdInfos(expectedIds);
RestResponse response = userRestSession.get("/accounts/" + admin.id() + "/external.ids");
@@ -165,27 +186,38 @@ public class ExternalIdIT extends AbstractDaemonTest {
public void deleteExternalIdsOfOtherUserNotAllowed() throws Exception {
List<AccountExternalIdInfo> extIds = gApi.accounts().self().getExternalIds();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("access database not permitted");
- gApi.accounts()
- .id(admin.id().get())
- .deleteExternalIds(extIds.stream().map(e -> e.identity).collect(toList()));
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.accounts()
+ .id(admin.id().get())
+ .deleteExternalIds(extIds.stream().map(e -> e.identity).collect(toList())));
+ assertThat(thrown).hasMessageThat().contains("access database not permitted");
}
@Test
public void deleteExternalIdOfOtherUserUnderOwnAccount_UnprocessableEntity() throws Exception {
List<AccountExternalIdInfo> extIds = gApi.accounts().self().getExternalIds();
requestScopeOperations.setApiUser(user.id());
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage(String.format("External id %s does not exist", extIds.get(0).identity));
- gApi.accounts()
- .self()
- .deleteExternalIds(extIds.stream().map(e -> e.identity).collect(toList()));
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () ->
+ gApi.accounts()
+ .self()
+ .deleteExternalIds(extIds.stream().map(e -> e.identity).collect(toList())));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(String.format("External id %s does not exist", extIds.get(0).identity));
}
@Test
public void deleteExternalIdsOfOtherUserWithAccessDatabase() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
List<AccountExternalIdInfo> externalIds = gApi.accounts().self().getExternalIds();
@@ -248,29 +280,33 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void fetchExternalIdsBranch() throws Exception {
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
+ final TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
// refs/meta/external-ids is only visible to users with the 'Access Database' capability
- try {
- fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
- fail("expected TransportException");
- } catch (TransportException e) {
- assertThat(e.getMessage())
- .isEqualTo(
- "Remote does not have " + RefNames.REFS_EXTERNAL_IDS + " available for fetch.");
- }
-
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ TransportException thrown =
+ assertThrows(
+ TransportException.class, () -> fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Remote does not have " + RefNames.REFS_EXTERNAL_IDS + " available for fetch.");
+
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
// re-clone to get new request context, otherwise the old global capabilities are still cached
// in the IdentifiedUser object
- allUsersRepo = cloneProject(allUsers, user);
- fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
+ TestRepository<InMemoryRepository> allUsersRepo2 = cloneProject(allUsers, user);
+ fetch(allUsersRepo2, RefNames.REFS_EXTERNAL_IDS);
}
@Test
public void pushToExternalIdsBranch() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
@@ -295,14 +331,21 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void pushToExternalIdsBranchRejectsExternalIdWithoutAccountId() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
insertExternalIdWithoutAccountId(
- allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.getRepository(),
+ allUsersRepo.getRevWalk(),
+ admin.newIdent(),
+ admin.id(),
+ "foo:bar");
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
allowPushOfExternalIds();
@@ -313,14 +356,21 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void pushToExternalIdsBranchRejectsExternalIdWithKeyThatDoesntMatchTheNoteId()
throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
insertExternalIdWithKeyThatDoesntMatchNoteId(
- allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.getRepository(),
+ allUsersRepo.getRevWalk(),
+ admin.newIdent(),
+ admin.id(),
+ "foo:bar");
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
allowPushOfExternalIds();
@@ -330,14 +380,17 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void pushToExternalIdsBranchRejectsExternalIdWithInvalidConfig() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
insertExternalIdWithInvalidConfig(
- allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), admin.newIdent(), "foo:bar");
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
allowPushOfExternalIds();
@@ -347,14 +400,17 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void pushToExternalIdsBranchRejectsExternalIdWithEmptyNote() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
insertExternalIdWithEmptyNote(
- allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), "foo:bar");
+ allUsersRepo.getRepository(), allUsersRepo.getRevWalk(), admin.newIdent(), "foo:bar");
allUsersRepo.reset(RefNames.REFS_EXTERNAL_IDS);
allowPushOfExternalIds();
@@ -387,7 +443,10 @@ public class ExternalIdIT extends AbstractDaemonTest {
private void testPushToExternalIdsBranchRejectsInvalidExternalId(ExternalId invalidExtId)
throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.REFS_EXTERNAL_IDS + ":" + RefNames.REFS_EXTERNAL_IDS);
@@ -403,7 +462,10 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void readExternalIdsWhenInvalidExternalIdsExist() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
requestScopeOperations.resetCurrentApiUser();
insertValidExternalIds();
@@ -424,7 +486,10 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void checkConsistency() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
requestScopeOperations.resetCurrentApiUser();
insertValidExternalIds();
@@ -446,9 +511,11 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void checkConsistencyNotAllowed() throws Exception {
- exception.expect(AuthException.class);
- exception.expectMessage("access database not permitted");
- gApi.config().server().checkConsistency(new ConsistencyCheckInput());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.config().server().checkConsistency(new ConsistencyCheckInput()));
+ assertThat(thrown).hasMessageThat().contains("access database not permitted");
}
private ConsistencyProblemInfo consistencyError(String message) {
@@ -525,7 +592,8 @@ public class ExternalIdIT extends AbstractDaemonTest {
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo)) {
String externalId = nextId(scheme, i);
- String noteId = insertExternalIdWithoutAccountId(repo, rw, externalId);
+ String noteId =
+ insertExternalIdWithoutAccountId(repo, rw, admin.newIdent(), admin.id(), externalId);
expectedProblems.add(
consistencyError(
"Invalid external ID config for note '"
@@ -535,7 +603,9 @@ public class ExternalIdIT extends AbstractDaemonTest {
+ ".accountId' is missing, expected account ID"));
externalId = nextId(scheme, i);
- noteId = insertExternalIdWithKeyThatDoesntMatchNoteId(repo, rw, externalId);
+ noteId =
+ insertExternalIdWithKeyThatDoesntMatchNoteId(
+ repo, rw, admin.newIdent(), admin.id(), externalId);
expectedProblems.add(
consistencyError(
"Invalid external ID config for note '"
@@ -546,12 +616,12 @@ public class ExternalIdIT extends AbstractDaemonTest {
+ noteId
+ "'"));
- noteId = insertExternalIdWithInvalidConfig(repo, rw, nextId(scheme, i));
+ noteId = insertExternalIdWithInvalidConfig(repo, rw, admin.newIdent(), nextId(scheme, i));
expectedProblems.add(
consistencyError(
"Invalid external ID config for note '" + noteId + "': Invalid line in config file"));
- noteId = insertExternalIdWithEmptyNote(repo, rw, nextId(scheme, i));
+ noteId = insertExternalIdWithEmptyNote(repo, rw, admin.newIdent(), nextId(scheme, i));
expectedProblems.add(
consistencyError(
"Invalid external ID config for note '"
@@ -570,125 +640,8 @@ public class ExternalIdIT extends AbstractDaemonTest {
"password");
}
- private String insertExternalIdWithoutAccountId(Repository repo, RevWalk rw, String externalId)
- throws IOException {
- return insertExternalId(
- repo,
- rw,
- (ins, noteMap) -> {
- ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id());
- ObjectId noteId = extId.key().sha1();
- Config c = new Config();
- extId.writeToConfig(c);
- c.unset("externalId", extId.key().get(), "accountId");
- byte[] raw = c.toText().getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
- return noteId;
- });
- }
-
- private String insertExternalIdWithKeyThatDoesntMatchNoteId(
- Repository repo, RevWalk rw, String externalId) throws IOException {
- return insertExternalId(
- repo,
- rw,
- (ins, noteMap) -> {
- ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id());
- ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
- Config c = new Config();
- extId.writeToConfig(c);
- byte[] raw = c.toText().getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
- return noteId;
- });
- }
-
- private String insertExternalIdWithInvalidConfig(Repository repo, RevWalk rw, String externalId)
- throws IOException {
- return insertExternalId(
- repo,
- rw,
- (ins, noteMap) -> {
- ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
- byte[] raw = "bad-config".getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
- return noteId;
- });
- }
-
- private String insertExternalIdWithEmptyNote(Repository repo, RevWalk rw, String externalId)
- throws IOException {
- return insertExternalId(
- repo,
- rw,
- (ins, noteMap) -> {
- ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
- byte[] raw = "".getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
- return noteId;
- });
- }
-
- private String insertExternalId(Repository repo, RevWalk rw, ExternalIdInserter extIdInserter)
- throws IOException {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- try (ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId noteId = extIdInserter.addNote(ins, noteMap);
-
- CommitBuilder cb = new CommitBuilder();
- cb.setMessage("Update external IDs");
- cb.setTreeId(noteMap.writeTree(ins));
- cb.setAuthor(admin.newIdent());
- cb.setCommitter(admin.newIdent());
- if (!rev.equals(ObjectId.zeroId())) {
- cb.setParentId(rev);
- } else {
- cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
- }
- if (cb.getTreeId() == null) {
- if (rev.equals(ObjectId.zeroId())) {
- cb.setTreeId(ins.insert(OBJ_TREE, new byte[] {})); // No parent, assume empty tree.
- } else {
- RevCommit p = rw.parseCommit(rev);
- cb.setTreeId(p.getTree()); // Copy tree from parent.
- }
- }
- ObjectId commitId = ins.insert(cb);
- ins.flush();
-
- RefUpdate u = repo.updateRef(RefNames.REFS_EXTERNAL_IDS);
- u.setExpectedOldObjectId(rev);
- u.setNewObjectId(commitId);
- RefUpdate.Result res = u.update();
- switch (res) {
- case NEW:
- case FAST_FORWARD:
- case NO_CHANGE:
- case RENAMED:
- case FORCED:
- break;
- case LOCK_FAILURE:
- case IO_FAILURE:
- case NOT_ATTEMPTED:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case REJECTED_MISSING_OBJECT:
- case REJECTED_OTHER_REASON:
- default:
- throw new IOException("Updating external IDs failed with " + res);
- }
- return noteId.getName();
- }
- }
-
private ExternalId createExternalIdForNonExistingAccount(String externalId) {
- return ExternalId.create(ExternalId.Key.parse(externalId), new Account.Id(1));
+ return ExternalId.create(ExternalId.Key.parse(externalId), Account.id(1));
}
private ExternalId createExternalIdWithInvalidEmail(String externalId) {
@@ -715,7 +668,7 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
- Account.Id accountId = new Account.Id(1024 * 100);
+ Account.Id accountId = Account.id(1024 * 100);
accountsUpdateProvider
.get()
.insert(
@@ -757,23 +710,25 @@ public class ExternalIdIT extends AbstractDaemonTest {
@Test
public void byAccountFailIfReadingExternalIdsFails() throws Exception {
+ assume().that(isPartialCacheReloadingEnabled()).isFalse();
+
try (AutoCloseable ctx = createFailOnLoadContext()) {
// update external ID branch so that external IDs need to be reloaded
insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id()));
- exception.expect(IOException.class);
- externalIds.byAccount(admin.id());
+ assertThrows(IOException.class, () -> externalIds.byAccount(admin.id()));
}
}
@Test
public void byEmailFailIfReadingExternalIdsFails() throws Exception {
+ assume().that(isPartialCacheReloadingEnabled()).isFalse();
+
try (AutoCloseable ctx = createFailOnLoadContext()) {
// update external ID branch so that external IDs need to be reloaded
insertExtIdBehindGerritsBack(ExternalId.create("foo", "bar", admin.id()));
- exception.expect(IOException.class);
- externalIds.byEmail(admin.email());
+ assertThrows(IOException.class, () -> externalIds.byEmail(admin.email()));
}
}
@@ -910,6 +865,10 @@ public class ExternalIdIT extends AbstractDaemonTest {
}
}
+ private boolean isPartialCacheReloadingEnabled() {
+ return cfg.getBoolean("cache", "external_ids_map", "enablePartialReloads", true);
+ }
+
private void insertExtId(ExternalId extId) throws Exception {
accountsUpdateProvider
.get()
@@ -976,9 +935,13 @@ public class ExternalIdIT extends AbstractDaemonTest {
return info;
}
- private void allowPushOfExternalIds() throws IOException, ConfigInvalidException {
- grant(allUsers, RefNames.REFS_EXTERNAL_IDS, Permission.READ);
- grant(allUsers, RefNames.REFS_EXTERNAL_IDS, Permission.PUSH);
+ private void allowPushOfExternalIds() {
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_EXTERNAL_IDS).group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_EXTERNAL_IDS).group(adminGroupUuid()))
+ .update();
}
private void assertRefUpdateFailure(RemoteRefUpdate update, String msg) {
@@ -990,9 +953,4 @@ public class ExternalIdIT extends AbstractDaemonTest {
externalIdReader.setFailOnLoad(true);
return () -> externalIdReader.setFailOnLoad(false);
}
-
- @FunctionalInterface
- private interface ExternalIdInserter {
- public ObjectId addNote(ObjectInserter ins, NoteMap noteMap) throws IOException;
- }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
index 27ae8b1295..9202f42803 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
@@ -19,8 +19,8 @@ import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAcco
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountDetailInfo;
-import com.google.gerrit.reviewdb.client.Account;
import org.junit.Test;
public class GetAccountDetailIT extends AbstractDaemonTest {
@@ -30,6 +30,6 @@ public class GetAccountDetailIT extends AbstractDaemonTest {
AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
assertAccountInfo(admin, info);
Account account = getAccount(admin.id());
- assertThat(info.registeredOn).isEqualTo(account.getRegisteredOn());
+ assertThat(info.registeredOn).isEqualTo(account.registeredOn());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountIT.java b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
index 11f7c0f0af..782638a107 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
@@ -14,19 +14,26 @@
package com.google.gerrit.acceptance.rest.account;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.account.AccountAssert.assertAccountInfo;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.inject.Inject;
import org.junit.Test;
@NoHttpd
public class GetAccountIT extends AbstractDaemonTest {
- @Test(expected = ResourceNotFoundException.class)
+ @Inject private AccountOperations accountOperations;
+
+ @Test
public void getNonExistingAccount_NotFound() throws Exception {
- gApi.accounts().id("non-existing").get();
+ assertThrows(ResourceNotFoundException.class, () -> gApi.accounts().id("non-existing").get());
}
@Test
@@ -50,6 +57,16 @@ public class GetAccountIT extends AbstractDaemonTest {
testGetAccount("self", admin);
}
+ @Test
+ public void getInactiveAccount() throws Exception {
+ accountOperations.account(user.id()).forUpdate().inactive().update();
+ AccountInfo accountInfo = gApi.accounts().id(user.id().get()).get();
+ assertThat(accountInfo._accountId).isEqualTo(user.id().get());
+ assertThat(accountInfo.name).isEqualTo(user.fullName());
+ assertThat(accountInfo.email).isEqualTo(user.email());
+ assertThat(accountInfo.inactive).isTrue();
+ }
+
private void testGetAccount(String id, TestAccount expectedAccount) throws Exception {
assertAccountInfo(expectedAccount, gApi.accounts().id(id).get());
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 4dec505066..faaba06cfe 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -15,9 +15,15 @@
package com.google.gerrit.acceptance.rest.account;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -28,10 +34,17 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
@@ -49,17 +62,11 @@ import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.account.AccountControl;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import org.apache.http.Header;
@@ -73,6 +80,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
@Inject private ApprovalsUtil approvalsUtil;
@Inject private ChangeMessagesUtil cmUtil;
@Inject private CommentsUtil commentsUtil;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
private RestSession anonRestSession;
@@ -106,11 +114,11 @@ public class ImpersonationIT extends AbstractDaemonTest {
revision.review(in);
PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
- assertThat(psa.getPatchSetId().get()).isEqualTo(1);
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getAccountId()).isEqualTo(user.id());
- assertThat(psa.getValue()).isEqualTo(1);
- assertThat(psa.getRealAccountId()).isEqualTo(admin.id());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(user.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(admin.id());
ChangeData cd = r.getChange();
ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
@@ -129,9 +137,10 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.onBehalfOf = user.id().toString();
in.message = "Message on behalf of";
- exception.expect(AuthException.class);
- exception.expectMessage("label required to post review on behalf of \"" + in.onBehalfOf + '"');
- revision.review(in);
+ AuthException thrown = assertThrows(AuthException.class, () -> revision.review(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("label required to post review on behalf of \"" + in.onBehalfOf + '"');
}
@Test
@@ -143,9 +152,10 @@ public class ImpersonationIT extends AbstractDaemonTest {
ReviewInput in = new ReviewInput().label("Not-A-Label", 5);
in.onBehalfOf = user.id().toString();
- exception.expect(BadRequestException.class);
- exception.expectMessage("label \"Not-A-Label\" is not a configured label");
- gApi.changes().id(changeId).current().review(in);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class, () -> gApi.changes().id(changeId).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("label \"Not-A-Label\" is not a configured label");
}
@Test
@@ -163,7 +173,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
@Test
public void voteOnBehalfOfLabelNotPermitted() throws Exception {
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType verified = Util.verified();
+ LabelType verified = TestLabels.verified();
u.getConfig().getLabelSections().put(verified.getName(), verified);
u.save();
}
@@ -175,10 +185,11 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.onBehalfOf = user.id().toString();
in.label("Verified", 1);
- exception.expect(AuthException.class);
- exception.expectMessage(
- "not permitted to modify label \"Verified\" on behalf of \"" + in.onBehalfOf + '"');
- revision.review(in);
+ AuthException thrown = assertThrows(AuthException.class, () -> revision.review(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "not permitted to modify label \"Verified\" on behalf of \"" + in.onBehalfOf + '"');
}
@Test
@@ -207,11 +218,11 @@ public class ImpersonationIT extends AbstractDaemonTest {
gApi.changes().id(r.getChangeId()).current().review(in);
PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
- assertThat(psa.getPatchSetId().get()).isEqualTo(1);
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getAccountId()).isEqualTo(user.id());
- assertThat(psa.getValue()).isEqualTo(1);
- assertThat(psa.getRealAccountId()).isEqualTo(admin.id());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(user.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(admin.id());
ChangeData cd = r.getChange();
Comment c = Iterables.getOnlyElement(commentsUtil.publishedByChange(cd.notes()));
@@ -266,9 +277,10 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.label("Code-Review", 1);
in.drafts = DraftHandling.PUBLISH;
- exception.expect(AuthException.class);
- exception.expectMessage("not allowed to modify other user's drafts");
- gApi.changes().id(r.getChangeId()).current().review(in);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(r.getChangeId()).current().review(in));
+ assertThat(thrown).hasMessageThat().contains("not allowed to modify other user's drafts");
}
@Test
@@ -281,10 +293,10 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.onBehalfOf = "doesnotexist";
in.label("Code-Review", 1);
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("not found");
- exception.expectMessage("doesnotexist");
- revision.review(in);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> revision.review(in));
+ assertThat(thrown).hasMessageThat().contains("not found");
+ assertThat(thrown).hasMessageThat().contains("doesnotexist");
}
@Test
@@ -299,9 +311,11 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.onBehalfOf = user.id().toString();
in.label("Code-Review", 1);
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("on_behalf_of account " + user.id() + " cannot see change");
- revision.review(in);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> revision.review(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("on_behalf_of account " + user.id() + " cannot see change");
}
@GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
@@ -318,10 +332,10 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.onBehalfOf = user.id().toString();
in.label("Code-Review", 1);
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("not found");
- exception.expectMessage(in.onBehalfOf);
- revision.review(in);
+ UnprocessableEntityException thrown =
+ assertThrows(UnprocessableEntityException.class, () -> revision.review(in));
+ assertThat(thrown).hasMessageThat().contains("not found");
+ assertThat(thrown).hasMessageThat().contains(in.onBehalfOf);
}
@Test
@@ -338,8 +352,8 @@ public class ImpersonationIT extends AbstractDaemonTest {
assertThat(cd.change().isMerged()).isTrue();
PatchSetApproval submitter =
approvalsUtil.getSubmitter(cd.notes(), cd.change().currentPatchSetId());
- assertThat(submitter.getAccountId()).isEqualTo(admin2.id());
- assertThat(submitter.getRealAccountId()).isEqualTo(admin.id());
+ assertThat(submitter.accountId()).isEqualTo(admin2.id());
+ assertThat(submitter.realAccountId()).isEqualTo(admin.id());
}
@Test
@@ -350,10 +364,12 @@ public class ImpersonationIT extends AbstractDaemonTest {
gApi.changes().id(changeId).current().review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = "doesnotexist";
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("not found");
- exception.expectMessage("doesnotexist");
- gApi.changes().id(changeId).current().submit(in);
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.changes().id(changeId).current().submit(in));
+ assertThat(thrown).hasMessageThat().contains("not found");
+ assertThat(thrown).hasMessageThat().contains("doesnotexist");
}
@Test
@@ -365,9 +381,15 @@ public class ImpersonationIT extends AbstractDaemonTest {
.review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = admin2.email();
- exception.expect(AuthException.class);
- exception.expectMessage("submit on behalf of other users not permitted");
- gApi.changes().id(project.get() + "~master~" + r.getChangeId()).current().submit(in);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.changes()
+ .id(project.get() + "~master~" + r.getChangeId())
+ .current()
+ .submit(in));
+ assertThat(thrown).hasMessageThat().contains("submit on behalf of other users not permitted");
}
@Test
@@ -380,9 +402,13 @@ public class ImpersonationIT extends AbstractDaemonTest {
gApi.changes().id(changeId).current().review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = user.email();
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("on_behalf_of account " + user.id() + " cannot see change");
- gApi.changes().id(changeId).current().submit(in);
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.changes().id(changeId).current().submit(in));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("on_behalf_of account " + user.id() + " cannot see change");
}
@GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
@@ -397,10 +423,12 @@ public class ImpersonationIT extends AbstractDaemonTest {
gApi.changes().id(changeId).current().review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = user.email();
- exception.expect(UnprocessableEntityException.class);
- exception.expectMessage("not found");
- exception.expectMessage(in.onBehalfOf);
- gApi.changes().id(changeId).current().submit(in);
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.changes().id(changeId).current().submit(in));
+ assertThat(thrown).hasMessageThat().contains("not found");
+ assertThat(thrown).hasMessageThat().contains(in.onBehalfOf);
}
@Test
@@ -510,11 +538,11 @@ public class ImpersonationIT extends AbstractDaemonTest {
adminRestSession.postWithHeader(endpoint, runAsHeader(user2.id()), in).assertOK();
PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
- assertThat(psa.getPatchSetId().get()).isEqualTo(1);
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getAccountId()).isEqualTo(user.id());
- assertThat(psa.getValue()).isEqualTo(1);
- assertThat(psa.getRealAccountId()).isEqualTo(admin.id()); // not user2
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(user.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(admin.id()); // not user2
ChangeData cd = r.getChange();
ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
@@ -546,54 +574,53 @@ public class ImpersonationIT extends AbstractDaemonTest {
}
private void allowCodeReviewOnBehalfOf() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType codeReviewType = Util.codeReview();
- String forCodeReviewAs = Permission.forLabelAs(codeReviewType.getName());
- String heads = "refs/heads/*";
- AccountGroup.UUID uuid = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- Util.allow(u.getConfig(), forCodeReviewAs, -1, 1, uuid, heads);
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(TestLabels.codeReview().getName())
+ .impersonation(true)
+ .ref("refs/heads/*")
+ .group(REGISTERED_USERS)
+ .range(-1, 1))
+ .update();
}
private void allowSubmitOnBehalfOf() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- String heads = "refs/heads/*";
- AccountGroup.UUID uuid = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- Util.allow(u.getConfig(), Permission.SUBMIT_AS, uuid, heads);
- Util.allow(u.getConfig(), Permission.SUBMIT, uuid, heads);
- LabelType codeReviewType = Util.codeReview();
- Util.allow(u.getConfig(), Permission.forLabel(codeReviewType.getName()), -2, 2, uuid, heads);
- u.save();
- }
+ String heads = "refs/heads/*";
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT_AS).ref(heads).group(REGISTERED_USERS))
+ .add(allow(Permission.SUBMIT).ref(heads).group(REGISTERED_USERS))
+ .add(
+ allowLabel(TestLabels.codeReview().getName())
+ .ref(heads)
+ .group(REGISTERED_USERS)
+ .range(-2, 2))
+ .update();
}
private void blockRead(GroupInfo group) throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.block(
- u.getConfig(), Permission.READ, new AccountGroup.UUID(group.id), "refs/heads/master");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(AccountGroup.uuid(group.id)))
+ .update();
}
private void allowRunAs() throws Exception {
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- Util.allow(
- u.getConfig(),
- GlobalCapability.RUN_AS,
- systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID());
- u.save();
- }
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.RUN_AS).group(ANONYMOUS_USERS))
+ .update();
}
private void removeRunAs() throws Exception {
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- Util.remove(
- u.getConfig(),
- GlobalCapability.RUN_AS,
- systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID());
- u.save();
- }
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(capabilityKey(GlobalCapability.RUN_AS).group(ANONYMOUS_USERS))
+ .update();
}
private static Header runAsHeader(Object user) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
index d0c1fa4ff0..2c9107c325 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.account;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -114,9 +115,11 @@ public class WatchedProjectsIT extends AbstractDaemonTest {
pwi.notifyNewPatchSets = true;
projectsToWatch.add(pwi);
- exception.expect(BadRequestException.class);
- exception.expectMessage("duplicate entry for project " + projectName);
- gApi.accounts().self().setWatchedProjects(projectsToWatch);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.accounts().self().setWatchedProjects(projectsToWatch));
+ assertThat(thrown).hasMessageThat().contains("duplicate entry for project " + projectName);
}
@Test
@@ -146,9 +149,9 @@ public class WatchedProjectsIT extends AbstractDaemonTest {
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
-
- exception.expect(UnprocessableEntityException.class);
- gApi.accounts().self().setWatchedProjects(projectsToWatch);
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.accounts().self().setWatchedProjects(projectsToWatch));
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
new file mode 100644
index 0000000000..b6ef5a37a1
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.auth;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import org.junit.Test;
+
+public class AuthenticationCheckIT extends AbstractDaemonTest {
+ @Test
+ public void authCheck_loggedInUser_returnsOk() throws Exception {
+ RestResponse r = adminRestSession.get("/auth-check");
+ r.assertNoContent();
+ }
+
+ @Test
+ public void authCheck_anonymousUser_returnsForbidden() throws Exception {
+ RestSession anonymous = new RestSession(server, null);
+ RestResponse r = anonymous.get("/auth-check");
+ r.assertForbidden();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/auth/BUILD b/javatests/com/google/gerrit/acceptance/rest/auth/BUILD
new file mode 100644
index 0000000000..5de1607107
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/auth/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob(["*IT.java"]),
+ group = "auth",
+ labels = ["rest"],
+)
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 55744ccb8e..8a284d9503 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -24,6 +24,7 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
import com.google.gerrit.acceptance.rest.util.RestCall;
+import com.google.gerrit.entities.Patch;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -35,7 +36,6 @@ import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.FixReplacementInfo;
import com.google.gerrit.extensions.common.FixSuggestionInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
-import com.google.gerrit.reviewdb.client.Patch;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ConfigRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ConfigRestApiBindingsIT.java
index 5f210cc6d5..00dcb4f6ba 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ConfigRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ConfigRestApiBindingsIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.binding;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.ImmutableList;
@@ -22,10 +23,12 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
import com.google.gerrit.acceptance.rest.util.RestCall;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
@@ -81,10 +84,15 @@ public class ConfigRestApiBindingsIT extends AbstractDaemonTest {
// Task deletion must be tested last
RestCall.delete("/config/server/tasks/%s"));
+ @Inject private ProjectOperations projectOperations;
+
@Test
public void configEndpoints() throws Exception {
// 'Access Database' is needed for the '/config/server/check.consistency' REST endpoint
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
RestApiCallHelper.execute(adminRestSession, CONFIG_ENDPOINTS);
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedChildRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedChildRestApiBindingsIT.java
index 27df565c75..b53f18271c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedChildRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedChildRestApiBindingsIT.java
@@ -15,15 +15,25 @@
package com.google.gerrit.acceptance.rest.binding;
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
+import static com.google.gerrit.server.change.RobotCommentResource.ROBOT_COMMENT_KIND;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
import com.google.gerrit.acceptance.rest.util.RestCall;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Change.Id;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
+import com.google.gerrit.extensions.common.FixSuggestionInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
@@ -31,12 +41,16 @@ import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.change.RobotCommentResource;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import org.junit.Test;
/**
@@ -53,7 +67,7 @@ public class PluginProvidedChildRestApiBindingsIT extends AbstractDaemonTest {
private static final String PLUGIN_NAME = "my-plugin";
- private static final ImmutableSet<RestCall> TEST_CALLS =
+ private static final ImmutableSet<RestCall> REVISION_TEST_CALLS =
ImmutableSet.of(
// Calls that have the plugin name as part of the collection name
RestCall.get("/changes/%s/revisions/%s/" + PLUGIN_NAME + "~test-collection/"),
@@ -69,6 +83,9 @@ public class PluginProvidedChildRestApiBindingsIT extends AbstractDaemonTest {
RestCall.post("/changes/%s/revisions/%s/test-collection/"),
RestCall.post("/changes/%s/revisions/%s/test-collection/1/update"));
+ private static final ImmutableSet<RestCall> ROBOTCOMMENT_TEST_CALLS =
+ ImmutableSet.of(RestCall.delete("/changes/%s/revisions/%s/robotcomments/%s"));
+
/**
* Module for all sys bindings.
*
@@ -88,6 +105,7 @@ public class PluginProvidedChildRestApiBindingsIT extends AbstractDaemonTest {
postOnCollection(TEST_KIND).to(TestPostOnCollection.class);
post(TEST_KIND, "update").to(TestPost.class);
get(TEST_KIND, "detail").to(TestGet.class);
+ delete(ROBOT_COMMENT_KIND).to(TestDelete.class);
}
});
}
@@ -107,7 +125,8 @@ public class PluginProvidedChildRestApiBindingsIT extends AbstractDaemonTest {
@Override
public RestView<RevisionResource> list() throws RestApiException {
- return (RestReadView<RevisionResource>) resource -> ImmutableList.of("one", "two");
+ return (RestReadView<RevisionResource>)
+ resource -> Response.ok(ImmutableList.of("one", "two"));
}
@Override
@@ -125,36 +144,90 @@ public class PluginProvidedChildRestApiBindingsIT extends AbstractDaemonTest {
static class TestPostOnCollection
implements RestCollectionModifyView<RevisionResource, TestPluginResource, String> {
@Override
- public Object apply(RevisionResource parentResource, String input) throws Exception {
- return "test";
+ public Response<String> apply(RevisionResource parentResource, String input) throws Exception {
+ return Response.ok("test");
}
}
@Singleton
static class TestPost implements RestModifyView<TestPluginResource, String> {
@Override
- public String apply(TestPluginResource resource, String input) throws Exception {
- return "test";
+ public Response<String> apply(TestPluginResource resource, String input) throws Exception {
+ return Response.ok("test");
}
}
@Singleton
static class TestGet implements RestReadView<TestPluginResource> {
@Override
- public String apply(TestPluginResource resource) throws Exception {
- return "test";
+ public Response<String> apply(TestPluginResource resource) throws Exception {
+ return Response.ok("test");
+ }
+ }
+
+ @Singleton
+ static class TestDelete implements RestModifyView<RobotCommentResource, String> {
+ @Override
+ public Response<?> apply(RobotCommentResource resource, String input) throws Exception {
+ return Response.none();
+ }
+ }
+
+ @Test
+ public void testRevisionEndpoints() throws Exception {
+ PatchSet.Id patchSetId = createChange().getPatchSetId();
+ try (AutoCloseable ignored = installPlugin(PLUGIN_NAME, MyPluginSysModule.class, null, null)) {
+ RestApiCallHelper.execute(
+ adminRestSession,
+ REVISION_TEST_CALLS.asList(),
+ String.valueOf(patchSetId.changeId().get()),
+ String.valueOf(patchSetId.get()));
}
}
@Test
- public void testEndpoints() throws Exception {
+ public void testRobotCommentEndpoints() throws Exception {
PatchSet.Id patchSetId = createChange().getPatchSetId();
+ String robotCommentUuid = createRobotComment(patchSetId.changeId());
try (AutoCloseable ignored = installPlugin(PLUGIN_NAME, MyPluginSysModule.class, null, null)) {
RestApiCallHelper.execute(
adminRestSession,
- TEST_CALLS.asList(),
- String.valueOf(patchSetId.changeId.id),
- String.valueOf(patchSetId.patchSetId));
+ ROBOTCOMMENT_TEST_CALLS.asList(),
+ String.valueOf(patchSetId.changeId().get()),
+ String.valueOf(patchSetId.get()),
+ robotCommentUuid);
}
}
+
+ private String createRobotComment(Change.Id changeId) throws Exception {
+ addRobotComment(changeId, createRobotCommentInput(PushOneCommit.FILE_NAME));
+ return Iterables.getOnlyElement(
+ Iterables.getOnlyElement(
+ gApi.changes().id(changeId.get()).current().robotComments().values()))
+ .id;
+ }
+
+ private void addRobotComment(Id changeId, RobotCommentInput robotCommentInput) throws Exception {
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.robotComments =
+ Collections.singletonMap(robotCommentInput.path, ImmutableList.of(robotCommentInput));
+ reviewInput.message = "Test robot comment";
+ reviewInput.tag = ChangeMessagesUtil.AUTOGENERATED_TAG_PREFIX;
+ gApi.changes().id(changeId.get()).current().review(reviewInput);
+ }
+
+ private static RobotCommentInput createRobotCommentInput(
+ String path, FixSuggestionInfo... fixSuggestionInfos) {
+ RobotCommentInput in = new RobotCommentInput();
+ in.robotId = "happyRobot";
+ in.robotRunId = "1";
+ in.message = "nit: trailing whitespace";
+ in.path = path;
+ in.url = "http://www.happy-robot.com";
+ in.properties = new HashMap<>();
+ in.properties.put("key1", "value1");
+ in.properties.put("key2", "value2");
+ in.fixSuggestions = Arrays.asList(fixSuggestionInfos);
+ return in;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedRootRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedRootRestApiBindingsIT.java
index 178a32656e..b447534914 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedRootRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/PluginProvidedRootRestApiBindingsIT.java
@@ -25,6 +25,7 @@ import com.google.gerrit.acceptance.rest.util.RestCall.Method;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
@@ -157,7 +158,8 @@ public class PluginProvidedRootRestApiBindingsIT extends AbstractDaemonTest {
@Override
public RestView<TopLevelResource> list() throws RestApiException {
- return (RestReadView<TopLevelResource>) resource -> ImmutableList.of("one", "two");
+ return (RestReadView<TopLevelResource>)
+ resource -> Response.ok(ImmutableList.of("one", "two"));
}
@Override
@@ -175,24 +177,24 @@ public class PluginProvidedRootRestApiBindingsIT extends AbstractDaemonTest {
static class TestPostOnCollection
implements RestCollectionModifyView<TopLevelResource, TestPluginResource, String> {
@Override
- public Object apply(TopLevelResource parentResource, String input) throws Exception {
- return "test";
+ public Response<String> apply(TopLevelResource parentResource, String input) throws Exception {
+ return Response.ok("test");
}
}
@Singleton
static class TestPost implements RestModifyView<TestPluginResource, String> {
@Override
- public String apply(TestPluginResource resource, String input) throws Exception {
- return "test";
+ public Response<String> apply(TestPluginResource resource, String input) throws Exception {
+ return Response.ok("test");
}
}
@Singleton
static class TestGet implements RestReadView<TestPluginResource> {
@Override
- public String apply(TestPluginResource resource) throws Exception {
- return "test";
+ public Response<String> apply(TestPluginResource resource) throws Exception {
+ return Response.ok("test");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index ed09ddd44c..f48a60372b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -17,7 +17,8 @@ package com.google.gerrit.acceptance.rest.binding;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.rest.util.RestCall.Method.GET;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.entities.RefNames.REFS_DASHBOARDS;
import static com.google.gerrit.server.restapi.project.DashboardsCollection.DEFAULT_DASHBOARD_NAME;
import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
@@ -29,10 +30,10 @@ import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
import com.google.gerrit.acceptance.rest.util.RestCall;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.TagInput;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
@@ -216,7 +217,7 @@ public class ProjectsRestApiBindingsIT extends AbstractDaemonTest {
testRepo
.commit()
.message("A change")
- .parent(getRemoteHead())
+ .parent(projectOperations.project(project).getHead("master"))
.add(filename, "content")
.insertChangeId()
.create();
@@ -234,7 +235,11 @@ public class ProjectsRestApiBindingsIT extends AbstractDaemonTest {
private void createDefaultDashboard() throws Exception {
String dashboardRef = REFS_DASHBOARDS + "team";
- grant(project, "refs/meta/*", Permission.CREATE);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref("refs/meta/*").group(adminGroupUuid()))
+ .update();
gApi.projects().name(project.get()).branch(dashboardRef).create(new BranchInput());
try (Repository r = repoManager.openRepository(project);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index a299b9a0c3..b822750cfe 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -16,15 +16,25 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
+import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -32,14 +42,27 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseTimezone;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -52,27 +75,18 @@ import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.LabelInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.TestSubmitInput;
import com.google.gerrit.server.git.validators.OnSubmitValidationListener;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.restapi.change.Submit;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -80,7 +94,7 @@ import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.TestTimeUtil;
+import com.google.gerrit.testing.GerritJUnit.ThrowingRunnable;
import com.google.inject.Inject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -103,11 +117,11 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.RefSpec;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
+@UseClockStep
+@UseTimezone(timezone = "US/Eastern")
public abstract class AbstractSubmit extends AbstractDaemonTest {
@ConfigSuite.Config
public static Config submitWholeTopicEnabled() {
@@ -115,57 +129,36 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Inject private ApprovalsUtil approvalsUtil;
- @Inject private DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
@Inject private IdentifiedUser.GenericFactory userFactory;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private Submit submitHandler;
-
- private RegistrationHandle onSubmitValidatorHandle;
- private String systemTimeZone;
-
- @Before
- public void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
- @After
- public void removeOnSubmitValidator() {
- if (onSubmitValidatorHandle != null) {
- onSubmitValidatorHandle.remove();
- }
- }
+ @Inject private ExtensionRegistry extensionRegistry;
protected abstract SubmitType getSubmitType();
@Test
@TestProjectInput(createEmptyCommit = false)
- public void submitToEmptyRepo() throws Exception {
+ public void submitToEmptyRepo() throws Throwable {
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
PushOneCommit.Result change = createChange();
assertThat(change.getCommit().getParents()).isEmpty();
- Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ Map<BranchNameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
assertThat(actual).hasSize(1);
submit(change.getChangeId());
- assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
+ assertThat(projectOperations.project(project).getHead("master").getId())
+ .isEqualTo(change.getCommit());
assertTrees(project, actual);
}
@Test
- public void submitSingleChange() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitSingleChange() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
- Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
- RevCommit headAfterSubmit = getRemoteHead();
+ Map<BranchNameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSubmit).isEqualTo(initialHead);
assertRefUpdatedEvents();
assertChangeMergedEvents();
@@ -183,13 +176,13 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitMultipleChangesOtherMergeConflictPreview() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChangesOtherMergeConflictPreview() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
PushOneCommit.Result change3 = createChange("Change 3", "d", "d");
@@ -223,7 +216,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
break;
case REBASE_IF_NECESSARY:
case REBASE_ALWAYS:
- String change2hash = change2.getChange().currentPatchSet().getRevision().get();
+ String change2hash = change2.getChange().currentPatchSet().commitId().name();
assertThat(e.getMessage())
.isEqualTo(
"Cannot rebase "
@@ -255,11 +248,11 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
break;
case CHERRY_PICK:
default:
- fail("Should not reach here.");
+ assertWithMessage("Should not reach here.").fail();
break;
}
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSubmit).isEqualTo(headAfterFirstSubmit);
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
@@ -267,19 +260,19 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitMultipleChangesPreview() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChangesPreview() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
PushOneCommit.Result change3 = createChange("Change 3", "d", "d");
PushOneCommit.Result change4 = createChange("Change 4", "e", "e");
// change 2 is not approved, but we ignore labels
approve(change3.getChangeId());
- Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change4.getChangeId());
+ Map<BranchNameKey, ObjectId> actual = fetchFromSubmitPreview(change4.getChangeId());
Map<String, Map<String, Integer>> expected = new HashMap<>();
expected.put(project.get(), new HashMap<>());
expected.get(project.get()).put("refs/heads/master", 3);
- assertThat(actual).containsKey(new Branch.NameKey(project, "refs/heads/master"));
+ assertThat(actual).containsKey(BranchNameKey.create(project, "refs/heads/master"));
if (getSubmitType() == SubmitType.CHERRY_PICK) {
// CherryPick ignores dependencies, thus only change and destination
// branch refs are modified.
@@ -293,7 +286,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
// check that the submit preview did not actually submit
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSubmit).isEqualTo(initialHead);
assertRefUpdatedEvents();
assertChangeMergedEvents();
@@ -305,10 +298,14 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitNoPermission() throws Exception {
+ public void submitNoPermission() throws Throwable {
// create project where submit is blocked
Project.NameKey p = projectOperations.newProject().create();
- block(p, "refs/*", Permission.SUBMIT, REGISTERED_USERS);
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
@@ -319,16 +316,16 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void noSelfSubmit() throws Exception {
+ public void noSelfSubmit() throws Throwable {
// create project where submit is blocked for the change owner
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.block(u.getConfig(), Permission.SUBMIT, CHANGE_OWNER, "refs/*");
- Util.allow(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel("Code-Review"), -2, +2, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.SUBMIT).ref("refs/*").group(CHANGE_OWNER))
+ .add(allow(Permission.SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
+ .add(allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
@@ -345,16 +342,16 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void onlySelfSubmit() throws Exception {
+ public void onlySelfSubmit() throws Throwable {
// create project where only the change owner can submit
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.block(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, "refs/*");
- Util.allow(u.getConfig(), Permission.SUBMIT, CHANGE_OWNER, "refs/*");
- Util.allow(
- u.getConfig(), Permission.forLabel("Code-Review"), -2, +2, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(block(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(CHANGE_OWNER))
+ .add(allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
@@ -372,7 +369,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitWholeTopicMultipleProjects() throws Exception {
+ public void submitWholeTopicMultipleProjects() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
@@ -408,7 +405,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitWholeTopicMultipleBranchesOnSameProject() throws Exception {
+ public void submitWholeTopicMultipleBranchesOnSameProject() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
@@ -416,7 +413,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
Project.NameKey keyA = createProjectForPush(getSubmitType());
TestRepository<?> repoA = cloneProject(keyA);
- RevCommit initialHead = getRemoteHead(keyA, "master");
+ RevCommit initialHead = projectOperations.project(keyA).getHead("master");
// Create the dev branch on the test project
BranchInput in = new BranchInput();
@@ -450,7 +447,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitWholeTopic() throws Exception {
+ public void submitWholeTopic() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "content", topic);
@@ -488,7 +485,82 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitReusingOldTopic() throws Exception {
+ public void submitWholeTopicWithMultipleTopics() throws Throwable {
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+ String topic1 = "test-topic-1";
+ String topic2 = "test-topic-2";
+ PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "content", topic1);
+ PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "content", topic1);
+ PushOneCommit.Result change3 = createChange("Change 3", "c.txt", "content", topic2);
+ PushOneCommit.Result change4 = createChange("Change 4", "d.txt", "content", topic2);
+ approve(change1.getChangeId());
+ approve(change2.getChangeId());
+ approve(change3.getChangeId());
+ approve(change4.getChangeId());
+ submit(change4.getChangeId());
+ String expectedTopic1 = name(topic1);
+ String expectedTopic2 = name(topic2);
+ if (getSubmitType() == SubmitType.CHERRY_PICK) {
+ change1.assertChange(Change.Status.NEW, expectedTopic1, admin);
+ change2.assertChange(Change.Status.NEW, expectedTopic1, admin);
+
+ } else {
+ change1.assertChange(Change.Status.MERGED, expectedTopic1, admin);
+ change2.assertChange(Change.Status.MERGED, expectedTopic1, admin);
+ }
+
+ // Check for the exact change to have the correct submitter.
+ assertSubmitter(change4);
+ // Also check submitters for changes submitted via the topic relationship.
+ assertSubmitter(change3);
+ if (getSubmitType() != SubmitType.CHERRY_PICK) {
+ assertSubmitter(change1);
+ assertSubmitter(change2);
+ }
+
+ // Check that the repo has the expected commits
+ List<RevCommit> log = getRemoteLog();
+ List<String> commitsInRepo = log.stream().map(RevCommit::getShortMessage).collect(toList());
+ int expectedCommitCount;
+ switch (getSubmitType()) {
+ case MERGE_ALWAYS:
+ // initial commit + 4 commits + merge commit
+ expectedCommitCount = 6;
+ break;
+ case CHERRY_PICK:
+ // initial commit + 2 commits
+ expectedCommitCount = 3;
+ break;
+ case FAST_FORWARD_ONLY:
+ case INHERIT:
+ case MERGE_IF_NECESSARY:
+ case REBASE_ALWAYS:
+ case REBASE_IF_NECESSARY:
+ default:
+ // initial commit + 4 commits
+ expectedCommitCount = 5;
+ break;
+ }
+ assertThat(log).hasSize(expectedCommitCount);
+
+ if (getSubmitType() == SubmitType.CHERRY_PICK) {
+ assertThat(commitsInRepo).containsAtLeast("Initial empty repository", "Change 3", "Change 4");
+ assertThat(commitsInRepo).doesNotContain("Change 1");
+ assertThat(commitsInRepo).doesNotContain("Change 2");
+ } else if (getSubmitType() == SubmitType.MERGE_ALWAYS) {
+ assertThat(commitsInRepo)
+ .contains(
+ String.format(
+ "Merge changes from topics \"%s\", \"%s\"", expectedTopic1, expectedTopic2));
+ } else {
+ assertThat(commitsInRepo)
+ .containsAtLeast(
+ "Initial empty repository", "Change 1", "Change 2", "Change 3", "Change 4");
+ }
+ }
+
+ @Test
+ public void submitReusingOldTopic() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
@@ -519,13 +591,13 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
private void assertSubmittedTogether(String changeId, Iterable<String> expected)
- throws Exception {
+ throws Throwable {
assertThat(gApi.changes().id(changeId).submittedTogether().stream().map(i -> i.changeId))
.containsExactlyElementsIn(expected);
}
@Test
- public void submitWorkInProgressChange() throws Exception {
+ public void submitWorkInProgressChange() throws Throwable {
PushOneCommit.Result change = pushTo("refs/for/master%wip");
Change.Id num = change.getChange().getId();
submitWithConflict(
@@ -539,15 +611,19 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitWithHiddenBranchInSameTopic() throws Exception {
+ public void submitWithHiddenBranchInSameTopic() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
PushOneCommit.Result visible = createChange("refs/for/master/" + name("topic"));
Change.Id num = visible.getChange().getId();
- createBranch(new Branch.NameKey(project, "hidden"));
+ createBranch(BranchNameKey.create(project, "hidden"));
PushOneCommit.Result hidden = createChange("refs/for/hidden/" + name("topic"));
approve(hidden.getChangeId());
- blockRead("refs/heads/hidden");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/hidden").group(REGISTERED_USERS))
+ .update();
submit(
visible.getChangeId(),
@@ -557,7 +633,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitChangeWhenParentOfOtherBranchTip() throws Exception {
+ public void submitChangeWhenParentOfOtherBranchTip() throws Throwable {
// Chain of two commits
// Push both to topic-branch
// Push the first commit for review and submit
@@ -588,7 +664,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitMergeOfNonChangeBranchTip() throws Exception {
+ public void submitMergeOfNonChangeBranchTip() throws Throwable {
// Merge a branch with commits that have not been submitted as
// changes.
//
@@ -598,7 +674,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// | /
// I -- master
//
- RevCommit master = getRemoteHead(project, "master");
+ RevCommit master = projectOperations.project(project).getHead("master");
PushOneCommit stableTip =
pushFactory.create(admin.newIdent(), testRepo, "Tip of branch stable", "stable.txt", "");
PushOneCommit.Result stable = stableTip.to("refs/heads/stable");
@@ -615,7 +691,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitMergeOfNonChangeBranchNonTip() throws Exception {
+ public void submitMergeOfNonChangeBranchNonTip() throws Throwable {
// Merge a branch with commits that have not been submitted as
// changes.
//
@@ -626,7 +702,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// | /
// I -- master
//
- RevCommit initial = getRemoteHead(project, "master");
+ RevCommit initial = projectOperations.project(project).getHead("master");
// push directly to stable to S1
PushOneCommit.Result s1 =
pushFactory
@@ -659,11 +735,11 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void submitChangeWithCommitThatWasAlreadyMerged() throws Exception {
+ public void submitChangeWithCommitThatWasAlreadyMerged() throws Throwable {
// create and submit a change
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
// set the status of the change back to NEW to simulate a failed submit that
// merged the commit but failed to update the change status
@@ -672,11 +748,11 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// submitting the change again should detect that the commit was already
// merged and just fix the change status to be MERGED
submit(change.getChangeId());
- assertThat(getRemoteHead()).isEqualTo(headAfterSubmit);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterSubmit);
}
@Test
- public void submitChangesWithCommitsThatWereAlreadyMerged() throws Exception {
+ public void submitChangesWithCommitsThatWereAlreadyMerged() throws Throwable {
// create and submit 2 changes
PushOneCommit.Result change1 = createChange();
PushOneCommit.Result change2 = createChange();
@@ -686,7 +762,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
submit(change2.getChangeId());
assertMerged(change1.getChangeId());
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
// set the status of the changes back to NEW to simulate a failed submit that
// merged the commits but failed to update the change status
@@ -696,11 +772,11 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// merged and just fix the change status to be MERGED
submit(change1.getChangeId());
submit(change2.getChangeId());
- assertThat(getRemoteHead()).isEqualTo(headAfterSubmit);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterSubmit);
}
@Test
- public void submitTopicWithCommitsThatWereAlreadyMerged() throws Exception {
+ public void submitTopicWithCommitsThatWereAlreadyMerged() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
// create and submit 2 changes with the same topic
@@ -710,7 +786,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
approve(change1.getChangeId());
submit(change2.getChangeId());
assertMerged(change1.getChangeId());
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
// set the status of the second change back to NEW to simulate a failed
// submit that merged the commits but failed to update the change status of
@@ -720,33 +796,38 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// submitting the topic again should detect that the commits were already
// merged and just fix the change status to be MERGED
submit(change2.getChangeId());
- assertThat(getRemoteHead()).isEqualTo(headAfterSubmit);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterSubmit);
}
@Test
- public void submitWithValidation() throws Exception {
+ public void submitWithValidation() throws Throwable {
AtomicBoolean called = new AtomicBoolean(false);
- this.addOnSubmitValidationListener(
- args -> {
- called.set(true);
- HashSet<String> refs = Sets.newHashSet(args.getCommands().keySet());
- assertThat(refs).contains("refs/heads/master");
- refs.remove("refs/heads/master");
- if (!refs.isEmpty()) {
- // Some submit strategies need to insert new patchset.
- assertThat(refs).hasSize(1);
- assertThat(refs.iterator().next()).startsWith(RefNames.REFS_CHANGES);
+ OnSubmitValidationListener listener =
+ new OnSubmitValidationListener() {
+ @Override
+ public void preBranchUpdate(Arguments args) throws ValidationException {
+ called.set(true);
+ HashSet<String> refs = Sets.newHashSet(args.getCommands().keySet());
+ assertThat(refs).contains("refs/heads/master");
+ refs.remove("refs/heads/master");
+ if (!refs.isEmpty()) {
+ // Some submit strategies need to insert new patchset.
+ assertThat(refs).hasSize(1);
+ assertThat(refs.iterator().next()).startsWith(RefNames.REFS_CHANGES);
+ }
}
- });
+ };
- PushOneCommit.Result change = createChange();
- approve(change.getChangeId());
- submit(change.getChangeId());
- assertThat(called.get()).isTrue();
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ PushOneCommit.Result change = createChange();
+ approve(change.getChangeId());
+ submit(change.getChangeId());
+ assertThat(called.get()).isTrue();
+ }
}
@Test
- public void submitWithValidationMultiRepo() throws Exception {
+ public void submitWithValidationMultiRepo() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
@@ -777,42 +858,47 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
// Since there are 2 repos, first submit attempt will fail, the second will
// succeed.
List<String> projectsCalled = new ArrayList<>(4);
- this.addOnSubmitValidationListener(
- args -> {
- String master = "refs/heads/master";
- assertThat(args.getCommands()).containsKey(master);
- ReceiveCommand cmd = args.getCommands().get(master);
- ObjectId newMasterId = cmd.getNewId();
- try (Repository repo = repoManager.openRepository(args.getProject())) {
- assertThat(repo.exactRef(master).getObjectId()).isEqualTo(cmd.getOldId());
- assertThat(args.getRef(master)).hasValue(newMasterId);
- args.getRevWalk().parseBody(args.getRevWalk().parseCommit(newMasterId));
- } catch (IOException e) {
- throw new AssertionError("failed checking new ref value", e);
+ OnSubmitValidationListener listener =
+ new OnSubmitValidationListener() {
+ @Override
+ public void preBranchUpdate(Arguments args) throws ValidationException {
+ String master = "refs/heads/master";
+ assertThat(args.getCommands()).containsKey(master);
+ ReceiveCommand cmd = args.getCommands().get(master);
+ ObjectId newMasterId = cmd.getNewId();
+ try (Repository repo = repoManager.openRepository(args.getProject())) {
+ assertThat(repo.exactRef(master).getObjectId()).isEqualTo(cmd.getOldId());
+ assertThat(args.getRef(master)).hasValue(newMasterId);
+ args.getRevWalk().parseBody(args.getRevWalk().parseCommit(newMasterId));
+ } catch (IOException e) {
+ throw new AssertionError("failed checking new ref value", e);
+ }
+ projectsCalled.add(args.getProject().get());
+ if (projectsCalled.size() == 2) {
+ throw new ValidationException("time to fail");
+ }
}
- projectsCalled.add(args.getProject().get());
- if (projectsCalled.size() == 2) {
- throw new ValidationException("time to fail");
- }
- });
- submitWithConflict(change4.getChangeId(), "time to fail");
- assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get());
- for (PushOneCommit.Result change : changes) {
- change.assertChange(Change.Status.NEW, name(topic), admin);
- }
+ };
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ submitWithConflict(change4.getChangeId(), "time to fail");
+ assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get());
+ for (PushOneCommit.Result change : changes) {
+ change.assertChange(Change.Status.NEW, name(topic), admin);
+ }
- submit(change4.getChangeId());
- assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get(), keyA.get(), keyB.get());
- for (PushOneCommit.Result change : changes) {
- change.assertChange(Change.Status.MERGED, name(topic), admin);
+ submit(change4.getChangeId());
+ assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get(), keyA.get(), keyB.get());
+ for (PushOneCommit.Result change : changes) {
+ change.assertChange(Change.Status.MERGED, name(topic), admin);
+ }
}
}
@Test
- public void submitWithCommitAndItsMergeCommitTogether() throws Exception {
+ public void submitWithCommitAndItsMergeCommitTogether() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
// Create a stable branch and bootstrap it.
gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
@@ -820,8 +906,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
pushFactory.create(user.newIdent(), testRepo, "initial commit", "a.txt", "a");
PushOneCommit.Result change = push.to("refs/heads/stable");
- RevCommit stable = getRemoteHead(project, "stable");
- RevCommit master = getRemoteHead(project, "master");
+ RevCommit stable = projectOperations.project(project).getHead("stable");
+ RevCommit master = projectOperations.project(project).getHead("master");
assertThat(master).isEqualTo(initialHead);
assertThat(stable).isEqualTo(change.getCommit());
@@ -874,13 +960,13 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
assertMerged(mergeId);
testRepo.git().fetch().call();
RevWalk rw = testRepo.getRevWalk();
- master = rw.parseCommit(getRemoteHead(project, "master"));
+ master = rw.parseCommit(projectOperations.project(project).getHead("master"));
assertThat(rw.isMergedInto(merge, master)).isTrue();
assertThat(rw.isMergedInto(fix, master)).isTrue();
}
@Test
- public void retrySubmitSingleChangeOnLockFailure() throws Exception {
+ public void retrySubmitSingleChangeOnLockFailure() throws Throwable {
PushOneCommit.Result change = createChange();
String id = change.getChangeId();
approve(id);
@@ -897,7 +983,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
testRepo.git().fetch().call();
RevWalk rw = testRepo.getRevWalk();
- RevCommit master = rw.parseCommit(getRemoteHead(project, "master"));
+ RevCommit master = rw.parseCommit(projectOperations.project(project).getHead("master"));
RevCommit patchSet = parseCurrentRevision(rw, change.getChangeId());
assertThat(rw.isMergedInto(patchSet, master)).isTrue();
@@ -905,7 +991,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void retrySubmitAfterTornTopicOnLockFailure() throws Exception {
+ public void retrySubmitAfterTornTopicOnLockFailure() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
String topic = "test-topic";
@@ -940,13 +1026,13 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
repoA.git().fetch().call();
RevWalk rwA = repoA.getRevWalk();
- RevCommit masterA = rwA.parseCommit(getRemoteHead(keyA, "master"));
+ RevCommit masterA = rwA.parseCommit(projectOperations.project(keyA).getHead("master"));
RevCommit change1Ps = parseCurrentRevision(rwA, change1.getChangeId());
assertThat(rwA.isMergedInto(change1Ps, masterA)).isTrue();
repoB.git().fetch().call();
RevWalk rwB = repoB.getRevWalk();
- RevCommit masterB = rwB.parseCommit(getRemoteHead(keyB, "master"));
+ RevCommit masterB = rwB.parseCommit(projectOperations.project(keyB).getHead("master"));
RevCommit change2Ps = parseCurrentRevision(rwB, change2.getChangeId());
assertThat(rwB.isMergedInto(change2Ps, masterB)).isTrue();
@@ -954,14 +1040,14 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
@Test
- public void authorAndCommitDateAreEqual() throws Exception {
+ public void authorAndCommitDateAreEqual() throws Throwable {
assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
ConfigInput ci = new ConfigInput();
ci.matchAuthorToCommitterDate = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(ci);
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
@@ -975,12 +1061,12 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
submit(change2.getChangeId());
- assertAuthorAndCommitDateEquals(getRemoteHead());
+ assertAuthorAndCommitDateEquals(projectOperations.project(project).getHead("master"));
}
@Test
@TestProjectInput(rejectEmptyCommit = InheritableBoolean.FALSE)
- public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitAllowed() throws Exception {
+ public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitAllowed() throws Throwable {
assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
@@ -997,7 +1083,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
@Test
@TestProjectInput(rejectEmptyCommit = InheritableBoolean.TRUE)
- public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitNotAllowed() throws Exception {
+ public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitNotAllowed() throws Throwable {
assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
@@ -1010,18 +1096,20 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
ChangeApi revert2 = gApi.changes().id(change.getChangeId()).revert();
approve(revert2.id());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "Change "
- + revert2.get()._number
- + ": Change could not be merged because the commit is empty. "
- + "Project policy requires all commits to contain modifications to at least one file.");
- revert2.current().submit();
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> revert2.current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "Change "
+ + revert2.get()._number
+ + ": Change could not be merged because the commit is empty. Project policy"
+ + " requires all commits to contain modifications to at least one file.");
}
@Test
@TestProjectInput(rejectEmptyCommit = InheritableBoolean.FALSE)
- public void submitEmptyCommitPatchSetCanFastForward_emptyCommitAllowed() throws Exception {
+ public void submitEmptyCommitPatchSetCanFastForward_emptyCommitAllowed() throws Throwable {
ChangeInput ci = new ChangeInput();
ci.subject = "Empty change";
ci.project = project.get();
@@ -1033,7 +1121,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
@Test
@TestProjectInput(rejectEmptyCommit = InheritableBoolean.TRUE)
- public void submitEmptyCommitPatchSetCanFastForward_emptyCommitNotAllowed() throws Exception {
+ public void submitEmptyCommitPatchSetCanFastForward_emptyCommitNotAllowed() throws Throwable {
ChangeInput ci = new ChangeInput();
ci.subject = "Empty change";
ci.project = project.get();
@@ -1041,53 +1129,55 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
ChangeApi change = gApi.changes().create(ci);
approve(change.id());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "Change "
- + change.get()._number
- + ": Change could not be merged because the commit is empty. "
- + "Project policy requires all commits to contain modifications to at least one file.");
- change.current().submit();
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> change.current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "Change "
+ + change.get()._number
+ + ": Change could not be merged because the commit is empty. Project policy"
+ + " requires all commits to contain modifications to at least one file.");
}
@Test
@TestProjectInput(createEmptyCommit = false, rejectEmptyCommit = InheritableBoolean.TRUE)
- public void submitNonemptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Exception {
+ public void submitNonemptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Throwable {
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
PushOneCommit.Result change = createChange();
assertThat(change.getCommit().getParents()).isEmpty();
- Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ Map<BranchNameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
assertThat(actual).hasSize(1);
submit(change.getChangeId());
- assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
+ assertThat(projectOperations.project(project).getHead("master").getId())
+ .isEqualTo(change.getCommit());
assertTrees(project, actual);
}
@Test
@TestProjectInput(createEmptyCommit = false, rejectEmptyCommit = InheritableBoolean.TRUE)
- public void submitEmptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Exception {
+ public void submitEmptyCommitToEmptyRepoWithRejectEmptyCommit_allowed() throws Throwable {
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
PushOneCommit.Result change =
pushFactory
.create(admin.newIdent(), testRepo, "Change 1", ImmutableMap.of())
.to("refs/for/master");
change.assertOkStatus();
- // TODO(dborowitz): Use EMPTY_TREE_ID after upgrading to https://git.eclipse.org/r/127473
- assertThat(change.getCommit().getTree())
- .isEqualTo(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
+ assertThat(change.getCommit().getTree()).isEqualTo(EMPTY_TREE_ID);
- Map<Branch.NameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
+ Map<BranchNameKey, ObjectId> actual = fetchFromSubmitPreview(change.getChangeId());
assertThat(projectOperations.project(project).hasHead("master")).isFalse();
assertThat(actual).hasSize(1);
submit(change.getChangeId());
- assertThat(getRemoteHead().getId()).isEqualTo(change.getCommit());
+ assertThat(projectOperations.project(project).getHead("master").getId())
+ .isEqualTo(change.getCommit());
assertTrees(project, actual);
}
- private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Exception {
+ private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Throwable {
for (PushOneCommit.Result change : changes) {
try (BatchUpdate bu =
batchUpdateFactory.create(project, userFactory.create(admin.id()), TimeUtil.nowTs())) {
@@ -1106,7 +1196,61 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
}
- private void assertSubmitter(PushOneCommit.Result change) throws Exception {
+ @Test
+ @GerritConfig(name = "index.reindexAfterRefUpdate", value = "true")
+ public void submitSchedulesOpenChangesOfSameBranchForReindexing() throws Throwable {
+ // Create a merged change.
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "Merged Change", "foo.txt", "foo");
+ PushOneCommit.Result mergedChange = push.to("refs/for/master");
+ mergedChange.assertOkStatus();
+ approve(mergedChange.getChangeId());
+ submit(mergedChange.getChangeId());
+
+ // Create some open changes.
+ PushOneCommit.Result change1 = createChange();
+ PushOneCommit.Result change2 = createChange();
+ PushOneCommit.Result change3 = createChange();
+
+ // Create a branch with one open change.
+ BranchInput in = new BranchInput();
+ in.revision = projectOperations.project(project).getHead("master").name();
+ gApi.projects().name(project.get()).branch("dev").create(in);
+ PushOneCommit.Result changeOtherBranch = createChange("refs/for/dev");
+
+ ChangeIndexedListener changeIndexedListener = mock(ChangeIndexedListener.class);
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedListener)) {
+ // submit a change, this should trigger asynchronous reindexing of the open changes on the
+ // same branch
+ approve(change1.getChangeId());
+ submit(change1.getChangeId());
+ assertThat(gApi.changes().id(change1.getChangeId()).get().status)
+ .isEqualTo(ChangeStatus.MERGED);
+
+ // on submit the change that is submitted gets reindexed synchronously
+ verify(changeIndexedListener, atLeast(1))
+ .onChangeScheduledForIndexing(project.get(), change1.getChange().getId().get());
+ verify(changeIndexedListener, atLeast(1))
+ .onChangeIndexed(project.get(), change1.getChange().getId().get());
+
+ // the open changes on the same branch get reindexed asynchronously
+ verify(changeIndexedListener, times(1))
+ .onChangeScheduledForIndexing(project.get(), change2.getChange().getId().get());
+ verify(changeIndexedListener, times(1))
+ .onChangeScheduledForIndexing(project.get(), change3.getChange().getId().get());
+
+ // merged changes don't get reindexed
+ verify(changeIndexedListener, times(0))
+ .onChangeScheduledForIndexing(project.get(), mergedChange.getChange().getId().get());
+
+ // open changes on other branches don't get reindexed
+ verify(changeIndexedListener, times(0))
+ .onChangeScheduledForIndexing(project.get(), changeOtherBranch.getChange().getId().get());
+ }
+ }
+
+ private void assertSubmitter(PushOneCommit.Result change) throws Throwable {
ChangeInfo info = get(change.getChangeId(), ListChangesOption.MESSAGES);
assertThat(info.messages).isNotNull();
Iterable<String> messages = Iterables.transform(info.messages, i -> i.message);
@@ -1129,102 +1273,86 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
}
- protected void submit(String changeId) throws Exception {
+ protected void submit(String changeId) throws Throwable {
submit(changeId, new SubmitInput(), null, null);
}
- protected void submit(String changeId, SubmitInput input) throws Exception {
+ protected void submit(String changeId, SubmitInput input) throws Throwable {
submit(changeId, input, null, null);
}
- protected void submitWithConflict(String changeId, String expectedError) throws Exception {
+ protected void submitWithConflict(String changeId, String expectedError) throws Throwable {
submit(changeId, new SubmitInput(), ResourceConflictException.class, expectedError);
}
protected void submit(
String changeId,
SubmitInput input,
- Class<? extends RestApiException> expectedExceptionType,
+ @Nullable Class<? extends RestApiException> expectedExceptionType,
String expectedExceptionMsg)
- throws Exception {
+ throws Throwable {
approve(changeId);
if (expectedExceptionType == null) {
assertSubmittable(changeId);
+ } else {
+ requireNonNull(expectedExceptionMsg);
}
- try {
- gApi.changes().id(changeId).current().submit(input);
- if (expectedExceptionType != null) {
- fail("Expected exception of type " + expectedExceptionType.getSimpleName());
- }
- } catch (RestApiException e) {
- if (expectedExceptionType == null) {
- throw e;
- }
- // More verbose than using assertThat and/or ExpectedException, but gives
- // us the stack trace.
- if (!expectedExceptionType.isAssignableFrom(e.getClass())
- || !e.getMessage().equals(expectedExceptionMsg)) {
- throw new AssertionError(
- "Expected exception of type "
- + expectedExceptionType.getSimpleName()
- + " with message: \""
- + expectedExceptionMsg
- + "\" but got exception of type "
- + e.getClass().getSimpleName()
- + " with message \""
- + e.getMessage()
- + "\"",
- e);
- }
+ ThrowingRunnable submit = () -> gApi.changes().id(changeId).current().submit(input);
+ if (expectedExceptionType != null) {
+ RestApiException thrown = assertThrows(expectedExceptionType, submit);
+ assertThat(thrown).hasMessageThat().isEqualTo(expectedExceptionMsg);
return;
}
+ submit.run();
ChangeInfo change = gApi.changes().id(changeId).info();
assertMerged(change.changeId);
}
- protected void assertSubmittable(String changeId) throws Exception {
- assertThat(get(changeId, SUBMITTABLE).submittable).named("submit bit on ChangeInfo").isTrue();
+ protected void assertSubmittable(String changeId) throws Throwable {
+ assertWithMessage("submit bit on ChangeInfo")
+ .that(get(changeId, SUBMITTABLE).submittable)
+ .isTrue();
RevisionResource rsrc = parseCurrentRevisionResource(changeId);
UiAction.Description desc = submitHandler.getDescription(rsrc);
- assertThat(desc.isVisible()).named("visible bit on submit action").isTrue();
- assertThat(desc.isEnabled()).named("enabled bit on submit action").isTrue();
+ assertWithMessage("visible bit on submit action").that(desc.isVisible()).isTrue();
+ assertWithMessage("enabled bit on submit action").that(desc.isEnabled()).isTrue();
}
- protected void assertChangeMergedEvents(String... expected) throws Exception {
+ protected void assertChangeMergedEvents(String... expected) throws Throwable {
eventRecorder.assertChangeMergedEvents(project.get(), "refs/heads/master", expected);
}
- protected void assertRefUpdatedEvents(RevCommit... expected) throws Exception {
+ protected void assertRefUpdatedEvents(RevCommit... expected) throws Throwable {
eventRecorder.assertRefUpdatedEvents(project.get(), "refs/heads/master", expected);
}
protected void assertCurrentRevision(String changeId, int expectedNum, ObjectId expectedId)
- throws Exception {
+ throws Throwable {
ChangeInfo c = get(changeId, CURRENT_REVISION);
assertThat(c.currentRevision).isEqualTo(expectedId.name());
assertThat(c.revisions.get(expectedId.name())._number).isEqualTo(expectedNum);
- try (Repository repo = repoManager.openRepository(new Project.NameKey(c.project))) {
- String refName = new PatchSet.Id(new Change.Id(c._number), expectedNum).toRefName();
+ try (Repository repo = repoManager.openRepository(Project.nameKey(c.project))) {
+ String refName = PatchSet.id(Change.id(c._number), expectedNum).toRefName();
Ref ref = repo.exactRef(refName);
- assertThat(ref).named(refName).isNotNull();
+ assertWithMessage(refName).that(ref).isNotNull();
assertThat(ref.getObjectId()).isEqualTo(expectedId);
}
}
- protected void assertNew(String changeId) throws Exception {
+ protected void assertNew(String changeId) throws Throwable {
assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW);
}
- protected void assertApproved(String changeId) throws Exception {
+ protected void assertApproved(String changeId) throws Throwable {
assertApproved(changeId, admin);
}
- protected void assertApproved(String changeId, TestAccount user) throws Exception {
+ protected void assertApproved(String changeId, TestAccount user) throws Throwable {
ChangeInfo c = get(changeId, DETAILED_LABELS);
LabelInfo cr = c.labels.get("Code-Review");
assertThat(cr.all).hasSize(1);
assertThat(cr.all.get(0).value).isEqualTo(2);
- assertThat(new Account.Id(cr.all.get(0)._accountId)).isEqualTo(user.id());
+ assertThat(Account.id(cr.all.get(0)._accountId)).isEqualTo(user.id());
}
protected void assertMerged(String changeId) throws RestApiException {
@@ -1244,40 +1372,40 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
.isEqualTo(commit.getCommitterIdent().getTimeZone());
}
- protected void assertSubmitter(String changeId, int psId) throws Exception {
+ protected void assertSubmitter(String changeId, int psId) throws Throwable {
assertSubmitter(changeId, psId, admin);
}
- protected void assertSubmitter(String changeId, int psId, TestAccount user) throws Exception {
+ protected void assertSubmitter(String changeId, int psId, TestAccount user) throws Throwable {
Change c = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
ChangeNotes cn = notesFactory.createChecked(c);
PatchSetApproval submitter =
- approvalsUtil.getSubmitter(cn, new PatchSet.Id(cn.getChangeId(), psId));
+ approvalsUtil.getSubmitter(cn, PatchSet.id(cn.getChangeId(), psId));
assertThat(submitter).isNotNull();
assertThat(submitter.isLegacySubmit()).isTrue();
- assertThat(submitter.getAccountId()).isEqualTo(user.id());
+ assertThat(submitter.accountId()).isEqualTo(user.id());
}
- protected void assertNoSubmitter(String changeId, int psId) throws Exception {
+ protected void assertNoSubmitter(String changeId, int psId) throws Throwable {
Change c = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
ChangeNotes cn = notesFactory.createChecked(c);
PatchSetApproval submitter =
- approvalsUtil.getSubmitter(cn, new PatchSet.Id(cn.getChangeId(), psId));
+ approvalsUtil.getSubmitter(cn, PatchSet.id(cn.getChangeId(), psId));
assertThat(submitter).isNull();
}
protected void assertCherryPick(TestRepository<?> testRepo, boolean contentMerge)
- throws Exception {
+ throws Throwable {
assertRebase(testRepo, contentMerge);
- RevCommit remoteHead = getRemoteHead();
+ RevCommit remoteHead = projectOperations.project(project).getHead("master");
assertThat(remoteHead.getFooterLines("Reviewed-On")).isNotEmpty();
assertThat(remoteHead.getFooterLines("Reviewed-By")).isNotEmpty();
}
- protected void assertRebase(TestRepository<?> testRepo, boolean contentMerge) throws Exception {
+ protected void assertRebase(TestRepository<?> testRepo, boolean contentMerge) throws Throwable {
Repository repo = testRepo.getRepository();
RevCommit localHead = getHead(repo, "HEAD");
- RevCommit remoteHead = getRemoteHead();
+ RevCommit remoteHead = projectOperations.project(project).getHead("master");
assertThat(localHead.getId()).isNotEqualTo(remoteHead.getId());
assertThat(remoteHead.getParentCount()).isEqualTo(1);
if (!contentMerge) {
@@ -1286,7 +1414,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
assertThat(remoteHead.getShortMessage()).isEqualTo(localHead.getShortMessage());
}
- protected List<RevCommit> getRemoteLog(Project.NameKey project, String branch) throws Exception {
+ protected List<RevCommit> getRemoteLog(Project.NameKey project, String branch) throws Throwable {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
rw.markStart(rw.parseCommit(repo.exactRef("refs/heads/" + branch).getObjectId()));
@@ -1294,22 +1422,17 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
}
- protected List<RevCommit> getRemoteLog() throws Exception {
+ protected List<RevCommit> getRemoteLog() throws Throwable {
return getRemoteLog(project, "master");
}
- protected void addOnSubmitValidationListener(OnSubmitValidationListener listener) {
- assertThat(onSubmitValidatorHandle).isNull();
- onSubmitValidatorHandle = onSubmitValidationListeners.add("gerrit", listener);
- }
-
- private String getLatestDiff(Repository repo) throws Exception {
+ private String getLatestDiff(Repository repo) throws Throwable {
ObjectId oldTreeId = repo.resolve("HEAD~1^{tree}");
ObjectId newTreeId = repo.resolve("HEAD^{tree}");
return getLatestDiff(repo, oldTreeId, newTreeId);
}
- private String getLatestRemoteDiff() throws Exception {
+ private String getLatestRemoteDiff() throws Throwable {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
ObjectId oldTreeId = repo.resolve("refs/heads/master~1^{tree}");
@@ -1319,7 +1442,7 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
private String getLatestDiff(Repository repo, ObjectId oldTreeId, ObjectId newTreeId)
- throws Exception {
+ throws Throwable {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (DiffFormatter fmt = new DiffFormatter(out)) {
fmt.setRepository(repo);
@@ -1330,15 +1453,19 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
}
// TODO(hanwen): the submodule tests have a similar method; maybe we could share code?
- protected Project.NameKey createProjectForPush(SubmitType submitType) throws Exception {
+ protected Project.NameKey createProjectForPush(SubmitType submitType) throws Throwable {
Project.NameKey project = projectOperations.newProject().submitType(submitType).create();
- grant(project, "refs/heads/*", Permission.PUSH);
- grant(project, "refs/for/refs/heads/*", Permission.SUBMIT);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(adminGroupUuid()))
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(adminGroupUuid()))
+ .update();
return project;
}
protected PushOneCommit.Result createChange(
- String subject, String fileName, String content, String topic) throws Exception {
+ String subject, String fileName, String content, String topic) throws Throwable {
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content);
return push.to("refs/for/master/" + name(topic));
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index 36a09fd402..a4fa84b11c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -19,23 +19,26 @@ import static com.google.common.truth.TruthJUnit.assume;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public abstract class AbstractSubmitByMerge extends AbstractSubmit {
+ @Inject private ProjectOperations projectOperations;
@Test
- public void submitWithMerge() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithMerge() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit oldHead = getRemoteHead();
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
submit(change2.getChangeId());
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head.getParentCount()).isEqualTo(2);
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertThat(head.getParent(1)).isEqualTo(change2.getCommit());
@@ -43,17 +46,17 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge() throws Exception {
+ public void submitWithContentMerge() throws Throwable {
PushOneCommit.Result change = createChange("Change 1", "a.txt", "aaa\nbbb\nccc\n");
submit(change.getChangeId());
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
submit(change2.getChangeId());
- RevCommit oldHead = getRemoteHead();
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
testRepo.reset(change.getCommit());
PushOneCommit.Result change3 = createChange("Change 3", "a.txt", "bbb\nccc\n");
submit(change3.getChangeId());
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head.getParentCount()).isEqualTo(2);
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertThat(head.getParent(1)).isEqualTo(change3.getCommit());
@@ -61,12 +64,12 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge_Conflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithContentMerge_Conflict() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit oldHead = getRemoteHead();
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
submitWithConflict(
@@ -78,22 +81,23 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
+ "Change could not be merged due to a path conflict. "
+ "Please rebase the change locally "
+ "and upload the rebased commit for review.");
- assertThat(getRemoteHead()).isEqualTo(oldHead);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(oldHead);
}
@Test
@TestProjectInput(createEmptyCommit = false)
- public void submitMultipleCommitsToEmptyRepoAsFastForward() throws Exception {
+ public void submitMultipleCommitsToEmptyRepoAsFastForward() throws Throwable {
PushOneCommit.Result change1 = createChange();
PushOneCommit.Result change2 = createChange();
approve(change1.getChangeId());
submit(change2.getChangeId());
- assertThat(getRemoteHead().getId()).isEqualTo(change2.getCommit());
+ assertThat(projectOperations.project(project).getHead("master").getId())
+ .isEqualTo(change2.getCommit());
}
@Test
@TestProjectInput(createEmptyCommit = false)
- public void submitMultipleCommitsToEmptyRepoWithOneMerge() throws Exception {
+ public void submitMultipleCommitsToEmptyRepoWithOneMerge() throws Throwable {
assume().that(isSubmitWholeTopicEnabled()).isTrue();
PushOneCommit.Result change1 =
pushFactory
@@ -108,7 +112,7 @@ public abstract class AbstractSubmitByMerge extends AbstractSubmit {
approve(change1.getChangeId());
submit(change2.getChangeId());
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head.getParents()).hasLength(2);
assertThat(head.getParent(0)).isEqualTo(change1.getCommit());
assertThat(head.getParent(1)).isEqualTo(change2.getCommit());
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
index c12adfaf7f..fff67f3de1 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -17,21 +17,25 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -40,6 +44,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Test;
public abstract class AbstractSubmitByRebase extends AbstractSubmit {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Override
@@ -47,42 +52,41 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithRebase() throws Exception {
+ public void submitWithRebase() throws Throwable {
submitWithRebase(admin);
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithRebaseWithoutAddPatchSetPermission() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.block(u.getConfig(), Permission.ADD_PATCH_SET, REGISTERED_USERS, "refs/*");
- Util.allow(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, "refs/heads/*");
- Util.allow(
- u.getConfig(),
- Permission.forLabel(Util.codeReview().getName()),
- -2,
- 2,
- REGISTERED_USERS,
- "refs/heads/*");
- u.save();
- }
+ public void submitWithRebaseWithoutAddPatchSetPermission() throws Throwable {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.ADD_PATCH_SET).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
+ .add(
+ allowLabel(TestLabels.codeReview().getName())
+ .ref("refs/heads/*")
+ .group(REGISTERED_USERS)
+ .range(-2, 2))
+ .update();
submitWithRebase(user);
}
protected ImmutableList<PushOneCommit.Result> submitWithRebase(TestAccount submitter)
- throws Exception {
+ throws Throwable {
requestScopeOperations.setApiUser(submitter.id());
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
submit(change2.getChangeId());
assertRebase(testRepo, false);
- RevCommit headAfterSecondSubmit = getRemoteHead();
+ RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSecondSubmit.getParent(0)).isEqualTo(headAfterFirstSubmit);
assertApproved(change2.getChangeId(), submitter);
assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
@@ -102,11 +106,11 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
}
@Test
- public void submitWithRebaseMultipleChanges() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithRebaseMultipleChanges() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "content");
submit(change1.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
assertCurrentRevision(change1.getChangeId(), 2, headAfterFirstSubmit);
} else {
@@ -127,7 +131,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
assertApproved(change3.getChangeId());
assertApproved(change4.getChangeId());
- RevCommit headAfterSecondSubmit = parse(getRemoteHead());
+ RevCommit headAfterSecondSubmit = parse(projectOperations.project(project).getHead("master"));
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
@@ -163,7 +167,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
}
@Test
- public void submitWithRebaseMergeCommit() throws Exception {
+ public void submitWithRebaseMergeCommit() throws Throwable {
/*
* (HEAD, origin/master, origin/HEAD) Merge changes X,Y
|\
@@ -175,7 +179,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
|/
* Initial empty repository
*/
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
PushOneCommit change2Push =
@@ -193,7 +197,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
approve(change2.getChangeId());
submit(change2.getChangeId());
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
assertThat(newHead.getParentCount()).isEqualTo(2);
RevCommit headParent1 = parse(newHead.getParent(0).getId());
@@ -219,12 +223,12 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge_Conflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithContentMerge_Conflict() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
submitWithConflict(
@@ -232,7 +236,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
"Cannot rebase "
+ change2.getCommit().name()
+ ": The change could not be rebased due to a conflict during merge.");
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head).isEqualTo(headAfterFirstSubmit);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
assertNoSubmitter(change2.getChangeId(), 1);
@@ -241,7 +245,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
}
- protected RevCommit parse(ObjectId id) throws Exception {
+ protected RevCommit parse(ObjectId id) throws Throwable {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
RevCommit c = rw.parseCommit(id);
@@ -251,8 +255,8 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
}
@Test
- public void submitAfterReorderOfCommits() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitAfterReorderOfCommits() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
// Create two commits and push.
RevCommit c1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
@@ -271,15 +275,15 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
approve(id1);
approve(id2);
submit(id1);
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertRefUpdatedEvents(initialHead, headAfterSubmit);
assertChangeMergedEvents(id2, headAfterSubmit.name(), id1, headAfterSubmit.name());
}
@Test
- public void submitChangesAfterBranchOnSecond() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitChangesAfterBranchOnSecond() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
approve(change.getChangeId());
@@ -287,13 +291,13 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
PushOneCommit.Result change2 = createChange();
approve(change2.getChangeId());
Project.NameKey project = change2.getChange().change().getProject();
- Branch.NameKey branch = new Branch.NameKey(project, "branch");
+ BranchNameKey branch = BranchNameKey.create(project, "branch");
createBranchWithRevision(branch, change2.getCommit().getName());
gApi.changes().id(change2.getChangeId()).current().submit();
assertMerged(change2.getChangeId());
assertMerged(change.getChangeId());
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(this.project).getHead("master");
assertRefUpdatedEvents(initialHead, newHead);
assertChangeMergedEvents(
change.getChangeId(), newHead.name(), change2.getChangeId(), newHead.name());
@@ -301,8 +305,8 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitFastForwardIdenticalTree() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitFastForwardIdenticalTree() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "a");
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "a");
@@ -313,18 +317,18 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
testRepo.reset(initialHead);
PushOneCommit.Result change0 = createChange("Change 0", "b.txt", "b");
submit(change0.getChangeId());
- RevCommit headAfterChange0 = getRemoteHead();
+ RevCommit headAfterChange0 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange0.getShortMessage()).isEqualTo("Change 0");
submit(change1.getChangeId());
- RevCommit headAfterChange1 = getRemoteHead();
+ RevCommit headAfterChange1 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange1.getShortMessage()).isEqualTo("Change 1");
assertThat(headAfterChange0).isEqualTo(headAfterChange1.getParent(0));
// Do manual rebase first.
gApi.changes().id(change2.getChangeId()).current().rebase();
submit(change2.getChangeId());
- RevCommit headAfterChange2 = getRemoteHead();
+ RevCommit headAfterChange2 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange2.getShortMessage()).isEqualTo("Change 2");
assertThat(headAfterChange1).isEqualTo(headAfterChange2.getParent(0));
@@ -334,7 +338,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitChainOneByOne() throws Exception {
+ public void submitChainOneByOne() throws Throwable {
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
submit(change1.getChangeId());
@@ -343,7 +347,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitChainFailsOnRework() throws Exception {
+ public void submitChainFailsOnRework() throws Throwable {
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
RevCommit headAfterChange1 = change1.getCommit();
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
@@ -351,7 +355,7 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
change1 =
amendChange(change1.getChangeId(), "subject 1 amend", "fileName 2", "rework content 2");
submit(change1.getChangeId());
- headAfterChange1 = getRemoteHead();
+ headAfterChange1 = projectOperations.project(project).getHead("master");
submitWithConflict(
change2.getChangeId(),
@@ -359,13 +363,13 @@ public abstract class AbstractSubmitByRebase extends AbstractSubmit {
+ change2.getCommit().getName()
+ ": "
+ "The change could not be rebased due to a conflict during merge.");
- assertThat(getRemoteHead()).isEqualTo(headAfterChange1);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterChange1);
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitChainOneByOneManualRebase() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitChainOneByOneManualRebase() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index e7f3d54bb4..dda7bbd89a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -22,9 +22,14 @@ import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVI
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.ActionVisitor;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ListChangesOption;
@@ -32,11 +37,8 @@ import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.change.RevisionJson;
+import com.google.gerrit.server.change.testing.TestChangeETagComputation;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
@@ -45,8 +47,6 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jgit.lib.Config;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
public class ActionsIT extends AbstractDaemonTest {
@@ -55,23 +55,9 @@ public class ActionsIT extends AbstractDaemonTest {
return submitWholeTopicEnabledConfig();
}
- @Inject private DynamicSet<ActionVisitor> actionVisitors;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private RevisionJson.Factory revisionJsonFactory;
-
- private RegistrationHandle visitorHandle;
-
- @Before
- public void setUp() {
- visitorHandle = null;
- }
-
- @After
- public void tearDown() {
- if (visitorHandle != null) {
- visitorHandle.remove();
- }
- }
+ @Inject private ExtensionRegistry extensionRegistry;
protected Map<String, ActionInfo> getActions(String id) throws Exception {
return gApi.changes().id(id).revision(1).actions();
@@ -206,6 +192,45 @@ public class ActionsIT extends AbstractDaemonTest {
}
@Test
+ public void pluginCanContributeToETagComputation() throws Exception {
+ String change = createChange().getChangeId();
+ String oldETag = getETag(change);
+
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag("foo"))) {
+ assertThat(getETag(change)).isNotEqualTo(oldETag);
+ }
+
+ assertThat(getETag(change)).isEqualTo(oldETag);
+ }
+
+ @Test
+ public void returningNullFromETagComputationDoesNotBreakGerrit() throws Exception {
+ String change = createChange().getChangeId();
+ String oldETag = getETag(change);
+
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(TestChangeETagComputation.withETag(null))) {
+ assertThat(getETag(change)).isEqualTo(oldETag);
+ }
+ }
+
+ @Test
+ public void throwingExceptionFromETagComputationDoesNotBreakGerrit() throws Exception {
+ String change = createChange().getChangeId();
+ String oldETag = getETag(change);
+
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ TestChangeETagComputation.withException(
+ new StorageException("exception during test")))) {
+ assertThat(getETag(change)).isEqualTo(oldETag);
+ }
+ }
+
+ @Test
public void revisionActionsTwoChangesInTopic_conflicting() throws Exception {
String changeId = createChangeWithTopic().getChangeId();
approve(changeId);
@@ -331,19 +356,18 @@ public class ActionsIT extends AbstractDaemonTest {
assertThat(origActions.keySet()).containsAtLeast("followup", "abandon");
assertThat(origActions.get("abandon").label).isEqualTo("Abandon");
- Visitor v = new Visitor();
- visitorHandle = actionVisitors.add("gerrit", v);
-
- Map<String, ActionInfo> newActions =
- gApi.changes().id(id).get(EnumSet.of(ListChangesOption.CHANGE_ACTIONS)).actions;
+ try (Registration registration = extensionRegistry.newRegistration().add(new Visitor())) {
+ Map<String, ActionInfo> newActions =
+ gApi.changes().id(id).get(EnumSet.of(ListChangesOption.CHANGE_ACTIONS)).actions;
- Set<String> expectedNames = new TreeSet<>(origActions.keySet());
- expectedNames.remove("followup");
- assertThat(newActions.keySet()).isEqualTo(expectedNames);
+ Set<String> expectedNames = new TreeSet<>(origActions.keySet());
+ expectedNames.remove("followup");
+ assertThat(newActions.keySet()).isEqualTo(expectedNames);
- ActionInfo abandon = newActions.get("abandon");
- assertThat(abandon).isNotNull();
- assertThat(abandon.label).isEqualTo("Abandon All Hope");
+ ActionInfo abandon = newActions.get("abandon");
+ assertThat(abandon).isNotNull();
+ assertThat(abandon.label).isEqualTo("Abandon All Hope");
+ }
}
@Test
@@ -351,7 +375,7 @@ public class ActionsIT extends AbstractDaemonTest {
String id = createChange().getChangeId();
amendChange(id);
ChangeInfo origChange = gApi.changes().id(id).get(CHANGE_ACTIONS);
- Change.Id changeId = new Change.Id(origChange._number);
+ Change.Id changeId = Change.id(origChange._number);
class Visitor implements ActionVisitor {
@Override
@@ -380,22 +404,22 @@ public class ActionsIT extends AbstractDaemonTest {
assertThat(origActions.keySet()).containsAtLeast("cherrypick", "rebase");
assertThat(origActions.get("rebase").label).isEqualTo("Rebase");
- Visitor v = new Visitor();
- visitorHandle = actionVisitors.add("gerrit", v);
-
- // Test different codepaths within ActionJson...
- // ...via revision API.
- visitedCurrentRevisionActionsAssertions(origActions, gApi.changes().id(id).current().actions());
-
- // ...via change API with option.
- EnumSet<ListChangesOption> opts = EnumSet.of(CURRENT_ACTIONS, CURRENT_REVISION);
- ChangeInfo changeInfo = gApi.changes().id(id).get(opts);
- RevisionInfo revisionInfo = Iterables.getOnlyElement(changeInfo.revisions.values());
- visitedCurrentRevisionActionsAssertions(origActions, revisionInfo.actions);
-
- // ...via ChangeJson directly.
- ChangeData cd = changeDataFactory.create(project, changeId);
- revisionJsonFactory.create(opts).getRevisionInfo(cd, cd.patchSet(new PatchSet.Id(changeId, 1)));
+ try (Registration registration = extensionRegistry.newRegistration().add(new Visitor())) {
+ // Test different codepaths within ActionJson...
+ // ...via revision API.
+ visitedCurrentRevisionActionsAssertions(
+ origActions, gApi.changes().id(id).current().actions());
+
+ // ...via change API with option.
+ EnumSet<ListChangesOption> opts = EnumSet.of(CURRENT_ACTIONS, CURRENT_REVISION);
+ ChangeInfo changeInfo = gApi.changes().id(id).get(opts);
+ RevisionInfo revisionInfo = Iterables.getOnlyElement(changeInfo.revisions.values());
+ visitedCurrentRevisionActionsAssertions(origActions, revisionInfo.actions);
+
+ // ...via ChangeJson directly.
+ ChangeData cd = changeDataFactory.create(project, changeId);
+ revisionJsonFactory.create(opts).getRevisionInfo(cd, cd.patchSet(PatchSet.id(changeId, 1)));
+ }
}
private void visitedCurrentRevisionActionsAssertions(
@@ -440,18 +464,17 @@ public class ActionsIT extends AbstractDaemonTest {
assertThat(origActions.keySet()).containsExactly("description");
assertThat(origActions.get("description").label).isEqualTo("Edit Description");
- Visitor v = new Visitor();
- visitorHandle = actionVisitors.add("gerrit", v);
-
- // Unlike for the current revision, actions for old revisions are only available via the
- // revision API.
- Map<String, ActionInfo> newActions = gApi.changes().id(id).revision(1).actions();
- assertThat(newActions).isNotNull();
- assertThat(newActions.keySet()).isEqualTo(origActions.keySet());
+ try (Registration registration = extensionRegistry.newRegistration().add(new Visitor())) {
+ // Unlike for the current revision, actions for old revisions are only available via the
+ // revision API.
+ Map<String, ActionInfo> newActions = gApi.changes().id(id).revision(1).actions();
+ assertThat(newActions).isNotNull();
+ assertThat(newActions.keySet()).isEqualTo(origActions.keySet());
- ActionInfo description = newActions.get("description");
- assertThat(description).isNotNull();
- assertThat(description.label).isEqualTo("Describify");
+ ActionInfo description = newActions.get("description");
+ assertThat(description).isNotNull();
+ assertThat(description.label).isEqualTo("Describify");
+ }
}
private void commonActionsAssertions(Map<String, ActionInfo> actions) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
index 2d6227bd47..0f5def699a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
@@ -15,47 +15,38 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.AssigneeInput;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.transport.RefSpec;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
import org.junit.Test;
@NoHttpd
+@UseClockStep
public class AssigneeIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
-
@Test
public void getNoAssignee() throws Exception {
PushOneCommit.Result r = createChange();
@@ -93,16 +84,32 @@ public class AssigneeIT extends AbstractDaemonTest {
}
@Test
- public void assigneeAddedAsReviewer() throws Exception {
- ReviewerState state = ReviewerState.CC;
+ public void assigneeAddedAsCc() throws Exception {
PushOneCommit.Result r = createChange();
- Iterable<AccountInfo> reviewers = getReviewers(r, state);
+ Iterable<AccountInfo> reviewers = getReviewers(r, ReviewerState.CC);
assertThat(reviewers).isNull();
+
assertThat(setAssignee(r, user.email())._accountId).isEqualTo(user.id().get());
- reviewers = getReviewers(r, state);
+ reviewers = getReviewers(r, ReviewerState.CC);
assertThat(reviewers).hasSize(1);
- AccountInfo reviewer = Iterables.getFirst(reviewers, null);
- assertThat(reviewer._accountId).isEqualTo(user.id().get());
+ assertThat(Iterables.getFirst(reviewers, null)._accountId).isEqualTo(user.id().get());
+ assertThat(getReviewers(r, ReviewerState.REVIEWER)).isNull();
+ }
+
+ @Test
+ public void assigneeStaysReviewer() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).addReviewer(user.email());
+ Iterable<AccountInfo> reviewers = getReviewers(r, ReviewerState.REVIEWER);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getFirst(reviewers, null)._accountId).isEqualTo(user.id().get());
+ assertThat(getReviewers(r, ReviewerState.CC)).isNull();
+
+ assertThat(setAssignee(r, user.email())._accountId).isEqualTo(user.id().get());
+ reviewers = getReviewers(r, ReviewerState.REVIEWER);
+ assertThat(reviewers).hasSize(1);
+ assertThat(Iterables.getFirst(reviewers, null)._accountId).isEqualTo(user.id().get());
+ assertThat(getReviewers(r, ReviewerState.CC)).isNull();
}
@Test
@@ -130,20 +137,17 @@ public class AssigneeIT extends AbstractDaemonTest {
public void setAssigneeToInactiveUser() throws Exception {
PushOneCommit.Result r = createChange();
gApi.accounts().id(user.id().get()).setActive(false);
- try {
- setAssignee(r, user.email());
- assert_().fail("expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- "Account '"
- + user.email()
- + "' only matches inactive accounts. To use an inactive account, retry with one"
- + " of the following exact account IDs:\n"
- + user.id()
- + ": User <user@example.com>");
- }
+ UnresolvableAccountException thrown =
+ assertThrows(UnresolvableAccountException.class, () -> setAssignee(r, user.email()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Account '"
+ + user.email()
+ + "' only matches inactive accounts. To use an inactive account, retry with one"
+ + " of the following exact account IDs:\n"
+ + user.id()
+ + ": User <user@example.com>");
}
@Test
@@ -159,24 +163,26 @@ public class AssigneeIT extends AbstractDaemonTest {
git().fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config")).call();
testRepo.reset(RefNames.REFS_CONFIG);
PushOneCommit.Result r = createChange("refs/for/refs/meta/config");
- exception.expect(AuthException.class);
- exception.expectMessage("read not permitted");
- setAssignee(r, user.email());
+ AuthException thrown = assertThrows(AuthException.class, () -> setAssignee(r, user.email()));
+ assertThat(thrown).hasMessageThat().contains("read not permitted");
}
@Test
public void setAssigneeNotAllowedWithoutPermission() throws Exception {
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("not permitted");
- setAssignee(r, user.email());
+ AuthException thrown = assertThrows(AuthException.class, () -> setAssignee(r, user.email()));
+ assertThat(thrown).hasMessageThat().contains("not permitted");
}
@Test
public void setAssigneeAllowedWithPermission() throws Exception {
PushOneCommit.Result r = createChange();
- grant(project, "refs/heads/master", Permission.EDIT_ASSIGNEE, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.EDIT_ASSIGNEE).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertThat(setAssignee(r, user.email())._accountId).isEqualTo(user.id().get());
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/BUILD b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
index 7ccf10f717..1eddccec48 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
@@ -20,14 +20,14 @@ acceptance_tests(
],
)
-acceptance_tests(
- srcs = SUBMIT_TESTS,
- group = "rest_change_submit",
+[acceptance_tests(
+ srcs = [f],
+ group = f[:f.index(".")],
labels = ["rest"],
deps = [
":submit_util",
],
-)
+) for f in SUBMIT_TESTS]
java_library(
name = "submit_util",
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeIdIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeIdIT.java
index 3f1608c2a1..bc52681fdb 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeIdIT.java
@@ -17,8 +17,8 @@ package com.google.gerrit.acceptance.rest.change;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.ChangeApi;
-import com.google.gerrit.reviewdb.client.Change;
import org.junit.Test;
public class ChangeIdIT extends AbstractDaemonTest {
@@ -98,14 +98,14 @@ public class ChangeIdIT extends AbstractDaemonTest {
// This test tests a redirect that is primarily intended for the UI (though the backend doesn't
// really care who the caller is). The redirect rewrites a shorthand change number URL (/123) to
// it's canonical long form (/c/project/+/123).
- int changeId = createChange().getChange().getId().id;
+ int changeId = createChange().getChange().getId().get();
RestResponse res = anonymousRestSession.get("/" + changeId);
res.assertTemporaryRedirect("/c/" + project.get() + "/+/" + changeId + "/");
}
@Test
public void changeNumberRedirectsWithTrailingSlash() throws Exception {
- int changeId = createChange().getChange().getId().id;
+ int changeId = createChange().getChange().getId().get();
RestResponse res = anonymousRestSession.get("/" + changeId + "/");
res.assertTemporaryRedirect("/c/" + project.get() + "/+/" + changeId + "/");
}
@@ -125,8 +125,8 @@ public class ChangeIdIT extends AbstractDaemonTest {
@Test
public void hiddenChangeNotFound() throws Exception {
Change.Id changeId = createChange().getChange().getId();
- gApi.changes().id(changeId.id).setPrivate(true, null);
- RestResponse res = anonymousRestSession.get("/" + changeId.id);
+ gApi.changes().id(changeId.get()).setPrivate(true, null);
+ RestResponse res = anonymousRestSession.get("/" + changeId.get());
res.assertNotFound();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java
index 59b6e29293..47fb20a43e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeIncludedInIT.java
@@ -15,20 +15,25 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.TagInput;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.inject.Inject;
import org.junit.Test;
@NoHttpd
public class ChangeIncludedInIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
@Test
public void includedInOpenChange() throws Exception {
Result result = createChange();
@@ -49,13 +54,17 @@ public class ChangeIncludedInIT extends AbstractDaemonTest {
.containsExactly("master");
assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags).isEmpty();
- grant(project, R_TAGS + "*", Permission.CREATE_TAG);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE_TAG).ref(R_TAGS + "*").group(adminGroupUuid()))
+ .update();
gApi.projects().name(project.get()).tag("test-tag").create(new TagInput());
assertThat(gApi.changes().id(result.getChangeId()).includedIn().tags)
.containsExactly("test-tag");
- createBranch(new Branch.NameKey(project.get(), "test-branch"));
+ createBranch(BranchNameKey.create(project, "test-branch"));
assertThat(gApi.changes().id(result.getChangeId()).includedIn().branches)
.containsExactly("master", "test-branch");
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index 51c5fc8686..8cfcbabb05 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -14,12 +14,15 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.parseCommitMessageRange;
import static com.google.gerrit.server.restapi.change.DeleteChangeMessage.createNewChangeMessage;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toSet;
import static org.eclipse.jgit.util.RawParseUtils.decode;
@@ -28,8 +31,12 @@ import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseTimezone;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.DeleteChangeMessageInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -37,10 +44,8 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -49,29 +54,16 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.RawParseUtils;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+@UseClockStep
+@UseTimezone(timezone = "US/Eastern")
@RunWith(ConfigSuite.class)
public class ChangeMessagesIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- private String systemTimeZone;
-
- @Before
- public void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
@Test
public void messagesNotReturnedByDefault() throws Exception {
String changeId = createChange().getChangeId();
@@ -159,17 +151,17 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
int changeNum = createOneChangeWithMultipleChangeMessagesInHistory();
requestScopeOperations.setApiUser(user.id());
- try {
- deleteOneChangeMessage(changeNum, 0, user, "spam");
- fail("expected AuthException");
- } catch (AuthException e) {
- assertThat(e.getMessage()).isEqualTo("administrate server not permitted");
- }
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> deleteOneChangeMessage(changeNum, 0, user, "spam"));
+ assertThat(thrown).hasMessageThat().isEqualTo("administrate server not permitted");
}
@Test
public void deleteCanBeAppliedWithAdministrateServerCapability() throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ADMINISTRATE_SERVER);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .update();
int changeNum = createOneChangeWithMultipleChangeMessagesInHistory();
requestScopeOperations.setApiUser(user.id());
deleteOneChangeMessage(changeNum, 0, user, "spam");
@@ -179,12 +171,15 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
public void deleteCannotBeAppliedWithEmptyChangeMessageUuid() throws Exception {
String changeId = createChange().getChangeId();
- try {
- gApi.changes().id(changeId).message("").delete(new DeleteChangeMessageInput("spam"));
- fail("expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- assertThat(e.getMessage()).isEqualTo("change message not found");
- }
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () ->
+ gApi.changes()
+ .id(changeId)
+ .message("")
+ .delete(new DeleteChangeMessageInput("spam")));
+ assertThat(thrown).hasMessageThat().isEqualTo("change message not found");
}
@Test
@@ -194,12 +189,11 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
String id = "8473b95934b5732ac55d26311a706c9c2bde9941";
input.reason = "spam";
- try {
- gApi.changes().id(changeId).message(id).delete(input);
- fail("expected ResourceNotFoundException");
- } catch (ResourceNotFoundException e) {
- assertThat(e.getMessage()).isEqualTo(String.format("change message %s not found", id));
- }
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeId).message(id).delete(input));
+ assertThat(thrown).hasMessageThat().isEqualTo(String.format("change message %s not found", id));
}
@Test
@@ -279,7 +273,7 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
List<ChangeMessageInfo> messagesBeforeDeletion = gApi.changes().id(changeNum).messages();
List<CommentInfo> commentsBefore = getChangeSortedComments(changeNum);
- List<RevCommit> commitsBefore = getChangeMetaCommitsInReverseOrder(new Change.Id(changeNum));
+ List<RevCommit> commitsBefore = getChangeMetaCommitsInReverseOrder(Change.id(changeNum));
String id = messagesBeforeDeletion.get(deletedMessageIndex).id;
DeleteChangeMessageInput input = new DeleteChangeMessageInput(reason);
@@ -306,8 +300,8 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
int deletedMessageIndex,
TestAccount deletedBy,
String deleteReason) {
- assertThat(messagesAfterDeletion)
- .named("after: %s; before: %s", messagesAfterDeletion, messagesBeforeDeletion)
+ assertWithMessage("after: %s; before: %s", messagesAfterDeletion, messagesBeforeDeletion)
+ .that(messagesAfterDeletion)
.hasSize(messagesBeforeDeletion.size());
for (int i = 0; i < messagesAfterDeletion.size(); ++i) {
@@ -340,8 +334,7 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
TestAccount deletedBy,
String deleteReason)
throws Exception {
- List<RevCommit> commitsAfterDeletion =
- getChangeMetaCommitsInReverseOrder(new Change.Id(changeNum));
+ List<RevCommit> commitsAfterDeletion = getChangeMetaCommitsInReverseOrder(Change.id(changeNum));
assertThat(commitsAfterDeletion).hasSize(commitsBeforeDeletion.size());
for (int i = 0; i < commitsBeforeDeletion.size(); i++) {
@@ -356,8 +349,8 @@ public class ChangeMessagesIT extends AbstractDaemonTest {
parseCommitMessageRange(commitBefore);
Optional<ChangeNoteUtil.CommitMessageRange> rangeAfter =
parseCommitMessageRange(commitAfter);
- assertThat(rangeBefore.isPresent()).isTrue();
- assertThat(rangeAfter.isPresent()).isTrue();
+ assertThat(rangeBefore).isPresent();
+ assertThat(rangeAfter).isPresent();
String subjectBefore =
decode(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
index d51221e1b2..10194eb075 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -14,6 +14,11 @@
package com.google.gerrit.acceptance.rest.change;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.blockLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -21,10 +26,10 @@ import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -121,8 +126,7 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
}
private void assertApproveFails(TestAccount a, String changeId) throws Exception {
- exception.expect(AuthException.class);
- approve(a, changeId);
+ assertThrows(AuthException.class, () -> approve(a, changeId));
}
private void grantApproveToChangeOwner(Project.NameKey project) throws Exception {
@@ -139,11 +143,24 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
private void grantApprove(Project.NameKey project, AccountGroup.UUID groupUUID, boolean exclusive)
throws Exception {
- grantLabel("Code-Review", -2, 2, project, "refs/heads/*", false, groupUUID, exclusive);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(groupUUID).range(-2, 2))
+ .setExclusiveGroup(labelPermissionKey("Code-Review").ref("refs/heads/*"), exclusive)
+ .update();
}
private void blockApproveForChangeOwner(Project.NameKey project) throws Exception {
- blockLabel("Code-Review", -2, 2, SystemGroupBackend.CHANGE_OWNER, "refs/heads/*", project);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ blockLabel("Code-Review")
+ .ref("refs/heads/*")
+ .group(SystemGroupBackend.CHANGE_OWNER)
+ .range(-2, 2))
+ .update();
}
private String createMyChange(TestRepository<InMemoryRepository> testRepo) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index 173b78d9c2..fbe6533766 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -15,11 +15,14 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -31,8 +34,10 @@ import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -49,7 +54,6 @@ import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.ReviewerUpdateInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ReviewerAdder;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gson.stream.JsonReader;
@@ -66,6 +70,7 @@ import org.junit.Test;
public class ChangeReviewersIT extends AbstractDaemonTest {
@Inject private GroupOperations groupOperations;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
@@ -332,20 +337,18 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
assertThat(result.reviewers).isNotNull();
assertThat(result.reviewers).hasSize(1);
- // Verify reviewer state. Both admin and user should be REVIEWERs now,
- // because admin gets forced into REVIEWER state by virtue of being owner.
+ // Verify reviewer state.
c = gApi.changes().id(r.getChangeId()).get();
- assertReviewers(c, REVIEWER, admin, user);
+ assertReviewers(c, REVIEWER, user);
assertReviewers(c, CC);
label = c.labels.get("Code-Review");
assertThat(label).isNotNull();
assertThat(label.all).isNotNull();
- assertThat(label.all).hasSize(2);
+ assertThat(label.all).hasSize(1);
Map<Integer, Integer> approvals = new HashMap<>();
for (ApprovalInfo approval : label.all) {
approvals.put(approval._accountId, approval.value);
}
- assertThat(approvals).containsEntry(admin.id().get(), 0);
assertThat(approvals).containsEntry(user.id().get(), 0);
// Comment as user without voting. This should delete the approval and
@@ -360,17 +363,16 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
// Verify reviewer state.
c = gApi.changes().id(r.getChangeId()).get();
- assertReviewers(c, REVIEWER, admin, user);
+ assertReviewers(c, REVIEWER, user);
assertReviewers(c, CC);
label = c.labels.get("Code-Review");
assertThat(label).isNotNull();
assertThat(label.all).isNotNull();
- assertThat(label.all).hasSize(2);
+ assertThat(label.all).hasSize(1);
approvals.clear();
for (ApprovalInfo approval : label.all) {
approvals.put(approval._accountId, approval.value);
}
- assertThat(approvals).containsEntry(admin.id().get(), 0);
assertThat(approvals).containsEntry(user.id().get(), 0);
}
@@ -386,8 +388,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
assertThat(result.reviewers).isNotNull();
assertThat(result.reviewers).hasSize(2);
- // Verify reviewer and CC were added. If not in NoteDb read mode, both
- // parties will be returned as CCed.
+ // Verify reviewer and CC were added.
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
assertReviewers(c, REVIEWER, admin, user);
assertReviewers(c, CC, observer);
@@ -488,7 +489,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
}
@Test
- public void noteDbAddReviewerToReviewerChangeInfo() throws Exception {
+ public void addReviewerToReviewerChangeInfo() throws Exception {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
AddReviewerInput in = new AddReviewerInput();
@@ -502,7 +503,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
gApi.changes().id(changeId).current().review(ReviewInput.dislike());
requestScopeOperations.setApiUser(user.id());
- // NoteDb adds reviewer to a change on every review.
+ // By posting a review the user is added as reviewer.
gApi.changes().id(changeId).current().review(ReviewInput.dislike());
deleteReviewer(changeId, user).assertNoContent();
@@ -662,9 +663,11 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(r.getChangeId()).current().review(new ReviewInput().label("Code-Review", 1));
requestScopeOperations.setApiUser(newUser.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -674,7 +677,11 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
// This test creates a new user so that it can explicitly check the REMOVE_REVIEWER permission
// rather than bypassing the check because of project or ref ownership.
TestAccount newUser = createAccounts(1, name("foo")).get(0);
- grant(project, RefNames.REFS + "*", Permission.REMOVE_REVIEWER, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.REMOVE_REVIEWER).ref(RefNames.REFS + "*").group(REGISTERED_USERS))
+ .update();
gApi.changes().id(r.getChangeId()).addReviewer(user.email());
assertThatUserIsOnlyReviewer(r.getChangeId());
@@ -690,9 +697,11 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
gApi.changes().id(r.getChangeId()).addReviewer(user.email());
requestScopeOperations.setApiUser(newUser.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -705,9 +714,11 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
input.state = ReviewerState.CC;
gApi.changes().id(r.getChangeId()).addReviewer(input);
requestScopeOperations.setApiUser(newUser.id());
- exception.expect(AuthException.class);
- exception.expectMessage("remove reviewer not permitted");
- gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove());
+ assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted");
}
@Test
@@ -742,6 +753,81 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(r.getChangeId()).addReviewer(input).ccs).isEmpty();
}
+ @Test
+ public void moveCcToReviewer() throws Exception {
+ // Create a change and add 'user' as CC.
+ String changeId = createChange().getChangeId();
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = user.email();
+ reviewerInput.state = ReviewerState.CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+
+ // Verify that 'user' is a CC on the change and that there are no reviewers.
+ ChangeInfo c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> ccs = c.reviewers.get(CC);
+ assertThat(ccs).isNotNull();
+ assertThat(ccs).hasSize(1);
+ assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(REVIEWER)).isNull();
+
+ // Move 'user' from CC to reviewer.
+ gApi.changes().id(changeId).addReviewer(user.id().toString());
+
+ // Verify that 'user' is a reviewer on the change now and that there are no CCs.
+ c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(CC)).isNull();
+ }
+
+ @Test
+ public void moveReviewerToCc() throws Exception {
+ // Allow everyone to approve changes.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .update();
+
+ // Create a change and add 'user' as reviewer.
+ String changeId = createChange().getChangeId();
+ gApi.changes().id(changeId).addReviewer(user.id().toString());
+
+ // Verify that 'user' is a reviewer on the change and that there are no CCs.
+ ChangeInfo c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(CC)).isNull();
+
+ // Let 'user' approve the change and verify that the change has the approval.
+ requestScopeOperations.setApiUser(user.id());
+ approve(changeId);
+ c = gApi.changes().id(changeId).get();
+ assertThat(c.labels.get("Code-Review").approved._accountId).isEqualTo(user.id().get());
+
+ // Move 'user' from reviewer to CC.
+ requestScopeOperations.setApiUser(admin.id());
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = user.id().toString();
+ reviewerInput.state = CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+
+ // Verify that 'user' is a CC on the change now and that there are no reviewers.
+ c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> ccs = c.reviewers.get(CC);
+ assertThat(ccs).isNotNull();
+ assertThat(ccs).hasSize(1);
+ assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(REVIEWER)).isNull();
+
+ // Verify that the approval of 'user' is still there.
+ assertThat(c.labels.get("Code-Review").approved._accountId).isEqualTo(user.id().get());
+ }
+
private void assertThatUserIsOnlyReviewer(String changeId) throws Exception {
AccountInfo userInfo = new AccountInfo(user.fullName(), user.getEmailAddress().getEmail());
userInfo._accountId = user.id().get();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index 9a907aaee3..243991b96c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -15,47 +15,46 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Before;
import org.junit.Test;
public class ConfigChangeIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
public void setUp() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.OWNER, REGISTERED_USERS, "refs/*");
- Util.allow(u.getConfig(), Permission.PUSH, REGISTERED_USERS, "refs/for/refs/meta/config");
- Util.allow(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, RefNames.REFS_CONFIG);
- u.save();
- }
-
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/for/refs/meta/config").group(REGISTERED_USERS))
+ .add(allow(Permission.SUBMIT).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
fetchRefsMetaConfig();
}
@@ -75,8 +74,8 @@ public class ConfigChangeIT extends AbstractDaemonTest {
}
private String testUpdateProjectConfig() throws Exception {
- Config cfg = readProjectConfig();
- assertThat(cfg.getString("project", null, "description")).isNull();
+ Config cfg = projectOperations.project(project).getConfig();
+ assertThat(cfg).stringValue("project", null, "description").isNull();
String desc = "new project description";
cfg.setString("project", null, "description", desc);
@@ -89,7 +88,12 @@ public class ConfigChangeIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(id).info().status).isEqualTo(ChangeStatus.MERGED);
assertThat(gApi.projects().name(project.get()).get().description).isEqualTo(desc);
fetchRefsMetaConfig();
- assertThat(readProjectConfig().getString("project", null, "description")).isEqualTo(desc);
+ assertThat(
+ projectOperations
+ .project(project)
+ .getConfig()
+ .getString("project", null, "description"))
+ .isEqualTo(desc);
String changeRev = gApi.changes().id(id).get().currentRevision;
String branchRev =
gApi.projects().name(project.get()).branch(RefNames.REFS_CONFIG).get().revision;
@@ -107,33 +111,31 @@ public class ConfigChangeIT extends AbstractDaemonTest {
gApi.projects().create(parent);
requestScopeOperations.setApiUser(user.id());
- Config cfg = readProjectConfig();
- assertThat(cfg.getString("access", null, "inheritFrom")).isAnyOf(null, allProjects.get());
+ Config cfg = projectOperations.project(project).getConfig();
+ assertThat(cfg).stringValue("access", null, "inheritFrom").isAnyOf(null, allProjects.get());
cfg.setString("access", null, "inheritFrom", parent.name);
PushOneCommit.Result r = createConfigChange(cfg);
String id = r.getChangeId();
gApi.changes().id(id).current().review(ReviewInput.approve());
- try {
- gApi.changes().id(id).current().submit();
- fail("expected submit to fail");
- } catch (ResourceConflictException e) {
- int n = gApi.changes().id(id).info()._number;
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- "Failed to submit 1 change due to the following problems:\n"
- + "Change "
- + n
- + ": Change contains a project configuration that"
- + " changes the parent project.\n"
- + "The change must be submitted by a Gerrit administrator.");
- }
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(id).current().submit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Failed to submit 1 change due to the following problems:\n"
+ + "Change "
+ + gApi.changes().id(id).info()._number
+ + ": Change contains a project configuration that"
+ + " changes the parent project.\n"
+ + "The change must be submitted by a Gerrit administrator.");
assertThat(gApi.projects().name(project.get()).get().parent).isEqualTo(allProjects.get());
fetchRefsMetaConfig();
- assertThat(readProjectConfig().getString("access", null, "inheritFrom"))
+ assertThat(
+ projectOperations.project(project).getConfig().getString("access", null, "inheritFrom"))
.isAnyOf(null, allProjects.get());
requestScopeOperations.setApiUser(admin.id());
@@ -141,7 +143,9 @@ public class ConfigChangeIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(id).info().status).isEqualTo(ChangeStatus.MERGED);
assertThat(gApi.projects().name(project.get()).get().parent).isEqualTo(parent.name);
fetchRefsMetaConfig();
- assertThat(readProjectConfig().getString("access", null, "inheritFrom")).isEqualTo(parent.name);
+ assertThat(
+ projectOperations.project(project).getConfig().getString("access", null, "inheritFrom"))
+ .isEqualTo(parent.name);
}
@Test
@@ -179,17 +183,6 @@ public class ConfigChangeIT extends AbstractDaemonTest {
testRepo.reset(RefNames.REFS_CONFIG);
}
- private Config readProjectConfig() throws Exception {
- RevWalk rw = testRepo.getRevWalk();
- RevTree tree = rw.parseTree(testRepo.getRepository().resolve("HEAD"));
- RevObject obj = rw.parseAny(testRepo.get(tree, "project.config"));
- ObjectLoader loader = rw.getObjectReader().open(obj);
- String text = new String(loader.getCachedBytes(), UTF_8);
- Config cfg = new Config();
- cfg.fromText(text);
- return cfg;
- }
-
private PushOneCommit.Result createConfigChange(Config cfg) throws Exception {
PushOneCommit.Result r =
pushFactory
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
index 9c42542188..3b26459958 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CorsIT.java
@@ -26,6 +26,7 @@ import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.HttpHeaders.ORIGIN;
import static com.google.common.net.HttpHeaders.VARY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -80,11 +81,11 @@ public class CorsIT extends AbstractDaemonTest {
String allowMethods = r.getHeader(ACCESS_CONTROL_ALLOW_METHODS);
String allowHeaders = r.getHeader(ACCESS_CONTROL_ALLOW_HEADERS);
- assertThat(allowOrigin).named(ACCESS_CONTROL_ALLOW_ORIGIN).isNull();
- assertThat(allowCred).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isNull();
- assertThat(maxAge).named(ACCESS_CONTROL_MAX_AGE).isNull();
- assertThat(allowMethods).named(ACCESS_CONTROL_ALLOW_METHODS).isNull();
- assertThat(allowHeaders).named(ACCESS_CONTROL_ALLOW_HEADERS).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_ORIGIN).that(allowOrigin).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_CREDENTIALS).that(allowCred).isNull();
+ assertWithMessage(ACCESS_CONTROL_MAX_AGE).that(maxAge).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_METHODS).that(allowMethods).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_HEADERS).that(allowHeaders).isNull();
}
@Test
@@ -162,7 +163,7 @@ public class CorsIT extends AbstractDaemonTest {
res.assertOK();
String vary = res.getHeader(VARY);
- assertThat(vary).named(VARY).isNotNull();
+ assertWithMessage(VARY).that(vary).isNotNull();
assertThat(Splitter.on(", ").splitToList(vary))
.containsExactly(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS);
checkCors(res, true, origin);
@@ -213,7 +214,7 @@ public class CorsIT extends AbstractDaemonTest {
auth = c.getValue();
}
}
- assertThat(auth).named("GerritAccount cookie").isNotNull();
+ assertWithMessage("GerritAccount cookie").that(auth).isNotNull();
cookies.clear();
UrlEncoded url =
@@ -232,16 +233,18 @@ public class CorsIT extends AbstractDaemonTest {
assertThat(r.getStatusLine().getStatusCode()).isEqualTo(200);
Header vary = r.getFirstHeader(VARY);
- assertThat(vary).named(VARY).isNotNull();
- assertThat(Splitter.on(", ").splitToList(vary.getValue())).named(VARY).contains(ORIGIN);
+ assertWithMessage(VARY).that(vary).isNotNull();
+ assertWithMessage(VARY).that(Splitter.on(", ").splitToList(vary.getValue())).contains(ORIGIN);
Header allowOrigin = r.getFirstHeader(ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowOrigin).named(ACCESS_CONTROL_ALLOW_ORIGIN).isNotNull();
- assertThat(allowOrigin.getValue()).named(ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo(origin);
+ assertWithMessage(ACCESS_CONTROL_ALLOW_ORIGIN).that(allowOrigin).isNotNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_ORIGIN).that(allowOrigin.getValue()).isEqualTo(origin);
Header allowAuth = r.getFirstHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS);
- assertThat(allowAuth).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isNotNull();
- assertThat(allowAuth.getValue()).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isEqualTo("true");
+ assertWithMessage(ACCESS_CONTROL_ALLOW_CREDENTIALS).that(allowAuth).isNotNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_CREDENTIALS)
+ .that(allowAuth.getValue())
+ .isEqualTo("true");
checkTopic(change, "test-xd");
}
@@ -264,7 +267,7 @@ public class CorsIT extends AbstractDaemonTest {
private void checkTopic(Result change, @Nullable String topic) throws RestApiException {
ChangeInfo info = gApi.changes().id(change.getChangeId()).get();
- StringSubject t = assertThat(info.topic).named("topic");
+ StringSubject t = assertWithMessage("topic").that(info.topic);
if (topic != null) {
t.isEqualTo(topic);
} else {
@@ -287,8 +290,8 @@ public class CorsIT extends AbstractDaemonTest {
private void checkCors(RestResponse r, boolean accept, String origin) {
String vary = r.getHeader(VARY);
- assertThat(vary).named(VARY).isNotNull();
- assertThat(Splitter.on(", ").splitToList(vary)).named(VARY).contains(ORIGIN);
+ assertWithMessage(VARY).that(vary).isNotNull();
+ assertWithMessage(VARY).that(Splitter.on(", ").splitToList(vary)).contains(ORIGIN);
String allowOrigin = r.getHeader(ACCESS_CONTROL_ALLOW_ORIGIN);
String allowCred = r.getHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS);
@@ -296,28 +299,28 @@ public class CorsIT extends AbstractDaemonTest {
String allowMethods = r.getHeader(ACCESS_CONTROL_ALLOW_METHODS);
String allowHeaders = r.getHeader(ACCESS_CONTROL_ALLOW_HEADERS);
if (accept) {
- assertThat(allowOrigin).named(ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo(origin);
- assertThat(allowCred).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isEqualTo("true");
- assertThat(maxAge).named(ACCESS_CONTROL_MAX_AGE).isEqualTo("600");
+ assertWithMessage(ACCESS_CONTROL_ALLOW_ORIGIN).that(allowOrigin).isEqualTo(origin);
+ assertWithMessage(ACCESS_CONTROL_ALLOW_CREDENTIALS).that(allowCred).isEqualTo("true");
+ assertWithMessage(ACCESS_CONTROL_MAX_AGE).that(maxAge).isEqualTo("600");
- assertThat(allowMethods).named(ACCESS_CONTROL_ALLOW_METHODS).isNotNull();
- assertThat(Splitter.on(", ").splitToList(allowMethods))
- .named(ACCESS_CONTROL_ALLOW_METHODS)
+ assertWithMessage(ACCESS_CONTROL_ALLOW_METHODS).that(allowMethods).isNotNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_METHODS)
+ .that(Splitter.on(", ").splitToList(allowMethods))
.containsExactly("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS");
- assertThat(allowHeaders).named(ACCESS_CONTROL_ALLOW_HEADERS).isNotNull();
- assertThat(Splitter.on(", ").splitToList(allowHeaders))
- .named(ACCESS_CONTROL_ALLOW_HEADERS)
+ assertWithMessage(ACCESS_CONTROL_ALLOW_HEADERS).that(allowHeaders).isNotNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_HEADERS)
+ .that(Splitter.on(", ").splitToList(allowHeaders))
.containsExactlyElementsIn(
Stream.of(AUTHORIZATION, CONTENT_TYPE, "X-Gerrit-Auth", "X-Requested-With")
.map(s -> s.toLowerCase(Locale.US))
.collect(ImmutableSet.toImmutableSet()));
} else {
- assertThat(allowOrigin).named(ACCESS_CONTROL_ALLOW_ORIGIN).isNull();
- assertThat(allowCred).named(ACCESS_CONTROL_ALLOW_CREDENTIALS).isNull();
- assertThat(maxAge).named(ACCESS_CONTROL_MAX_AGE).isNull();
- assertThat(allowMethods).named(ACCESS_CONTROL_ALLOW_METHODS).isNull();
- assertThat(allowHeaders).named(ACCESS_CONTROL_ALLOW_HEADERS).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_ORIGIN).that(allowOrigin).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_CREDENTIALS).that(allowCred).isNull();
+ assertWithMessage(ACCESS_CONTROL_MAX_AGE).that(maxAge).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_METHODS).that(allowMethods).isNull();
+ assertWithMessage(ACCESS_CONTROL_ALLOW_HEADERS).that(allowHeaders).isNull();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index bc675e539c..a1167ede8a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -15,10 +15,11 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.common.data.Permission.READ;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
import com.google.common.base.Strings;
@@ -27,7 +28,13 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseSystemTime;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -37,17 +44,14 @@ import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.MergeInput;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.submit.ChangeAlreadyMergedException;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@@ -64,23 +68,13 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
import org.junit.Test;
+@UseClockStep
public class CreateChangeIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
-
@Test
public void createEmptyChange_MissingBranch() throws Exception {
ChangeInput ci = new ChangeInput();
@@ -135,6 +129,13 @@ public class CreateChangeIT extends AbstractDaemonTest {
}
@Test
+ public void createNewChange_RequiresAuthentication() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ assertCreateFails(
+ newChangeInput(ChangeStatus.NEW), AuthException.class, "Authentication required");
+ }
+
+ @Test
public void createNewChange() throws Exception {
ChangeInfo info = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
assertThat(info.revisions.get(info.currentRevision).commit.message)
@@ -162,6 +163,31 @@ public class CreateChangeIT extends AbstractDaemonTest {
}
@Test
+ public void cannotCreateChangeWithChangeIfOfExistingChangeOnSameBranch() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ ChangeInput ci = newChangeInput(ChangeStatus.NEW);
+ ci.subject = "Subject\n\nChange-Id: " + changeId;
+ assertCreateFails(
+ ci,
+ ResourceConflictException.class,
+ "A change with Change-Id " + changeId + " already exists for this branch.");
+ }
+
+ @Test
+ public void canCreateChangeWithChangeIfOfExistingChangeOnOtherBranch() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ createBranch(BranchNameKey.create(project, "other"));
+
+ ChangeInput ci = newChangeInput(ChangeStatus.NEW);
+ ci.subject = "Subject\n\nChange-Id: " + changeId;
+ ci.branch = "other";
+ ChangeInfo info = assertCreateSucceeds(ci);
+ assertThat(info.changeId).isEqualTo(changeId);
+ }
+
+ @Test
public void notificationsOnChangeCreation() throws Exception {
requestScopeOperations.setApiUser(user.id());
watch(project.get());
@@ -300,7 +326,11 @@ public class CreateChangeIT extends AbstractDaemonTest {
public void createChangeWithoutAccessToParentCommitFails() throws Exception {
Map<String, PushOneCommit.Result> results =
changeInTwoBranches("invisible-branch", "a.txt", "visible-branch", "b.txt");
- block(project, "refs/heads/invisible-branch", READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(READ).ref("refs/heads/invisible-branch").group(REGISTERED_USERS))
+ .update();
ChangeInput in = newChangeInput(ChangeStatus.NEW);
in.branch = "visible-branch";
@@ -315,7 +345,7 @@ public class CreateChangeIT extends AbstractDaemonTest {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
RevCommit commit =
- rw.parseCommit(repo.exactRef(changeMetaRef(new Change.Id(c._number))).getObjectId());
+ rw.parseCommit(repo.exactRef(changeMetaRef(Change.id(c._number))).getObjectId());
assertThat(commit.getShortMessage()).isEqualTo("Create change");
@@ -379,8 +409,8 @@ public class CreateChangeIT extends AbstractDaemonTest {
// Create 2 branches.
String branchA = "branchA";
String branchB = "branchB";
- createBranch(new Branch.NameKey(project, branchA));
- createBranch(new Branch.NameKey(project, branchB));
+ createBranch(BranchNameKey.create(project, branchA));
+ createBranch(BranchNameKey.create(project, branchB));
// Push an octopus merge to both of the branches.
Result octopusA =
@@ -483,7 +513,7 @@ public class CreateChangeIT extends AbstractDaemonTest {
cherry.current().review(ReviewInput.approve());
cherry.current().submit();
- ObjectId remoteId = getRemoteHead();
+ ObjectId remoteId = projectOperations.project(project).getHead("master");
assertThat(remoteId).isNotEqualTo(commitId);
ChangeInput in = newMergeChangeInput("master", commitId.getName(), "");
@@ -491,45 +521,13 @@ public class CreateChangeIT extends AbstractDaemonTest {
}
@Test
- public void sha1sOfTwoNewChangesDiffer() throws Exception {
- ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
- ChangeInfo info1 = assertCreateSucceeds(changeInput);
- ChangeInfo info2 = assertCreateSucceeds(changeInput);
- assertThat(info1.currentRevision).isNotEqualTo(info2.currentRevision);
- assertThat(info1.changeId).isNotEqualTo(info2.changeId);
- }
-
- @Test
- public void sha1sOfTwoNewChangesDifferIfCreatedConcurrently() throws Exception {
- ExecutorService executor = Executors.newFixedThreadPool(2);
- try {
- for (int i = 0; i < 10; i++) {
- ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
-
- CyclicBarrier sync = new CyclicBarrier(2);
- Callable<ChangeInfo> createChange =
- () -> {
- requestScopeOperations.setApiUser(admin.id());
- sync.await();
- return assertCreateSucceeds(changeInput);
- };
-
- Future<ChangeInfo> changeInfo1 = executor.submit(createChange);
- Future<ChangeInfo> changeInfo2 = executor.submit(createChange);
- assertThat(changeInfo1.get().currentRevision)
- .isNotEqualTo(changeInfo2.get().currentRevision);
- assertThat(changeInfo1.get().changeId).isNotEqualTo(changeInfo2.get().changeId);
- }
- } finally {
- executor.shutdown();
- executor.awaitTermination(5, TimeUnit.SECONDS);
- }
- }
-
- @Test
public void createChangeOnExistingBranchNotPermitted() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
- blockRead("refs/heads/*");
+ createBranch(BranchNameKey.create(project, "foo"));
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
ChangeInput input = newChangeInput(ChangeStatus.NEW);
input.branch = "foo";
@@ -548,7 +546,11 @@ public class CreateChangeIT extends AbstractDaemonTest {
@Test
public void createChangeOnNonExistingBranchNotPermitted() throws Exception {
- blockRead("refs/heads/*");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
ChangeInput input = newChangeInput(ChangeStatus.NEW);
input.branch = "foo";
@@ -568,6 +570,42 @@ public class CreateChangeIT extends AbstractDaemonTest {
input, BadRequestException.class, "Cannot create merge: destination branch does not exist");
}
+ @Test
+ @UseSystemTime
+ public void sha1sOfTwoNewChangesDiffer() throws Exception {
+ ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
+ ChangeInfo info1 = assertCreateSucceeds(changeInput);
+ ChangeInfo info2 = assertCreateSucceeds(changeInput);
+ assertThat(info1.currentRevision).isNotEqualTo(info2.currentRevision);
+ }
+
+ @Test
+ @UseSystemTime
+ public void sha1sOfTwoNewChangesDifferIfCreatedConcurrently() throws Exception {
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ try {
+ for (int i = 0; i < 10; i++) {
+ ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
+
+ CyclicBarrier sync = new CyclicBarrier(2);
+ Callable<ChangeInfo> createChange =
+ () -> {
+ requestScopeOperations.setApiUser(admin.id());
+ sync.await();
+ return assertCreateSucceeds(changeInput);
+ };
+
+ Future<ChangeInfo> changeInfo1 = executor.submit(createChange);
+ Future<ChangeInfo> changeInfo2 = executor.submit(createChange);
+ assertThat(changeInfo1.get().currentRevision)
+ .isNotEqualTo(changeInfo2.get().currentRevision);
+ }
+ } finally {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+ }
+
private ChangeInput newChangeInput(ChangeStatus status) {
ChangeInput in = new ChangeInput();
in.project = project.get();
@@ -604,9 +642,8 @@ public class CreateChangeIT extends AbstractDaemonTest {
private void assertCreateFails(
ChangeInput in, Class<? extends RestApiException> errType, String errSubstring)
throws Exception {
- exception.expect(errType);
- exception.expectMessage(errSubstring);
- gApi.changes().create(in);
+ Throwable thrown = assertThrows(errType, () -> gApi.changes().create(in));
+ assertThat(thrown).hasMessageThat().contains(errSubstring);
}
// TODO(davido): Expose setting of account preferences in the API
@@ -665,8 +702,8 @@ public class CreateChangeIT extends AbstractDaemonTest {
initialCommit.assertOkStatus();
// create two new branches
- createBranch(new Branch.NameKey(project, branchA));
- createBranch(new Branch.NameKey(project, branchB));
+ createBranch(BranchNameKey.create(project, branchA));
+ createBranch(BranchNameKey.create(project, branchB));
// create a commit in branchA
Result changeA =
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/DeleteVoteIT.java b/javatests/com/google/gerrit/acceptance/rest/change/DeleteVoteIT.java
index 286980f392..37fa2ce96b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/DeleteVoteIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/DeleteVoteIT.java
@@ -23,11 +23,11 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
@@ -104,6 +104,6 @@ public class DeleteVoteIT extends AbstractDaemonTest {
}
private Iterable<Account.Id> getReviewers(Collection<AccountInfo> r) {
- return Iterables.transform(r, a -> new Account.Id(a._accountId));
+ return Iterables.transform(r, a -> Account.id(a._accountId));
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
index c13df497d7..c57a035a2b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -15,9 +15,11 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -25,29 +27,21 @@ import com.google.common.truth.IterableSubject;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
import org.junit.Test;
@NoHttpd
+@UseClockStep
public class HashtagsIT extends AbstractDaemonTest {
- @BeforeClass
- public static void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @AfterClass
- public static void restoreTime() {
- TestTimeUtil.useSystemTime();
- }
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@@ -78,9 +72,9 @@ public class HashtagsIT extends AbstractDaemonTest {
public void addInvalidHashtag() throws Exception {
PushOneCommit.Result r = createChange();
- exception.expect(BadRequestException.class);
- exception.expectMessage("hashtags may not contain commas");
- addHashtags(r, "invalid,hashtag");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> addHashtags(r, "invalid,hashtag"));
+ assertThat(thrown).hasMessageThat().contains("hashtags may not contain commas");
}
@Test
@@ -258,15 +252,18 @@ public class HashtagsIT extends AbstractDaemonTest {
public void addHashtagWithoutPermissionNotAllowed() throws Exception {
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("edit hashtags not permitted");
- addHashtags(r, "MyHashtag");
+ AuthException thrown = assertThrows(AuthException.class, () -> addHashtags(r, "MyHashtag"));
+ assertThat(thrown).hasMessageThat().contains("edit hashtags not permitted");
}
@Test
public void addHashtagWithPermissionAllowed() throws Exception {
PushOneCommit.Result r = createChange();
- grant(project, "refs/heads/master", Permission.EDIT_HASHTAGS, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.EDIT_HASHTAGS).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
addHashtags(r, "MyHashtag");
assertThatGet(r).containsExactly("MyHashtag");
@@ -304,7 +301,7 @@ public class HashtagsIT extends AbstractDaemonTest {
private ChangeMessageInfo getLastMessage(PushOneCommit.Result r) throws Exception {
ChangeMessageInfo lastMessage =
Iterables.getLast(gApi.changes().id(r.getChange().getId().get()).get().messages, null);
- assertThat(lastMessage).named(lastMessage.message).isNotNull();
+ assertWithMessage(lastMessage.message).that(lastMessage).isNotNull();
return lastMessage;
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
index f49d1fb156..3030b02a6e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -24,10 +26,9 @@ import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import java.util.List;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -48,7 +49,11 @@ public class IndexChangeIT extends AbstractDaemonTest {
@Test
public void indexChangeOnNonVisibleBranch() throws Exception {
String changeId = createChange().getChangeId();
- blockRead("refs/heads/master");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
userRestSession.post("/changes/" + changeId + "/index/").assertNotFound();
}
@@ -62,15 +67,15 @@ public class IndexChangeIT extends AbstractDaemonTest {
// Create a project and restrict its visibility to the group
Project.NameKey p = projectOperations.newProject().create();
- try (ProjectConfigUpdate u = updateProject(p)) {
- Util.allow(
- u.getConfig(),
- Permission.READ,
- groupCache.get(new AccountGroup.NameKey(group)).get().getGroupUUID(),
- "refs/*");
- Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(p)
+ .forUpdate()
+ .add(
+ allow(Permission.READ)
+ .ref("refs/*")
+ .group(groupCache.get(AccountGroup.nameKey(group)).get().getGroupUUID()))
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Clone it and push a change as a regular user
TestRepository<InMemoryRepository> repo = cloneProject(p, user);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
index 2448ff881c..063f1a0656 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
@@ -16,17 +16,22 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.changes.MoveInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -35,10 +40,7 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.inject.Inject;
import java.util.Arrays;
import org.eclipse.jgit.junit.TestRepository;
@@ -48,15 +50,16 @@ import org.junit.Test;
@NoHttpd
public class MoveChangeIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
public void moveChangeWithShortRef() throws Exception {
// Move change to a different branch using short ref name
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
- move(r.getChangeId(), newBranch.getShortName());
+ move(r.getChangeId(), newBranch.shortName());
assertThat(r.getChange().change().getDest()).isEqualTo(newBranch);
}
@@ -64,9 +67,9 @@ public class MoveChangeIT extends AbstractDaemonTest {
public void moveChangeWithFullRef() throws Exception {
// Move change to a different branch using full ref name
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
- move(r.getChangeId(), newBranch.get());
+ move(r.getChangeId(), newBranch.branch());
assertThat(r.getChange().change().getDest()).isEqualTo(newBranch);
}
@@ -74,10 +77,10 @@ public class MoveChangeIT extends AbstractDaemonTest {
public void moveChangeWithMessage() throws Exception {
// Provide a message using --message flag
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
String moveMessage = "Moving for the move test";
- move(r.getChangeId(), newBranch.get(), moveMessage);
+ move(r.getChangeId(), newBranch.branch(), moveMessage);
assertThat(r.getChange().change().getDest()).isEqualTo(newBranch);
StringBuilder expectedMessage = new StringBuilder();
expectedMessage.append("Change destination moved from master to moveTest");
@@ -90,49 +93,59 @@ public class MoveChangeIT extends AbstractDaemonTest {
public void moveChangeToSameRefAsCurrent() throws Exception {
// Move change to the branch same as change's destination
PushOneCommit.Result r = createChange();
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Change is already destined for the specified branch");
- move(r.getChangeId(), r.getChange().change().getDest().get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> move(r.getChangeId(), r.getChange().change().getDest().branch()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Change is already destined for the specified branch");
}
@Test
public void moveChangeToSameChangeId() throws Exception {
// Move change to a branch with existing change with same change ID
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
int changeNum = r.getChange().change().getChangeId();
- createChange(newBranch.get(), r.getChangeId());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "Destination "
- + newBranch.getShortName()
- + " has a different change with same change key "
- + r.getChangeId());
- move(changeNum, newBranch.get());
+ createChange(newBranch.branch(), r.getChangeId());
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> move(changeNum, newBranch.branch()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "Destination "
+ + newBranch.shortName()
+ + " has a different change with same change key "
+ + r.getChangeId());
}
@Test
public void moveChangeToNonExistentRef() throws Exception {
// Move change to a non-existing branch
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch =
- new Branch.NameKey(r.getChange().change().getProject(), "does_not_exist");
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Destination " + newBranch.get() + " not found in the project");
- move(r.getChangeId(), newBranch.get());
+ BranchNameKey newBranch =
+ BranchNameKey.create(r.getChange().change().getProject(), "does_not_exist");
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> move(r.getChangeId(), newBranch.branch()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Destination " + newBranch.branch() + " not found in the project");
}
@Test
public void moveClosedChange() throws Exception {
// Move a change which is not open
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
merge(r);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Change is merged");
- move(r.getChangeId(), newBranch.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> move(r.getChangeId(), newBranch.branch()));
+ assertThat(thrown).hasMessageThat().contains("Change is merged");
}
@Test
@@ -153,43 +166,51 @@ public class MoveChangeIT extends AbstractDaemonTest {
pushHead(testRepo, "refs/for/master", false, false);
// Try to move the merge commit to another branch
- Branch.NameKey newBranch = new Branch.NameKey(r1.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch =
+ BranchNameKey.create(r1.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Merge commit cannot be moved");
- move(GitUtil.getChangeId(testRepo, c).get(), newBranch.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> move(GitUtil.getChangeId(testRepo, c).get(), newBranch.branch()));
+ assertThat(thrown).hasMessageThat().contains("Merge commit cannot be moved");
}
@Test
public void moveChangeToBranchWithoutUploadPerms() throws Exception {
// Move change to a destination where user doesn't have upload permissions
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch =
- new Branch.NameKey(r.getChange().change().getProject(), "blocked_branch");
+ BranchNameKey newBranch =
+ BranchNameKey.create(r.getChange().change().getProject(), "blocked_branch");
createBranch(newBranch);
- block(
- "refs/for/" + newBranch.get(),
- Permission.PUSH,
- systemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
- exception.expect(AuthException.class);
- exception.expectMessage("move not permitted");
- move(r.getChangeId(), newBranch.get());
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/for/" + newBranch.branch()).group(REGISTERED_USERS))
+ .update();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> move(r.getChangeId(), newBranch.branch()));
+ assertThat(thrown).hasMessageThat().contains("move not permitted");
}
@Test
public void moveChangeFromBranchWithoutAbandonPerms() throws Exception {
// Move change for which user does not have abandon permissions
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
- block(
- r.getChange().change().getDest().get(),
- Permission.ABANDON,
- systemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ block(Permission.ABANDON)
+ .ref(r.getChange().change().getDest().branch())
+ .group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("move not permitted");
- move(r.getChangeId(), newBranch.get());
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> move(r.getChangeId(), newBranch.branch()));
+ assertThat(thrown).hasMessageThat().contains("move not permitted");
}
@Test
@@ -202,52 +223,56 @@ public class MoveChangeIT extends AbstractDaemonTest {
int changeNum = r.getChange().change().getChangeId();
// Create a branch with that same commit
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
BranchInput bi = new BranchInput();
bi.revision = r.getCommit().name();
- gApi.projects().name(newBranch.getParentKey().get()).branch(newBranch.get()).create(bi);
+ gApi.projects().name(newBranch.project().get()).branch(newBranch.branch()).create(bi);
// Try to move the change to the branch with the same commit
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- "Current patchset revision is reachable from tip of " + newBranch.get());
- move(changeNum, newBranch.get());
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> move(changeNum, newBranch.branch()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Current patchset revision is reachable from tip of " + newBranch.branch());
}
@Test
public void moveChangeWithCurrentPatchSetLocked() throws Exception {
// Move change that is locked
PushOneCommit.Result r = createChange();
- Branch.NameKey newBranch = new Branch.NameKey(r.getChange().change().getProject(), "moveTest");
+ BranchNameKey newBranch = BranchNameKey.create(r.getChange().change().getProject(), "moveTest");
createBranch(newBranch);
+ LabelType patchSetLock = TestLabels.patchSetLock();
try (ProjectConfigUpdate u = updateProject(project)) {
- LabelType patchSetLock = Util.patchSetLock();
u.getConfig().getLabelSections().put(patchSetLock.getName(), patchSetLock);
- AccountGroup.UUID registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS).getUUID();
- Util.allow(
- u.getConfig(),
- Permission.forLabel(patchSetLock.getName()),
- 0,
- 1,
- registeredUsers,
- "refs/heads/*");
u.save();
}
- grant(project, "refs/heads/*", Permission.LABEL + "Patch-Set-Lock");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(
+ allowLabel(patchSetLock.getName())
+ .ref("refs/heads/*")
+ .group(REGISTERED_USERS)
+ .range(0, 1))
+ .update();
revision(r).review(new ReviewInput().label("Patch-Set-Lock", 1));
- exception.expect(ResourceConflictException.class);
- exception.expectMessage(
- String.format("The current patch set of change %s is locked", r.getChange().getId()));
- move(r.getChangeId(), newBranch.get());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> move(r.getChangeId(), newBranch.branch()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ String.format("The current patch set of change %s is locked", r.getChange().getId()));
}
@Test
public void moveChangeOnlyKeepVetoVotes() throws Exception {
// A vote for a label will be kept after moving if the label's function is *WithBlock and the
// vote holds the minimum value.
- createBranch(new Branch.NameKey(project, "foo"));
+ createBranch(BranchNameKey.create(project, "foo"));
String codeReviewLabel = "Code-Review"; // 'Code-Review' uses 'MaxWithBlock' function.
String testLabelA = "Label-A";
@@ -257,16 +282,13 @@ public class MoveChangeIT extends AbstractDaemonTest {
configLabel(testLabelB, LabelFunction.MAX_NO_BLOCK);
configLabel(testLabelC, LabelFunction.NO_BLOCK);
- AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(
- u.getConfig(), Permission.forLabel(testLabelA), -1, +1, registered, "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel(testLabelB), -1, +1, registered, "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel(testLabelC), -1, +1, registered, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(testLabelA).ref("refs/heads/*").group(REGISTERED_USERS).range(-1, +1))
+ .add(allowLabel(testLabelB).ref("refs/heads/*").group(REGISTERED_USERS).range(-1, +1))
+ .add(allowLabel(testLabelC).ref("refs/heads/*").group(REGISTERED_USERS).range(-1, +1))
+ .update();
String changeId = createChange().getChangeId();
gApi.changes().id(changeId).current().review(ReviewInput.reject());
@@ -301,17 +323,16 @@ public class MoveChangeIT extends AbstractDaemonTest {
}
@Test
- public void moveToBranchWithoutLabel() throws Exception {
- createBranch(new Branch.NameKey(project, "foo"));
+ public void moveToBranchThatDoesNotHaveCustomLabel() throws Exception {
+ createBranch(BranchNameKey.create(project, "foo"));
String testLabelA = "Label-A";
configLabel(testLabelA, LabelFunction.MAX_WITH_BLOCK, Arrays.asList("refs/heads/master"));
- AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(
- u.getConfig(), Permission.forLabel(testLabelA), -1, +1, registered, "refs/heads/master");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(testLabelA).ref("refs/heads/master").group(REGISTERED_USERS).range(-1, +1))
+ .update();
String changeId = createChange().getChangeId();
@@ -326,16 +347,26 @@ public class MoveChangeIT extends AbstractDaemonTest {
move(changeId, "foo");
- // TODO(dpursehouse): Assert about state of labels after move
+ // "foo" branch does not have the custom label
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email()).votes().keySet())
+ .isEmpty();
+
+ // Move back to master and confirm that the custom label score is still there
+ move(changeId, "master");
+
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email()).votes().keySet())
+ .containsExactly(testLabelA);
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email()).votes().values())
+ .containsExactly((short) -1);
}
@Test
public void moveNoDestinationBranchSpecified() throws Exception {
PushOneCommit.Result r = createChange();
- exception.expect(BadRequestException.class);
- exception.expectMessage("destination branch is required");
- move(r.getChangeId(), null);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> move(r.getChangeId(), null));
+ assertThat(thrown).hasMessageThat().contains("destination branch is required");
}
@Test
@@ -343,9 +374,9 @@ public class MoveChangeIT extends AbstractDaemonTest {
public void moveCanBeDisabledByConfig() throws Exception {
PushOneCommit.Result r = createChange();
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("move changes endpoint is disabled");
- move(r.getChangeId(), null);
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> move(r.getChangeId(), null));
+ assertThat(thrown).hasMessageThat().contains("move changes endpoint is disabled");
}
private void move(int changeNum, String destination) throws RestApiException {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/PluginFieldsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/PluginFieldsIT.java
index 649c7ae107..76493161a9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/PluginFieldsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/PluginFieldsIT.java
@@ -21,8 +21,8 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.gerrit.acceptance.AbstractPluginFieldsTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.util.List;
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
index ca4288f636..1a3c10f75f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
@@ -15,21 +15,21 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -81,9 +81,9 @@ public class PrivateByDefaultIT extends AbstractDaemonTest {
setPrivateByDefault(project2, InheritableBoolean.TRUE);
ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("private changes are disabled");
- gApi.changes().create(input);
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> gApi.changes().create(input));
+ assertThat(thrown).hasMessageThat().contains("private changes are disabled");
}
@Test
@@ -125,22 +125,6 @@ public class PrivateByDefaultIT extends AbstractDaemonTest {
result.assertErrorStatus();
}
- @Test
- @GerritConfig(name = "change.disablePrivateChanges", value = "true")
- public void pushDraftsWithPrivateByDefaultAndDisablePrivateChangesTrue() throws Exception {
- setPrivateByDefault(project2, InheritableBoolean.TRUE);
-
- RevCommit initialHead = getRemoteHead(project2, "master");
- TestRepository<InMemoryRepository> testRepo = cloneProject(project2);
- PushOneCommit.Result result =
- pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%draft");
- result.assertErrorStatus();
-
- testRepo.reset(initialHead);
- result = pushFactory.create(admin.newIdent(), testRepo).to("refs/drafts/master");
- result.assertErrorStatus();
- }
-
private void setPrivateByDefault(Project.NameKey proj, InheritableBoolean value)
throws Exception {
ConfigInput input = new ConfigInput();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 2ad4acabc6..6f519f1d40 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -19,8 +19,11 @@ import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVI
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.ChangeStatus;
@@ -28,9 +31,6 @@ import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.submit.CommitMergeStatus;
import com.google.inject.Inject;
import java.util.List;
@@ -39,7 +39,8 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public class SubmitByCherryPickIT extends AbstractSubmit {
- @Inject private DynamicSet<ChangeMessageModifier> changeMessageModifiers;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
@Override
protected SubmitType getSubmitType() {
@@ -47,12 +48,12 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitWithCherryPickIfFastForwardPossible() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithCherryPickIfFastForwardPossible() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
assertCherryPick(testRepo, false);
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
assertThat(newHead.getParent(0)).isEqualTo(change.getCommit().getParent(0));
assertRefUpdatedEvents(initialHead, newHead);
@@ -60,17 +61,17 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitWithCherryPick() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithCherryPick() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
submit(change2.getChangeId());
assertCherryPick(testRepo, false);
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
assertThat(newHead.getParentCount()).isEqualTo(1);
assertThat(newHead.getParent(0)).isEqualTo(headAfterFirstSubmit);
assertCurrentRevision(change2.getChangeId(), 2, newHead);
@@ -85,17 +86,15 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void changeMessageOnSubmit() throws Exception {
+ public void changeMessageOnSubmit() throws Throwable {
PushOneCommit.Result change = createChange();
- RegistrationHandle handle =
- changeMessageModifiers.add(
- "gerrit",
- (newCommitMessage, original, mergeTip, destination) ->
- newCommitMessage + "Custom: " + destination.get());
- try {
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ (newCommitMessage, original, mergeTip, destination) ->
+ newCommitMessage + "Custom: " + destination.branch())) {
submit(change.getChangeId());
- } finally {
- handle.remove();
}
testRepo.git().fetch().setRemote("origin").call();
ChangeInfo info = get(change.getChangeId(), CURRENT_REVISION);
@@ -107,20 +106,20 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithContentMerge() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "aaa\nbbb\nccc\n");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
submit(change2.getChangeId());
- RevCommit headAfterSecondSubmit = getRemoteHead();
+ RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(change.getCommit());
PushOneCommit.Result change3 = createChange("Change 3", "a.txt", "bbb\nccc\n");
submit(change3.getChangeId());
assertCherryPick(testRepo, true);
- RevCommit headAfterThirdSubmit = getRemoteHead();
+ RevCommit headAfterThirdSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterThirdSubmit.getParent(0)).isEqualTo(headAfterSecondSubmit);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, headAfterThirdSubmit);
@@ -145,12 +144,12 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge_Conflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithContentMerge_Conflict() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
submitWithConflict(
@@ -162,7 +161,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
+ "merged due to a path conflict. Please rebase the change locally and "
+ "upload the rebased commit for review.");
- assertThat(getRemoteHead()).isEqualTo(newHead);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(newHead);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
assertNoSubmitter(change2.getChangeId(), 1);
@@ -171,18 +170,18 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitOutOfOrder() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitOutOfOrder() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
createChange("Change 2", "b.txt", "other content");
PushOneCommit.Result change3 = createChange("Change 3", "c.txt", "different content");
submit(change3.getChangeId());
assertCherryPick(testRepo, false);
- RevCommit headAfterSecondSubmit = getRemoteHead();
+ RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSecondSubmit.getParent(0)).isEqualTo(headAfterFirstSubmit);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, headAfterSecondSubmit);
@@ -199,12 +198,12 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitOutOfOrder_Conflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitOutOfOrder_Conflict() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
createChange("Change 2", "b.txt", "other content");
PushOneCommit.Result change3 = createChange("Change 3", "b.txt", "different content");
@@ -217,7 +216,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
+ "merged due to a path conflict. Please rebase the change locally and "
+ "upload the rebased commit for review.");
- assertThat(getRemoteHead()).isEqualTo(newHead);
+ assertThat(projectOperations.project(project).getHead("master")).isEqualTo(newHead);
assertCurrentRevision(change3.getChangeId(), 1, change3.getCommit());
assertNoSubmitter(change3.getChangeId(), 1);
@@ -226,8 +225,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitMultipleChanges() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChanges() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
@@ -254,8 +253,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitDependentNonConflictingChangesOutOfOrder() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitDependentNonConflictingChangesOutOfOrder() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
@@ -264,11 +263,11 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
// Submit succeeds; change2 is successfully cherry-picked onto head.
submit(change2.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
// Submit succeeds; change is successfully cherry-picked onto head
// (which was change2's cherry-pick).
submit(change.getChangeId());
- RevCommit headAfterSecondSubmit = getRemoteHead();
+ RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
// change is the new tip.
List<RevCommit> log = getRemoteLog();
@@ -290,8 +289,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitDependentConflictingChangesOutOfOrder() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitDependentConflictingChangesOutOfOrder() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b1");
@@ -322,8 +321,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
}
@Test
- public void submitSubsetOfDependentChanges() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitSubsetOfDependentChanges() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
@@ -334,7 +333,7 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
// related to change 3 by topic or ancestor (due to cherrypicking!)
approve(change2.getChangeId());
submit(change3.getChangeId());
- RevCommit newHead = getRemoteHead();
+ RevCommit newHead = projectOperations.project(project).getHead("master");
assertNew(change.getChangeId());
assertNew(change2.getChangeId());
@@ -345,8 +344,8 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitIdenticalTree() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitIdenticalTree() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "a");
@@ -354,12 +353,13 @@ public class SubmitByCherryPickIT extends AbstractSubmit {
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "a");
submit(change1.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterFirstSubmit.getShortMessage()).isEqualTo("Change 1");
submit(change2.getChangeId(), new SubmitInput(), null, null);
- assertThat(getRemoteHead()).isEqualTo(headAfterFirstSubmit);
+ assertThat(projectOperations.project(project).getHead("master"))
+ .isEqualTo(headAfterFirstSubmit);
ChangeInfo info2 = get(change2.getChangeId(), MESSAGES);
assertThat(info2.status).isEqualTo(ChangeStatus.MERGED);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 974180c83f..670cff2c96 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -16,19 +16,23 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.inject.Inject;
import java.util.Map;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.junit.Test;
public class SubmitByFastForwardIT extends AbstractSubmit {
+ @Inject private ProjectOperations projectOperations;
@Override
protected SubmitType getSubmitType() {
@@ -36,11 +40,11 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
}
@Test
- public void submitWithFastForward() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithFastForward() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit updatedHead = getRemoteHead();
+ RevCommit updatedHead = projectOperations.project(project).getHead("master");
assertThat(updatedHead.getId()).isEqualTo(change.getCommit());
assertThat(updatedHead.getParent(0)).isEqualTo(initialHead);
assertSubmitter(change.getChangeId(), 1);
@@ -50,8 +54,8 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
}
@Test
- public void submitMultipleChangesWithFastForward() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChangesWithFastForward() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
PushOneCommit.Result change2 = createChange();
@@ -64,7 +68,7 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
approve(id2);
submit(id3);
- RevCommit updatedHead = getRemoteHead();
+ RevCommit updatedHead = projectOperations.project(project).getHead("master");
assertThat(updatedHead.getId()).isEqualTo(change3.getCommit());
assertThat(updatedHead.getParent(0).getId()).isEqualTo(change2.getCommit());
assertSubmitter(change.getChangeId(), 1);
@@ -82,12 +86,12 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
}
@Test
- public void submitTwoChangesWithFastForward_missingDependency() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitTwoChangesWithFastForward_missingDependency() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange();
PushOneCommit.Result change2 = createChange();
- Change.Id id1 = change1.getPatchSetId().getParentKey();
+ Change.Id id1 = change1.getPatchSetId().changeId();
submitWithConflict(
change2.getChangeId(),
"Failed to submit 2 changes due to the following problems:\n"
@@ -95,19 +99,19 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
+ id1
+ ": needs Code-Review");
- RevCommit updatedHead = getRemoteHead();
+ RevCommit updatedHead = projectOperations.project(project).getHead("master");
assertThat(updatedHead.getId()).isEqualTo(initialHead.getId());
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
@Test
- public void submitFastForwardNotPossible_Conflict() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitFastForwardNotPossible_Conflict() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
@@ -128,7 +132,8 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
+ ": Project policy requires "
+ "all submissions to be a fast-forward. Please rebase the change "
+ "locally and upload again for review.");
- assertThat(getRemoteHead()).isEqualTo(headAfterFirstSubmit);
+ assertThat(projectOperations.project(project).getHead("master"))
+ .isEqualTo(headAfterFirstSubmit);
assertSubmitter(change.getChangeId(), 1);
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
@@ -136,11 +141,15 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
}
@Test
- public void submitSameCommitsAsInExperimentalBranch() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitSameCommitsAsInExperimentalBranch() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
- grant(project, "refs/heads/*", Permission.CREATE);
- grant(project, "refs/heads/experimental", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref("refs/heads/*").group(adminGroupUuid()))
+ .add(allow(Permission.PUSH).ref("refs/heads/experimental").group(adminGroupUuid()))
+ .update();
RevCommit c1 = commitBuilder().add("b.txt", "1").message("commit at tip").create();
String id1 = GitUtil.getChangeId(testRepo, c1).get();
@@ -153,9 +162,9 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
.isEqualTo(c1.getId());
submit(id1);
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
- assertThat(getRemoteHead().getId()).isEqualTo(c1.getId());
+ assertThat(projectOperations.project(project).getHead("master").getId()).isEqualTo(c1.getId());
assertSubmitter(id1, 1);
assertRefUpdatedEvents(initialHead, headAfterSubmit);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
index 9bc5a2f592..f80bdcadab 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -17,11 +17,14 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.extensions.client.SubmitType;
+import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
+ @Inject private ProjectOperations projectOperations;
@Override
protected SubmitType getSubmitType() {
@@ -29,11 +32,11 @@ public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
}
@Test
- public void submitWithMergeIfFastForwardPossible() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithMergeIfFastForwardPossible() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit headAfterSubmit = getRemoteHead();
+ RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSubmit.getParentCount()).isEqualTo(2);
assertThat(headAfterSubmit.getParent(0)).isEqualTo(initialHead);
assertThat(headAfterSubmit.getParent(1)).isEqualTo(change.getCommit());
@@ -46,8 +49,8 @@ public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
}
@Test
- public void submitMultipleChanges() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChanges() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
// Submit a change so that the remote head advances
PushOneCommit.Result change = createChange("Change 1", "b", "b");
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 5ebcd85ae3..b259d90b54 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -16,14 +16,22 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -33,9 +41,6 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import java.io.File;
import java.io.InputStream;
@@ -63,11 +68,11 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void submitWithFastForward() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithFastForward() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit updatedHead = getRemoteHead();
+ RevCommit updatedHead = projectOperations.project(project).getHead("master");
assertThat(updatedHead.getId()).isEqualTo(change.getCommit());
assertThat(updatedHead.getParent(0)).isEqualTo(initialHead);
assertSubmitter(change.getChangeId(), 1);
@@ -79,8 +84,8 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void submitMultipleChanges() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitMultipleChanges() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
@@ -136,13 +141,13 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void submitChangesAcrossRepos() throws Exception {
+ public void submitChangesAcrossRepos() throws Throwable {
Project.NameKey p1 = projectOperations.newProject().create();
Project.NameKey p2 = projectOperations.newProject().create();
Project.NameKey p3 = projectOperations.newProject().create();
- RevCommit initialHead2 = getRemoteHead(p2, "master");
- RevCommit initialHead3 = getRemoteHead(p3, "master");
+ RevCommit initialHead2 = projectOperations.project(p2).getHead("master");
+ RevCommit initialHead3 = projectOperations.project(p3).getHead("master");
TestRepository<?> repo1 = cloneProject(p1);
TestRepository<?> repo2 = cloneProject(p2);
@@ -180,7 +185,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
approve(change3.getChangeId());
// get a preview before submitting:
- Map<Branch.NameKey, ObjectId> preview = fetchFromSubmitPreview(change1b.getChangeId());
+ Map<BranchNameKey, ObjectId> preview = fetchFromSubmitPreview(change1b.getChangeId());
submit(change1b.getChangeId());
RevCommit tip1 = getRemoteLog(p1, "master").get(0);
@@ -196,24 +201,24 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
// check that the preview matched what happened:
assertThat(preview).hasSize(3);
- assertThat(preview).containsKey(new Branch.NameKey(p1, "refs/heads/master"));
+ assertThat(preview).containsKey(BranchNameKey.create(p1, "refs/heads/master"));
assertTrees(p1, preview);
- assertThat(preview).containsKey(new Branch.NameKey(p2, "refs/heads/master"));
+ assertThat(preview).containsKey(BranchNameKey.create(p2, "refs/heads/master"));
assertTrees(p2, preview);
- assertThat(preview).containsKey(new Branch.NameKey(p3, "refs/heads/master"));
+ assertThat(preview).containsKey(BranchNameKey.create(p3, "refs/heads/master"));
assertTrees(p3, preview);
} else {
assertThat(tip2.getShortMessage()).isEqualTo(initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(initialHead3.getShortMessage());
assertThat(preview).hasSize(1);
- assertThat(preview.get(new Branch.NameKey(p1, "refs/heads/master"))).isNotNull();
+ assertThat(preview.get(BranchNameKey.create(p1, "refs/heads/master"))).isNotNull();
}
}
@Test
- public void submitChangesAcrossReposBlocked() throws Exception {
+ public void submitChangesAcrossReposBlocked() throws Throwable {
Project.NameKey p1 = projectOperations.newProject().create();
Project.NameKey p2 = projectOperations.newProject().create();
Project.NameKey p3 = projectOperations.newProject().create();
@@ -222,9 +227,9 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
TestRepository<?> repo2 = cloneProject(p2);
TestRepository<?> repo3 = cloneProject(p3);
- RevCommit initialHead1 = getRemoteHead(p1, "master");
- RevCommit initialHead2 = getRemoteHead(p2, "master");
- RevCommit initialHead3 = getRemoteHead(p3, "master");
+ RevCommit initialHead1 = projectOperations.project(p1).getHead("master");
+ RevCommit initialHead2 = projectOperations.project(p2).getHead("master");
+ RevCommit initialHead3 = projectOperations.project(p3).getHead("master");
PushOneCommit.Result change1a =
createChange(
@@ -277,15 +282,12 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
+ "and upload the rebased commit for review.";
// Get a preview before submitting:
- try (BinaryResult r = gApi.changes().id(change1b.getChangeId()).current().submitPreview()) {
- // We cannot just use the ExpectedException infrastructure as provided
- // by AbstractDaemonTest, as then we'd stop early and not test the
- // actual submit.
-
- fail("expected failure");
- } catch (RestApiException e) {
- assertThat(e.getMessage()).isEqualTo(msg);
- }
+ RestApiException thrown =
+ assertThrows(
+ RestApiException.class,
+ () -> gApi.changes().id(change1b.getChangeId()).current().submitPreview().close());
+ assertThat(thrown.getMessage()).isEqualTo(msg);
+
submitWithConflict(change1b.getChangeId(), msg);
} else {
submit(change1b.getChangeId());
@@ -313,13 +315,13 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void submitWithMergedAncestorsOnOtherBranch() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithMergedAncestorsOnOtherBranch() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 =
createChange(testRepo, "master", "base commit", "a.txt", "1", "");
submit(change1.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
gApi.projects().name(project.get()).branch("branch").create(new BranchInput());
@@ -362,12 +364,12 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void submitWithOpenAncestorsOnOtherBranch() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithOpenAncestorsOnOtherBranch() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 =
createChange(testRepo, "master", "base commit", "a.txt", "1", "");
submit(change1.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
gApi.projects().name(project.get()).branch("branch").create(new BranchInput());
@@ -395,7 +397,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
Project.NameKey p3 = projectOperations.newProject().create();
TestRepository<?> repo3 = cloneProject(p3);
- RevCommit repo3Head = getRemoteHead(p3, "master");
+ RevCommit repo3Head = projectOperations.project(p3).getHead("master");
PushOneCommit.Result change3b =
createChange(
repo3,
@@ -435,8 +437,8 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void gerritWorkflow() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void gerritWorkflow() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
// We'll setup a master and a stable branch.
// Then we create a change to be applied to master, which is
@@ -462,8 +464,8 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
gApi.changes().id(cherryId).current().submit();
// Create the merge locally
- RevCommit stable = getRemoteHead(project, "stable");
- RevCommit master = getRemoteHead(project, "master");
+ RevCommit stable = projectOperations.project(project).getHead("stable");
+ RevCommit master = projectOperations.project(project).getHead("master");
testRepo.git().fetch().call();
testRepo.git().branchCreate().setName("stable").setStartPoint(stable).call();
testRepo.git().branchCreate().setName("master").setStartPoint(master).call();
@@ -493,7 +495,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void openChangeForTargetBranchPreventsMerge() throws Exception {
+ public void openChangeForTargetBranchPreventsMerge() throws Throwable {
gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
// Propose a change for master, but leave it open for master!
@@ -517,7 +519,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
change3.getChangeId(),
"Failed to submit 1 change due to the following problems:\n"
+ "Change "
- + change3.getPatchSetId().getParentKey().get()
+ + change3.getPatchSetId().changeId().get()
+ ": Depends on change that was not submitted."
+ " Commit "
+ change3.getCommit().name()
@@ -532,7 +534,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void dependencyOnOutdatedPatchSetPreventsMerge() throws Exception {
+ public void dependencyOnOutdatedPatchSetPreventsMerge() throws Throwable {
// Create a change
PushOneCommit change = pushFactory.create(user.newIdent(), testRepo, "fix", "a.txt", "foo");
PushOneCommit.Result changeResult = change.to("refs/for/master");
@@ -574,7 +576,7 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void dependencyOnDeletedChangePreventsMerge() throws Exception {
+ public void dependencyOnDeletedChangePreventsMerge() throws Throwable {
// Create a change
PushOneCommit change = pushFactory.create(user.newIdent(), testRepo, "fix", "a.txt", "foo");
PushOneCommit.Result changeResult = change.to("refs/for/master");
@@ -608,9 +610,13 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void dependencyOnChangeForNonVisibleBranchPreventsMerge() throws Exception {
- grantLabel("Code-Review", -2, 2, project, "refs/heads/*", false, REGISTERED_USERS, false);
- grant(project, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS);
+ public void dependencyOnChangeForNonVisibleBranchPreventsMerge() throws Throwable {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Create a change
PushOneCommit change = pushFactory.create(admin.newIdent(), testRepo, "fix", "a.txt", "foo");
@@ -624,23 +630,26 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
// Move the first change to a destination branch that is non-visible to user so that user cannot
// this change anymore.
- Branch.NameKey secretBranch = new Branch.NameKey(project, "secretBranch");
+ BranchNameKey secretBranch = BranchNameKey.create(project, "secretBranch");
gApi.projects()
- .name(secretBranch.getParentKey().get())
- .branch(secretBranch.get())
+ .name(secretBranch.project().get())
+ .branch(secretBranch.branch())
.create(new BranchInput());
- gApi.changes().id(changeResult.getChangeId()).move(secretBranch.get());
- block(secretBranch.get(), "read", ANONYMOUS_USERS);
+ gApi.changes().id(changeResult.getChangeId()).move(secretBranch.branch());
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(READ).ref(secretBranch.branch()).group(ANONYMOUS_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
// Verify that user cannot see the first change.
- try {
- gApi.changes().id(changeResult.getChangeId()).get();
- fail("expected failure");
- } catch (ResourceNotFoundException e) {
- assertThat(e.getMessage()).isEqualTo("Not found: " + changeResult.getChangeId());
- }
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeResult.getChangeId()).get());
+ assertThat(thrown).hasMessageThat().isEqualTo("Not found: " + changeResult.getChangeId());
// Submit is expected to fail.
submitWithConflict(
@@ -663,9 +672,13 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
}
@Test
- public void dependencyOnHiddenChangePreventsMerge() throws Exception {
- grantLabel("Code-Review", -2, 2, project, "refs/heads/*", false, REGISTERED_USERS, false);
- grant(project, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS);
+ public void dependencyOnHiddenChangePreventsMerge() throws Throwable {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
// Create a change
PushOneCommit change = pushFactory.create(admin.newIdent(), testRepo, "fix", "a.txt", "foo");
@@ -684,30 +697,29 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
requestScopeOperations.setApiUser(user.id());
// Verify that user cannot see the first change.
- try {
- gApi.changes().id(changeResult.getChangeId()).get();
- fail("expected failure");
- } catch (ResourceNotFoundException e) {
- assertThat(e.getMessage()).isEqualTo("Not found: " + changeResult.getChangeId());
- }
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(changeResult.getChangeId()).get());
+ assertThat(thrown).hasMessageThat().isEqualTo("Not found: " + changeResult.getChangeId());
// Submit is expected to fail.
- try {
- gApi.changes().id(change2Result.getChangeId()).current().submit();
- fail("expected failure");
- } catch (AuthException e) {
- assertThat(e.getMessage())
- .isEqualTo(
- "A change to be submitted with "
- + change2Result.getChange().getId().id
- + " is not visible");
- }
+ AuthException thrown2 =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(change2Result.getChangeId()).current().submit());
+ assertThat(thrown2)
+ .hasMessageThat()
+ .isEqualTo(
+ "A change to be submitted with "
+ + change2Result.getChange().getId().get()
+ + " is not visible");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
@Test
- public void dependencyOnHiddenChangeUsingTopicPreventsMerge() throws Exception {
+ public void dependencyOnHiddenChangeUsingTopicPreventsMerge() throws Throwable {
// Construct a topic where a change included by topic depends on a private change that is not
// visible to the submitting user
// (c1) --- topic --- (c2b)
@@ -718,10 +730,18 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
Project.NameKey p1 = projectOperations.newProject().create();
Project.NameKey p2 = projectOperations.newProject().create();
- grantLabel("Code-Review", -2, 2, p1, "refs/heads/*", false, REGISTERED_USERS, false);
- grant(p1, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS);
- grantLabel("Code-Review", -2, 2, p2, "refs/heads/*", false, REGISTERED_USERS, false);
- grant(p2, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS);
+ projectOperations
+ .project(p1)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(p2)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
TestRepository<?> repo1 = cloneProject(p1);
TestRepository<?> repo2 = cloneProject(p2);
@@ -744,30 +764,27 @@ public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
requestScopeOperations.setApiUser(user.id());
// Verify that user cannot see change2a
- try {
- gApi.changes().id(change2a.getChangeId()).get();
- fail("expected failure");
- } catch (ResourceNotFoundException e) {
- assertThat(e.getMessage()).isEqualTo("Not found: " + change2a.getChangeId());
- }
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.changes().id(change2a.getChangeId()).get());
+ assertThat(thrown).hasMessageThat().isEqualTo("Not found: " + change2a.getChangeId());
// Submit is expected to fail.
- try {
- gApi.changes().id(change1.getChangeId()).current().submit();
- fail("expected failure");
- } catch (AuthException e) {
- assertThat(e.getMessage())
- .isEqualTo(
- "A change to be submitted with "
- + change1.getChange().getId().id
- + " is not visible");
- }
+ AuthException thrown2 =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(change1.getChangeId()).current().submit());
+ assertThat(thrown2)
+ .hasMessageThat()
+ .isEqualTo(
+ "A change to be submitted with "
+ + change1.getChange().getId().get()
+ + " is not visible");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
@Test
- public void testPreviewSubmitTgz() throws Exception {
+ public void testPreviewSubmitTgz() throws Throwable {
Project.NameKey p1 = projectOperations.newProject().create();
TestRepository<?> repo1 = cloneProject(p1);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
index eb8dea572d..388c4f4ca2 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
@@ -15,35 +15,35 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.config.UrlFormatter;
import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
-import java.util.ArrayDeque;
-import java.util.Deque;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
- @Inject private DynamicSet<ChangeMessageModifier> changeMessageModifiers;
@Inject private DynamicItem<UrlFormatter> urlFormatter;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
@Override
protected SubmitType getSubmitType() {
@@ -52,12 +52,12 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithPossibleFastForward() throws Exception {
- RevCommit oldHead = getRemoteHead();
+ public void submitWithPossibleFastForward() throws Throwable {
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head.getId()).isNotEqualTo(change.getCommit());
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertApproved(change.getChangeId());
@@ -72,7 +72,7 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void alwaysAddFooters() throws Exception {
+ public void alwaysAddFooters() throws Throwable {
PushOneCommit.Result change1 = createChange();
PushOneCommit.Result change2 = createChange();
@@ -89,21 +89,22 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
}
@Test
- public void rebaseInvokesChangeMessageModifiers() throws Exception {
+ public void rebaseInvokesChangeMessageModifiers() throws Throwable {
ChangeMessageModifier modifier1 =
(msg, orig, tip, dest) -> msg + "This-change-before-rebase: " + orig.name() + "\n";
ChangeMessageModifier modifier2 =
(msg, orig, tip, dest) -> msg + "Previous-step-tip: " + tip.name() + "\n";
ChangeMessageModifier modifier3 =
- (msg, orig, tip, dest) -> msg + "Dest: " + dest.getShortName() + "\n";
+ (msg, orig, tip, dest) -> msg + "Dest: " + dest.shortName() + "\n";
- try (AutoCloseable ignored = installChangeMessageModifiers(modifier1, modifier2, modifier3)) {
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(modifier1).add(modifier2).add(modifier3)) {
ImmutableList<PushOneCommit.Result> changes = submitWithRebase(admin);
ChangeData cd1 = changes.get(0).getChange();
ChangeData cd2 = changes.get(1).getChange();
assertThat(cd2.patchSets()).hasSize(2);
- String change1CurrentCommit = cd1.currentPatchSet().getRevision().get();
- String change2Ps1Commit = cd2.patchSet(new PatchSet.Id(cd2.getId(), 1)).getRevision().get();
+ String change1CurrentCommit = cd1.currentPatchSet().commitId().name();
+ String change2Ps1Commit = cd2.patchSet(PatchSet.id(cd2.getId(), 1)).commitId().name();
assertThat(gApi.changes().id(cd2.getId().get()).revision(2).commit(false).message)
.isEqualTo(
@@ -120,65 +121,52 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
}
@Test
- public void failingChangeMessageModifierShortCircuits() throws Exception {
+ public void failingChangeMessageModifierShortCircuits() throws Throwable {
ChangeMessageModifier modifier1 =
(msg, orig, tip, dest) -> {
throw new IllegalStateException("boom");
};
ChangeMessageModifier modifier2 = (msg, orig, tip, dest) -> msg + "A-footer: value\n";
- try (AutoCloseable ignored = installChangeMessageModifiers(modifier1, modifier2)) {
- try {
- submitWithRebase();
- assert_().fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- Throwable cause = Throwables.getRootCause(e);
- assertThat(cause).isInstanceOf(RuntimeException.class);
- assertThat(cause).hasMessageThat().isEqualTo("boom");
- }
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(modifier1).add(modifier2)) {
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> submitWithRebase());
+ Throwable cause = Throwables.getRootCause(thrown);
+ assertThat(cause).isInstanceOf(RuntimeException.class);
+ assertThat(cause).hasMessageThat().isEqualTo("boom");
}
}
@Test
- public void changeMessageModifierReturningNullShortCircuits() throws Exception {
+ public void changeMessageModifierReturningNullShortCircuits() throws Throwable {
ChangeMessageModifier modifier1 = (msg, orig, tip, dest) -> null;
ChangeMessageModifier modifier2 = (msg, orig, tip, dest) -> msg + "A-footer: value\n";
- try (AutoCloseable ignored = installChangeMessageModifiers(modifier1, modifier2)) {
- try {
- submitWithRebase();
- assert_().fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- Throwable cause = Throwables.getRootCause(e);
- assertThat(cause).isInstanceOf(RuntimeException.class);
- assertThat(cause)
- .hasMessageThat()
- .isEqualTo(
- modifier1.getClass().getName()
- + ".onSubmit from plugin modifier-1 returned null instead of new commit"
- + " message");
- }
- }
- }
-
- private AutoCloseable installChangeMessageModifiers(ChangeMessageModifier... modifiers) {
- Deque<RegistrationHandle> handles = new ArrayDeque<>(modifiers.length);
- for (int i = 0; i < modifiers.length; i++) {
- handles.push(changeMessageModifiers.add("modifier-" + (i + 1), modifiers[i]));
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(modifier1, "modifier-1")
+ .add(modifier2, "modifier-2")) {
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> submitWithRebase());
+ Throwable cause = Throwables.getRootCause(thrown);
+ assertThat(cause).isInstanceOf(RuntimeException.class);
+ assertThat(cause)
+ .hasMessageThat()
+ .isEqualTo(
+ modifier1.getClass().getName()
+ + ".onSubmit from plugin modifier-1 returned null instead of new commit"
+ + " message");
}
- return () -> {
- while (!handles.isEmpty()) {
- handles.pop().remove();
- }
- };
}
- private void assertLatestRevisionHasFooters(PushOneCommit.Result change) throws Exception {
+ private void assertLatestRevisionHasFooters(PushOneCommit.Result change) throws Throwable {
RevCommit c = getCurrentCommit(change);
assertThat(c.getFooterLines(FooterConstants.CHANGE_ID)).isNotEmpty();
assertThat(c.getFooterLines(FooterConstants.REVIEWED_BY)).isNotEmpty();
assertThat(c.getFooterLines(FooterConstants.REVIEWED_ON)).isNotEmpty();
}
- private RevCommit getCurrentCommit(PushOneCommit.Result change) throws Exception {
+ private RevCommit getCurrentCommit(PushOneCommit.Result change) throws Throwable {
testRepo.git().fetch().setRemote("origin").call();
ChangeInfo info = get(change.getChangeId(), CURRENT_REVISION);
RevCommit c = testRepo.getRevWalk().parseCommit(ObjectId.fromString(info.currentRevision));
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 7bb31a0197..01b58eea5c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -18,12 +18,15 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
+import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
+ @Inject private ProjectOperations projectOperations;
@Override
protected SubmitType getSubmitType() {
@@ -32,11 +35,11 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithFastForward() throws Exception {
- RevCommit oldHead = getRemoteHead();
+ public void submitWithFastForward() throws Throwable {
+ RevCommit oldHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
- RevCommit head = getRemoteHead();
+ RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head.getId()).isEqualTo(change.getCommit());
assertThat(head.getParent(0)).isEqualTo(oldHead);
assertApproved(change.getChangeId());
@@ -50,20 +53,20 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
- public void submitWithContentMerge() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ public void submitWithContentMerge() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "aaa\nbbb\nccc\n");
submit(change.getChangeId());
- RevCommit headAfterFirstSubmit = getRemoteHead();
+ RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
submit(change2.getChangeId());
- RevCommit headAfterSecondSubmit = getRemoteHead();
+ RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(change.getCommit());
PushOneCommit.Result change3 = createChange("Change 3", "a.txt", "bbb\nccc\n");
submit(change3.getChangeId());
assertRebase(testRepo, true);
- RevCommit headAfterThirdSubmit = getRemoteHead();
+ RevCommit headAfterThirdSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterThirdSubmit.getParent(0)).isEqualTo(headAfterSecondSubmit);
assertApproved(change3.getChangeId());
assertCurrentRevision(change3.getChangeId(), 2, headAfterThirdSubmit);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
index 87fc9f6b72..73f10e5475 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
@@ -21,8 +21,8 @@ import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.restapi.change.Submit;
@@ -186,8 +186,8 @@ public class SubmitResolvingMergeCommitIT extends AbstractDaemonTest {
String project2Name = name("Project2");
gApi.projects().create(project1Name);
gApi.projects().create(project2Name);
- TestRepository<InMemoryRepository> project1 = cloneProject(new Project.NameKey(project1Name));
- TestRepository<InMemoryRepository> project2 = cloneProject(new Project.NameKey(project2Name));
+ TestRepository<InMemoryRepository> project1 = cloneProject(Project.nameKey(project1Name));
+ TestRepository<InMemoryRepository> project2 = cloneProject(Project.nameKey(project2Name));
PushOneCommit.Result a = createChange(project1, "A");
PushOneCommit.Result b =
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 9bbe1dd9c4..8e0042cf89 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -16,7 +16,12 @@ package com.google.gerrit.acceptance.rest.change;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
@@ -29,14 +34,17 @@ import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.EmailInput;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import java.util.List;
import java.util.Set;
@@ -130,6 +138,48 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
}
@Test
+ @GerritConfig(name = "index.maxTerms", value = "10")
+ public void suggestReviewersTooManyQueryTerms() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // Do a query which doesn't exceed index.maxTerms succeeds (add only 9 terms, since on
+ // 'inactive:1' term is implicitly added) and assert that a result is returned
+ StringBuilder query = new StringBuilder();
+ for (int i = 1; i <= 9; i++) {
+ query.append(name("u")).append(" ");
+ }
+ assertThat(suggestReviewers(changeId, query.toString())).isNotEmpty();
+
+ // Do a query which exceed index.maxTerms succeeds (10 terms plus 'inactive:1' term which is
+ // implicitly added).
+ query.append(name("u"));
+ BadRequestException exception =
+ assertThrows(BadRequestException.class, () -> suggestReviewers(changeId, query.toString()));
+ assertThat(exception).hasMessageThat().isEqualTo("too many terms in query");
+ }
+
+ @Test
+ public void suggestReviewersWithExcludeGroups() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // by default groups are included
+ List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, name("user"));
+ assertReviewers(
+ reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of(group1, group2, group3));
+
+ // exclude groups
+ reviewers =
+ gApi.changes().id(changeId).suggestReviewers(name("user")).excludeGroups(true).get();
+ assertReviewers(reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of());
+
+ // explicitly include groups
+ reviewers =
+ gApi.changes().id(changeId).suggestReviewers(name("user")).excludeGroups(false).get();
+ assertReviewers(
+ reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of(group1, group2, group3));
+ }
+
+ @Test
@GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
public void suggestReviewersSameGroupVisibility() throws Exception {
String changeId = createChange().getChangeId();
@@ -160,8 +210,12 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
List<SuggestedReviewerInfo> reviewers;
requestScopeOperations.setApiUser(user3.id());
- block("refs/*", "read", ANONYMOUS_USERS);
- allow("refs/*", "read", group1);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(READ).ref("refs/*").group(ANONYMOUS_USERS))
+ .add(allow(READ).ref("refs/*").group(group1))
+ .update();
reviewers = suggestReviewers(changeId, user2.username(), 2);
assertThat(reviewers).isEmpty();
}
@@ -178,7 +232,10 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
// Clear cached group info.
requestScopeOperations.setApiUser(user1.id());
- allowGlobalCapabilities(group1, GlobalCapability.VIEW_ALL_ACCOUNTS);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.VIEW_ALL_ACCOUNTS).group(group1))
+ .update();
reviewers = suggestReviewers(changeId, user2.username(), 2);
assertThat(reviewers).hasSize(1);
assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(user2.fullName());
@@ -376,109 +433,101 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
}
@Test
- @GerritConfig(name = "suggest.maxSuggestedReviewers", value = "10")
- public void reviewerRanking() throws Exception {
- // Assert that user are ranked by the number of times they have applied a
- // a label to a change (highest), added comments (medium) or owned a
- // change (low).
- String fullName = "Primum Finalis";
- TestAccount userWhoOwns = user("customuser1", fullName);
- TestAccount reviewer1 = user("customuser2", fullName);
- TestAccount reviewer2 = user("customuser3", fullName);
- TestAccount userWhoComments = user("customuser4", fullName);
- TestAccount userWhoLooksForSuggestions = user("customuser5", fullName);
-
- // Create a change as userWhoOwns and add some reviews
- requestScopeOperations.setApiUser(userWhoOwns.id());
- String changeId1 = createChangeFromApi();
-
- requestScopeOperations.setApiUser(reviewer1.id());
- reviewChange(changeId1);
-
- requestScopeOperations.setApiUser(user1.id());
- String changeId2 = createChangeFromApi();
-
- requestScopeOperations.setApiUser(reviewer1.id());
- reviewChange(changeId2);
+ public void suggestNoInactiveAccounts() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ String changeIdReviewed = createChangeFromApi();
+ String changeId = createChangeFromApi();
- requestScopeOperations.setApiUser(reviewer2.id());
- reviewChange(changeId2);
+ String name = name("foo");
+ TestAccount foo1 = accountCreator.create(name + "-1");
+ requestScopeOperations.setApiUser(foo1.id());
+ reviewChange(changeIdReviewed);
+ assertThat(gApi.accounts().id(foo1.username()).getActive()).isTrue();
- // Create a comment as a different user
- requestScopeOperations.setApiUser(userWhoComments.id());
- ReviewInput ri = new ReviewInput();
- ri.message = "Test";
- gApi.changes().id(changeId1).revision(1).review(ri);
+ TestAccount foo2 = accountCreator.create(name + "-2");
+ requestScopeOperations.setApiUser(foo2.id());
+ reviewChange(changeIdReviewed);
+ assertThat(gApi.accounts().id(foo2.username()).getActive()).isTrue();
- // Create a change as a new user to assert that we receive the correct
- // ranking
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
- requestScopeOperations.setApiUser(userWhoLooksForSuggestions.id());
- List<SuggestedReviewerInfo> reviewers = suggestReviewers(createChangeFromApi(), "Pri", 4);
- assertThat(reviewers.stream().map(r -> r.account._accountId).collect(toList()))
- .containsExactly(
- reviewer1.id().get(),
- reviewer2.id().get(),
- userWhoOwns.id().get(),
- userWhoComments.id().get())
- .inOrder();
+ requestScopeOperations.setApiUser(user.id());
+ gApi.accounts().id(foo2.username()).setActive(false);
+ assertThat(gApi.accounts().id(foo2.id().get()).getActive()).isFalse();
+ assertReviewers(suggestReviewers(changeId, name), ImmutableList.of(foo1), ImmutableList.of());
}
@Test
- public void reviewerRankingProjectIsolation() throws Exception {
- // Create new project
- Project.NameKey newProject = projectOperations.newProject().create();
+ public void suggestNoExistingReviewers() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = createChangeFromApi();
+ String changeIdReviewed = createChangeFromApi();
- // Create users who review changes in both the default and the new project
- String fullName = "Primum Finalis";
- TestAccount userWhoOwns = user("customuser1", fullName);
- TestAccount reviewer1 = user("customuser2", fullName);
- TestAccount reviewer2 = user("customuser3", fullName);
+ String name = name("foo");
+ TestAccount foo1 = accountCreator.create(name + "-1");
+ requestScopeOperations.setApiUser(foo1.id());
+ reviewChange(changeIdReviewed);
- requestScopeOperations.setApiUser(userWhoOwns.id());
- String changeId1 = createChangeFromApi();
+ TestAccount foo2 = accountCreator.create(name + "-2");
+ requestScopeOperations.setApiUser(foo2.id());
+ reviewChange(changeIdReviewed);
- requestScopeOperations.setApiUser(reviewer1.id());
- reviewChange(changeId1);
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
- requestScopeOperations.setApiUser(userWhoOwns.id());
- String changeId2 = createChangeFromApi(newProject);
+ gApi.changes().id(changeId).addReviewer(foo2.id().toString());
+ assertReviewers(suggestReviewers(changeId, name), ImmutableList.of(foo1), ImmutableList.of());
+ }
- requestScopeOperations.setApiUser(reviewer2.id());
- reviewChange(changeId2);
+ @Test
+ public void suggestCcAsReviewer() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = createChangeFromApi();
+ String changeIdReviewed = createChangeFromApi();
- requestScopeOperations.setApiUser(userWhoOwns.id());
- String changeId3 = createChangeFromApi(newProject);
+ String name = name("foo");
+ TestAccount foo1 = accountCreator.create(name + "-1");
+ requestScopeOperations.setApiUser(foo1.id());
+ reviewChange(changeIdReviewed);
- requestScopeOperations.setApiUser(reviewer2.id());
- reviewChange(changeId3);
+ TestAccount foo2 = accountCreator.create(name + "-2");
+ requestScopeOperations.setApiUser(foo2.id());
+ reviewChange(changeIdReviewed);
- requestScopeOperations.setApiUser(userWhoOwns.id());
- List<SuggestedReviewerInfo> reviewers = suggestReviewers(createChangeFromApi(), "Prim", 4);
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
- // Assert that reviewer1 is on top, even though reviewer2 has more reviews
- // in other projects
- assertThat(reviewers.stream().map(r -> r.account._accountId).collect(toList()))
- .containsExactly(reviewer1.id().get(), reviewer2.id().get())
- .inOrder();
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = foo2.id().toString();
+ reviewerInput.state = ReviewerState.CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
}
@Test
- public void suggestNoInactiveAccounts() throws Exception {
+ public void suggestReviewerAsCc() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = createChangeFromApi();
+ String changeIdReviewed = createChangeFromApi();
+
String name = name("foo");
TestAccount foo1 = accountCreator.create(name + "-1");
- assertThat(gApi.accounts().id(foo1.username()).getActive()).isTrue();
+ requestScopeOperations.setApiUser(foo1.id());
+ reviewChange(changeIdReviewed);
TestAccount foo2 = accountCreator.create(name + "-2");
- assertThat(gApi.accounts().id(foo2.username()).getActive()).isTrue();
+ requestScopeOperations.setApiUser(foo2.id());
+ reviewChange(changeIdReviewed);
- String changeId = createChange().getChangeId();
- assertReviewers(
- suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
+ assertReviewers(suggestCcs(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
- gApi.accounts().id(foo2.username()).setActive(false);
- assertThat(gApi.accounts().id(foo2.id().get()).getActive()).isFalse();
- assertReviewers(suggestReviewers(changeId, name), ImmutableList.of(foo1), ImmutableList.of());
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = foo2.id().toString();
+ reviewerInput.state = ReviewerState.REVIEWER;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+ assertReviewers(suggestCcs(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
}
@Test
@@ -486,25 +535,17 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
String secondaryEmail = "foo.secondary@example.com";
TestAccount foo = createAccountWithSecondaryEmail("foo", secondaryEmail);
- List<SuggestedReviewerInfo> reviewers =
- suggestReviewers(createChange().getChangeId(), secondaryEmail, 4);
- assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
-
- reviewers = suggestReviewers(createChange().getChangeId(), "secondary", 4);
+ List<SuggestedReviewerInfo> reviewers = suggestReviewers(createChangeFromApi(), "secondary", 4);
assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
}
@Test
public void cannotSuggestBySecondaryEmailWithoutModifyAccount() throws Exception {
- String secondaryEmail = "foo.secondary@example.com";
- createAccountWithSecondaryEmail("foo", secondaryEmail);
-
+ // Test that even if the account exists, the result is still empty since
+ // it shouldn't match to that account based only on the secondary email.
+ createAccountWithSecondaryEmail("foo", "foo.secondary@example.com");
requestScopeOperations.setApiUser(user.id());
- List<SuggestedReviewerInfo> reviewers =
- suggestReviewers(createChange().getChangeId(), secondaryEmail, 4);
- assertThat(reviewers).isEmpty();
-
- reviewers = suggestReviewers(createChange().getChangeId(), "secondary2", 4);
+ List<SuggestedReviewerInfo> reviewers = suggestReviewers(createChangeFromApi(), "secondary", 4);
assertThat(reviewers).isEmpty();
}
@@ -525,6 +566,24 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
assertThat(Iterables.getOnlyElement(reviewers).account.secondaryEmails).isNull();
}
+ @Test
+ public void suggestsPeopleWithNoReviewsWhenExplicitlyQueried() throws Exception {
+ TestAccount newTeamMember = accountCreator.create("newTeamMember");
+
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = createChangeFromApi();
+ String changeIdReviewed = createChangeFromApi();
+
+ TestAccount reviewer = accountCreator.create("newReviewer");
+ requestScopeOperations.setApiUser(reviewer.id());
+ reviewChange(changeIdReviewed);
+
+ List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "new", 4);
+ assertThat(reviewers.stream().map(r -> r.account._accountId).collect(toList()))
+ .containsExactly(reviewer.id().get(), newTeamMember.id().get())
+ .inOrder();
+ }
+
private TestAccount createAccountWithSecondaryEmail(String name, String secondaryEmail)
throws Exception {
TestAccount foo = accountCreator.create(name(name), "foo.primary@example.com", "Foo");
@@ -545,6 +604,10 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
return gApi.changes().id(changeId).suggestReviewers(query).withLimit(n).get();
}
+ private List<SuggestedReviewerInfo> suggestCcs(String changeId, String query) throws Exception {
+ return gApi.changes().id(changeId).suggestCcs(query).get();
+ }
+
private AccountGroup.UUID createGroupWithArbitraryMembers(int numMembers) {
Set<Account.Id> members =
IntStream.rangeClosed(1, numMembers)
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java b/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
index 49692dde42..525f5d5888 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
@@ -15,127 +15,194 @@
package com.google.gerrit.acceptance.rest.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
import org.junit.Test;
public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- private Project.NameKey project1;
- private Project.NameKey project2;
-
- @Before
- public void setUp() throws Exception {
- project1 = projectOperations.newProject().create();
- project2 = projectOperations.newProject().parent(project1).create();
- }
-
- @After
- public void tearDown() throws Exception {
- requestScopeOperations.setApiUser(admin.id());
- GeneralPreferencesInfo prefs = gApi.accounts().id(admin.id().get()).getPreferences();
- prefs.workInProgressByDefault = false;
- gApi.accounts().id(admin.id().get()).setPreferences(prefs);
- }
-
@Test
public void createChangeWithWorkInProgressByDefaultForProjectDisabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
ChangeInfo info =
- gApi.changes().create(new ChangeInput(project2.get(), "master", "empty change")).get();
+ gApi.changes().create(new ChangeInput(project.get(), "master", "empty change")).get();
assertThat(info.workInProgress).isNull();
}
@Test
public void createChangeWithWorkInProgressByDefaultForProjectEnabled() throws Exception {
- setWorkInProgressByDefaultForProject(project2);
- ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
+ Project.NameKey project = projectOperations.newProject().create();
+ setWorkInProgressByDefaultForProject(project);
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
assertThat(gApi.changes().create(input).get().workInProgress).isTrue();
}
@Test
public void createChangeWithWorkInProgressByDefaultForUserEnabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
setWorkInProgressByDefaultForUser();
- ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
assertThat(gApi.changes().create(input).get().workInProgress).isTrue();
}
@Test
public void createChangeBypassWorkInProgressByDefaultForProjectEnabled() throws Exception {
- setWorkInProgressByDefaultForProject(project2);
- ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
+ Project.NameKey project = projectOperations.newProject().create();
+ setWorkInProgressByDefaultForProject(project);
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
input.workInProgress = false;
assertThat(gApi.changes().create(input).get().workInProgress).isNull();
}
@Test
public void createChangeBypassWorkInProgressByDefaultForUserEnabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
setWorkInProgressByDefaultForUser();
- ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
input.workInProgress = false;
assertThat(gApi.changes().create(input).get().workInProgress).isNull();
}
@Test
public void createChangeWithWorkInProgressByDefaultForProjectInherited() throws Exception {
- setWorkInProgressByDefaultForProject(project1);
+ Project.NameKey parentProject = projectOperations.newProject().create();
+ Project.NameKey childProject = projectOperations.newProject().parent(parentProject).create();
+ setWorkInProgressByDefaultForProject(parentProject);
ChangeInfo info =
- gApi.changes().create(new ChangeInput(project2.get(), "master", "empty change")).get();
+ gApi.changes().create(new ChangeInput(childProject.get(), "master", "empty change")).get();
assertThat(info.workInProgress).isTrue();
}
@Test
public void pushWithWorkInProgressByDefaultForProjectEnabled() throws Exception {
- setWorkInProgressByDefaultForProject(project2);
- assertThat(createChange(project2).getChange().change().isWorkInProgress()).isTrue();
+ Project.NameKey project = projectOperations.newProject().create();
+ setWorkInProgressByDefaultForProject(project);
+ assertThat(createChange(project).getChange().change().isWorkInProgress()).isTrue();
}
@Test
public void pushWithWorkInProgressByDefaultForUserEnabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
setWorkInProgressByDefaultForUser();
- assertThat(createChange(project2).getChange().change().isWorkInProgress()).isTrue();
+ assertThat(createChange(project).getChange().change().isWorkInProgress()).isTrue();
}
@Test
public void pushBypassWorkInProgressByDefaultForProjectEnabled() throws Exception {
- setWorkInProgressByDefaultForProject(project2);
+ Project.NameKey project = projectOperations.newProject().create();
+ setWorkInProgressByDefaultForProject(project);
assertThat(
- createChange(project2, "refs/for/master%ready").getChange().change().isWorkInProgress())
+ createChange(project, "refs/for/master%ready").getChange().change().isWorkInProgress())
.isFalse();
}
@Test
public void pushBypassWorkInProgressByDefaultForUserEnabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
setWorkInProgressByDefaultForUser();
assertThat(
- createChange(project2, "refs/for/master%ready").getChange().change().isWorkInProgress())
+ createChange(project, "refs/for/master%ready").getChange().change().isWorkInProgress())
.isFalse();
}
@Test
public void pushWithWorkInProgressByDefaultForProjectDisabled() throws Exception {
- assertThat(createChange(project2).getChange().change().isWorkInProgress()).isFalse();
+ Project.NameKey project = projectOperations.newProject().create();
+ assertThat(createChange(project).getChange().change().isWorkInProgress()).isFalse();
}
@Test
public void pushWorkInProgressByDefaultForProjectInherited() throws Exception {
- setWorkInProgressByDefaultForProject(project1);
- assertThat(createChange(project2).getChange().change().isWorkInProgress()).isTrue();
+ Project.NameKey parentProject = projectOperations.newProject().create();
+ Project.NameKey childProject = projectOperations.newProject().parent(parentProject).create();
+ setWorkInProgressByDefaultForProject(parentProject);
+ assertThat(createChange(childProject).getChange().change().isWorkInProgress()).isTrue();
+ }
+
+ @Test
+ public void pushNewPatchSetWithWorkInProgressByDefaultForUserEnabled() throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
+
+ // Create change.
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project);
+ PushOneCommit.Result result =
+ pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master");
+ result.assertOkStatus();
+
+ String changeId = result.getChangeId();
+ assertThat(gApi.changes().id(changeId).get().workInProgress).isNull();
+
+ setWorkInProgressByDefaultForUser();
+
+ // Create new patch set on existing change, this shouldn't mark the change as WIP.
+ result = pushFactory.create(admin.newIdent(), testRepo, changeId).to("refs/for/master");
+ result.assertOkStatus();
+ assertThat(gApi.changes().id(changeId).get().workInProgress).isNull();
+ }
+
+ @Test
+ public void pushNewPatchSetAndNewChangeAtOnceWithWorkInProgressByDefaultForUserEnabled()
+ throws Exception {
+ Project.NameKey project = projectOperations.newProject().create();
+
+ // Create change.
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project);
+ RevCommit initialHead = getHead(testRepo.getRepository(), "HEAD");
+ RevCommit commit1a =
+ testRepo.commit().parent(initialHead).message("Change 1").insertChangeId().create();
+ String changeId1 = GitUtil.getChangeId(testRepo, commit1a).get();
+ testRepo.reset(commit1a);
+ PushResult result = pushHead(testRepo, "refs/for/master", false);
+ assertPushOk(result, "refs/for/master");
+ assertThat(gApi.changes().id(changeId1).get().workInProgress).isNull();
+
+ setWorkInProgressByDefaultForUser();
+
+ // Clone the repo again. The test connection keeps an AccountState internally, so we need to
+ // create a new connection after changing account properties.
+ PatchSet.Id ps1OfChange1 =
+ PatchSet.id(Change.id(gApi.changes().id(changeId1).get()._number), 1);
+ testRepo = cloneProject(project);
+ testRepo.git().fetch().setRefSpecs(RefNames.patchSetRef(ps1OfChange1) + ":c1").call();
+ testRepo.reset("c1");
+
+ // Create a new patch set on the existing change and in the same push create a new successor
+ // change.
+ RevCommit commit1b = testRepo.amend(commit1a).create();
+ testRepo.reset(commit1b);
+ RevCommit commit2 =
+ testRepo.commit().parent(commit1b).message("Change 2").insertChangeId().create();
+ String changeId2 = GitUtil.getChangeId(testRepo, commit2).get();
+ testRepo.reset(commit2);
+ result = pushHead(testRepo, "refs/for/master", false);
+ assertPushOk(result, "refs/for/master");
+
+ // Check that the existing change (changeId1) is not marked as WIP, but only the newly created
+ // change (changeId2).
+ assertThat(gApi.changes().id(changeId1).get().workInProgress).isNull();
+ assertThat(gApi.changes().id(changeId2).get().workInProgress).isTrue();
}
private void setWorkInProgressByDefaultForProject(Project.NameKey p) throws Exception {
@@ -145,9 +212,12 @@ public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
}
private void setWorkInProgressByDefaultForUser() throws Exception {
- GeneralPreferencesInfo prefs = gApi.accounts().id(admin.id().get()).getPreferences();
+ GeneralPreferencesInfo prefs = new GeneralPreferencesInfo();
prefs.workInProgressByDefault = true;
gApi.accounts().id(admin.id().get()).setPreferences(prefs);
+ // Generate a new API scope. User preferences are stored in IdentifiedUser, so we need to flush
+ // that entity.
+ requestScopeOperations.resetCurrentApiUser();
}
private PushOneCommit.Result createChange(Project.NameKey p) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
index 7ef915b958..daeb032a82 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -15,19 +15,24 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH;
import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH_ALL;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
import com.google.gerrit.server.restapi.config.PostCaches;
+import com.google.inject.Inject;
import java.util.Arrays;
import org.junit.Test;
public class CacheOperationsIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Test
public void flushAll() throws Exception {
@@ -124,8 +129,11 @@ public class CacheOperationsIT extends AbstractDaemonTest {
@Test
public void flushWebSessions_Forbidden() throws Exception {
- allowGlobalCapabilities(
- REGISTERED_USERS, GlobalCapability.FLUSH_CACHES, GlobalCapability.VIEW_CACHES);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.FLUSH_CACHES).group(REGISTERED_USERS))
+ .add(allowCapability(GlobalCapability.VIEW_CACHES).group(REGISTERED_USERS))
+ .update();
try {
RestResponse r =
userRestSession.post(
@@ -138,8 +146,11 @@ public class CacheOperationsIT extends AbstractDaemonTest {
"/config/server/caches/", new PostCaches.Input(FLUSH, Arrays.asList("web_sessions")))
.assertForbidden();
} finally {
- removeGlobalCapabilities(
- REGISTERED_USERS, GlobalCapability.FLUSH_CACHES, GlobalCapability.VIEW_CACHES);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(capabilityKey(GlobalCapability.FLUSH_CACHES).group(REGISTERED_USERS))
+ .remove(capabilityKey(GlobalCapability.VIEW_CACHES).group(REGISTERED_USERS))
+ .update();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
index caecefade0..a161ec4eb7 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
@@ -15,15 +15,20 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
+import com.google.inject.Inject;
import org.junit.Test;
public class FlushCacheIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Test
public void flushCache() throws Exception {
@@ -65,8 +70,11 @@ public class FlushCacheIT extends AbstractDaemonTest {
@Test
public void flushWebSessionsCache_Forbidden() throws Exception {
- allowGlobalCapabilities(
- REGISTERED_USERS, GlobalCapability.VIEW_CACHES, GlobalCapability.FLUSH_CACHES);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.FLUSH_CACHES).group(REGISTERED_USERS))
+ .add(allowCapability(GlobalCapability.VIEW_CACHES).group(REGISTERED_USERS))
+ .update();
try {
RestResponse r = userRestSession.post("/config/server/caches/accounts/flush");
r.assertOK();
@@ -74,8 +82,11 @@ public class FlushCacheIT extends AbstractDaemonTest {
userRestSession.post("/config/server/caches/web_sessions/flush").assertForbidden();
} finally {
- removeGlobalCapabilities(
- REGISTERED_USERS, GlobalCapability.VIEW_CACHES, GlobalCapability.FLUSH_CACHES);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(capabilityKey(GlobalCapability.FLUSH_CACHES).group(REGISTERED_USERS))
+ .remove(capabilityKey(GlobalCapability.VIEW_CACHES).group(REGISTERED_USERS))
+ .update();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
index fa35d19813..a3c1722982 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
@@ -15,84 +15,90 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.ChangeIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.server.restapi.config.IndexChanges;
import com.google.inject.Inject;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
public class IndexChangesIT extends AbstractDaemonTest {
- @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
-
- private ChangeIndexedCounter changeIndexedCounter;
- private RegistrationHandle changeIndexedCounterHandle;
-
- @Before
- public void addChangeIndexedCounter() {
- changeIndexedCounter = new ChangeIndexedCounter();
- changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
- }
-
- @After
- public void removeChangeIndexedCounter() {
- if (changeIndexedCounterHandle != null) {
- changeIndexedCounterHandle.remove();
- }
- }
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
@Test
public void indexRequestFromNonAdminRejected() throws Exception {
- String changeId = createChange().getChangeId();
- IndexChanges.Input in = new IndexChanges.Input();
- in.changes = ImmutableSet.of(changeId);
- changeIndexedCounter.clear();
- userRestSession.post("/config/server/index.changes", in).assertForbidden();
- assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0);
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ String changeId = createChange().getChangeId();
+ IndexChanges.Input in = new IndexChanges.Input();
+ in.changes = ImmutableSet.of(changeId);
+ changeIndexedCounter.clear();
+ userRestSession.post("/config/server/index.changes", in).assertForbidden();
+ assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0);
+ }
}
@Test
public void indexVisibleChange() throws Exception {
- String changeId = createChange().getChangeId();
- IndexChanges.Input in = new IndexChanges.Input();
- in.changes = ImmutableSet.of(changeId);
- changeIndexedCounter.clear();
- adminRestSession.post("/config/server/index.changes", in).assertOK();
- assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ String changeId = createChange().getChangeId();
+ IndexChanges.Input in = new IndexChanges.Input();
+ in.changes = ImmutableSet.of(changeId);
+ changeIndexedCounter.clear();
+ adminRestSession.post("/config/server/index.changes", in).assertOK();
+ assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+ }
}
@Test
public void indexNonVisibleChange() throws Exception {
- String changeId = createChange().getChangeId();
- ChangeInfo changeInfo = info(changeId);
- blockRead("refs/heads/master");
- IndexChanges.Input in = new IndexChanges.Input();
- changeIndexedCounter.clear();
- in.changes = ImmutableSet.of(changeId);
- adminRestSession.post("/config/server/index.changes", in).assertOK();
- assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1);
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ String changeId = createChange().getChangeId();
+ ChangeInfo changeInfo = info(changeId);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
+ IndexChanges.Input in = new IndexChanges.Input();
+ changeIndexedCounter.clear();
+ in.changes = ImmutableSet.of(changeId);
+ adminRestSession.post("/config/server/index.changes", in).assertOK();
+ assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1);
+ }
}
@Test
public void indexMultipleChanges() throws Exception {
- ImmutableSet.Builder<String> changeIds = ImmutableSet.builder();
- for (int i = 0; i < 10; i++) {
- changeIds.add(createChange().getChangeId());
- }
- IndexChanges.Input in = new IndexChanges.Input();
- in.changes = changeIds.build();
- changeIndexedCounter.clear();
- adminRestSession.post("/config/server/index.changes", in).assertOK();
- for (String changeId : in.changes) {
- assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ ImmutableSet.Builder<String> changeIds = ImmutableSet.builder();
+ for (int i = 0; i < 10; i++) {
+ changeIds.add(createChange().getChangeId());
+ }
+ IndexChanges.Input in = new IndexChanges.Input();
+ in.changes = changeIds.build();
+ changeIndexedCounter.clear();
+ adminRestSession.post("/config/server/index.changes", in).assertOK();
+ for (String changeId : in.changes) {
+ assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1);
+ }
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 4a740182e2..1d87ca1af0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -168,6 +168,8 @@ public class ServerInfoIT extends AbstractDaemonTest {
assertThat(i.change.replyLabel).isEqualTo("Reply\u2026");
assertThat(i.change.updateDelay).isEqualTo(300);
assertThat(i.change.disablePrivateChanges).isNull();
+ assertThat(i.change.submitWholeTopic).isNull();
+ assertThat(i.change.excludeMergeableInChangeInfo).isNull();
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
@@ -190,4 +192,18 @@ public class ServerInfoIT extends AbstractDaemonTest {
// user
assertThat(i.user.anonymousCowardName).isEqualTo(AnonymousCowardNameProvider.DEFAULT);
}
+
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void serverConfigWithSubmitWholeTopic() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.change.submitWholeTopic).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "change.api.excludeMergeableInChangeInfo", value = "true")
+ public void serverConfigWithExcludeMergeableInChangeInfo() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.change.excludeMergeableInChangeInfo).isTrue();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
index 35812fd4a7..9276b9a5c3 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AbstractPushTag.java
@@ -14,21 +14,25 @@
package com.google.gerrit.acceptance.rest.project;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.createAnnotatedTag;
import static com.google.gerrit.acceptance.GitUtil.deleteRef;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.GitUtil.updateAnnotatedTag;
import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.ANNOTATED;
import static com.google.gerrit.acceptance.rest.project.AbstractPushTag.TagType.LIGHTWEIGHT;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.base.MoreObjects;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -57,12 +61,14 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
return config;
}
+ @Inject private ProjectOperations projectOperations;
+
private RevCommit initialHead;
private TagType tagType;
@Before
public void setUpTestEnvironment() throws Exception {
- initialHead = getRemoteHead();
+ initialHead = projectOperations.project(project).getHead("master");
tagType = getTagType();
blockAnonymousRead();
}
@@ -235,7 +241,11 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
}
if (!newCommit) {
- grant(project, "refs/for/refs/heads/master", Permission.SUBMIT, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/master").group(REGISTERED_USERS))
+ .update();
pushHead(testRepo, "refs/for/master%submit");
}
@@ -245,7 +255,7 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
? pushHead(testRepo, tagRef, false, force)
: GitUtil.pushTag(testRepo, tagName, !createTag);
RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
- assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
+ assertWithMessage(tagType.name()).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
return tagName;
}
@@ -253,16 +263,28 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
String tagRef = tagRef(tagName);
PushResult r = deleteRef(testRepo, tagRef);
RemoteRefUpdate refUpdate = r.getRemoteUpdate(tagRef);
- assertThat(refUpdate.getStatus()).named(tagType.name()).isEqualTo(expectedStatus);
+ assertWithMessage(tagType.name()).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
}
- private void removeReadAccessOnRefsStar() throws Exception {
- removePermission(allProjects, "refs/heads/*", Permission.READ);
- removePermission(project, "refs/heads/*", Permission.READ);
+ private void removeReadAccessOnRefsStar() {
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .remove(permissionKey(Permission.READ).ref("refs/*"))
+ .update();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.READ).ref("refs/*"))
+ .update();
}
- private void grantReadAccessOnRefsHeadsStar() throws Exception {
- grant(project, "refs/heads/*", Permission.READ, false, REGISTERED_USERS);
+ private void grantReadAccessOnRefsHeadsStar() {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
}
private void allowReadingAllTag() throws Exception {
@@ -280,30 +302,54 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
// visible since by default READ access to it is exclusively granted to the project owners only.
// This means to make all refs, and thus all tags, visible, we must allow registered users to
// see the refs/meta/config branch.
- allow(project, "refs/meta/config", Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/meta/config").group(REGISTERED_USERS))
+ .update();
}
private void allowTagCreation() throws Exception {
- grant(project, "refs/tags/*", tagType.createPermission, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(tagType.createPermission).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
}
private void allowPushOnRefsTags() throws Exception {
removePushFromRefsTags();
- grant(project, "refs/tags/*", Permission.PUSH, false, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
}
private void allowForcePushOnRefsTags() throws Exception {
removePushFromRefsTags();
- grant(project, "refs/tags/*", Permission.PUSH, true, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS).force(true))
+ .update();
}
private void allowTagDeletion() throws Exception {
removePushFromRefsTags();
- grant(project, "refs/tags/*", Permission.DELETE, true, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE).ref("refs/tags/*").group(REGISTERED_USERS).force(true))
+ .update();
}
private void removePushFromRefsTags() throws Exception {
- removePermission(project, "refs/tags/*", Permission.PUSH);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(permissionKey(Permission.PUSH).ref("refs/tags/*"))
+ .update();
}
private void commit(PersonIdent ident, String subject) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index b199e32c97..eb9a81a62b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -15,13 +15,17 @@ package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
@@ -30,26 +34,23 @@ import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectConfig;
@@ -75,9 +76,9 @@ public class AccessIT extends AbstractDaemonTest {
private static final String LABEL_CODE_REVIEW = "Code-Review";
- @Inject private DynamicSet<FileHistoryWebLink> fileHistoryWebLinkDynamicSet;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
private Project.NameKey newProjectName;
@@ -92,45 +93,45 @@ public class AccessIT extends AbstractDaemonTest {
assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
}
+ private Registration newFileHistoryWebLink() {
+ FileHistoryWebLink weblink =
+ new FileHistoryWebLink() {
+ @Override
+ public WebLinkInfo getFileHistoryWebLink(
+ String projectName, String revision, String fileName) {
+ return new WebLinkInfo(
+ "name", "imageURL", "http://view/" + projectName + "/" + fileName);
+ }
+ };
+ return extensionRegistry.newRegistration().add(weblink);
+ }
+
@Test
public void webLink() throws Exception {
- RegistrationHandle handle =
- fileHistoryWebLinkDynamicSet.add(
- "gerrit",
- (projectName, revision, fileName) ->
- new WebLinkInfo("name", "imageURL", "http://view/" + projectName + "/" + fileName));
- try {
+ try (Registration registration = newFileHistoryWebLink()) {
ProjectAccessInfo info = pApi().access();
assertThat(info.configWebLinks).hasSize(1);
assertThat(info.configWebLinks.get(0).url)
.isEqualTo("http://view/" + newProjectName + "/project.config");
- } finally {
- handle.remove();
}
}
@Test
public void webLinkNoRefsMetaConfig() throws Exception {
- RegistrationHandle handle =
- fileHistoryWebLinkDynamicSet.add(
- "gerrit",
- (projectName, revision, fileName) ->
- new WebLinkInfo("name", "imageURL", "http://view/" + projectName + "/" + fileName));
- try (Repository repo = repoManager.openRepository(newProjectName)) {
+ try (Repository repo = repoManager.openRepository(newProjectName);
+ Registration registration = newFileHistoryWebLink()) {
RefUpdate u = repo.updateRef(RefNames.REFS_CONFIG);
u.setForceUpdate(true);
assertThat(u.delete()).isEqualTo(Result.FORCED);
// This should not crash.
pApi().access();
- } finally {
- handle.remove();
}
}
@Test
public void addAccessSection() throws Exception {
- RevCommit initialHead = getRemoteHead(newProjectName, RefNames.REFS_CONFIG);
+ RevCommit initialHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
@@ -140,7 +141,7 @@ public class AccessIT extends AbstractDaemonTest {
assertThat(pApi().access().local).isEqualTo(accessInput.add);
- RevCommit updatedHead = getRemoteHead(newProjectName, RefNames.REFS_CONFIG);
+ RevCommit updatedHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(
newProjectName.get(), RefNames.REFS_CONFIG, null, initialHead, initialHead, updatedHead);
}
@@ -148,8 +149,7 @@ public class AccessIT extends AbstractDaemonTest {
@Test
public void createAccessChangeNop() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
- exception.expect(BadRequestException.class);
- pApi().accessChange(accessInput);
+ assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
}
@Test
@@ -174,7 +174,11 @@ public class AccessIT extends AbstractDaemonTest {
@Test
public void createAccessChange() throws Exception {
- allow(newProjectName, RefNames.REFS_CONFIG, Permission.READ, REGISTERED_USERS);
+ projectOperations
+ .project(newProjectName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
// User can see the branch
requestScopeOperations.setApiUser(user.id());
pApi().branch("refs/heads/master").get();
@@ -211,12 +215,7 @@ public class AccessIT extends AbstractDaemonTest {
// check that the change took effect.
requestScopeOperations.setApiUser(user.id());
- try {
- BranchInfo info = pApi().branch("refs/heads/master").get();
- fail("wanted failure, got " + newGson().toJson(info));
- } catch (ResourceNotFoundException e) {
- // OK.
- }
+ assertThrows(ResourceNotFoundException.class, () -> pApi().branch("refs/heads/master").get());
// Restore.
accessInput.add.clear();
@@ -331,8 +330,7 @@ public class AccessIT extends AbstractDaemonTest {
pApi().access(accessInput);
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- pApi().access();
+ assertThrows(ResourceNotFoundException.class, () -> pApi().access());
}
@Test
@@ -351,8 +349,7 @@ public class AccessIT extends AbstractDaemonTest {
accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- pApi().access();
+ assertThrows(ResourceNotFoundException.class, () -> pApi().access());
}
@Test
@@ -414,9 +411,8 @@ public class AccessIT extends AbstractDaemonTest {
accessInput.parent = newParentProjectName;
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- exception.expectMessage("administrate server not permitted");
- pApi().access(accessInput);
+ AuthException thrown = assertThrows(AuthException.class, () -> pApi().access(accessInput));
+ assertThat(thrown).hasMessageThat().contains("administrate server not permitted");
}
@Test
@@ -441,8 +437,8 @@ public class AccessIT extends AbstractDaemonTest {
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThrows(
+ AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
@@ -470,8 +466,7 @@ public class AccessIT extends AbstractDaemonTest {
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
- exception.expect(BadRequestException.class);
- pApi().access(accessInput);
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
}
@Test
@@ -484,9 +479,9 @@ public class AccessIT extends AbstractDaemonTest {
accessSectionInfo.permissions.put(Permission.PUSH, permissionInfo);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- exception.expect(BadRequestException.class);
- gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
@@ -497,8 +492,8 @@ public class AccessIT extends AbstractDaemonTest {
accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThrows(
+ AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
@@ -577,7 +572,7 @@ public class AccessIT extends AbstractDaemonTest {
.file(ProjectConfig.PROJECT_CONFIG)
.asString();
cfg.fromText(config);
- assertThat(cfg.getString(access, refsFor, unknownPermission)).isEqualTo(registeredUsers);
+ assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
// Make permission change through API
ProjectAccessInput accessInput = newProjectAccessInput();
@@ -596,16 +591,20 @@ public class AccessIT extends AbstractDaemonTest {
.file(ProjectConfig.PROJECT_CONFIG)
.asString();
cfg.fromText(config);
- assertThat(cfg.getString(access, refsFor, unknownPermission)).isEqualTo(registeredUsers);
+ assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
}
@Test
public void allUsersCanOnlyInheritFromAllProjects() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
accessInput.parent = project.get();
- exception.expect(BadRequestException.class);
- exception.expectMessage(allUsers.get() + " must inherit from " + allProjects.get());
- gApi.projects().name(allUsers.get()).access(accessInput);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allUsers.get()).access(accessInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(allUsers.get() + " must inherit from " + allProjects.get());
}
@Test
@@ -655,9 +654,9 @@ public class AccessIT extends AbstractDaemonTest {
String invalidRef = Constants.R_HEADS + "stable_*";
accessInput.add.put(invalidRef, accessSectionInfo);
- exception.expect(BadRequestException.class);
- exception.expectMessage("Invalid Name: " + invalidRef);
- pApi().access(accessInput);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
+ assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
}
@Test
@@ -669,9 +668,9 @@ public class AccessIT extends AbstractDaemonTest {
String invalidRef = Constants.R_HEADS + "stable_*";
accessInput.add.put(invalidRef, accessSectionInfo);
- exception.expect(BadRequestException.class);
- exception.expectMessage("Invalid Name: " + invalidRef);
- pApi().accessChange(accessInput);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
+ assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
}
private ProjectApi pApi() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BUILD b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
index 6e66aab4d4..ca187a341c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
@@ -1,9 +1,9 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
-acceptance_tests(
- srcs = glob(["*IT.java"]),
- group = "rest_project",
+[acceptance_tests(
+ srcs = [f],
+ group = f[:f.index(".")],
labels = ["rest"],
deps = [
":project",
@@ -11,7 +11,7 @@ acceptance_tests(
":refassert",
"//lib/commons:lang",
],
-)
+) for f in glob(["*IT.java"])]
java_library(
name = "refassert",
@@ -31,8 +31,8 @@ java_library(
"ProjectAssert.java",
],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//lib:guava",
"//lib/truth",
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CheckMergeabilityIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CheckMergeabilityIT.java
index 7667fc0c9f..a2f976a3ff 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CheckMergeabilityIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CheckMergeabilityIT.java
@@ -20,12 +20,14 @@ import com.google.common.base.Strings;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.common.MergeableInfo;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RefSpec;
@@ -34,20 +36,19 @@ import org.junit.Test;
public class CheckMergeabilityIT extends AbstractDaemonTest {
- private Branch.NameKey branch;
+ @Inject private ProjectOperations projectOperations;
+
+ private BranchNameKey branch;
@Before
public void setUp() throws Exception {
- branch = new Branch.NameKey(project, "test");
- gApi.projects()
- .name(branch.getParentKey().get())
- .branch(branch.get())
- .create(new BranchInput());
+ branch = BranchNameKey.create(project, "test");
+ gApi.projects().name(branch.project().get()).branch(branch.branch()).create(new BranchInput());
}
@Test
public void checkMergeableCommit() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo
.branch("HEAD")
.commit()
@@ -82,7 +83,7 @@ public class CheckMergeabilityIT extends AbstractDaemonTest {
@Test
public void checkUnMergeableCommit() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo
.branch("HEAD")
.commit()
@@ -117,7 +118,7 @@ public class CheckMergeabilityIT extends AbstractDaemonTest {
@Test
public void checkOursMergeStrategy() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo
.branch("HEAD")
.commit()
@@ -211,7 +212,7 @@ public class CheckMergeabilityIT extends AbstractDaemonTest {
cherry.current().review(ReviewInput.approve());
cherry.current().submit();
- ObjectId remoteId = getRemoteHead();
+ ObjectId remoteId = projectOperations.project(project).getHead("master");
assertThat(remoteId).isNotEqualTo(commitId);
assertContentMerged("master", commitId.getName(), "recursive");
}
@@ -237,7 +238,7 @@ public class CheckMergeabilityIT extends AbstractDaemonTest {
@Test
public void checkInvalidStrategy() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
testRepo
.branch("HEAD")
.commit()
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 698f7e03c0..85d383eb29 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -16,14 +16,22 @@ package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_HEADS;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.entities.RefNames.REFS_HEADS;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -31,23 +39,20 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
public class CreateBranchIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- private Branch.NameKey testBranch;
+ private BranchNameKey testBranch;
@Before
public void setUp() throws Exception {
- testBranch = new Branch.NameKey(project, "test");
+ testBranch = BranchNameKey.create(project, "test");
}
@Test
@@ -104,17 +109,25 @@ public class CreateBranchIT extends AbstractDaemonTest {
@Test
public void createMetaBranch() throws Exception {
String metaRef = RefNames.REFS_META + "foo";
- allow(metaRef, Permission.CREATE, REGISTERED_USERS);
- allow(metaRef, Permission.PUSH, REGISTERED_USERS);
- assertCreateSucceeds(new Branch.NameKey(project, metaRef));
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(metaRef).group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(metaRef).group(REGISTERED_USERS))
+ .update();
+ assertCreateSucceeds(BranchNameKey.create(project, metaRef));
}
@Test
public void createUserBranch_Conflict() throws Exception {
- allow(allUsers, RefNames.REFS_USERS + "*", Permission.CREATE, REGISTERED_USERS);
- allow(allUsers, RefNames.REFS_USERS + "*", Permission.PUSH, REGISTERED_USERS);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
+ .update();
assertCreateFails(
- new Branch.NameKey(allUsers, RefNames.refsUsers(new Account.Id(1))),
+ BranchNameKey.create(allUsers, RefNames.refsUsers(Account.id(1))),
RefNames.refsUsers(admin.id()),
ResourceConflictException.class,
"Not allowed to create user branch.");
@@ -122,10 +135,14 @@ public class CreateBranchIT extends AbstractDaemonTest {
@Test
public void createGroupBranch_Conflict() throws Exception {
- allow(allUsers, RefNames.REFS_GROUPS + "*", Permission.CREATE, REGISTERED_USERS);
- allow(allUsers, RefNames.REFS_GROUPS + "*", Permission.PUSH, REGISTERED_USERS);
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
assertCreateFails(
- new Branch.NameKey(allUsers, RefNames.refsGroups(new AccountGroup.UUID("foo"))),
+ BranchNameKey.create(allUsers, RefNames.refsGroups(AccountGroup.uuid("foo"))),
RefNames.refsGroups(adminGroupUuid()),
ResourceConflictException.class,
"Not allowed to create group branch.");
@@ -133,81 +150,85 @@ public class CreateBranchIT extends AbstractDaemonTest {
@Test
public void createWithRevision() throws Exception {
- RevCommit revision = getRemoteHead(project, "master");
+ RevCommit revision = projectOperations.project(project).getHead("master");
// update master so that points to a different revision than the revision on which we create the
// new branch
pushTo("refs/heads/master");
- assertThat(getRemoteHead(project, "master")).isNotEqualTo(revision);
+ assertThat(projectOperations.project(project).getHead("master")).isNotEqualTo(revision);
BranchInput input = new BranchInput();
input.revision = revision.name();
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(revision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(revision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch())).isEqualTo(revision);
}
@Test
public void createWithoutSpecifyingRevision() throws Exception {
// If revision is not specified, the branch is created based on HEAD, which points to master.
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
BranchInput input = new BranchInput();
input.revision = null;
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(expectedRevision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(expectedRevision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch()))
+ .isEqualTo(expectedRevision);
}
@Test
public void createWithEmptyRevision() throws Exception {
// If revision is not specified, the branch is created based on HEAD, which points to master.
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
BranchInput input = new BranchInput();
input.revision = "";
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(expectedRevision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(expectedRevision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch()))
+ .isEqualTo(expectedRevision);
}
@Test
public void createRevisionIsTrimmed() throws Exception {
- RevCommit revision = getRemoteHead(project, "master");
+ RevCommit revision = projectOperations.project(project).getHead("master");
BranchInput input = new BranchInput();
input.revision = "\t" + revision.name();
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(revision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(revision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch())).isEqualTo(revision);
}
@Test
public void createWithBranchNameAsRevision() throws Exception {
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
BranchInput input = new BranchInput();
input.revision = "master";
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(expectedRevision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(expectedRevision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch()))
+ .isEqualTo(expectedRevision);
}
@Test
public void createWithFullBranchNameAsRevision() throws Exception {
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
BranchInput input = new BranchInput();
input.revision = "refs/heads/master";
BranchInfo created = branch(testBranch).create(input).get();
- assertThat(created.ref).isEqualTo(testBranch.get());
+ assertThat(created.ref).isEqualTo(testBranch.branch());
assertThat(created.revision).isEqualTo(expectedRevision.name());
- assertThat(getRemoteHead(project, testBranch.getShortName())).isEqualTo(expectedRevision);
+ assertThat(projectOperations.project(project).getHead(testBranch.branch()))
+ .isEqualTo(expectedRevision);
}
@Test
@@ -238,44 +259,51 @@ public class CreateBranchIT extends AbstractDaemonTest {
}
private void blockCreateReference() throws Exception {
- block("refs/*", Permission.CREATE, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.CREATE).ref("refs/*").group(ANONYMOUS_USERS))
+ .update();
}
private void grantOwner() throws Exception {
- allow("refs/*", Permission.OWNER, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
}
- private BranchApi branch(Branch.NameKey branch) throws Exception {
- return gApi.projects().name(branch.getParentKey().get()).branch(branch.get());
+ private BranchApi branch(BranchNameKey branch) throws Exception {
+ return gApi.projects().name(branch.project().get()).branch(branch.branch());
}
- private void assertCreateSucceeds(Branch.NameKey branch) throws Exception {
+ private void assertCreateSucceeds(BranchNameKey branch) throws Exception {
BranchInfo created = branch(branch).create(new BranchInput()).get();
- assertThat(created.ref).isEqualTo(branch.get());
+ assertThat(created.ref).isEqualTo(branch.branch());
}
private void assertCreateFails(
- Branch.NameKey branch, Class<? extends RestApiException> errType, String errMsg)
+ BranchNameKey branch, Class<? extends RestApiException> errType, String errMsg)
throws Exception {
assertCreateFails(branch, null, errType, errMsg);
}
private void assertCreateFails(
- Branch.NameKey branch,
+ BranchNameKey branch,
String revision,
Class<? extends RestApiException> errType,
String errMsg)
throws Exception {
BranchInput in = new BranchInput();
in.revision = revision;
+ RestApiException thrown = assertThrows(errType, () -> branch(branch).create(in));
if (errMsg != null) {
- exception.expectMessage(errMsg);
+ assertThat(thrown).hasMessageThat().contains(errMsg);
}
- exception.expect(errType);
- branch(branch).create(in);
}
- private void assertCreateFails(Branch.NameKey branch, Class<? extends RestApiException> errType)
+ private void assertCreateFails(BranchNameKey branch, Class<? extends RestApiException> errType)
throws Exception {
assertCreateFails(branch, errType, null);
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 463532f71e..6b2baa7b48 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -19,7 +19,10 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectOwners;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
@@ -30,8 +33,13 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -43,10 +51,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
@@ -72,6 +76,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Test;
public class CreateProjectIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
@@ -86,7 +91,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
// for more extensive coverage of the LabelTypeInfo.
assertThat(p.labels).hasSize(1);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
@@ -162,7 +167,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName).get();
assertThat(p.name).isEqualTo(newProjectName);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
@@ -175,7 +180,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName + ".git").get();
assertThat(p.name).isEqualTo(newProjectName);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
@@ -186,7 +191,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName + "/").get();
assertThat(p.name).isEqualTo(newProjectName);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
@@ -197,7 +202,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
String newProjectName = name("newProject/newProject");
ProjectInfo p = gApi.projects().create(newProjectName).get();
assertThat(p.name).isEqualTo(newProjectName);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertProjectInfo(projectState.getProject(), p);
assertHead(newProjectName, "refs/heads/master");
@@ -216,7 +221,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
in.requireChangeId = InheritableBoolean.TRUE;
ProjectInfo p = gApi.projects().create(in).get();
assertThat(p.name).isEqualTo(newProjectName);
- Project project = projectCache.get(new Project.NameKey(newProjectName)).getProject();
+ Project project = projectCache.get(Project.nameKey(newProjectName)).getProject();
assertProjectInfo(project, p);
assertThat(project.getDescription()).isEqualTo(in.description);
assertThat(project.getConfiguredSubmitType()).isEqualTo(in.submitType);
@@ -242,7 +247,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
in.name = childName;
in.parent = parentName;
gApi.projects().create(in);
- Project project = projectCache.get(new Project.NameKey(childName)).getProject();
+ Project project = projectCache.get(Project.nameKey(childName)).getProject();
assertThat(project.getParentName()).isEqualTo(in.parent);
}
@@ -265,12 +270,12 @@ public class CreateProjectIT extends AbstractDaemonTest {
in.owners.add(
Integer.toString(
groupCache
- .get(new AccountGroup.NameKey("Administrators"))
+ .get(AccountGroup.nameKey("Administrators"))
.orElse(null)
.getId()
.get())); // by ID
gApi.projects().create(in);
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
Set<AccountGroup.UUID> expectedOwnerIds = Sets.newHashSetWithExpectedSize(3);
expectedOwnerIds.add(SystemGroupBackend.ANONYMOUS_USERS);
expectedOwnerIds.add(SystemGroupBackend.REGISTERED_USERS);
@@ -323,7 +328,12 @@ public class CreateProjectIT extends AbstractDaemonTest {
@Test
public void createProjectWithCapability() throws Exception {
- allowGlobalCapabilities(SystemGroupBackend.REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(
+ allowCapability(GlobalCapability.CREATE_PROJECT)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
try {
requestScopeOperations.setApiUser(user.id());
ProjectInput in = new ProjectInput();
@@ -331,8 +341,12 @@ public class CreateProjectIT extends AbstractDaemonTest {
ProjectInfo p = gApi.projects().create(in).get();
assertThat(p.name).isEqualTo(in.name);
} finally {
- removeGlobalCapabilities(
- SystemGroupBackend.REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(
+ capabilityKey(GlobalCapability.CREATE_PROJECT)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
}
}
@@ -355,7 +369,12 @@ public class CreateProjectIT extends AbstractDaemonTest {
public void createProjectWithCreateProjectCapabilityAndParentNotVisible() throws Exception {
Project parent = projectCache.get(allProjects).getProject();
parent.setState(com.google.gerrit.extensions.client.ProjectState.HIDDEN);
- allowGlobalCapabilities(SystemGroupBackend.REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(
+ allowCapability(GlobalCapability.CREATE_PROJECT)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
try {
requestScopeOperations.setApiUser(user.id());
ProjectInput in = new ProjectInput();
@@ -364,8 +383,12 @@ public class CreateProjectIT extends AbstractDaemonTest {
assertThat(p.name).isEqualTo(in.name);
} finally {
parent.setState(com.google.gerrit.extensions.client.ProjectState.ACTIVE);
- removeGlobalCapabilities(
- SystemGroupBackend.REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(
+ capabilityKey(GlobalCapability.CREATE_PROJECT)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
}
}
@@ -447,13 +470,13 @@ public class CreateProjectIT extends AbstractDaemonTest {
}
private void assertHead(String projectName, String expectedRef) throws Exception {
- try (Repository repo = repoManager.openRepository(new Project.NameKey(projectName))) {
+ try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
assertThat(repo.exactRef(Constants.HEAD).getTarget().getName()).isEqualTo(expectedRef);
}
}
private void assertEmptyCommit(String projectName, String... refs) throws Exception {
- Project.NameKey projectKey = new Project.NameKey(projectName);
+ Project.NameKey projectKey = Project.nameKey(projectName);
try (Repository repo = repoManager.openRepository(projectKey);
RevWalk rw = new RevWalk(repo);
TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
@@ -469,12 +492,11 @@ public class CreateProjectIT extends AbstractDaemonTest {
private void assertCreateFails(ProjectInput in, Class<? extends RestApiException> errType)
throws Exception {
- exception.expect(errType);
- gApi.projects().create(in);
+ assertThrows(errType, () -> gApi.projects().create(in));
}
private Optional<String> readProjectConfig(String projectName) throws Exception {
- try (Repository repo = repoManager.openRepository(new Project.NameKey(projectName));
+ try (Repository repo = repoManager.openRepository(Project.nameKey(projectName));
TestRepository<Repository> tr = new TestRepository<>(repo)) {
RevWalk rw = tr.getRevWalk();
Ref ref = repo.exactRef(RefNames.REFS_CONFIG);
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index ccdc4973dd..5636014f91 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -25,6 +27,8 @@ import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -32,8 +36,6 @@ import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import org.junit.Before;
import org.junit.Test;
@@ -42,12 +44,12 @@ public class DeleteBranchIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- private Branch.NameKey testBranch;
+ private BranchNameKey testBranch;
@Before
public void setUp() throws Exception {
project = projectOperations.newProject().create();
- testBranch = new Branch.NameKey(project, "test");
+ testBranch = BranchNameKey.create(project, "test");
branch(testBranch).create(new BranchInput());
}
@@ -100,7 +102,7 @@ public class DeleteBranchIT extends AbstractDaemonTest {
@Test
public void deleteBranchByRestWithoutRefsHeadsPrefix() throws Exception {
grantDelete();
- String ref = testBranch.getShortName();
+ String ref = testBranch.shortName();
assertThat(ref).doesNotMatch(R_HEADS);
assertDeleteByRestSucceeds(testBranch, ref);
}
@@ -108,14 +110,14 @@ public class DeleteBranchIT extends AbstractDaemonTest {
@Test
public void deleteBranchByRestWithFullName() throws Exception {
grantDelete();
- assertDeleteByRestSucceeds(testBranch, testBranch.get());
+ assertDeleteByRestSucceeds(testBranch, testBranch.branch());
}
@Test
public void deleteBranchByRestFailsWithUnencodedFullName() throws Exception {
grantDelete();
RestResponse r =
- userRestSession.delete("/projects/" + project.get() + "/branches/" + testBranch.get());
+ userRestSession.delete("/projects/" + project.get() + "/branches/" + testBranch.branch());
r.assertNotFound();
branch(testBranch).get();
}
@@ -123,10 +125,14 @@ public class DeleteBranchIT extends AbstractDaemonTest {
@Test
public void deleteMetaBranch() throws Exception {
String metaRef = RefNames.REFS_META + "foo";
- allow(metaRef, Permission.CREATE, REGISTERED_USERS);
- allow(metaRef, Permission.PUSH, REGISTERED_USERS);
-
- Branch.NameKey metaBranch = new Branch.NameKey(project, metaRef);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(metaRef).group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(metaRef).group(REGISTERED_USERS))
+ .update();
+
+ BranchNameKey metaBranch = BranchNameKey.create(project, metaRef);
branch(metaBranch).create(new BranchInput());
grantDelete();
@@ -135,22 +141,36 @@ public class DeleteBranchIT extends AbstractDaemonTest {
@Test
public void deleteUserBranch_Conflict() throws Exception {
- allow(allUsers, RefNames.REFS_USERS + "*", Permission.CREATE, REGISTERED_USERS);
- allow(allUsers, RefNames.REFS_USERS + "*", Permission.PUSH, REGISTERED_USERS);
-
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Not allowed to delete user branch.");
- branch(new Branch.NameKey(allUsers, RefNames.refsUsers(admin.id()))).delete();
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
+ .update();
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> branch(BranchNameKey.create(allUsers, RefNames.refsUsers(admin.id()))).delete());
+ assertThat(thrown).hasMessageThat().contains("Not allowed to delete user branch.");
}
@Test
public void deleteGroupBranch_Conflict() throws Exception {
- allow(allUsers, RefNames.REFS_GROUPS + "*", Permission.CREATE, REGISTERED_USERS);
- allow(allUsers, RefNames.REFS_GROUPS + "*", Permission.PUSH, REGISTERED_USERS);
-
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Not allowed to delete group branch.");
- branch(new Branch.NameKey(allUsers, RefNames.refsGroups(adminGroupUuid()))).delete();
+ projectOperations
+ .project(allUsers)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
+ .update();
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ branch(BranchNameKey.create(allUsers, RefNames.refsGroups(adminGroupUuid())))
+ .delete());
+ assertThat(thrown).hasMessageThat().contains("Not allowed to delete group branch.");
}
@Test
@@ -158,7 +178,7 @@ public class DeleteBranchIT extends AbstractDaemonTest {
MethodNotAllowedException thrown =
assertThrows(
MethodNotAllowedException.class,
- () -> branch(new Branch.NameKey(allUsers, RefNames.REFS_CONFIG)).delete());
+ () -> branch(BranchNameKey.create(allUsers, RefNames.REFS_CONFIG)).delete());
assertThat(thrown).hasMessageThat().contains("not allowed to delete branch refs/meta/config");
}
@@ -167,31 +187,47 @@ public class DeleteBranchIT extends AbstractDaemonTest {
MethodNotAllowedException thrown =
assertThrows(
MethodNotAllowedException.class,
- () -> branch(new Branch.NameKey(allUsers, RefNames.HEAD)).delete());
+ () -> branch(BranchNameKey.create(allUsers, RefNames.HEAD)).delete());
assertThat(thrown).hasMessageThat().contains("not allowed to delete HEAD");
}
private void blockForcePush() throws Exception {
- block("refs/heads/*", Permission.PUSH, ANONYMOUS_USERS).setForce(true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
+ .update();
}
private void grantForcePush() throws Exception {
- grant(project, "refs/heads/*", Permission.PUSH, true, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
+ .update();
}
private void grantDelete() throws Exception {
- allow("refs/*", Permission.DELETE, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE).ref("refs/*").group(ANONYMOUS_USERS))
+ .update();
}
private void grantOwner() throws Exception {
- allow("refs/*", Permission.OWNER, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
}
- private BranchApi branch(Branch.NameKey branch) throws Exception {
- return gApi.projects().name(branch.getParentKey().get()).branch(branch.get());
+ private BranchApi branch(BranchNameKey branch) throws Exception {
+ return gApi.projects().name(branch.project().get()).branch(branch.branch());
}
- private void assertDeleteByRestSucceeds(Branch.NameKey branch, String ref) throws Exception {
+ private void assertDeleteByRestSucceeds(BranchNameKey branch, String ref) throws Exception {
RestResponse r =
userRestSession.delete(
"/projects/"
@@ -199,24 +235,21 @@ public class DeleteBranchIT extends AbstractDaemonTest {
+ "/branches/"
+ IdString.fromDecoded(ref).encoded());
r.assertNoContent();
- exception.expect(ResourceNotFoundException.class);
- branch(branch).get();
+ assertThrows(ResourceNotFoundException.class, () -> branch(branch).get());
}
- private void assertDeleteSucceeds(Branch.NameKey branch) throws Exception {
+ private void assertDeleteSucceeds(BranchNameKey branch) throws Exception {
assertThat(branch(branch).get().canDelete).isTrue();
String branchRev = branch(branch).get().revision;
branch(branch).delete();
eventRecorder.assertRefUpdatedEvents(
- project.get(), branch.get(), null, branchRev, branchRev, null);
- exception.expect(ResourceNotFoundException.class);
- branch(branch).get();
+ project.get(), branch.branch(), null, branchRev, branchRev, null);
+ assertThrows(ResourceNotFoundException.class, () -> branch(branch).get());
}
- private void assertDeleteForbidden(Branch.NameKey branch) throws Exception {
+ private void assertDeleteForbidden(BranchNameKey branch) throws Exception {
assertThat(branch(branch).get().canDelete).isNull();
- exception.expect(AuthException.class);
- exception.expectMessage("not permitted: delete");
- branch(branch).delete();
+ AuthException thrown = assertThrows(AuthException.class, () -> branch(branch).delete());
+ assertThat(thrown).hasMessageThat().contains("not permitted: delete");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
index 4d7212312d..ad90109642 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
@@ -25,8 +26,10 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
@@ -34,7 +37,6 @@ import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import java.util.HashMap;
import java.util.List;
@@ -48,12 +50,17 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
private static final ImmutableList<String> BRANCHES =
ImmutableList.of("refs/heads/test-1", "refs/heads/test-2", "test-3", "refs/meta/foo");
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
public void setUp() throws Exception {
- allow("refs/*", Permission.CREATE, REGISTERED_USERS);
- allow("refs/*", Permission.PUSH, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.PUSH).ref("refs/*").group(REGISTERED_USERS))
+ .update();
for (String name : BRANCHES) {
project().branch(name).create(new BranchInput());
}
@@ -77,12 +84,8 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
DeleteBranchesInput input = new DeleteBranchesInput();
input.branches = branchToDelete;
requestScopeOperations.setApiUser(user.id());
- try {
- project().deleteBranches(input);
- fail("Expected AuthException");
- } catch (AuthException e) {
- assertThat(e).hasMessageThat().isEqualTo("not permitted: delete on refs/heads/test-1");
- }
+ AuthException thrown = assertThrows(AuthException.class, () -> project().deleteBranches(input));
+ assertThat(thrown).hasMessageThat().isEqualTo("not permitted: delete on refs/heads/test-1");
requestScopeOperations.setApiUser(admin.id());
assertBranches(BRANCHES);
}
@@ -92,12 +95,9 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
DeleteBranchesInput input = new DeleteBranchesInput();
input.branches = BRANCHES;
requestScopeOperations.setApiUser(user.id());
- try {
- project().deleteBranches(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e).hasMessageThat().isEqualTo(errorMessageForBranches(BRANCHES));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteBranches(input));
+ assertThat(thrown).hasMessageThat().isEqualTo(errorMessageForBranches(BRANCHES));
requestScopeOperations.setApiUser(admin.id());
assertBranches(BRANCHES);
}
@@ -108,14 +108,11 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
List<String> branches = Lists.newArrayList(BRANCHES);
branches.add("refs/heads/does-not-exist");
input.branches = branches;
- try {
- project().deleteBranches(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(errorMessageForBranches(ImmutableList.of("refs/heads/does-not-exist")));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteBranches(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(errorMessageForBranches(ImmutableList.of("refs/heads/does-not-exist")));
assertBranchesDeleted(BRANCHES);
}
@@ -127,40 +124,37 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
List<String> branches = Lists.newArrayList("refs/heads/does-not-exist");
branches.addAll(BRANCHES);
input.branches = branches;
- try {
- project().deleteBranches(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(errorMessageForBranches(ImmutableList.of("refs/heads/does-not-exist")));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteBranches(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(errorMessageForBranches(ImmutableList.of("refs/heads/does-not-exist")));
assertBranchesDeleted(BRANCHES);
}
@Test
public void missingInput() throws Exception {
DeleteBranchesInput input = null;
- exception.expect(BadRequestException.class);
- exception.expectMessage("branches must be specified");
- project().deleteBranches(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> project().deleteBranches(input));
+ assertThat(thrown).hasMessageThat().contains("branches must be specified");
}
@Test
public void missingBranchList() throws Exception {
DeleteBranchesInput input = new DeleteBranchesInput();
- exception.expect(BadRequestException.class);
- exception.expectMessage("branches must be specified");
- project().deleteBranches(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> project().deleteBranches(input));
+ assertThat(thrown).hasMessageThat().contains("branches must be specified");
}
@Test
public void emptyBranchList() throws Exception {
DeleteBranchesInput input = new DeleteBranchesInput();
input.branches = Lists.newArrayList();
- exception.expect(BadRequestException.class);
- exception.expectMessage("branches must be specified");
- project().deleteBranches(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> project().deleteBranches(input));
+ assertThat(thrown).hasMessageThat().contains("branches must be specified");
}
@Test
@@ -198,7 +192,7 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
private HashMap<String, RevCommit> initialRevisions(List<String> branches) throws Exception {
HashMap<String, RevCommit> result = new HashMap<>();
for (String branch : branches) {
- result.put(branch, getRemoteHead(project, branch));
+ result.put(branch, projectOperations.project(project).getHead(branch));
}
return result;
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
index 07bb2b1d30..9770031200 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagIT.java
@@ -15,12 +15,16 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.TagApi;
@@ -35,6 +39,7 @@ import org.junit.Test;
public class DeleteTagIT extends AbstractDaemonTest {
private static final String TAG = "refs/tags/test";
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
@@ -97,19 +102,35 @@ public class DeleteTagIT extends AbstractDaemonTest {
}
private void blockForcePush() throws Exception {
- block("refs/tags/*", Permission.PUSH, ANONYMOUS_USERS).setForce(true);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS).force(true))
+ .update();
}
private void grantForcePush() throws Exception {
- grant(project, "refs/tags/*", Permission.PUSH, true, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS).force(true))
+ .update();
}
private void grantDelete() throws Exception {
- allow("refs/tags/*", Permission.DELETE, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.DELETE).ref("refs/tags/*").group(ANONYMOUS_USERS))
+ .update();
}
private void grantOwner() throws Exception {
- allow("refs/tags/*", Permission.OWNER, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/tags/*").group(REGISTERED_USERS))
+ .update();
}
private TagApi tag() throws Exception {
@@ -122,14 +143,12 @@ public class DeleteTagIT extends AbstractDaemonTest {
String tagRev = tagInfo.revision;
tag().delete();
eventRecorder.assertRefUpdatedEvents(project.get(), TAG, null, tagRev, tagRev, null);
- exception.expect(ResourceNotFoundException.class);
- tag().get();
+ assertThrows(ResourceNotFoundException.class, () -> tag().get());
}
private void assertDeleteForbidden() throws Exception {
assertThat(tag().get().canDelete).isNull();
- exception.expect(AuthException.class);
- exception.expectMessage("not permitted: delete");
- tag().delete();
+ AuthException thrown = assertThrows(AuthException.class, () -> tag().delete());
+ assertThat(thrown).hasMessageThat().contains("not permitted: delete");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagsIT.java
index fae9d00d4e..46e2345243 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteTagsIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -23,6 +24,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
@@ -41,6 +43,7 @@ public class DeleteTagsIT extends AbstractDaemonTest {
private static final ImmutableList<String> TAGS =
ImmutableList.of("refs/tags/test-1", "refs/tags/test-2", "refs/tags/test-3", "test-4");
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
@@ -66,12 +69,9 @@ public class DeleteTagsIT extends AbstractDaemonTest {
DeleteTagsInput input = new DeleteTagsInput();
input.tags = TAGS;
requestScopeOperations.setApiUser(user.id());
- try {
- project().deleteTags(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e).hasMessageThat().isEqualTo(errorMessageForTags(TAGS));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
+ assertThat(thrown).hasMessageThat().isEqualTo(errorMessageForTags(TAGS));
requestScopeOperations.setApiUser(admin.id());
assertTags(TAGS);
}
@@ -82,14 +82,11 @@ public class DeleteTagsIT extends AbstractDaemonTest {
List<String> tags = Lists.newArrayList(TAGS);
tags.add("refs/tags/does-not-exist");
input.tags = tags;
- try {
- project().deleteTags(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
assertTagsDeleted();
}
@@ -101,14 +98,11 @@ public class DeleteTagsIT extends AbstractDaemonTest {
List<String> tags = Lists.newArrayList("refs/tags/does-not-exist");
tags.addAll(TAGS);
input.tags = tags;
- try {
- project().deleteTags(input);
- fail("Expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> project().deleteTags(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(errorMessageForTags(ImmutableList.of("refs/tags/does-not-exist")));
assertTagsDeleted();
}
@@ -128,7 +122,7 @@ public class DeleteTagsIT extends AbstractDaemonTest {
HashMap<String, RevCommit> result = new HashMap<>();
for (String tag : tags) {
String ref = prefixRef(tag);
- result.put(ref, getRemoteHead(project, ref));
+ result.put(ref, projectOperations.project(project).getHead(ref));
}
return result;
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/FileBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/FileBranchIT.java
index 63f41ad34b..a7f3174f03 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/FileBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/FileBranchIT.java
@@ -15,25 +15,26 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Branch;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class FileBranchIT extends AbstractDaemonTest {
- private Branch.NameKey branch;
+ private BranchNameKey branch;
@Before
public void setUp() throws Exception {
- branch = new Branch.NameKey(project, "master");
+ branch = BranchNameKey.create(project, "master");
PushOneCommit.Result change = createChange();
approve(change.getChangeId());
revision(change).submit();
@@ -45,12 +46,12 @@ public class FileBranchIT extends AbstractDaemonTest {
assertThat(content.asString()).isEqualTo(PushOneCommit.FILE_CONTENT);
}
- @Test(expected = ResourceNotFoundException.class)
+ @Test
public void getNonExistingFile() throws Exception {
- branch().file("does-not-exist");
+ assertThrows(ResourceNotFoundException.class, () -> branch().file("does-not-exist"));
}
private BranchApi branch() throws Exception {
- return gApi.projects().name(branch.getParentKey().get()).branch(branch.get());
+ return gApi.projects().name(branch.project().get()).branch(branch.branch());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
index 48527af552..0bdaad02e0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
@@ -19,7 +19,7 @@ import com.google.gerrit.acceptance.GcAssert;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.inject.Inject;
import org.junit.Before;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
index 56265c6893..964cff7b30 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetBranchIT.java
@@ -15,6 +15,10 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -26,17 +30,18 @@ import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
@@ -75,22 +80,30 @@ public class GetBranchIT extends AbstractDaemonTest {
}
@Test
- public void cannotGetNonVisibleBranch() throws Exception {
+ public void cannotGetNonVisibleBranch() {
String branchName = "master";
// block read access to the branch
- block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, RefNames.fullName(branchName));
}
@Test
- public void cannotGetNonVisibleBranchByShortName() throws Exception {
+ public void cannotGetNonVisibleBranchByShortName() {
String branchName = "master";
// block read access to the branch
- block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, branchName);
@@ -114,7 +127,11 @@ public class GetBranchIT extends AbstractDaemonTest {
Change.Id changeId = createChange("refs/for/" + branchName).getPatchSetId().changeId();
// block read access to the branch
- block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
+ .update();
// a user without the 'Access Database' capability cannot see the change ref
requestScopeOperations.setApiUser(user.id());
@@ -160,7 +177,11 @@ public class GetBranchIT extends AbstractDaemonTest {
gApi.changes().id(changeId.get()).edit().create();
// make the change non-visible by blocking read access on the destination
- block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
+ .update();
// user cannot see their own change edit refs if the change is no longer visible
String changeEditRef = RefNames.refsEdit(user.id(), changeId, PatchSet.id(changeId, 1));
@@ -193,17 +214,30 @@ public class GetBranchIT extends AbstractDaemonTest {
// a non-project owner cannot get the refs/meta/config branch even with the 'Access Database'
// capability
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
try {
assertBranchNotFound(project, RefNames.REFS_CONFIG);
} finally {
- removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(
+ capabilityKey(GlobalCapability.ACCESS_DATABASE)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
}
requestScopeOperations.setApiUser(user.id());
// a project owner can get the refs/meta/config branch
- allow(project, "refs/*", Permission.OWNER, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
+ .update();
assertBranchFound(project, RefNames.REFS_CONFIG);
}
@@ -265,7 +299,11 @@ public class GetBranchIT extends AbstractDaemonTest {
// This is because READ access for refs/groups/* on All-Users is by default granted to
// REGISTERED_USERS, and if an ALLOW rule and a BLOCK rule are on the same project and ref,
// the ALLOW rule takes precedence.
- block(allProjects, "refs/groups/*", Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/groups/*").group(ANONYMOUS_USERS))
+ .update();
assertBranchNotFound(allUsers, groupRef);
}
@@ -378,7 +416,11 @@ public class GetBranchIT extends AbstractDaemonTest {
TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get();
// block read access to the branch
- block(allProjects, RefNames.fullName("master"), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS))
+ .update();
// if the user cannot see the project, the tag is not visible
requestScopeOperations.setApiUser(user.id());
@@ -393,9 +435,13 @@ public class GetBranchIT extends AbstractDaemonTest {
}
@Test
- public void cannotGetSymbolicRefThatPointsToNonVisibleBranch() throws Exception {
+ public void cannotGetSymbolicRefThatPointsToNonVisibleBranch() {
// block read access to the branch to which HEAD points by default
- block(allProjects, RefNames.fullName("master"), Permission.READ, ANONYMOUS_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS))
+ .update();
// since 'master' is not visible, 'HEAD' which points to 'master' is also not visible
requestScopeOperations.setApiUser(user.id());
@@ -445,13 +491,23 @@ public class GetBranchIT extends AbstractDaemonTest {
testGetRefWithAccessDatabase(allProjects, RefNames.REFS_VERSION);
}
- private void testGetRefWithAccessDatabase(Project.NameKey project, String ref) throws Exception {
- allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ private void testGetRefWithAccessDatabase(Project.NameKey project, String ref)
+ throws RestApiException {
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
+ .update();
try {
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, ref);
} finally {
- removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(
+ capabilityKey(GlobalCapability.ACCESS_DATABASE)
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
index d7365781b0..8911163da4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -14,14 +14,16 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.junit.Test;
@@ -69,8 +71,10 @@ public class GetChildProjectIT extends AbstractDaemonTest {
}
private void assertChildNotFound(Project.NameKey parent, String child) throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage(child);
- gApi.projects().name(parent.get()).child(child).get();
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(parent.get()).child(child).get());
+ assertThat(thrown).hasMessageThat().contains(child);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
index 18c706b9f5..b18db81256 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -15,14 +15,18 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.inject.Inject;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@@ -32,12 +36,14 @@ import org.junit.Before;
import org.junit.Test;
public class GetCommitIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
+
private TestRepository<Repository> repo;
@Before
public void setUp() throws Exception {
repo = GitUtil.newTestRepository(repoManager.openRepository(project));
- blockRead("refs/*");
+ blockRead();
}
@After
@@ -107,8 +113,17 @@ public class GetCommitIT extends AbstractDaemonTest {
@Test
public void getOpenChange_NotFound() throws Exception {
+ // Need to unblock read to allow the push operation to succeed if not, when retrieving the
+ // advertised refs during
+ // the push, the client won't be sent the initial commit and will send it again as part of the
+ // change.
+ unblockRead();
+
PushOneCommit.Result r = pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master");
r.assertOkStatus();
+
+ // Re-blocking the read
+ blockRead();
assertNotFound(r.getCommit());
}
@@ -119,6 +134,14 @@ public class GetCommitIT extends AbstractDaemonTest {
}
}
+ private void blockRead() {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ }
+
private void assertNotFound(ObjectId id) throws Exception {
userRestSession.get("/projects/" + project.get() + "/commits/" + id.name()).assertNotFound();
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
index 989050cc30..e9aa589b38 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -54,8 +55,9 @@ public class GetProjectIT extends AbstractDaemonTest {
assertThat(p.name).isEqualTo(name);
}
- @Test(expected = ResourceNotFoundException.class)
+ @Test
public void getProjectNotExisting() throws Exception {
- gApi.projects().name("does-not-exist").get();
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.projects().name("does-not-exist").get());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index ec1c708c20..91a2c4bd83 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -15,36 +15,48 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.gerrit.acceptance.rest.project.RefAssert.assertRefs;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import org.junit.Test;
@NoHttpd
public class ListBranchesIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
public void listBranchesOfNonExistingProject_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name("non-existing").branches().get();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name("non-existing").branches().get());
}
@Test
public void listBranchesOfNonVisibleProject_NotFound() throws Exception {
- blockRead("refs/*");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name(project.get()).branches().get();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).branches().get());
}
@Test
@@ -59,7 +71,7 @@ public class ListBranchesIT extends AbstractDaemonTest {
public void listBranches() throws Exception {
String master = pushTo("refs/heads/master").getCommit().name();
String dev = pushTo("refs/heads/dev").getCommit().name();
- String refsConfig = getRemoteHead(project, RefNames.REFS_CONFIG).name();
+ String refsConfig = projectOperations.project(project).getHead(RefNames.REFS_CONFIG).name();
assertRefs(
ImmutableList.of(
branch("HEAD", "master", false),
@@ -71,7 +83,11 @@ public class ListBranchesIT extends AbstractDaemonTest {
@Test
public void listBranchesSomeHidden() throws Exception {
- blockRead("refs/heads/dev");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/dev").group(REGISTERED_USERS))
+ .update();
String master = pushTo("refs/heads/master").getCommit().name();
pushTo("refs/heads/dev");
requestScopeOperations.setApiUser(user.id());
@@ -84,7 +100,11 @@ public class ListBranchesIT extends AbstractDaemonTest {
@Test
public void listBranchesHeadHidden() throws Exception {
- blockRead("refs/heads/master");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
pushTo("refs/heads/master");
String dev = pushTo("refs/heads/dev").getCommit().name();
requestScopeOperations.setApiUser(user.id());
@@ -96,7 +116,10 @@ public class ListBranchesIT extends AbstractDaemonTest {
public void listBranchesUsingPagination() throws Exception {
BranchInfo head = branch("HEAD", "master", false);
BranchInfo refsConfig =
- branch(RefNames.REFS_CONFIG, getRemoteHead(project, RefNames.REFS_CONFIG).name(), false);
+ branch(
+ RefNames.REFS_CONFIG,
+ projectOperations.project(project).getHead(RefNames.REFS_CONFIG).name(),
+ false);
BranchInfo master =
branch("refs/heads/master", pushTo("refs/heads/master").getCommit().getName(), false);
BranchInfo branch1 =
@@ -170,11 +193,6 @@ public class ListBranchesIT extends AbstractDaemonTest {
}
private void assertBadRequest(ListRefsRequest<BranchInfo> req) throws Exception {
- try {
- req.get();
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- }
+ assertThrows(BadRequestException.class, () -> req.get());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index 7746820203..7535deac46 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -14,13 +14,15 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertThatNameList;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Test;
@@ -31,9 +33,11 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
@Test
public void listChildrenOfNonExistingProject_NotFound() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- exception.expectMessage("non-existing");
- gApi.projects().name(name("non-existing")).child("children");
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(name("non-existing")).child("children"));
+ assertThat(thrown).hasMessageThat().contains("non-existing");
}
@Test
@@ -47,7 +51,7 @@ public class ListChildProjectsIT extends AbstractDaemonTest {
Project.NameKey child1_1 = projectOperations.newProject().parent(child1).create();
Project.NameKey child1_2 = projectOperations.newProject().parent(child1).create();
- assertThatNameList(gApi.projects().name(child1.get()).children()).isOrdered();
+ assertThatNameList(gApi.projects().name(child1.get()).children()).isInOrder();
assertThatNameList(gApi.projects().name(child1.get()).children())
.containsExactly(child1_1, child1_2);
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 9dba8cfe81..29d3eb2363 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -16,7 +16,9 @@ package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertThatNameList;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
@@ -31,6 +33,7 @@ import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.Projects.ListRequest;
@@ -39,9 +42,7 @@ import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.json.OutputFormat;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.project.ProjectCacheImpl;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.restapi.project.ListProjects;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@@ -65,7 +66,7 @@ public class ListProjectsIT extends AbstractDaemonTest {
Project.NameKey someProject = projectOperations.newProject().create();
assertThatNameList(gApi.projects().list().get())
.containsExactly(allProjects, allUsers, project, someProject);
- assertThatNameList(gApi.projects().list().get()).isOrdered();
+ assertThatNameList(gApi.projects().list().get()).isInOrder();
}
@Test
@@ -73,10 +74,11 @@ public class ListProjectsIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
assertThatNameList(gApi.projects().list().get()).contains(project);
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
assertThatNameList(gApi.projects().list().get()).doesNotContain(project);
}
@@ -243,7 +245,7 @@ public class ListProjectsIT extends AbstractDaemonTest {
int n = 5;
assertThat(all).hasSize(n);
assertThatNameList(gApi.projects().list().withPrefix(pre).withStart(n - 1).get())
- .containsExactly(new Project.NameKey(Iterables.getLast(all).name));
+ .containsExactly(Project.nameKey(Iterables.getLast(all).name));
}
@Test
@@ -337,11 +339,6 @@ public class ListProjectsIT extends AbstractDaemonTest {
}
private void assertBadRequest(ListRequest req) throws Exception {
- try {
- req.get();
- fail("Expected BadRequestException");
- } catch (BadRequestException expected) {
- // Expected.
- }
+ assertThrows(BadRequestException.class, () -> req.get());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/javatests/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
index 3b5a3a4e76..3f583a220a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -21,10 +21,10 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.truth.IterableSubject;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.project.ProjectState;
import java.util.List;
import java.util.Set;
@@ -38,7 +38,7 @@ public class ProjectAssert {
.that(Url.decode(info.id))
.isEqualTo(info.name);
}
- return assertThat(Iterables.transform(actual, p -> new Project.NameKey(p.name)));
+ return assertThat(Iterables.transform(actual, p -> Project.nameKey(p.name)));
}
public static void assertProjectInfo(Project project, ProjectInfo info) {
@@ -47,7 +47,7 @@ public class ProjectAssert {
assertThat(info.name).isEqualTo(project.getName());
}
assertThat(Url.decode(info.id)).isEqualTo(project.getName());
- Project.NameKey parentName = project.getParent(new Project.NameKey("All-Projects"));
+ Project.NameKey parentName = project.getParent(Project.nameKey("All-Projects"));
if (parentName != null) {
assertThat(info.parent).isEqualTo(parentName.get());
} else {
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index bf2a5342b2..76c30a956b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -20,8 +20,8 @@ import static com.google.gerrit.acceptance.GitUtil.fetch;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import java.util.Arrays;
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/RefAssert.java b/javatests/com/google/gerrit/acceptance/rest/project/RefAssert.java
index b3e3d2f8cf..a93fc0f177 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/RefAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/RefAssert.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.api.projects.RefInfo;
@@ -38,10 +39,12 @@ public class RefAssert {
public static void assertRefInfo(RefInfo expected, RefInfo actual) {
assertThat(actual.ref).isEqualTo(expected.ref);
if (expected.revision != null) {
- assertThat(actual.revision).named("revision of " + actual.ref).isEqualTo(expected.revision);
+ assertWithMessage("revision of " + actual.ref)
+ .that(actual.revision)
+ .isEqualTo(expected.revision);
}
- assertThat(toBoolean(actual.canDelete))
- .named("can delete " + actual.ref)
+ assertWithMessage("can delete " + actual.ref)
+ .that(toBoolean(actual.canDelete))
.isEqualTo(toBoolean(expected.canDelete));
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
index c945a2b359..f5d2db464e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/TagsIT.java
@@ -15,7 +15,10 @@
package com.google.gerrit.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.collect.FluentIterable;
@@ -23,6 +26,7 @@ import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
@@ -62,6 +66,7 @@ public class TagsIT extends AbstractDaemonTest {
+ "=XFeC\n"
+ "-----END PGP SIGNATURE-----";
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
@@ -76,29 +81,39 @@ public class TagsIT extends AbstractDaemonTest {
@Test
public void listTagsOfNonExistingProject() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name("does-not-exist").tags().get();
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.projects().name("does-not-exist").tags().get());
}
@Test
public void getTagOfNonExistingProject() throws Exception {
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name("does-not-exist").tag("tag").get();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name("does-not-exist").tag("tag").get());
}
@Test
public void listTagsOfNonVisibleProject() throws Exception {
- blockRead("refs/*");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
requestScopeOperations.setApiUser(user.id());
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name(project.get()).tags().get();
+ assertThrows(
+ ResourceNotFoundException.class, () -> gApi.projects().name(project.get()).tags().get());
}
@Test
public void getTagOfNonVisibleProject() throws Exception {
- blockRead("refs/*");
- exception.expect(ResourceNotFoundException.class);
- gApi.projects().name(project.get()).tag("tag").get();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).tag("tag").get());
}
@Test
@@ -173,7 +188,11 @@ public class TagsIT extends AbstractDaemonTest {
assertThat(tags.get(1).ref).isEqualTo(R_TAGS + tag2.ref);
assertThat(tags.get(1).revision).isEqualTo(tag2.revision);
- blockRead("refs/heads/hidden");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/hidden").group(REGISTERED_USERS))
+ .update();
tags = getTags().get();
assertThat(tags).hasSize(1);
assertThat(tags.get(0).ref).isEqualTo(R_TAGS + tag1.ref);
@@ -182,7 +201,7 @@ public class TagsIT extends AbstractDaemonTest {
@Test
public void lightweightTag() throws Exception {
- grant(project, R_TAGS + "*", Permission.CREATE);
+ grantTagPermissions();
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
PushOneCommit.Result r = push.to("refs/heads/master");
@@ -214,7 +233,7 @@ public class TagsIT extends AbstractDaemonTest {
@Test
public void annotatedTag() throws Exception {
- grant(project, R_TAGS + "*", Permission.CREATE_TAG);
+ grantTagPermissions();
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
PushOneCommit.Result r = push.to("refs/heads/master");
@@ -261,30 +280,38 @@ public class TagsIT extends AbstractDaemonTest {
assertThat(result.ref).isEqualTo(R_TAGS + "test");
input.ref = "refs/tags/test";
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("tag \"" + R_TAGS + "test\" already exists");
- tag(input.ref).create(input);
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("tag \"" + R_TAGS + "test\" already exists");
}
@Test
public void createTagNotAllowed() throws Exception {
- block(R_TAGS + "*", Permission.CREATE, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.CREATE).ref(R_TAGS + "*").group(REGISTERED_USERS))
+ .update();
TagInput input = new TagInput();
input.ref = "test";
- exception.expect(AuthException.class);
- exception.expectMessage("not permitted: create");
- tag(input.ref).create(input);
+ AuthException thrown = assertThrows(AuthException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("not permitted: create");
}
@Test
public void createAnnotatedTagNotAllowed() throws Exception {
- block(R_TAGS + "*", Permission.CREATE_TAG, REGISTERED_USERS);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.CREATE_TAG).ref(R_TAGS + "*").group(REGISTERED_USERS))
+ .update();
TagInput input = new TagInput();
input.ref = "test";
input.message = "annotation";
- exception.expect(AuthException.class);
- exception.expectMessage("Cannot create annotated tag \"" + R_TAGS + "test\"");
- tag(input.ref).create(input);
+ AuthException thrown = assertThrows(AuthException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cannot create annotated tag \"" + R_TAGS + "test\"");
}
@Test
@@ -292,9 +319,9 @@ public class TagsIT extends AbstractDaemonTest {
TagInput input = new TagInput();
input.ref = "test";
input.message = SIGNED_ANNOTATION;
- exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("Cannot create signed tag \"" + R_TAGS + "test\"");
- tag(input.ref).create(input);
+ MethodNotAllowedException thrown =
+ assertThrows(MethodNotAllowedException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("Cannot create signed tag \"" + R_TAGS + "test\"");
}
@Test
@@ -302,9 +329,9 @@ public class TagsIT extends AbstractDaemonTest {
TagInput input = new TagInput();
input.ref = "test";
- exception.expect(BadRequestException.class);
- exception.expectMessage("ref must match URL");
- tag("TEST").create(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> tag("TEST").create(input));
+ assertThat(thrown).hasMessageThat().contains("ref must match URL");
}
@Test
@@ -314,9 +341,9 @@ public class TagsIT extends AbstractDaemonTest {
TagInput input = new TagInput();
input.ref = "refs/heads/test";
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid tag name \"" + input.ref + "\"");
- tag(input.ref).create(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid tag name \"" + input.ref + "\"");
}
@Test
@@ -326,9 +353,9 @@ public class TagsIT extends AbstractDaemonTest {
TagInput input = new TagInput();
input.ref = "//";
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid tag name \"refs/tags/\"");
- tag(input.ref).create(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid tag name \"refs/tags/\"");
}
@Test
@@ -339,9 +366,9 @@ public class TagsIT extends AbstractDaemonTest {
input.ref = "test";
input.revision = "abcdefg";
- exception.expect(BadRequestException.class);
- exception.expectMessage("Invalid base revision");
- tag(input.ref).create(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> tag(input.ref).create(input));
+ assertThat(thrown).hasMessageThat().contains("Invalid base revision");
}
@Test
@@ -349,7 +376,7 @@ public class TagsIT extends AbstractDaemonTest {
grantTagPermissions();
// If revision is not specified, the tag is created based on HEAD, which points to master.
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
TagInput input = new TagInput();
input.ref = "test";
@@ -365,7 +392,7 @@ public class TagsIT extends AbstractDaemonTest {
grantTagPermissions();
// If revision is not specified, the tag is created based on HEAD, which points to master.
- RevCommit expectedRevision = getRemoteHead(project, "master");
+ RevCommit expectedRevision = projectOperations.project(project).getHead("master");
TagInput input = new TagInput();
input.ref = "test";
@@ -380,7 +407,7 @@ public class TagsIT extends AbstractDaemonTest {
public void baseRevisionIsTrimmed() throws Exception {
grantTagPermissions();
- RevCommit revision = getRemoteHead(project, "master");
+ RevCommit revision = projectOperations.project(project).getHead("master");
TagInput input = new TagInput();
input.ref = "test";
@@ -428,19 +455,18 @@ public class TagsIT extends AbstractDaemonTest {
}
private void assertBadRequest(ListRefsRequest<TagInfo> req) throws Exception {
- try {
- req.get();
- fail("Expected BadRequestException");
- } catch (BadRequestException e) {
- // Expected
- }
+ assertThrows(BadRequestException.class, () -> req.get());
}
private void grantTagPermissions() throws Exception {
- grant(project, R_TAGS + "*", Permission.CREATE);
- grant(project, R_TAGS + "", Permission.DELETE);
- grant(project, R_TAGS + "*", Permission.CREATE_TAG);
- grant(project, R_TAGS + "*", Permission.CREATE_SIGNED_TAG);
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.CREATE).ref(R_TAGS + "*").group(adminGroupUuid()))
+ .add(allow(Permission.DELETE).ref(R_TAGS + "").group(adminGroupUuid()))
+ .add(allow(Permission.CREATE_TAG).ref(R_TAGS + "*").group(adminGroupUuid()))
+ .add(allow(Permission.CREATE_SIGNED_TAG).ref(R_TAGS + "*").group(adminGroupUuid()))
+ .update();
}
private static void removeAllBranchPermissions(ProjectConfig cfg, String... permissions) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/util/RestApiCallHelper.java b/javatests/com/google/gerrit/acceptance/rest/util/RestApiCallHelper.java
index 52e72febf4..f98fb452b6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/util/RestApiCallHelper.java
+++ b/javatests/com/google/gerrit/acceptance/rest/util/RestApiCallHelper.java
@@ -15,7 +15,6 @@
package com.google.gerrit.acceptance.rest.util;
import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.Truth.assert_;
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
@@ -76,7 +75,8 @@ public class RestApiCallHelper {
response = restSession.delete(uri);
break;
default:
- assert_().fail(String.format("unsupported method: %s", restCall.httpMethod().name()));
+ assertWithMessage(String.format("unsupported method: %s", restCall.httpMethod().name()))
+ .fail();
throw new IllegalStateException();
}
diff --git a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
index fa1b4678c0..e924143050 100644
--- a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
@@ -15,8 +15,9 @@
package com.google.gerrit.acceptance.server.account;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -25,8 +26,8 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.TestAccount;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountVisibility;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountResolver;
@@ -81,26 +82,16 @@ public class AccountResolverIT extends AbstractDaemonTest {
}
private void checkBySelfFails() throws Exception {
- Result result = resolveAsResult("self");
- assertThat(result.asIdSet()).isEmpty();
- assertThat(result.isSelf()).isTrue();
- try {
- result.asUnique();
- assert_().fail("expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e).hasMessageThat().isEqualTo("Resolving account 'self' requires login");
- assertThat(e.isSelf()).isTrue();
- }
-
- result = resolveAsResult("me");
- assertThat(result.asIdSet()).isEmpty();
- assertThat(result.isSelf()).isTrue();
- try {
- result.asUnique();
- assert_().fail("expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e).hasMessageThat().isEqualTo("Resolving account 'me' requires login");
- assertThat(e.isSelf()).isTrue();
+ for (String input : ImmutableList.of("self", "me")) {
+ Result result = resolveAsResult(input);
+ assertThat(result.asIdSet()).isEmpty();
+ assertThat(result.isSelf()).isTrue();
+ UnresolvableAccountException thrown =
+ assertThrows(UnresolvableAccountException.class, () -> result.asUnique());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("Resolving account '%s' requires login", input));
+ assertThat(thrown.isSelf()).isTrue();
}
}
@@ -123,7 +114,7 @@ public class AccountResolverIT extends AbstractDaemonTest {
Account.Id idWithExistingIdAsFullname =
accountOperations.newAccount().fullname(existingId.toString()).create();
- Account.Id nonexistentId = new Account.Id(sequences.nextAccountId());
+ Account.Id nonexistentId = Account.id(sequences.nextAccountId());
accountOperations.newAccount().fullname(nonexistentId.toString()).create();
assertThat(resolve(existingId)).containsExactly(existingId);
@@ -137,7 +128,7 @@ public class AccountResolverIT extends AbstractDaemonTest {
Account.Id existingId = accountOperations.newAccount().fullname("Test User").create();
accountOperations.newAccount().fullname(existingId.toString()).create();
- Account.Id nonexistentId = new Account.Id(sequences.nextAccountId());
+ Account.Id nonexistentId = Account.id(sequences.nextAccountId());
accountOperations.newAccount().fullname("Any Name (" + nonexistentId + ")").create();
accountOperations.newAccount().fullname(nonexistentId.toString()).create();
@@ -259,31 +250,28 @@ public class AccountResolverIT extends AbstractDaemonTest {
assertThat(resolve(account.accountId())).containsExactly(id);
for (String input : inputs) {
- assertThat(resolve(input)).named("results for %s (active)", input).containsExactly(id);
+ assertWithMessage("results for %s (active)", input).that(resolve(input)).containsExactly(id);
}
gApi.accounts().id(id.get()).setActive(false);
assertThat(resolve(account.accountId())).containsExactly(id);
for (String input : inputs) {
Result result = accountResolver.resolve(input);
- assertThat(result.asIdSet()).named("results for %s (inactive)", input).isEmpty();
- try {
- result.asUnique();
- assert_().fail("expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- "Account '"
- + input
- + "' only matches inactive accounts. To use an inactive account, retry"
- + " with one of the following exact account IDs:\n"
- + id
- + ": "
- + nameEmail);
- }
- assertThat(resolveByNameOrEmail(input))
- .named("results by name or email for %s (inactive)", input)
+ assertWithMessage("results for %s (inactive)", input).that(result.asIdSet()).isEmpty();
+ UnresolvableAccountException thrown =
+ assertThrows(UnresolvableAccountException.class, () -> result.asUnique());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Account '"
+ + input
+ + "' only matches inactive accounts. To use an inactive account, retry"
+ + " with one of the following exact account IDs:\n"
+ + id
+ + ": "
+ + nameEmail);
+ assertWithMessage("results by name or email for %s (inactive)", input)
+ .that(resolveByNameOrEmail(input))
.isEmpty();
}
}
@@ -352,6 +340,6 @@ public class AccountResolverIT extends AbstractDaemonTest {
accountsUpdateProvider
.get()
.update("Force set preferred email", id, (s, u) -> u.setPreferredEmail(email));
- assertThat(result.map(a -> a.getAccount().getPreferredEmail())).hasValue(email);
+ assertThat(result.map(a -> a.account().preferredEmail())).hasValue(email);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 35fe48f7d3..7ac803eae9 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -18,10 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
-import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
@@ -31,6 +31,9 @@ import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -45,9 +48,6 @@ import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
@@ -61,11 +61,11 @@ import com.google.inject.Provider;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -98,8 +98,9 @@ public class CommentsIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
- exception.expect(ResourceNotFoundException.class);
- getPublishedComment(changeId, revId, "non-existing");
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> getPublishedComment(changeId, revId, "non-existing"));
}
@Test
@@ -140,12 +141,11 @@ public class CommentsIT extends AbstractDaemonTest {
addDraft(changeId, revId, c4);
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
assertThat(result).hasSize(1);
- assertThat(Lists.transform(result.get(path), infoToDraft(path)))
- .containsExactly(c1, c2, c3, c4);
+ assertThat(result.get(path).stream().map(infoToDraft(path))).containsExactly(c1, c2, c3, c4);
List<CommentInfo> list = getDraftCommentsAsList(changeId);
assertThat(list).hasSize(4);
- assertThat(Lists.transform(list, infoToDraft(path))).containsExactly(c1, c2, c3, c4);
+ assertThat(list.stream().map(infoToDraft(path))).containsExactly(c1, c2, c3, c4);
}
}
@@ -246,11 +246,10 @@ public class CommentsIT extends AbstractDaemonTest {
revision(r).review(input);
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
- assertThat(Lists.transform(result.get(file), infoToInput(file)))
- .containsExactly(c1, c2, c3, c4);
+ assertThat(result.get(file).stream().map(infoToInput(file))).containsExactly(c1, c2, c3, c4);
List<CommentInfo> list = getPublishedCommentsAsList(changeId);
- assertThat(Lists.transform(list, infoToInput(file))).containsExactly(c1, c2, c3, c4);
+ assertThat(list.stream().map(infoToInput(file))).containsExactly(c1, c2, c3, c4);
}
// for the commit message comments on the auto-merge are not possible
@@ -268,10 +267,10 @@ public class CommentsIT extends AbstractDaemonTest {
revision(r).review(input);
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
- assertThat(Lists.transform(result.get(file), infoToInput(file))).containsExactly(c1, c2, c3);
+ assertThat(result.get(file).stream().map(infoToInput(file))).containsExactly(c1, c2, c3);
List<CommentInfo> list = getPublishedCommentsAsList(changeId);
- assertThat(Lists.transform(list, infoToInput(file))).containsExactly(c1, c2, c3);
+ assertThat(list.stream().map(infoToInput(file))).containsExactly(c1, c2, c3);
}
}
@@ -282,9 +281,40 @@ public class CommentsIT extends AbstractDaemonTest {
CommentInput c = newComment(Patch.COMMIT_MSG, Side.PARENT, 0, "comment on auto-merge", false);
input.comments = new HashMap<>();
input.comments.put(Patch.COMMIT_MSG, ImmutableList.of(c));
- exception.expect(BadRequestException.class);
- exception.expectMessage("cannot comment on " + Patch.COMMIT_MSG + " on auto-merge");
- revision(r).review(input);
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> revision(r).review(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("cannot comment on " + Patch.COMMIT_MSG + " on auto-merge");
+ }
+
+ @Test
+ public void postCommentsReplacingDrafts() throws Exception {
+ String file = "file";
+ PushOneCommit push =
+ pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "contents");
+ PushOneCommit.Result r = push.to("refs/for/master");
+ String changeId = r.getChangeId();
+ String revId = r.getCommit().getName();
+
+ DraftInput draft = newDraft(file, Side.REVISION, 0, "comment");
+ addDraft(changeId, revId, draft);
+ Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+ CommentInfo draftInfo = Iterables.getOnlyElement(drafts.get(draft.path));
+
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.drafts = DraftHandling.KEEP;
+ reviewInput.message = "foo";
+ CommentInput comment = newComment(file, Side.REVISION, 0, "comment", false);
+ // Replace the existing draft.
+ comment.id = draftInfo.id;
+ reviewInput.comments = new HashMap<>();
+ reviewInput.comments.put(comment.path, ImmutableList.of(comment));
+ revision(r).review(reviewInput);
+
+ // DraftHandling.KEEP is ignored on publishing a comment.
+ drafts = getDraftComments(changeId, revId);
+ assertThat(drafts).isEmpty();
}
@Test
@@ -350,12 +380,11 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
List<CommentInfo> actualComments = result.get(file);
- assertThat(Lists.transform(actualComments, infoToInput(file)))
+ assertThat(actualComments.stream().map(infoToInput(file)))
.containsExactlyElementsIn(expectedComments);
List<CommentInfo> list = getPublishedCommentsAsList(changeId);
- assertThat(Lists.transform(list, infoToInput(file)))
- .containsExactlyElementsIn(expectedComments);
+ assertThat(list.stream().map(infoToInput(file))).containsExactlyElementsIn(expectedComments);
}
/**
@@ -434,7 +463,7 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
assertThat(result).isNotEmpty();
List<CommentInfo> actualComments = result.get(file);
- assertThat(Lists.transform(actualComments, infoToDraft(file)))
+ assertThat(actualComments.stream().map(infoToDraft(file)))
.containsExactlyElementsIn(expectedDrafts);
}
@@ -888,8 +917,9 @@ public class CommentsIT extends AbstractDaemonTest {
DeleteCommentInput input = new DeleteCommentInput("contains confidential information");
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.changes().id(result.getChangeId()).current().comment(uuid).delete(input);
+ assertThrows(
+ AuthException.class,
+ () -> gApi.changes().id(result.getChangeId()).current().comment(uuid).delete(input));
}
@Test
@@ -1047,20 +1077,6 @@ public class CommentsIT extends AbstractDaemonTest {
assertThat(getChangeSortedComments(id.get())).hasSize(3);
}
- @Test
- public void jsonCommentHasLegacyFormatFalse() throws Exception {
- PushOneCommit.Result result = createChange();
- Change.Id changeId = result.getChange().getId();
- addComment(result.getChangeId(), "comment");
-
- Collection<com.google.gerrit.reviewdb.client.Comment> comments =
- notesFactory.createChecked(project, changeId).getComments().values();
- assertThat(comments).hasSize(1);
- com.google.gerrit.reviewdb.client.Comment comment = comments.iterator().next();
- assertThat(comment.message).isEqualTo("comment");
- assertThat(comment.legacyFormat).isFalse();
- }
-
private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception {
return getPublishedComments(changeId, revId).values().stream()
.flatMap(List::stream)
@@ -1101,17 +1117,16 @@ public class CommentsIT extends AbstractDaemonTest {
RevCommit commitBefore = beforeDelete.get(i);
RevCommit commitAfter = afterDelete.get(i);
- Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapBefore =
+ Map<String, com.google.gerrit.entities.Comment> commentMapBefore =
DeleteCommentRewriter.getPublishedComments(
- noteUtil, changeId, reader, NoteMap.read(reader, commitBefore));
- Map<String, com.google.gerrit.reviewdb.client.Comment> commentMapAfter =
+ noteUtil, reader, NoteMap.read(reader, commitBefore));
+ Map<String, com.google.gerrit.entities.Comment> commentMapAfter =
DeleteCommentRewriter.getPublishedComments(
- noteUtil, changeId, reader, NoteMap.read(reader, commitAfter));
+ noteUtil, reader, NoteMap.read(reader, commitAfter));
if (commentMapBefore.containsKey(targetCommentUuid)) {
assertThat(commentMapAfter).containsKey(targetCommentUuid);
- com.google.gerrit.reviewdb.client.Comment comment =
- commentMapAfter.get(targetCommentUuid);
+ com.google.gerrit.entities.Comment comment = commentMapAfter.get(targetCommentUuid);
assertThat(comment.message).isEqualTo(expectedMessage);
comment.message = commentMapBefore.get(targetCommentUuid).message;
commentMapAfter.put(targetCommentUuid, comment);
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 7c375bd0d5..1e2d1ba7b8 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -25,14 +25,14 @@ import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ProblemInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.ChangeInserter;
@@ -131,7 +131,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
assertProblems(
notes,
null,
- problem("Ref missing: " + ps.getId().toRefName()),
+ problem("Ref missing: " + ps.id().toRefName()),
problem("Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
}
@@ -142,7 +142,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
PatchSet ps = insertMissingPatchSet(notes, rev);
notes = reload(notes);
- String refName = ps.getId().toRefName();
+ String refName = ps.id().toRefName();
assertProblems(
notes,
new FixInput(),
@@ -153,8 +153,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void patchSetRefMissing() throws Exception {
ChangeNotes notes = insertChange();
- serverSideTestRepo.update(
- "refs/other/foo", ObjectId.fromString(psUtil.current(notes).getRevision().get()));
+ serverSideTestRepo.update("refs/other/foo", psUtil.current(notes).commitId());
String refName = notes.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
@@ -164,15 +163,15 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void patchSetRefMissingWithFix() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
- serverSideTestRepo.update("refs/other/foo", ObjectId.fromString(rev));
+ ObjectId commitId = psUtil.current(notes).commitId();
+ serverSideTestRepo.update("refs/other/foo", commitId);
String refName = notes.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
assertProblems(
notes, new FixInput(), problem("Ref missing: " + refName, FIXED, "Repaired patch set ref"));
- assertThat(serverSideTestRepo.getRepository().exactRef(refName).getObjectId().name())
- .isEqualTo(rev);
+ assertThat(serverSideTestRepo.getRepository().exactRef(refName).getObjectId())
+ .isEqualTo(commitId);
}
@Test
@@ -189,13 +188,13 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
assertProblems(
notes,
fix,
- problem("Ref missing: " + ps2.getId().toRefName()),
+ problem("Ref missing: " + ps2.id().toRefName()),
problem("Object missing: patch set 2: " + rev2, FIXED, "Deleted patch set"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(1);
- assertThat(psUtil.get(notes, ps1.getId())).isNotNull();
- assertThat(psUtil.get(notes, ps2.getId())).isNull();
+ assertThat(psUtil.get(notes, ps1.id())).isNotNull();
+ assertThat(psUtil.get(notes, ps2.id())).isNull();
}
@Test
@@ -218,17 +217,17 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
assertProblems(
notes,
fix,
- problem("Ref missing: " + ps2.getId().toRefName()),
+ problem("Ref missing: " + ps2.id().toRefName()),
problem("Object missing: patch set 2: " + rev2, FIXED, "Deleted patch set"),
- problem("Ref missing: " + ps4.getId().toRefName()),
+ problem("Ref missing: " + ps4.id().toRefName()),
problem("Object missing: patch set 4: " + rev4, FIXED, "Deleted patch set"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(3);
- assertThat(psUtil.get(notes, ps1.getId())).isNotNull();
- assertThat(psUtil.get(notes, ps2.getId())).isNull();
- assertThat(psUtil.get(notes, ps3.getId())).isNotNull();
- assertThat(psUtil.get(notes, ps4.getId())).isNull();
+ assertThat(psUtil.get(notes, ps1.id())).isNotNull();
+ assertThat(psUtil.get(notes, ps2.id())).isNull();
+ assertThat(psUtil.get(notes, ps3.id())).isNotNull();
+ assertThat(psUtil.get(notes, ps4.id())).isNull();
}
@Test
@@ -245,7 +244,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
+ "\n"
+ "Patch-set: 1\n"
+ "Branch: "
- + c.getDest().get()
+ + c.getDest().branch()
+ "\n"
+ "Change-id: "
+ c.getKey().get()
@@ -265,7 +264,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
assertProblems(
notes,
fix,
- problem("Ref missing: " + ps.getId().toRefName()),
+ problem("Ref missing: " + ps.id().toRefName()),
problem(
"Object missing: patch set 1: " + rev,
FIX_FAILED,
@@ -280,13 +279,13 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
public void duplicatePatchSetRevisions() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
- String rev = ps1.getRevision().get();
- notes =
- incrementPatchSet(
- notes, serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
+ notes = incrementPatchSet(notes, serverSideTestRepo.getRevWalk().parseCommit(ps1.commitId()));
- assertProblems(notes, null, problem("Multiple patch sets pointing to " + rev + ": [1, 2]"));
+ assertProblems(
+ notes,
+ null,
+ problem("Multiple patch sets pointing to " + ps1.commitId().name() + ": [1, 2]"));
}
@Test
@@ -322,14 +321,13 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
}
notes = reload(notes);
- String rev = psUtil.current(notes).getRevision().get();
ObjectId tip = getDestRef(notes);
assertProblems(
notes,
null,
problem(
"Patch set 1 ("
- + rev
+ + psUtil.current(notes).commitId().name()
+ ") is not merged into destination ref"
+ " refs/heads/master ("
+ tip.name()
@@ -339,40 +337,40 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void newChangeIsMerged() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
+ ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
+ .branch(notes.getChange().getDest().branch())
+ .update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
assertProblems(
notes,
null,
problem(
"Patch set 1 ("
- + rev
+ + commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
- + rev
+ + commitId.name()
+ "), but change status is NEW"));
}
@Test
public void newChangeIsMergedWithFix() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
+ ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
+ .branch(notes.getChange().getDest().branch())
+ .update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
assertProblems(
notes,
new FixInput(),
problem(
"Patch set 1 ("
- + rev
+ + commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
- + rev
+ + commitId.name()
+ "), but change status is NEW",
FIXED,
"Marked change as merged"));
@@ -385,10 +383,10 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void extensionApiReturnsUpdatedValueAfterFix() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
+ ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
+ .branch(notes.getChange().getDest().branch())
+ .update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
ChangeInfo info = gApi.changes().id(notes.getChangeId().get()).info();
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
@@ -400,22 +398,22 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void expectedMergedCommitIsLatestPatchSet() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
+ ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
+ .branch(notes.getChange().getDest().branch())
+ .update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
FixInput fix = new FixInput();
- fix.expectMergedAs = rev;
+ fix.expectMergedAs = commitId.name();
assertProblems(
notes,
fix,
problem(
"Patch set 1 ("
- + rev
+ + commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
- + rev
+ + commitId.name()
+ "), but change status is NEW",
FIXED,
"Marked change as merged"));
@@ -428,9 +426,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void expectedMergedCommitNotMergedIntoDestination() throws Exception {
ChangeNotes notes = insertChange();
- String rev = psUtil.current(notes).getRevision().get();
- RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
- serverSideTestRepo.branch(notes.getChange().getDest().get()).update(commit);
+ RevCommit commit =
+ serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
+ serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit);
FixInput fix = new FixInput();
RevCommit other = serverSideTestRepo.commit().message(commit.getFullMessage()).create();
@@ -450,9 +448,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void createNewPatchSetForExpectedMergeCommitWithNoChangeId() throws Exception {
ChangeNotes notes = insertChange();
- String dest = notes.getChange().getDest().get();
- String rev = psUtil.current(notes).getRevision().get();
- RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
+ String dest = notes.getChange().getDest().branch();
+ RevCommit commit =
+ serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
RevCommit mergedAs =
serverSideTestRepo
@@ -481,9 +479,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
"Inserted as patch set 2"));
notes = reload(notes);
- PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
+ PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
- assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
+ assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(mergedAs);
assertNoProblems(notes, null);
}
@@ -491,9 +489,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void createNewPatchSetForExpectedMergeCommitWithChangeId() throws Exception {
ChangeNotes notes = insertChange();
- String dest = notes.getChange().getDest().get();
- String rev = psUtil.current(notes).getRevision().get();
- RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
+ String dest = notes.getChange().getDest().branch();
+ RevCommit commit =
+ serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
RevCommit mergedAs =
serverSideTestRepo
@@ -529,9 +527,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
"Inserted as patch set 2"));
notes = reload(notes);
- PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
+ PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
- assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
+ assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(mergedAs);
assertNoProblems(notes, null);
}
@@ -539,41 +537,43 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void expectedMergedCommitIsOldPatchSetOfSameChange() throws Exception {
ChangeNotes notes = insertChange();
- PatchSet ps1 = psUtil.current(notes);
- String rev1 = ps1.getRevision().get();
+ ObjectId commitId1 = psUtil.current(notes).commitId();
notes = incrementPatchSet(notes);
PatchSet ps2 = psUtil.current(notes);
serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev1)));
+ .branch(notes.getChange().getDest().branch())
+ .update(serverSideTestRepo.getRevWalk().parseCommit(commitId1));
FixInput fix = new FixInput();
- fix.expectMergedAs = rev1;
+ fix.expectMergedAs = commitId1.name();
assertProblems(
notes,
fix,
- problem("No patch set found for merged commit " + rev1, FIXED, "Marked change as merged"),
+ problem(
+ "No patch set found for merged commit " + commitId1.name(),
+ FIXED,
+ "Marked change as merged"),
problem(
"Expected merge commit "
- + rev1
+ + commitId1.name()
+ " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED,
"Deleted patch set"),
problem(
"Expected merge commit "
- + rev1
+ + commitId1.name()
+ " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED,
"Inserted as patch set 3"));
notes = reload(notes);
- PatchSet.Id psId3 = new PatchSet.Id(notes.getChangeId(), 3);
+ PatchSet.Id psId3 = PatchSet.id(notes.getChangeId(), 3);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId3);
assertThat(notes.getChange().isMerged()).isTrue();
- assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps2.getId(), psId3);
- assertThat(psUtil.get(notes, psId3).getRevision().get()).isEqualTo(rev1);
+ assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps2.id(), psId3);
+ assertThat(psUtil.get(notes, psId3).commitId()).isEqualTo(commitId1);
}
@Test
@@ -582,47 +582,46 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
PatchSet ps1 = psUtil.current(notes);
// Create dangling ref so next ID in the database becomes 3.
- PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
+ PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
- String rev2 = commit2.name();
serverSideTestRepo.branch(psId2.toRefName()).update(commit2);
notes = incrementPatchSet(notes);
PatchSet ps3 = psUtil.current(notes);
- assertThat(ps3.getId().get()).isEqualTo(3);
+ assertThat(ps3.id().get()).isEqualTo(3);
- serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev2)));
+ serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit2);
FixInput fix = new FixInput();
- fix.expectMergedAs = rev2;
+ fix.expectMergedAs = commit2.name();
assertProblems(
notes,
fix,
- problem("No patch set found for merged commit " + rev2, FIXED, "Marked change as merged"),
+ problem(
+ "No patch set found for merged commit " + commit2.name(),
+ FIXED,
+ "Marked change as merged"),
problem(
"Expected merge commit "
- + rev2
+ + commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED,
"Deleted patch set"),
problem(
"Expected merge commit "
- + rev2
+ + commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED,
"Inserted as patch set 4"));
notes = reload(notes);
- PatchSet.Id psId4 = new PatchSet.Id(notes.getChangeId(), 4);
+ PatchSet.Id psId4 = PatchSet.id(notes.getChangeId(), 4);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId4);
assertThat(notes.getChange().isMerged()).isTrue();
- assertThat(psUtil.byChangeAsMap(notes).keySet())
- .containsExactly(ps1.getId(), ps3.getId(), psId4);
- assertThat(psUtil.get(notes, psId4).getRevision().get()).isEqualTo(rev2);
+ assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.id(), ps3.id(), psId4);
+ assertThat(psUtil.get(notes, psId4).commitId()).isEqualTo(commit2);
}
@Test
@@ -631,24 +630,24 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
PatchSet ps1 = psUtil.current(notes);
// Create dangling ref with no patch set.
- PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
+ PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
- String rev2 = commit2.name();
serverSideTestRepo.branch(psId2.toRefName()).update(commit2);
- serverSideTestRepo
- .branch(notes.getChange().getDest().get())
- .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev2)));
+ serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit2);
FixInput fix = new FixInput();
- fix.expectMergedAs = rev2;
+ fix.expectMergedAs = commit2.name();
assertProblems(
notes,
fix,
- problem("No patch set found for merged commit " + rev2, FIXED, "Marked change as merged"),
+ problem(
+ "No patch set found for merged commit " + commit2.name(),
+ FIXED,
+ "Marked change as merged"),
problem(
"Expected merge commit "
- + rev2
+ + commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 1",
FIXED,
@@ -657,17 +656,17 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(notes.getChange().isMerged()).isTrue();
- assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.getId(), psId2);
- assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(rev2);
+ assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.id(), psId2);
+ assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(commit2);
}
@Test
public void expectedMergedCommitWithMismatchedChangeId() throws Exception {
ChangeNotes notes = insertChange();
- String dest = notes.getChange().getDest().get();
+ String dest = notes.getChange().getDest().branch();
RevCommit parent = serverSideTestRepo.branch(dest).commit().message("parent").create();
- String rev = psUtil.current(notes).getRevision().get();
- RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
+ RevCommit commit =
+ serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
serverSideTestRepo.branch(dest).update(commit);
String badId = "I0000000000000000000000000000000000000000";
@@ -700,19 +699,19 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void expectedMergedCommitMatchesMultiplePatchSets() throws Exception {
ChangeNotes notes1 = insertChange();
- PatchSet.Id psId1 = psUtil.current(notes1).getId();
- String dest = notes1.getChange().getDest().get();
- String rev = psUtil.current(notes1).getRevision().get();
- RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
+ PatchSet.Id psId1 = psUtil.current(notes1).id();
+ String dest = notes1.getChange().getDest().branch();
+ RevCommit commit =
+ serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes1).commitId());
serverSideTestRepo.branch(dest).update(commit);
ChangeNotes notes2 = insertChange();
notes2 = incrementPatchSet(notes2, commit);
- PatchSet.Id psId2 = psUtil.current(notes2).getId();
+ PatchSet.Id psId2 = psUtil.current(notes2).id();
ChangeNotes notes3 = insertChange();
notes3 = incrementPatchSet(notes3, commit);
- PatchSet.Id psId3 = psUtil.current(notes3).getId();
+ PatchSet.Id psId3 = psUtil.current(notes3).id();
FixInput fix = new FixInput();
fix.expectMergedAs = commit.name();
@@ -744,10 +743,10 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
}
private ChangeNotes insertChange(TestAccount owner, String dest) throws Exception {
- Change.Id id = new Change.Id(sequences.nextChangeId());
+ Change.Id id = Change.id(sequences.nextChangeId());
ChangeInserter ins;
try (BatchUpdate bu = newUpdate(owner.id())) {
- RevCommit commit = patchSetCommit(new PatchSet.Id(id, 1));
+ RevCommit commit = patchSetCommit(PatchSet.id(id, 1));
bu.setNotify(NotifyResolver.Result.none());
ins =
changeInserterFactory
@@ -842,14 +841,14 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
private ObjectId getDestRef(ChangeNotes notes) throws Exception {
return serverSideTestRepo
.getRepository()
- .exactRef(notes.getChange().getDest().get())
+ .exactRef(notes.getChange().getDest().branch())
.getObjectId();
}
private ChangeNotes mergeChange(ChangeNotes notes) throws Exception {
- final ObjectId oldId = getDestRef(notes);
- final ObjectId newId = ObjectId.fromString(psUtil.current(notes).getRevision().get());
- final String dest = notes.getChange().getDest().get();
+ ObjectId oldId = getDestRef(notes);
+ ObjectId newId = psUtil.current(notes).commitId();
+ String dest = notes.getChange().getDest().branch();
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(
diff --git a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index b9b7ab39c8..e369d1b4c3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -15,35 +15,36 @@
package com.google.gerrit.acceptance.server.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assertThat;
-import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.truth.Correspondence;
-import com.google.common.truth.Correspondence.BinaryPredicate;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseTimezone;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.index.IndexConfig;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.restapi.change.ChangesCollection;
import com.google.gerrit.server.restapi.change.GetRelated;
@@ -52,7 +53,6 @@ import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
@@ -64,11 +64,11 @@ import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
+@UseClockStep
+@UseTimezone(timezone = "US/Eastern")
public class GetRelatedIT extends AbstractDaemonTest {
private static final int MAX_TERMS = 10;
@@ -81,22 +81,9 @@ public class GetRelatedIT extends AbstractDaemonTest {
@Inject private AccountOperations accountOperations;
@Inject private GroupOperations groupOperations;
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
- private String systemTimeZone;
-
- @Before
- public void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
@Inject private IndexConfig indexConfig;
@Inject private ChangesCollection changes;
@@ -129,14 +116,14 @@ public class GetRelatedIT extends AbstractDaemonTest {
testRepo.reset(c1_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id ps1_1 = getPatchSetId(c1_1);
- String oldETag = changes.parse(ps1_1.getParentKey()).getETag();
+ String oldETag = changes.parse(ps1_1.changeId()).getETag();
testRepo.reset(c2_1);
pushHead(testRepo, "refs/for/master", false);
PatchSet.Id ps2_1 = getPatchSetId(c2_1);
// Push of change 2 should not affect groups (or anything else) of change 1.
- assertThat(changes.parse(ps1_1.getParentKey()).getETag()).isEqualTo(oldETag);
+ assertThat(changes.parse(ps1_1.changeId()).getETag()).isEqualTo(oldETag);
for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
assertRelated(ps, changeAndCommit(ps2_1, c2_1, 1), changeAndCommit(ps1_1, c1_1, 1));
@@ -498,7 +485,7 @@ public class GetRelatedIT extends AbstractDaemonTest {
PatchSet.Id ps1_1 = getPatchSetId(c1_1);
PatchSet.Id ps2_1 = getPatchSetId(c2_1);
- PatchSet.Id ps2_edit = new PatchSet.Id(ch2.getId(), 0);
+ PatchSet.Id ps2_edit = PatchSet.id(ch2.getId(), 0);
PatchSet.Id ps3_1 = getPatchSetId(c3_1);
for (PatchSet.Id ps : ImmutableList.of(ps1_1, ps2_1, ps3_1)) {
@@ -512,7 +499,7 @@ public class GetRelatedIT extends AbstractDaemonTest {
assertRelated(
ps2_edit,
changeAndCommit(ps3_1, c3_1, 1),
- changeAndCommit(new PatchSet.Id(ch2.getId(), 0), editRev, 1),
+ changeAndCommit(PatchSet.id(ch2.getId(), 0), editRev, 1),
changeAndCommit(ps1_1, c1_1, 1));
}
@@ -533,7 +520,7 @@ public class GetRelatedIT extends AbstractDaemonTest {
// Pretend PS1,1 was pushed before the groups field was added.
clearGroups(psId1_1);
- indexer.index(changeDataFactory.create(project, psId1_1.getParentKey()));
+ indexer.index(changeDataFactory.create(project, psId1_1.changeId()));
// PS1,1 has no groups, so disappeared from related changes.
assertRelated(psId2_1);
@@ -568,7 +555,7 @@ public class GetRelatedIT extends AbstractDaemonTest {
PatchSet.Id psId1_1 = getPatchSetId(c1_1);
PatchSet.Id psId2_1 = getPatchSetId(c2_1);
- PatchSet.Id psId2_2 = new PatchSet.Id(psId2_1.changeId, psId2_1.get() + 1);
+ PatchSet.Id psId2_2 = PatchSet.id(psId2_1.changeId(), psId2_1.get() + 1);
assertRelated(psId2_2, changeAndCommit(psId2_2, c2_2, 2), changeAndCommit(psId1_1, c1_1, 1));
}
@@ -611,11 +598,10 @@ public class GetRelatedIT extends AbstractDaemonTest {
Account.Id accountId = accountOperations.newAccount().create();
AccountGroup.UUID groupUuid = groupOperations.newGroup().addMember(accountId).create();
- try (ProjectConfigUpdate u = updateProject(allProjects)) {
- PermissionRule rule = Util.allow(u.getConfig(), GlobalCapability.QUERY_LIMIT, groupUuid);
- rule.setRange(0, 2);
- u.save();
- }
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(GlobalCapability.QUERY_LIMIT).group(groupUuid).range(0, 2))
+ .update();
requestScopeOperations.setApiUser(accountId);
assertRelated(lastPsId, expected);
@@ -651,13 +637,8 @@ public class GetRelatedIT extends AbstractDaemonTest {
private static Correspondence<RelatedChangeAndCommitInfo, String>
getRelatedChangeToStatusCorrespondence() {
return Correspondence.from(
- new BinaryPredicate<RelatedChangeAndCommitInfo, String>() {
- @Override
- public boolean apply(
- RelatedChangeAndCommitInfo relatedChangeAndCommitInfo, String status) {
- return Objects.equals(relatedChangeAndCommitInfo.status, status);
- }
- },
+ (relatedChangeAndCommitInfo, status) ->
+ Objects.equals(relatedChangeAndCommitInfo.status, status),
"has status");
}
@@ -678,7 +659,7 @@ public class GetRelatedIT extends AbstractDaemonTest {
PatchSet.Id psId, ObjectId commitId, int currentRevisionNum) {
RelatedChangeAndCommitInfo result = new RelatedChangeAndCommitInfo();
result.project = project.get();
- result._changeNumber = psId.getParentKey().get();
+ result._changeNumber = psId.changeId().get();
result.commit = new CommitInfo();
result.commit.commit = commitId.name();
result._revisionNumber = psId.get();
@@ -690,12 +671,11 @@ public class GetRelatedIT extends AbstractDaemonTest {
private void clearGroups(PatchSet.Id psId) throws Exception {
try (BatchUpdate bu = batchUpdateFactory.create(project, user(user), TimeUtil.nowTs())) {
bu.addOp(
- psId.getParentKey(),
+ psId.changeId(),
new BatchUpdateOp() {
@Override
public boolean updateChange(ChangeContext ctx) {
- PatchSet ps = psUtil.get(ctx.getNotes(), psId);
- psUtil.setGroups(ctx.getUpdate(psId), ps, ImmutableList.of());
+ ctx.getUpdate(psId).setGroups(ImmutableList.of());
return true;
}
});
@@ -711,19 +691,19 @@ public class GetRelatedIT extends AbstractDaemonTest {
private void assertRelated(PatchSet.Id psId, List<RelatedChangeAndCommitInfo> expected)
throws Exception {
List<RelatedChangeAndCommitInfo> actual =
- gApi.changes().id(psId.getParentKey().get()).revision(psId.get()).related().changes;
- assertThat(actual).named("related to " + psId).hasSize(expected.size());
+ gApi.changes().id(psId.changeId().get()).revision(psId.get()).related().changes;
+ assertWithMessage("related to " + psId).that(actual).hasSize(expected.size());
for (int i = 0; i < actual.size(); i++) {
String name = "index " + i + " related to " + psId;
RelatedChangeAndCommitInfo a = actual.get(i);
RelatedChangeAndCommitInfo e = expected.get(i);
- assertThat(a.project).named("project of " + name).isEqualTo(e.project);
- assertThat(a._changeNumber).named("change ID of " + name).isEqualTo(e._changeNumber);
+ assertWithMessage("project of " + name).that(a.project).isEqualTo(e.project);
+ assertWithMessage("change ID of " + name).that(a._changeNumber).isEqualTo(e._changeNumber);
// Don't bother checking changeId; assume _changeNumber is sufficient.
- assertThat(a._revisionNumber).named("revision of " + name).isEqualTo(e._revisionNumber);
- assertThat(a.commit.commit).named("commit of " + name).isEqualTo(e.commit.commit);
- assertThat(a._currentRevisionNumber)
- .named("current revision of " + name)
+ assertWithMessage("revision of " + name).that(a._revisionNumber).isEqualTo(e._revisionNumber);
+ assertWithMessage("commit of " + name).that(a.commit.commit).isEqualTo(e.commit.commit);
+ assertWithMessage("current revision of " + name)
+ .that(a._currentRevisionNumber)
.isEqualTo(e._currentRevisionNumber);
assertThat(a.status).isEqualTo(e.status);
}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
index 42fdae42b7..b23f9a3c95 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
@@ -25,9 +25,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.server.patch.IntraLineDiff;
import com.google.gerrit.server.patch.IntraLineDiffArgs;
import com.google.gerrit.server.patch.IntraLineDiffKey;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 4224953841..66ff82b438 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -25,13 +25,13 @@ import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import java.util.EnumSet;
@@ -115,7 +115,7 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
@Test
public void respectWholeTopic() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
// Create two independent commits and push.
RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
String id1 = getChangeId(c1_1);
@@ -137,7 +137,7 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
@Test
public void anonymousWholeTopic() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
RevCommit a = commitBuilder().add("a", "1").message("change 1").create();
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
String id1 = getChangeId(a);
@@ -159,7 +159,7 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
@Test
public void topicChaining() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
String id1 = getChangeId(c1_1);
@@ -207,7 +207,7 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
@Test
public void respectTopicsOnAncestors() throws Exception {
- RevCommit initialHead = getRemoteHead();
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
String id1 = getChangeId(c1_1);
diff --git a/javatests/com/google/gerrit/acceptance/server/config/BUILD b/javatests/com/google/gerrit/acceptance/server/config/BUILD
new file mode 100644
index 0000000000..17802bdffc
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/config/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob(["*IT.java"]),
+ group = "server_config",
+ labels = ["server"],
+)
diff --git a/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java b/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java
new file mode 100644
index 0000000000..d01a81dd73
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.Sandboxed;
+import com.google.gerrit.server.config.GerritIsReplicaProvider;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class GerritIsReplicaIT extends AbstractDaemonTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return new Config();
+ }
+
+ @Inject GerritIsReplicaProvider isReplicaProvider;
+
+ @Test
+ public void isNotReplica() {
+ assertThat(isReplicaProvider.get()).isFalse();
+ }
+
+ @Test
+ @Sandboxed
+ public void isReplica() throws Exception {
+ restartAsSlave();
+ assertThat(isReplicaProvider.get()).isTrue();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
index 4f98dd0683..8469fffb25 100644
--- a/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
@@ -15,71 +15,48 @@
package com.google.gerrit.acceptance.server.event;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.events.CommentAddedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class CommentAddedEventIT extends AbstractDaemonTest {
- @Inject private DynamicSet<CommentAddedListener> source;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
private final LabelType label =
- category("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
+ label("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
private final LabelType pLabel =
- category("CustomLabel2", value(1, "Positive"), value(0, "No score"));
-
- private RegistrationHandle eventListenerRegistration;
- private CommentAddedListener.Event lastCommentAddedEvent;
+ label("CustomLabel2", value(1, "Positive"), value(0, "No score"));
@Before
public void setUp() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- AccountGroup.UUID anonymousUsers = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- Util.allow(
- u.getConfig(),
- Permission.forLabel(label.getName()),
- -1,
- 1,
- anonymousUsers,
- "refs/heads/*");
- Util.allow(
- u.getConfig(),
- Permission.forLabel(pLabel.getName()),
- 0,
- 1,
- anonymousUsers,
- "refs/heads/*");
- u.save();
- }
-
- eventListenerRegistration = source.add("gerrit", event -> lastCommentAddedEvent = event);
- }
-
- @After
- public void cleanup() {
- eventListenerRegistration.remove();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(label.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .add(allowLabel(pLabel.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(0, 1))
+ .update();
}
private void saveLabelConfig() throws Exception {
@@ -90,16 +67,30 @@ public class CommentAddedEventIT extends AbstractDaemonTest {
}
}
+ private static class TestListener implements CommentAddedListener {
+ private CommentAddedListener.Event lastCommentAddedEvent;
+
+ @Override
+ public void onCommentAdded(Event event) {
+ lastCommentAddedEvent = event;
+ }
+
+ public CommentAddedListener.Event getLastCommentAddedEvent() {
+ assertThat(lastCommentAddedEvent).isNotNull();
+ return lastCommentAddedEvent;
+ }
+ }
+
/* Need to lookup info for the label under test since there can be multiple
* labels defined. By default Gerrit already has a Code-Review label.
*/
- private ApprovalValues getApprovalValues(LabelType label) {
+ private ApprovalValues getApprovalValues(LabelType label, TestListener listener) {
ApprovalValues res = new ApprovalValues();
- ApprovalInfo info = lastCommentAddedEvent.getApprovals().get(label.getName());
+ ApprovalInfo info = listener.getLastCommentAddedEvent().getApprovals().get(label.getName());
if (info != null) {
res.value = info.value;
}
- info = lastCommentAddedEvent.getOldApprovals().get(label.getName());
+ info = listener.getLastCommentAddedEvent().getOldApprovals().get(label.getName());
if (info != null) {
res.oldValue = info.value;
}
@@ -110,15 +101,18 @@ public class CommentAddedEventIT extends AbstractDaemonTest {
public void newChangeWithVote() throws Exception {
saveLabelConfig();
- // push a new change with -1 vote
- PushOneCommit.Result r = createChange();
- ReviewInput reviewInput = new ReviewInput().label(label.getName(), (short) -1);
- revision(r).review(reviewInput);
- ApprovalValues attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(0);
- assertThat(attr.value).isEqualTo(-1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
+ TestListener listener = new TestListener();
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ // push a new change with -1 vote
+ PushOneCommit.Result r = createChange();
+ ReviewInput reviewInput = new ReviewInput().label(label.getName(), (short) -1);
+ revision(r).review(reviewInput);
+ ApprovalValues attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(0);
+ assertThat(attr.value).isEqualTo(-1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
+ }
}
@Test
@@ -129,17 +123,19 @@ public class CommentAddedEventIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
ReviewInput reviewInput = new ReviewInput().message(label.getName());
revision(r).review(reviewInput);
-
- // push a new revision with +1 vote
- ChangeInfo c = info(r.getChangeId());
- r = amendChange(c.changeId);
- reviewInput = new ReviewInput().label(label.getName(), (short) 1);
- revision(r).review(reviewInput);
- ApprovalValues attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(0);
- assertThat(attr.value).isEqualTo(1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 2: %s+1", label.getName()));
+ TestListener listener = new TestListener();
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ // push a new revision with +1 vote
+ ChangeInfo c = info(r.getChangeId());
+ r = amendChange(c.changeId);
+ reviewInput = new ReviewInput().label(label.getName(), (short) 1);
+ revision(r).review(reviewInput);
+ ApprovalValues attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(0);
+ assertThat(attr.value).isEqualTo(1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 2: %s+1", label.getName()));
+ }
}
@Test
@@ -149,114 +145,120 @@ public class CommentAddedEventIT extends AbstractDaemonTest {
// push a change
PushOneCommit.Result r = createChange();
- // review with message only, do not apply votes
- ReviewInput reviewInput = new ReviewInput().message(label.getName());
- revision(r).review(reviewInput);
- // reply message only so vote is shown as 0
- ApprovalValues attr = getApprovalValues(label);
- assertThat(attr.oldValue).isNull();
- assertThat(attr.value).isEqualTo(0);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1:\n\n%s", label.getName()));
-
- // transition from un-voted to -1 vote
- reviewInput = new ReviewInput().label(label.getName(), -1);
- revision(r).review(reviewInput);
- attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(0);
- assertThat(attr.value).isEqualTo(-1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
-
- // transition vote from -1 to 0
- reviewInput = new ReviewInput().label(label.getName(), 0);
- revision(r).review(reviewInput);
- attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(-1);
- assertThat(attr.value).isEqualTo(0);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: -%s", label.getName()));
-
- // transition vote from 0 to 1
- reviewInput = new ReviewInput().label(label.getName(), 1);
- revision(r).review(reviewInput);
- attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(0);
- assertThat(attr.value).isEqualTo(1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s+1", label.getName()));
-
- // transition vote from 1 to -1
- reviewInput = new ReviewInput().label(label.getName(), -1);
- revision(r).review(reviewInput);
- attr = getApprovalValues(label);
- assertThat(attr.oldValue).isEqualTo(1);
- assertThat(attr.value).isEqualTo(-1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
-
- // review with message only, do not apply votes
- reviewInput = new ReviewInput().message(label.getName());
- revision(r).review(reviewInput);
- attr = getApprovalValues(label);
- assertThat(attr.oldValue).isNull(); // no vote change so not included
- assertThat(attr.value).isEqualTo(-1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1:\n\n%s", label.getName()));
+ TestListener listener = new TestListener();
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ // review with message only, do not apply votes
+ ReviewInput reviewInput = new ReviewInput().message(label.getName());
+ revision(r).review(reviewInput);
+ // reply message only so vote is shown as 0
+ ApprovalValues attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isNull();
+ assertThat(attr.value).isEqualTo(0);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1:\n\n%s", label.getName()));
+
+ // transition from un-voted to -1 vote
+ reviewInput = new ReviewInput().label(label.getName(), -1);
+ revision(r).review(reviewInput);
+ attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(0);
+ assertThat(attr.value).isEqualTo(-1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
+
+ // transition vote from -1 to 0
+ reviewInput = new ReviewInput().label(label.getName(), 0);
+ revision(r).review(reviewInput);
+ attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(-1);
+ assertThat(attr.value).isEqualTo(0);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: -%s", label.getName()));
+
+ // transition vote from 0 to 1
+ reviewInput = new ReviewInput().label(label.getName(), 1);
+ revision(r).review(reviewInput);
+ attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(0);
+ assertThat(attr.value).isEqualTo(1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s+1", label.getName()));
+
+ // transition vote from 1 to -1
+ reviewInput = new ReviewInput().label(label.getName(), -1);
+ revision(r).review(reviewInput);
+ attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isEqualTo(1);
+ assertThat(attr.value).isEqualTo(-1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s-1", label.getName()));
+
+ // review with message only, do not apply votes
+ reviewInput = new ReviewInput().message(label.getName());
+ revision(r).review(reviewInput);
+ attr = getApprovalValues(label, listener);
+ assertThat(attr.oldValue).isNull(); // no vote change so not included
+ assertThat(attr.value).isEqualTo(-1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1:\n\n%s", label.getName()));
+ }
}
@Test
public void reviewChange_MultipleVotes() throws Exception {
- saveLabelConfig();
- PushOneCommit.Result r = createChange();
- ReviewInput reviewInput = new ReviewInput().label(label.getName(), -1);
- reviewInput.message = label.getName();
- revision(r).review(reviewInput);
-
- ChangeInfo c = get(r.getChangeId(), DETAILED_LABELS);
- LabelInfo q = c.labels.get(label.getName());
- assertThat(q.all).hasSize(1);
- ApprovalValues labelAttr = getApprovalValues(label);
- assertThat(labelAttr.oldValue).isEqualTo(0);
- assertThat(labelAttr.value).isEqualTo(-1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s-1\n\n%s", label.getName(), label.getName()));
-
- // there should be 3 approval labels (label, pLabel, and CRVV)
- assertThat(lastCommentAddedEvent.getApprovals()).hasSize(3);
-
- // check the approvals that were not voted on
- ApprovalValues pLabelAttr = getApprovalValues(pLabel);
- assertThat(pLabelAttr.oldValue).isNull();
- assertThat(pLabelAttr.value).isEqualTo(0);
-
- LabelType crLabel = LabelType.withDefaultValues("Code-Review");
- ApprovalValues crlAttr = getApprovalValues(crLabel);
- assertThat(crlAttr.oldValue).isNull();
- assertThat(crlAttr.value).isEqualTo(0);
-
- // update pLabel approval
- reviewInput = new ReviewInput().label(pLabel.getName(), 1);
- reviewInput.message = pLabel.getName();
- revision(r).review(reviewInput);
-
- c = get(r.getChangeId(), DETAILED_LABELS);
- q = c.labels.get(label.getName());
- assertThat(q.all).hasSize(1);
- pLabelAttr = getApprovalValues(pLabel);
- assertThat(pLabelAttr.oldValue).isEqualTo(0);
- assertThat(pLabelAttr.value).isEqualTo(1);
- assertThat(lastCommentAddedEvent.getComment())
- .isEqualTo(String.format("Patch Set 1: %s+1\n\n%s", pLabel.getName(), pLabel.getName()));
-
- // check the approvals that were not voted on
- labelAttr = getApprovalValues(label);
- assertThat(labelAttr.oldValue).isNull();
- assertThat(labelAttr.value).isEqualTo(-1);
-
- crlAttr = getApprovalValues(crLabel);
- assertThat(crlAttr.oldValue).isNull();
- assertThat(crlAttr.value).isEqualTo(0);
+ TestListener listener = new TestListener();
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ saveLabelConfig();
+ PushOneCommit.Result r = createChange();
+ ReviewInput reviewInput = new ReviewInput().label(label.getName(), -1);
+ reviewInput.message = label.getName();
+ revision(r).review(reviewInput);
+
+ ChangeInfo c = get(r.getChangeId(), DETAILED_LABELS);
+ LabelInfo q = c.labels.get(label.getName());
+ assertThat(q.all).hasSize(1);
+ ApprovalValues labelAttr = getApprovalValues(label, listener);
+ assertThat(labelAttr.oldValue).isEqualTo(0);
+ assertThat(labelAttr.value).isEqualTo(-1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s-1\n\n%s", label.getName(), label.getName()));
+
+ // there should be 3 approval labels (label, pLabel, and CRVV)
+ assertThat(listener.getLastCommentAddedEvent().getApprovals()).hasSize(3);
+
+ // check the approvals that were not voted on
+ ApprovalValues pLabelAttr = getApprovalValues(pLabel, listener);
+ assertThat(pLabelAttr.oldValue).isNull();
+ assertThat(pLabelAttr.value).isEqualTo(0);
+
+ LabelType crLabel = LabelType.withDefaultValues("Code-Review");
+ ApprovalValues crlAttr = getApprovalValues(crLabel, listener);
+ assertThat(crlAttr.oldValue).isNull();
+ assertThat(crlAttr.value).isEqualTo(0);
+
+ // update pLabel approval
+ reviewInput = new ReviewInput().label(pLabel.getName(), 1);
+ reviewInput.message = pLabel.getName();
+ revision(r).review(reviewInput);
+
+ c = get(r.getChangeId(), DETAILED_LABELS);
+ q = c.labels.get(label.getName());
+ assertThat(q.all).hasSize(1);
+ pLabelAttr = getApprovalValues(pLabel, listener);
+ assertThat(pLabelAttr.oldValue).isEqualTo(0);
+ assertThat(pLabelAttr.value).isEqualTo(1);
+ assertThat(listener.getLastCommentAddedEvent().getComment())
+ .isEqualTo(String.format("Patch Set 1: %s+1\n\n%s", pLabel.getName(), pLabel.getName()));
+
+ // check the approvals that were not voted on
+ labelAttr = getApprovalValues(label, listener);
+ assertThat(labelAttr.oldValue).isNull();
+ assertThat(labelAttr.value).isEqualTo(-1);
+
+ crlAttr = getApprovalValues(crLabel, listener);
+ assertThat(crlAttr.oldValue).isNull();
+ assertThat(crlAttr.value).isEqualTo(0);
+ }
}
private static class ApprovalValues {
diff --git a/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
new file mode 100644
index 0000000000..8744cfadbd
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/event/EventPayloadIT.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.event;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.GerritConfig;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.extensions.events.RevisionCreatedListener;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@NoHttpd
+public class EventPayloadIT extends AbstractDaemonTest {
+ @Inject private ExtensionRegistry extensionRegistry;
+
+ @Test
+ public void defaultOptions() throws Exception {
+ RevisionCreatedListener listener =
+ new RevisionCreatedListener() {
+ @Override
+ public void onRevisionCreated(Event event) {
+ assertThat(event.getChange().submittable).isNotNull();
+ assertThat(event.getRevision().files).isNotEmpty();
+ }
+ };
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ createChange();
+ }
+ }
+
+ @Test
+ @GerritConfig(name = "event.payload.listChangeOptions", value = "SKIP_MERGEABLE")
+ public void configuredOptions() throws Exception {
+ RevisionCreatedListener listener =
+ new RevisionCreatedListener() {
+ @Override
+ public void onRevisionCreated(Event event) {
+ assertThat(event.getChange().submittable).isNull();
+ assertThat(event.getChange().mergeable).isNull();
+ assertThat(event.getRevision().files).isNull();
+ assertThat(event.getChange().subject).isNotEmpty();
+ }
+ };
+ try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
+ createChange();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/git/receive/BUILD b/javatests/com/google/gerrit/acceptance/server/git/receive/BUILD
new file mode 100644
index 0000000000..760e7f416e
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/git/receive/BUILD
@@ -0,0 +1,8 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob(["*IT.java"]),
+ group = "receive",
+ labels = ["server"],
+ deps = [],
+)
diff --git a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
new file mode 100644
index 0000000000..6677583a38
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
@@ -0,0 +1,136 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.git.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.testing.TestCommentHelper;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+
+/**
+ * Tests for comment validation when publishing drafts via the {@code --publish-comments} option.
+ */
+public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
+ @Inject private CommentValidator mockCommentValidator;
+ @Inject private TestCommentHelper testCommentHelper;
+
+ private static final String COMMENT_TEXT = "The comment text";
+
+ @Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
+
+ @Override
+ public Module createModule() {
+ return new FactoryModule() {
+ @Override
+ public void configure() {
+ CommentValidator mockCommentValidator = mock(CommentValidator.class);
+ bind(CommentValidator.class)
+ .annotatedWith(Exports.named(mockCommentValidator.getClass()))
+ .toInstance(mockCommentValidator);
+ bind(CommentValidator.class).toInstance(mockCommentValidator);
+ }
+ };
+ }
+
+ @Before
+ public void resetMock() {
+ initMocks(this);
+ clearInvocations(mockCommentValidator);
+ }
+
+ @Test
+ public void validateComments_commentOK() throws Exception {
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of());
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
+ DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, comment);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
+ Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
+ amendResult.assertOkStatus();
+ amendResult.assertNotMessage("Comment validation failure:");
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(1);
+ }
+
+ @Test
+ public void validateComments_commentRejected() throws Exception {
+ CommentForValidation commentForValidation =
+ CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
+ DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, comment);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
+ Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
+ amendResult.assertOkStatus();
+ amendResult.assertMessage("Comment validation failure:");
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
+ }
+
+ @Test
+ public void validateComments_inlineVsFileComments_allOK() throws Exception {
+ when(mockCommentValidator.validateComments(capture.capture())).thenReturn(ImmutableList.of());
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
+ DraftInput draftFile = testCommentHelper.newDraft(COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, draftFile);
+ DraftInput draftInline =
+ testCommentHelper.newDraft(
+ result.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
+ testCommentHelper.addDraft(changeId, revId, draftInline);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
+ amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
+ assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(2);
+ assertThat(capture.getAllValues()).hasSize(1);
+ assertThat(capture.getValue())
+ .containsExactly(
+ CommentForValidation.create(
+ CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
+ CommentForValidation.create(
+ CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/httpd/BUILD b/javatests/com/google/gerrit/acceptance/server/httpd/BUILD
new file mode 100644
index 0000000000..d1a64c01ed
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/httpd/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob(["*IT.java"]),
+ group = "server_httpd",
+ labels = ["server"],
+)
diff --git a/javatests/com/google/gerrit/acceptance/server/httpd/HttpLogoutServletIT.java b/javatests/com/google/gerrit/acceptance/server/httpd/HttpLogoutServletIT.java
new file mode 100644
index 0000000000..1dea800849
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/httpd/HttpLogoutServletIT.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.httpd;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HttpLogoutServletIT extends StandaloneSiteTest {
+ private static final String LOCALHOST = InetAddress.getLoopbackAddress().getHostName();
+
+ @ConfigSuite.Config
+ public static Config secondConfig() throws IOException {
+ Config cfg = new Config();
+ cfg.setString("auth", null, "logouturl", "/test-logout");
+ cfg.setString("gerrit", null, "canonicalWebUrl", "https://" + LOCALHOST + ":8443/");
+ cfg.setString("httpd", null, "listenUrl", "proxy-https://" + LOCALHOST + ":" + getFreePort());
+ return cfg;
+ }
+
+ @Inject @GerritServerConfig private Config gerritConfig;
+
+ private HttpClient httpClient;
+
+ @Before
+ public void setUp() {
+ httpClient = HttpClientBuilder.create().disableRedirectHandling().build();
+ }
+
+ @Test
+ public void shouldHonourCanonicalWebUrlProxyWhenRedirectAfterLogout() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+
+ URIish listenUrl = new URIish(gerritConfig.getString("httpd", null, "listenUrl"));
+ URIish canonicalWebUrl =
+ new URIish(gerritConfig.getString("gerrit", null, "canonicalWebUrl"));
+
+ String logoutPath =
+ Optional.ofNullable(baseConfig.getString("auth", null, "logouturl")).orElse("/");
+
+ HttpGet getLogout = new HttpGet("/logout");
+ getLogout.addHeader("X-Forwarded-Host", canonicalWebUrl.getHost());
+ getLogout.addHeader("X-Forwarded-Port", "" + canonicalWebUrl.getPort());
+ getLogout.addHeader("X-Forwarded-Proto", canonicalWebUrl.getScheme());
+
+ HttpResponse logoutResponse =
+ httpClient.execute(new HttpHost(listenUrl.getHost(), listenUrl.getPort()), getLogout);
+
+ assertThat(logoutResponse.getStatusLine().getStatusCode())
+ .isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY);
+ assertThat(getLocationHeaderURIish(logoutResponse))
+ .containsExactly(canonicalWebUrl.setPath(logoutPath));
+ }
+ }
+
+ private List<URIish> getLocationHeaderURIish(HttpResponse logoutResponse) {
+ return Arrays.stream(logoutResponse.getHeaders("Location"))
+ .map(h -> h.getValue())
+ .map(HttpLogoutServletIT::unsafeNewURIish)
+ .filter(u -> u.isPresent())
+ .map(u -> u.get())
+ .collect(Collectors.toList());
+ }
+
+ private static Optional<URIish> unsafeNewURIish(String uri) {
+ try {
+ return Optional.of(new URIish(uri));
+ } catch (URISyntaxException e) {
+ return Optional.empty();
+ }
+ }
+
+ private static int getFreePort() throws IOException {
+ try (ServerSocket s = new ServerSocket(0)) {
+ return s.getLocalPort();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/BUILD b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
index 5d7e65e10c..f90924b3d7 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
@@ -7,18 +7,18 @@ DEPS = [
"//java/com/google/gerrit/mail",
]
-acceptance_tests(
- srcs = glob(
- ["*IT.java"],
- exclude = ["AbstractMailIT.java"],
- ),
- group = "server_mail",
+[acceptance_tests(
+ srcs = [f],
+ group = f[:f.index(".")],
labels = [
"no_windows",
"server",
],
deps = DEPS + [":util"],
-)
+) for f in glob(
+ ["*IT.java"],
+ exclude = ["AbstractMailIT.java"],
+)]
java_library(
name = "util",
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index 25e07085f9..2dc1e246c7 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -14,6 +14,9 @@
package com.google.gerrit.acceptance.server.mail;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.api.changes.NotifyHandling.ALL;
import static com.google.gerrit.extensions.api.changes.NotifyHandling.NONE;
import static com.google.gerrit.extensions.api.changes.NotifyHandling.OWNER;
@@ -31,9 +34,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.truth.Truth;
import com.google.gerrit.acceptance.AbstractNotificationTest;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AssigneeInput;
@@ -50,9 +55,6 @@ import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.CommitMessageInput;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.restapi.change.PostReview;
import com.google.inject.Inject;
import org.eclipse.jgit.junit.TestRepository;
@@ -61,6 +63,7 @@ import org.junit.Before;
import org.junit.Test;
public class ChangeNotificationsIT extends AbstractNotificationTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
/*
@@ -80,14 +83,14 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
@Before
public void grantPermissions() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- ProjectConfig cfg = u.getConfig();
- Util.allow(cfg, Permission.FORGE_COMMITTER, REGISTERED_USERS, "refs/*");
- Util.allow(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/*");
- Util.allow(cfg, Permission.ABANDON, REGISTERED_USERS, "refs/*");
- Util.allow(cfg, Permission.forLabel("Code-Review"), -2, +2, REGISTERED_USERS, "refs/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.FORGE_COMMITTER).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(Permission.ABANDON).ref("refs/*").group(REGISTERED_USERS))
+ .add(allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
}
/*
@@ -599,7 +602,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
if (notify != null) {
in.notify = notify;
}
- gApi.changes().id(changeId).revision("current").review(in);
+ gApi.changes().id(changeId).current().review(in);
};
}
@@ -858,7 +861,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
public void noCommentAndSetWorkInProgress() throws Exception {
StagedChange sc = stageReviewableChange();
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
assertThat(sender).didNotSend();
}
@@ -866,7 +869,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
public void commentAndSetWorkInProgress() throws Exception {
StagedChange sc = stageReviewableChange();
ReviewInput in = ReviewInput.noScore().message("ok").setWorkInProgress(true);
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
assertThat(sender)
.sent("comment", sc)
.cc(sc.reviewer, sc.ccer)
@@ -881,7 +884,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
public void commentOnWipChangeAndStartReview() throws Exception {
StagedChange sc = stageWipChange();
ReviewInput in = ReviewInput.noScore().message("ok").setWorkInProgress(false);
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
assertThat(sender)
.sent("comment", sc)
.cc(sc.reviewer, sc.ccer)
@@ -896,7 +899,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
public void addReviewerOnWipChangeAndStartReview() throws Exception {
StagedChange sc = stageWipChange();
ReviewInput in = ReviewInput.noScore().reviewer(other.email()).setWorkInProgress(false);
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
assertThat(sender)
.sent("comment", sc)
.cc(sc.reviewer, sc.ccer, other)
@@ -920,7 +923,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
StagedChange sc = stageWipChange();
ReviewInput in =
ReviewInput.noScore().message(PostReview.START_REVIEW_MESSAGE).setWorkInProgress(false);
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
Truth.assertThat(sender.getMessages()).isNotEmpty();
String body = sender.getMessages().get(0).body();
int idx = body.indexOf(PostReview.START_REVIEW_MESSAGE);
@@ -950,7 +953,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
ReviewInput in = ReviewInput.recommend();
in.notify = notify;
in.tag = tag;
- gApi.changes().id(changeId).revision("current").review(in);
+ gApi.changes().id(changeId).current().review(in);
}
/*
@@ -1253,7 +1256,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
private void recommend(StagedChange sc, TestAccount by) throws Exception {
requestScopeOperations.setApiUser(by.id());
- gApi.changes().id(sc.changeId).revision("current").review(ReviewInput.recommend());
+ gApi.changes().id(sc.changeId).current().review(ReviewInput.recommend());
}
private interface Stager {
@@ -1267,7 +1270,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
.reviewer(extraReviewer.email())
.reviewer(extraCcer.email(), ReviewerState.CC, false);
requestScopeOperations.setApiUser(extraReviewer.id());
- gApi.changes().id(sc.changeId).revision("current").review(in);
+ gApi.changes().id(sc.changeId).current().review(in);
sender.clear();
return sc;
}
@@ -1516,15 +1519,16 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
}
merge(sc.changeId, sc.owner);
- assertThat(sender)
- .named(name)
+ assertWithMessage(name)
+ .about(fakeEmailSenders())
+ .that(sender)
.sent("merged", sc)
.cc(sc.reviewer, sc.ccer)
.cc(StagedUsers.REVIEWER_BY_EMAIL, StagedUsers.CC_BY_EMAIL)
.bcc(sc.starrer)
.bcc(ALL_COMMENTS, SUBMITTED_CHANGES)
.noOneElse();
- assertThat(sender).named(name).didNotSend();
+ assertWithMessage(name).about(fakeEmailSenders()).that(sender).didNotSend();
}
}
@@ -1626,7 +1630,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
throws Exception {
setEmailStrategy(by, emailStrategy);
requestScopeOperations.setApiUser(by.id());
- gApi.changes().id(changeId).revision("current").submit();
+ gApi.changes().id(changeId).current().submit();
}
private void merge(String changeId, TestAccount by, NotifyHandling notify) throws Exception {
@@ -1640,13 +1644,13 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
requestScopeOperations.setApiUser(by.id());
SubmitInput in = new SubmitInput();
in.notify = notify;
- gApi.changes().id(changeId).revision("current").submit(in);
+ gApi.changes().id(changeId).current().submit(in);
}
private StagedChange stageChangeReadyForMerge() throws Exception {
StagedChange sc = stageReviewableChange();
requestScopeOperations.setApiUser(sc.reviewer.id());
- gApi.changes().id(sc.changeId).revision("current").review(ReviewInput.approve());
+ gApi.changes().id(sc.changeId).current().review(ReviewInput.approve());
sender.clear();
return sc;
}
@@ -2039,7 +2043,7 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
StagedChange sc, TestAccount by, @Nullable NotifyHandling notify, EmailStrategy emailStrategy)
throws Exception {
setEmailStrategy(by, emailStrategy);
- CommitInfo commit = gApi.changes().id(sc.changeId).revision("current").commit(false);
+ CommitInfo commit = gApi.changes().id(sc.changeId).current().commit(false);
CommitMessageInput in = new CommitMessageInput();
in.message = "update\n" + commit.message;
in.notify = notify;
@@ -2254,8 +2258,8 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
private StagedChange stageChange() throws Exception {
StagedChange sc = stageReviewableChange();
requestScopeOperations.setApiUser(admin.id());
- gApi.changes().id(sc.changeId).revision("current").review(ReviewInput.approve());
- gApi.changes().id(sc.changeId).revision("current").submit();
+ gApi.changes().id(sc.changeId).current().review(ReviewInput.approve());
+ gApi.changes().id(sc.changeId).current().submit();
sender.clear();
return sc;
}
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
index 1386aec56f..0826c166ab 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
@@ -15,11 +15,12 @@
package com.google.gerrit.acceptance.server.mail;
import static com.google.common.truth.Truth.assertThat;
-import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.acceptance.UseTimezone;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -27,7 +28,6 @@ import com.google.gerrit.mail.EmailHeader;
import com.google.gerrit.mail.MailProcessingUtil;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testing.FakeEmailSender;
-import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
import java.sql.Timestamp;
import java.time.ZoneId;
@@ -37,28 +37,14 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
/** Tests the presence of required metadata in email headers, text and html. */
+@UseClockStep
+@UseTimezone(timezone = "US/Eastern")
public class MailMetadataIT extends AbstractDaemonTest {
@Inject private RequestScopeOperations requestScopeOperations;
- private String systemTimeZone;
-
- @Before
- public void setTimeForTesting() {
- systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
- TestTimeUtil.resetWithClockStep(1, SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- System.setProperty("user.timezone", systemTimeZone);
- }
-
@Test
public void metadataOnNewChange() throws Exception {
PushOneCommit.Result newChange = createChange();
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index f917fd894b..5531709f0b 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -15,26 +15,68 @@
package com.google.gerrit.acceptance.server.mail;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.mail.MailMessage;
import com.google.gerrit.mail.MailProcessingUtil;
import com.google.gerrit.server.mail.receive.MailProcessor;
import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.gerrit.testing.TestCommentHelper;
import com.google.inject.Inject;
+import com.google.inject.Module;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.List;
+import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
public class MailProcessorIT extends AbstractMailIT {
@Inject private MailProcessor mailProcessor;
@Inject private AccountOperations accountOperations;
+ @Inject private TestCommentHelper testCommentHelper;
+
+ private static final CommentValidator mockCommentValidator = mock(CommentValidator.class);
+
+ private static final String COMMENT_TEXT = "The comment text";
+
+ @Override
+ public Module createModule() {
+ return new FactoryModule() {
+ @Override
+ public void configure() {
+ bind(CommentValidator.class)
+ .annotatedWith(Exports.named(mockCommentValidator.getClass()))
+ .toInstance(mockCommentValidator);
+ bind(CommentValidator.class).toInstance(mockCommentValidator);
+ }
+ };
+ }
+
+ @BeforeClass
+ public static void setUpMock() {
+ // Let the mock comment validator accept all comments during test setup.
+ when(mockCommentValidator.validateComments(any())).thenReturn(ImmutableList.of());
+ }
+
+ @Before
+ public void setUp() {
+ clearInvocations(mockCommentValidator);
+ }
@Test
public void parseAndPersistChangeMessage() throws Exception {
@@ -223,7 +265,87 @@ public class MailProcessorIT extends AbstractMailIT {
assertThat(message.headers()).containsKey("Subject");
}
+ @Test
+ public void validateChangeMessage_rejected() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
+
+ setupFailValidation(CommentForValidation.CommentType.CHANGE_MESSAGE);
+
+ MailMessage.Builder b = messageBuilderWithDefaultFields();
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", COMMENT_TEXT, null, null, null);
+ b.textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ Collection<CommentInfo> commentsBefore = testCommentHelper.getPublishedComments(changeId);
+ mailProcessor.process(b.build());
+ assertThat(testCommentHelper.getPublishedComments(changeId)).isEqualTo(commentsBefore);
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body()).contains("rejected one or more comments");
+ }
+
+ @Test
+ public void validateInlineComment_rejected() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
+
+ setupFailValidation(CommentForValidation.CommentType.INLINE_COMMENT);
+
+ MailMessage.Builder b = messageBuilderWithDefaultFields();
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, COMMENT_TEXT, null, null);
+ b.textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ Collection<CommentInfo> commentsBefore = testCommentHelper.getPublishedComments(changeId);
+ mailProcessor.process(b.build());
+ assertThat(testCommentHelper.getPublishedComments(changeId)).isEqualTo(commentsBefore);
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body()).contains("rejected one or more comments");
+ }
+
+ @Test
+ public void validateFileComment_rejected() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
+
+ setupFailValidation(CommentForValidation.CommentType.FILE_COMMENT);
+
+ MailMessage.Builder b = messageBuilderWithDefaultFields();
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, null, COMMENT_TEXT, null);
+ b.textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ Collection<CommentInfo> commentsBefore = testCommentHelper.getPublishedComments(changeId);
+ mailProcessor.process(b.build());
+ assertThat(testCommentHelper.getPublishedComments(changeId)).isEqualTo(commentsBefore);
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body()).contains("rejected one or more comments");
+ }
+
private String getChangeUrl(ChangeInfo changeInfo) {
return canonicalWebUrl.get() + "c/" + changeInfo.project + "/+/" + changeInfo._number;
}
+
+ private void setupFailValidation(CommentForValidation.CommentType type) {
+ CommentForValidation commentForValidation = CommentForValidation.create(type, COMMENT_TEXT);
+
+ when(mockCommentValidator.validateComments(
+ ImmutableList.of(CommentForValidation.create(type, COMMENT_TEXT))))
+ .thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
index bdb3f3bc3c..ee3bcb019f 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
@@ -7,9 +7,6 @@ acceptance_tests(
"notedb",
"server",
],
- # TODO(dborowitz): Fix leaks in local disk tests so we can reduce heap size.
- # http://crbug.com/gerrit/8567
- vm_args = ["-Xmx1024m"],
deps = [
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
index 748c4eaf9b..c502c798ae 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
@@ -15,17 +15,19 @@
package com.google.gerrit.acceptance.server.notedb;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateListener;
@@ -126,12 +128,9 @@ public class NoteDbOnlyIT extends AbstractDaemonTest {
throw new ResourceConflictException(msg);
}
});
- try {
- bu.execute();
- fail("expected ResourceConflictException");
- } catch (ResourceConflictException e) {
- assertThat(e).hasMessageThat().isEqualTo(msg);
- }
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> bu.execute());
+ assertThat(thrown).hasMessageThat().isEqualTo(msg);
}
// If updateChange hadn't failed, backup would have been updated to master2.
@@ -188,18 +187,13 @@ public class NoteDbOnlyIT extends AbstractDaemonTest {
@Test
public void missingChange() throws Exception {
- Change.Id changeId = new Change.Id(1234567);
+ Change.Id changeId = Change.id(1234567);
assertNoSuchChangeException(() -> notesFactory.create(project, changeId));
assertNoSuchChangeException(() -> notesFactory.createChecked(project, changeId));
}
private void assertNoSuchChangeException(Callable<?> callable) throws Exception {
- try {
- callable.call();
- fail("expected NoSuchChangeException");
- } catch (NoSuchChangeException e) {
- // Expected.
- }
+ assertThrows(NoSuchChangeException.class, () -> callable.call());
}
private class ConcurrentWritingListener implements BatchUpdateListener {
@@ -306,8 +300,8 @@ public class NoteDbOnlyIT extends AbstractDaemonTest {
if (repo instanceof InMemoryRepository) {
((InMemoryRepository) repo).setPerformsAtomicTransactions(true);
} else {
- assertThat(repo.getRefDatabase().performsAtomicTransactions())
- .named("performsAtomicTransactions on %s", repo)
+ assertWithMessage("performsAtomicTransactions on %s", repo)
+ .that(repo.getRefDatabase().performsAtomicTransactions())
.isTrue();
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
index 2919e5fe97..12cae84b0b 100644
--- a/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
@@ -19,9 +19,9 @@ import static org.junit.Assert.assertNotEquals;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.conditions.BooleanCondition;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -122,7 +122,7 @@ public class PermissionBackendConditionIT extends AbstractDaemonTest {
@Test
public void refPermissions_sameResourceAndUserEquals() throws Exception {
- Branch.NameKey branch = new Branch.NameKey(project, "branch");
+ BranchNameKey branch = BranchNameKey.create(project, "branch");
BooleanCondition cond1 = pb.user(user()).ref(branch).testCond(RefPermission.READ);
BooleanCondition cond2 = pb.user(user()).ref(branch).testCond(RefPermission.READ);
@@ -132,7 +132,7 @@ public class PermissionBackendConditionIT extends AbstractDaemonTest {
@Test
public void refPermissions_sameResourceAndDifferentUserDoesNotEqual() throws Exception {
- Branch.NameKey branch = new Branch.NameKey(project, "branch");
+ BranchNameKey branch = BranchNameKey.create(project, "branch");
BooleanCondition cond1 = pb.user(user()).ref(branch).testCond(RefPermission.READ);
BooleanCondition cond2 = pb.user(admin()).ref(branch).testCond(RefPermission.READ);
@@ -142,8 +142,8 @@ public class PermissionBackendConditionIT extends AbstractDaemonTest {
@Test
public void refPermissions_differentResourceAndSameUserDoesNotEqual() throws Exception {
- Branch.NameKey branch1 = new Branch.NameKey(project, "branch");
- Branch.NameKey branch2 = new Branch.NameKey(project, "branch2");
+ BranchNameKey branch1 = BranchNameKey.create(project, "branch");
+ BranchNameKey branch2 = BranchNameKey.create(project, "branch2");
BooleanCondition cond1 = pb.user(user()).ref(branch1).testCond(RefPermission.READ);
BooleanCondition cond2 = pb.user(user()).ref(branch2).testCond(RefPermission.READ);
@@ -153,8 +153,8 @@ public class PermissionBackendConditionIT extends AbstractDaemonTest {
@Test
public void refPermissions_differentResourceAndSameUserDoesNotEqual2() throws Exception {
- Branch.NameKey branch1 = new Branch.NameKey(project, "branch");
- Branch.NameKey branch2 = new Branch.NameKey(projectOperations.newProject().create(), "branch");
+ BranchNameKey branch1 = BranchNameKey.create(project, "branch");
+ BranchNameKey branch2 = BranchNameKey.create(projectOperations.newProject().create(), "branch");
BooleanCondition cond1 = pb.user(user()).ref(branch1).testCond(RefPermission.READ);
BooleanCondition cond2 = pb.user(user()).ref(branch2).testCond(RefPermission.READ);
diff --git a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 6cbe40e56b..3b7d826049 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -15,6 +15,8 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
import static com.google.gerrit.common.data.LabelFunction.ANY_WITH_BLOCK;
import static com.google.gerrit.common.data.LabelFunction.MAX_NO_BLOCK;
import static com.google.gerrit.common.data.LabelFunction.MAX_WITH_BLOCK;
@@ -24,68 +26,50 @@ import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LAB
import static com.google.gerrit.extensions.client.ListChangesOption.LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.events.CommentAddedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import java.util.Arrays;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class CustomLabelIT extends AbstractDaemonTest {
- @Inject private DynamicSet<CommentAddedListener> source;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
private final LabelType label =
- category("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
+ label("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative"));
- private final LabelType P = category("CustomLabel2", value(1, "Positive"), value(0, "No score"));
-
- private RegistrationHandle eventListenerRegistration;
- private CommentAddedListener.Event lastCommentAddedEvent;
+ private final LabelType P = label("CustomLabel2", value(1, "Positive"), value(0, "No score"));
@Before
public void setUp() throws Exception {
- try (ProjectConfigUpdate u = updateProject(project)) {
- AccountGroup.UUID anonymousUsers = systemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
- Util.allow(
- u.getConfig(),
- Permission.forLabel(label.getName()),
- -1,
- 1,
- anonymousUsers,
- "refs/heads/*");
- Util.allow(
- u.getConfig(), Permission.forLabel(P.getName()), 0, 1, anonymousUsers, "refs/heads/*");
- u.save();
- }
-
- eventListenerRegistration = source.add("gerrit", event -> lastCommentAddedEvent = event);
- }
-
- @After
- public void cleanup() {
- eventListenerRegistration.remove();
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(label.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .add(allowLabel(P.getName()).ref("refs/heads/*").group(ANONYMOUS_USERS).range(0, 1))
+ .update();
}
@Test
@@ -172,28 +156,41 @@ public class CustomLabelIT extends AbstractDaemonTest {
assertThat(q.blocking).isTrue();
}
- @Test
- public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
- P.setFunction(ANY_WITH_BLOCK);
- saveLabelConfig();
- PushOneCommit.Result r = createChange();
- AddReviewerInput in = new AddReviewerInput();
- in.reviewer = user.email();
- gApi.changes().id(r.getChangeId()).addReviewer(in);
+ private static class TestListener implements CommentAddedListener {
+ public CommentAddedListener.Event lastCommentAddedEvent;
- ReviewInput input = new ReviewInput().label(P.getName(), 0);
- input.message = "foo";
+ @Override
+ public void onCommentAdded(Event event) {
+ lastCommentAddedEvent = event;
+ }
+ }
- revision(r).review(input);
- ChangeInfo c = getWithLabels(r);
- LabelInfo q = c.labels.get(P.getName());
- assertThat(q.all).hasSize(2);
- assertThat(q.approved).isNull();
- assertThat(q.recommended).isNull();
- assertThat(q.disliked).isNull();
- assertThat(q.rejected).isNull();
- assertThat(q.blocking).isNull();
- assertThat(lastCommentAddedEvent.getComment()).isEqualTo("Patch Set 1:\n\n" + input.message);
+ @Test
+ public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
+ TestListener testListener = new TestListener();
+ try (Registration registration = extensionRegistry.newRegistration().add(testListener)) {
+ P.setFunction(ANY_WITH_BLOCK);
+ saveLabelConfig();
+ PushOneCommit.Result r = createChange();
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email();
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+
+ ReviewInput input = new ReviewInput().label(P.getName(), 0);
+ input.message = "foo";
+
+ revision(r).review(input);
+ ChangeInfo c = getWithLabels(r);
+ LabelInfo q = c.labels.get(P.getName());
+ assertThat(q.all).hasSize(1);
+ assertThat(q.approved).isNull();
+ assertThat(q.recommended).isNull();
+ assertThat(q.disliked).isNull();
+ assertThat(q.rejected).isNull();
+ assertThat(q.blocking).isNull();
+ assertThat(testListener.lastCommentAddedEvent.getComment())
+ .isEqualTo("Patch Set 1:\n\n" + input.message);
+ }
}
@Test
@@ -265,15 +262,17 @@ public class CustomLabelIT extends AbstractDaemonTest {
assertPermitted(info, P.getName(), 0, 1);
assertPermitted(info, label.getName());
- ReviewInput in = new ReviewInput();
- in.label(P.getName(), P.getMax().getValue());
- revision(r).review(in);
-
- in = new ReviewInput();
- in.label(label.getName(), label.getMax().getValue());
- exception.expect(ResourceConflictException.class);
- exception.expectMessage("Voting on labels disallowed after submit: " + label.getName());
- revision(r).review(in);
+ ReviewInput postSubmitReview1 = new ReviewInput();
+ postSubmitReview1.label(P.getName(), P.getMax().getValue());
+ revision(r).review(postSubmitReview1);
+
+ ReviewInput postSubmitReview2 = new ReviewInput();
+ postSubmitReview2.label(label.getName(), label.getMax().getValue());
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> revision(r).review(postSubmitReview2));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Voting on labels disallowed after submit: " + label.getName());
}
@Test
@@ -289,11 +288,11 @@ public class CustomLabelIT extends AbstractDaemonTest {
value(-1, "I would prefer this is not merged as is"),
value(-2, "This shall not be merged"));
- AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(u.getConfig(), Permission.forLabel(testLabel), -2, +2, registered, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(testLabel).ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
@@ -311,11 +310,12 @@ public class CustomLabelIT extends AbstractDaemonTest {
assertThat(gApi.changes().id(changeId).get().submittable).isTrue();
// Update admin's permitted range for 'Test-Label' to be -1...+1.
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.remove(u.getConfig(), Permission.forLabel(testLabel), registered, "refs/heads/*");
- Util.allow(u.getConfig(), Permission.forLabel(testLabel), -1, +1, registered, "refs/heads/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .remove(labelPermissionKey(testLabel).ref("refs/heads/*").group(REGISTERED_USERS))
+ .add(allowLabel(testLabel).ref("refs/heads/*").group(REGISTERED_USERS).range(-1, +1))
+ .update();
// Verify admin doesn't have +2 permission any more.
assertPermitted(gApi.changes().id(changeId).get(), testLabel, -1, 0, 1);
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index 4ed16eead6..29574c4e84 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.server.StarredChangesUtil.IGNORE_LABEL;
import com.google.common.collect.ImmutableSet;
@@ -25,12 +26,12 @@ import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.testing.FakeEmailSender.Message;
@@ -218,7 +219,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// push a change to watched project -> should trigger email notification
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), watchedRepo, "TRIGGER", "a", "a1")
@@ -229,7 +230,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// notification
String notWatchedProject = projectOperations.newProject().create().get();
TestRepository<InMemoryRepository> notWatchedRepo =
- cloneProject(new Project.NameKey(notWatchedProject), admin);
+ cloneProject(Project.nameKey(notWatchedProject), admin);
r =
pushFactory
.create(admin.newIdent(), notWatchedRepo, "DONT_TRIGGER", "a", "a1")
@@ -261,7 +262,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// user
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), watchedRepo, "TRIGGER", "a.txt", "a1")
@@ -305,15 +306,15 @@ public class ProjectWatchIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
// watch keyword in project as user
- watch(watchedProject, "multimaster");
+ watch(watchedProject, "multiprimary");
// push a change with keyword -> should trigger email notification
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
- .create(admin.newIdent(), watchedRepo, "Document multimaster setup", "a.txt", "a1")
+ .create(admin.newIdent(), watchedRepo, "Document multiprimary setup", "a.txt", "a1")
.to("refs/for/master");
r.assertOkStatus();
@@ -322,7 +323,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
assertThat(messages).hasSize(1);
Message m = messages.get(0);
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
- assertThat(m.body()).contains("Change subject: Document multimaster setup\n");
+ assertThat(m.body()).contains("Change subject: Document multiprimary setup\n");
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
sender.clear();
@@ -347,8 +348,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// push a change to any project -> should trigger email notification
requestScopeOperations.setApiUser(admin.id());
- TestRepository<InMemoryRepository> anyRepo =
- cloneProject(new Project.NameKey(anyProject), admin);
+ TestRepository<InMemoryRepository> anyRepo = cloneProject(Project.nameKey(anyProject), admin);
PushOneCommit.Result r =
pushFactory.create(admin.newIdent(), anyRepo, "TRIGGER", "a", "a1").to("refs/for/master");
r.assertOkStatus();
@@ -374,8 +374,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// push a change to watched file in any project -> should trigger email
// notification for user
requestScopeOperations.setApiUser(admin.id());
- TestRepository<InMemoryRepository> anyRepo =
- cloneProject(new Project.NameKey(anyProject), admin);
+ TestRepository<InMemoryRepository> anyRepo = cloneProject(Project.nameKey(anyProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), anyRepo, "TRIGGER", "a.txt", "a1")
@@ -419,16 +418,15 @@ public class ProjectWatchIT extends AbstractDaemonTest {
requestScopeOperations.setApiUser(user.id());
// watch keyword in project as user
- watch(allProjects.get(), "multimaster");
+ watch(allProjects.get(), "multiprimary");
// push a change with keyword to any project -> should trigger email
// notification
requestScopeOperations.setApiUser(admin.id());
- TestRepository<InMemoryRepository> anyRepo =
- cloneProject(new Project.NameKey(anyProject), admin);
+ TestRepository<InMemoryRepository> anyRepo = cloneProject(Project.nameKey(anyProject), admin);
PushOneCommit.Result r =
pushFactory
- .create(admin.newIdent(), anyRepo, "Document multimaster setup", "a.txt", "a1")
+ .create(admin.newIdent(), anyRepo, "Document multiprimary setup", "a.txt", "a1")
.to("refs/for/master");
r.assertOkStatus();
@@ -437,7 +435,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
assertThat(messages).hasSize(1);
Message m = messages.get(0);
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
- assertThat(m.body()).contains("Change subject: Document multimaster setup\n");
+ assertThat(m.body()).contains("Change subject: Document multiprimary setup\n");
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
sender.clear();
@@ -463,7 +461,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// push a change to watched project
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), watchedRepo, "ignored change", "a", "a1")
@@ -496,7 +494,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// push a private change to watched project -> should not trigger email notification
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), watchedRepo, "private change", "a", "a1")
@@ -514,12 +512,14 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// create group that can view all private changes
GroupInfo groupThatCanViewPrivateChanges =
gApi.groups().create("groupThatCanViewPrivateChanges").get();
- grant(
- new Project.NameKey(watchedProject),
- "refs/*",
- Permission.VIEW_PRIVATE_CHANGES,
- false,
- new AccountGroup.UUID(groupThatCanViewPrivateChanges.id));
+ projectOperations
+ .project(Project.nameKey(watchedProject))
+ .forUpdate()
+ .add(
+ allow(Permission.VIEW_PRIVATE_CHANGES)
+ .ref("refs/*")
+ .group(AccountGroup.uuid(groupThatCanViewPrivateChanges.id)))
+ .update();
// watch project as user that can't view private changes
requestScopeOperations.setApiUser(user.id());
@@ -536,7 +536,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// userThatCanViewPrivateChanges, but not for user
requestScopeOperations.setApiUser(admin.id());
TestRepository<InMemoryRepository> watchedRepo =
- cloneProject(new Project.NameKey(watchedProject), admin);
+ cloneProject(Project.nameKey(watchedProject), admin);
PushOneCommit.Result r =
pushFactory
.create(admin.newIdent(), watchedRepo, "TRIGGER", "a", "a1")
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
index da3a257915..11d39b445d 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
@@ -15,21 +15,24 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.project.testing.Util;
import com.google.inject.Inject;
import java.io.File;
import java.util.List;
@@ -39,6 +42,7 @@ import org.junit.Test;
@UseLocalDisk
public class ReflogIT extends AbstractDaemonTest {
+ @Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
@@ -55,7 +59,7 @@ public class ReflogIT extends AbstractDaemonTest {
gApi.changes().id(id.get()).topic("foo");
ReflogEntry last = repo.getReflogReader(changeMetaRef(id)).getLastEntry();
- assertThat(last).named("last RefLogEntry").isNotNull();
+ assertWithMessage("last RefLogEntry").that(last).isNotNull();
assertThat(last.getComment()).isEqualTo("restapi.change.PutTopic");
}
}
@@ -85,8 +89,8 @@ public class ReflogIT extends AbstractDaemonTest {
@Test
public void regularUserIsNotAllowedToGetReflog() throws Exception {
requestScopeOperations.setApiUser(user.id());
- exception.expect(AuthException.class);
- gApi.projects().name(project.get()).branch("master").reflog();
+ assertThrows(
+ AuthException.class, () -> gApi.projects().name(project.get()).branch("master").reflog());
}
@Test
@@ -94,11 +98,11 @@ public class ReflogIT extends AbstractDaemonTest {
GroupApi groupApi = gApi.groups().create(name("get-reflog"));
groupApi.addMembers("user");
- try (ProjectConfigUpdate u = updateProject(project)) {
- Util.allow(
- u.getConfig(), Permission.OWNER, new AccountGroup.UUID(groupApi.get().id), "refs/*");
- u.save();
- }
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.OWNER).ref("refs/*").group(AccountGroup.uuid(groupApi.get().id)))
+ .update();
requestScopeOperations.setApiUser(user.id());
gApi.projects().name(project.get()).branch("master").reflog();
diff --git a/javatests/com/google/gerrit/acceptance/server/quota/DefaultQuotaBackendIT.java b/javatests/com/google/gerrit/acceptance/server/quota/DefaultQuotaBackendIT.java
index adc7807101..b3647e7363 100644
--- a/javatests/com/google/gerrit/acceptance/server/quota/DefaultQuotaBackendIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/quota/DefaultQuotaBackendIT.java
@@ -15,16 +15,17 @@
package com.google.gerrit.acceptance.server.quota;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.resetToStrict;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.quota.QuotaBackend;
import com.google.gerrit.server.quota.QuotaEnforcer;
@@ -34,13 +35,12 @@ import com.google.gerrit.server.quota.QuotaResponse;
import com.google.inject.Inject;
import com.google.inject.Module;
import java.util.Collections;
-import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
public class DefaultQuotaBackendIT extends AbstractDaemonTest {
- private static final QuotaEnforcer quotaEnforcer = EasyMock.createStrictMock(QuotaEnforcer.class);
+ private static final QuotaEnforcer quotaEnforcer = mock(QuotaEnforcer.class);
private IdentifiedUser identifiedAdmin;
@Inject private QuotaBackend quotaBackend;
@@ -60,14 +60,13 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
@Before
public void setUp() {
identifiedAdmin = identifiedUserFactory.create(admin.id());
- resetToStrict(quotaEnforcer);
+ clearInvocations(quotaEnforcer);
}
@Test
public void requestTokenForUser() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
assertThat(quotaBackend.user(identifiedAdmin).requestToken("testGroup"))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
}
@@ -76,8 +75,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
public void requestTokenForUserAndAccount() {
QuotaRequestContext ctx =
QuotaRequestContext.builder().user(identifiedAdmin).account(user.id()).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
assertThat(quotaBackend.user(identifiedAdmin).account(user.id()).requestToken("testGroup"))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
}
@@ -86,8 +84,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
public void requestTokenForUserAndProject() {
QuotaRequestContext ctx =
QuotaRequestContext.builder().user(identifiedAdmin).project(project).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
assertThat(quotaBackend.user(identifiedAdmin).project(project).requestToken("testGroup"))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
}
@@ -101,8 +98,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
.change(changeId)
.project(project)
.build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
assertThat(
quotaBackend.user(identifiedAdmin).change(changeId, project).requestToken("testGroup"))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
@@ -111,8 +107,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
@Test
public void requestTokens() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 123)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 123)).thenReturn(QuotaResponse.ok());
assertThat(quotaBackend.user(identifiedAdmin).requestTokens("testGroup", 123))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
}
@@ -120,8 +115,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
@Test
public void dryRun() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.dryRun("testGroup", ctx, 123)).andReturn(QuotaResponse.ok());
- replay(quotaEnforcer);
+ when(quotaEnforcer.dryRun("testGroup", ctx, 123)).thenReturn(QuotaResponse.ok());
assertThat(quotaBackend.user(identifiedAdmin).dryRun("testGroup", 123))
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
}
@@ -131,8 +125,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
QuotaRequestContext ctx =
QuotaRequestContext.builder().user(identifiedAdmin).account(user.id()).build();
QuotaResponse r = QuotaResponse.ok(10L);
- expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenReturn(r);
assertThat(quotaBackend.user(identifiedAdmin).account(user.id()).availableTokens("testGroup"))
.isEqualTo(singletonAggregation(r));
}
@@ -142,8 +135,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
QuotaRequestContext ctx =
QuotaRequestContext.builder().user(identifiedAdmin).project(project).build();
QuotaResponse r = QuotaResponse.ok(10L);
- expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenReturn(r);
assertThat(quotaBackend.user(identifiedAdmin).project(project).availableTokens("testGroup"))
.isEqualTo(singletonAggregation(r));
}
@@ -158,8 +150,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
.project(project)
.build();
QuotaResponse r = QuotaResponse.ok(10L);
- expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenReturn(r);
assertThat(
quotaBackend
.user(identifiedAdmin)
@@ -172,8 +163,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
public void availableTokens() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
QuotaResponse r = QuotaResponse.ok(10L);
- expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenReturn(r);
assertThat(quotaBackend.user(identifiedAdmin).availableTokens("testGroup"))
.isEqualTo(singletonAggregation(r));
}
@@ -181,56 +171,51 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
@Test
public void requestTokenError() throws Exception {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1))
- .andReturn(QuotaResponse.error("failed"));
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1))
+ .thenReturn(QuotaResponse.error("failed"));
QuotaResponse.Aggregated result = quotaBackend.user(identifiedAdmin).requestToken("testGroup");
assertThat(result).isEqualTo(singletonAggregation(QuotaResponse.error("failed")));
- exception.expect(QuotaException.class);
- exception.expectMessage("failed");
- result.throwOnError();
+ QuotaException thrown = assertThrows(QuotaException.class, () -> result.throwOnError());
+ assertThat(thrown).hasMessageThat().contains("failed");
}
@Test
public void availableTokensError() throws Exception {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.availableTokens("testGroup", ctx))
- .andReturn(QuotaResponse.error("failed"));
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenReturn(QuotaResponse.error("failed"));
QuotaResponse.Aggregated result =
quotaBackend.user(identifiedAdmin).availableTokens("testGroup");
assertThat(result).isEqualTo(singletonAggregation(QuotaResponse.error("failed")));
- exception.expect(QuotaException.class);
- exception.expectMessage("failed");
- result.throwOnError();
+ QuotaException thrown = assertThrows(QuotaException.class, () -> result.throwOnError());
+ assertThat(thrown).hasMessageThat().contains("failed");
}
@Test
public void requestTokenPluginThrowsAndRethrows() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.requestTokens("testGroup", ctx, 1)).andThrow(new NullPointerException());
- replay(quotaEnforcer);
+ when(quotaEnforcer.requestTokens("testGroup", ctx, 1)).thenThrow(new NullPointerException());
- exception.expect(NullPointerException.class);
- quotaBackend.user(identifiedAdmin).requestToken("testGroup");
+ assertThrows(
+ NullPointerException.class,
+ () -> quotaBackend.user(identifiedAdmin).requestToken("testGroup"));
}
@Test
public void availableTokensPluginThrowsAndRethrows() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcer.availableTokens("testGroup", ctx)).andThrow(new NullPointerException());
- replay(quotaEnforcer);
+ when(quotaEnforcer.availableTokens("testGroup", ctx)).thenThrow(new NullPointerException());
- exception.expect(NullPointerException.class);
- quotaBackend.user(identifiedAdmin).availableTokens("testGroup");
+ assertThrows(
+ NullPointerException.class,
+ () -> quotaBackend.user(identifiedAdmin).availableTokens("testGroup"));
}
private Change.Id retrieveChangeId() throws Exception {
// use REST API so that repository size quota doesn't have to be stubbed
ChangeInfo changeInfo =
gApi.changes().create(new ChangeInput(project.get(), "master", "test")).get();
- return new Change.Id(changeInfo._number);
+ return Change.id(changeInfo._number);
}
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
diff --git a/javatests/com/google/gerrit/acceptance/server/quota/MultipleQuotaPluginsIT.java b/javatests/com/google/gerrit/acceptance/server/quota/MultipleQuotaPluginsIT.java
index f5f11610c6..c6b09ccc5a 100644
--- a/javatests/com/google/gerrit/acceptance/server/quota/MultipleQuotaPluginsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/quota/MultipleQuotaPluginsIT.java
@@ -15,11 +15,12 @@
package com.google.gerrit.acceptance.server.quota;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.resetToStrict;
-import static org.easymock.EasyMock.verify;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -33,15 +34,12 @@ import com.google.gerrit.server.quota.QuotaResponse;
import com.google.inject.Inject;
import com.google.inject.Module;
import java.util.OptionalLong;
-import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
public class MultipleQuotaPluginsIT extends AbstractDaemonTest {
- private static final QuotaEnforcer quotaEnforcerA =
- EasyMock.createStrictMock(QuotaEnforcer.class);
- private static final QuotaEnforcer quotaEnforcerB =
- EasyMock.createStrictMock(QuotaEnforcer.class);
+ private static final QuotaEnforcer quotaEnforcerA = mock(QuotaEnforcer.class);
+ private static final QuotaEnforcer quotaEnforcerB = mock(QuotaEnforcer.class);
private IdentifiedUser identifiedAdmin;
@Inject private QuotaBackend quotaBackend;
@@ -65,93 +63,86 @@ public class MultipleQuotaPluginsIT extends AbstractDaemonTest {
@Before
public void setUp() {
identifiedAdmin = identifiedUserFactory.create(admin.id());
- resetToStrict(quotaEnforcerA);
- resetToStrict(quotaEnforcerB);
+ clearInvocations(quotaEnforcerA);
+ clearInvocations(quotaEnforcerB);
}
@Test
public void refillsOnError() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcerA.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- expect(quotaEnforcerB.requestTokens("testGroup", ctx, 1))
- .andReturn(QuotaResponse.error("fail"));
- quotaEnforcerA.refill("testGroup", ctx, 1);
- expectLastCall();
-
- replay(quotaEnforcerA);
- replay(quotaEnforcerB);
+ when(quotaEnforcerA.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
+ when(quotaEnforcerB.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.error("fail"));
assertThat(quotaBackend.user(identifiedAdmin).requestToken("testGroup"))
.isEqualTo(
QuotaResponse.Aggregated.create(
ImmutableList.of(QuotaResponse.ok(), QuotaResponse.error("fail"))));
+
+ verify(quotaEnforcerA).requestTokens("testGroup", ctx, 1);
+ verify(quotaEnforcerB).requestTokens("testGroup", ctx, 1);
+ verify(quotaEnforcerA).refill("testGroup", ctx, 1);
}
@Test
public void refillsOnException() {
NullPointerException exception = new NullPointerException();
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcerA.requestTokens("testGroup", ctx, 1)).andThrow(exception);
- expect(quotaEnforcerB.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.ok());
- quotaEnforcerB.refill("testGroup", ctx, 1);
- expectLastCall();
-
- replay(quotaEnforcerA);
- replay(quotaEnforcerB);
-
- try {
- quotaBackend.user(identifiedAdmin).requestToken("testGroup");
- fail("expected a NullPointerException");
- } catch (NullPointerException e) {
- assertThat(exception).isEqualTo(e);
- }
-
- verify(quotaEnforcerA);
+ when(quotaEnforcerA.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.ok());
+ when(quotaEnforcerB.requestTokens("testGroup", ctx, 1)).thenThrow(exception);
+
+ NullPointerException thrown =
+ assertThrows(
+ NullPointerException.class,
+ () -> quotaBackend.user(identifiedAdmin).requestToken("testGroup"));
+ assertThat(thrown).isEqualTo(exception);
+
+ verify(quotaEnforcerA).requestTokens("testGroup", ctx, 1);
+ verify(quotaEnforcerB).requestTokens("testGroup", ctx, 1);
+ verify(quotaEnforcerA).refill("testGroup", ctx, 1);
}
@Test
public void doesNotRefillNoOp() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcerA.requestTokens("testGroup", ctx, 1))
- .andReturn(QuotaResponse.error("fail"));
- expect(quotaEnforcerB.requestTokens("testGroup", ctx, 1)).andReturn(QuotaResponse.noOp());
-
- replay(quotaEnforcerA);
- replay(quotaEnforcerB);
+ when(quotaEnforcerA.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.error("fail"));
+ when(quotaEnforcerB.requestTokens("testGroup", ctx, 1)).thenReturn(QuotaResponse.noOp());
assertThat(quotaBackend.user(identifiedAdmin).requestToken("testGroup"))
.isEqualTo(
QuotaResponse.Aggregated.create(
ImmutableList.of(QuotaResponse.error("fail"), QuotaResponse.noOp())));
+
+ verify(quotaEnforcerA).requestTokens("testGroup", ctx, 1);
+ verify(quotaEnforcerB).requestTokens("testGroup", ctx, 1);
}
@Test
public void minimumAvailableTokens() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcerA.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(20L));
- expect(quotaEnforcerB.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(10L));
-
- replay(quotaEnforcerA);
- replay(quotaEnforcerB);
+ when(quotaEnforcerA.availableTokens("testGroup", ctx)).thenReturn(QuotaResponse.ok(20L));
+ when(quotaEnforcerB.availableTokens("testGroup", ctx)).thenReturn(QuotaResponse.ok(10L));
OptionalLong tokens =
quotaBackend.user(identifiedAdmin).availableTokens("testGroup").availableTokens();
- assertThat(tokens.isPresent()).isTrue();
+ assertThat(tokens).isPresent();
assertThat(tokens.getAsLong()).isEqualTo(10L);
+
+ verify(quotaEnforcerA).availableTokens("testGroup", ctx);
+ verify(quotaEnforcerB).availableTokens("testGroup", ctx);
}
@Test
public void ignoreNoOpForAvailableTokens() {
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
- expect(quotaEnforcerA.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.noOp());
- expect(quotaEnforcerB.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(20L));
-
- replay(quotaEnforcerA);
- replay(quotaEnforcerB);
+ when(quotaEnforcerA.availableTokens("testGroup", ctx)).thenReturn(QuotaResponse.noOp());
+ when(quotaEnforcerB.availableTokens("testGroup", ctx)).thenReturn(QuotaResponse.ok(20L));
OptionalLong tokens =
quotaBackend.user(identifiedAdmin).availableTokens("testGroup").availableTokens();
- assertThat(tokens.isPresent()).isTrue();
+ assertThat(tokens).isPresent();
assertThat(tokens.getAsLong()).isEqualTo(20L);
+
+ verify(quotaEnforcerA).availableTokens("testGroup", ctx);
+ verify(quotaEnforcerB).availableTokens("testGroup", ctx);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java b/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
index 24c90fce32..2692584419 100644
--- a/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/quota/RepositorySizeQuotaIT.java
@@ -18,12 +18,13 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
import static com.google.gerrit.server.quota.QuotaResponse.ok;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.resetToStrict;
-import static org.easymock.EasyMock.verify;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.UseLocalDisk;
@@ -33,7 +34,6 @@ import com.google.gerrit.server.quota.QuotaBackend;
import com.google.gerrit.server.quota.QuotaResponse;
import com.google.inject.Module;
import java.util.Collections;
-import org.easymock.EasyMock;
import org.eclipse.jgit.api.errors.TooLargePackException;
import org.eclipse.jgit.api.errors.TransportException;
import org.junit.Before;
@@ -42,9 +42,9 @@ import org.junit.Test;
@UseLocalDisk
public class RepositorySizeQuotaIT extends AbstractDaemonTest {
private static final QuotaBackend.WithResource quotaBackendWithResource =
- EasyMock.createStrictMock(QuotaBackend.WithResource.class);
+ mock(QuotaBackend.WithResource.class);
private static final QuotaBackend.WithUser quotaBackendWithUser =
- EasyMock.createStrictMock(QuotaBackend.WithUser.class);
+ mock(QuotaBackend.WithUser.class);
@Override
public Module createModule() {
@@ -70,56 +70,40 @@ public class RepositorySizeQuotaIT extends AbstractDaemonTest {
@Before
public void setUp() {
- resetToStrict(quotaBackendWithResource);
- resetToStrict(quotaBackendWithUser);
+ clearInvocations(quotaBackendWithResource);
+ clearInvocations(quotaBackendWithUser);
}
@Test
public void pushWithAvailableTokens() throws Exception {
- expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
- .andReturn(singletonAggregation(ok(277L)))
- .times(2);
- expect(quotaBackendWithResource.requestTokens(eq(REPOSITORY_SIZE_GROUP), anyLong()))
- .andReturn(singletonAggregation(ok()));
- expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
- replay(quotaBackendWithResource);
- replay(quotaBackendWithUser);
+ when(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
+ .thenReturn(singletonAggregation(ok(277L)));
+ when(quotaBackendWithResource.requestTokens(eq(REPOSITORY_SIZE_GROUP), anyLong()))
+ .thenReturn(singletonAggregation(ok()));
+ when(quotaBackendWithUser.project(project)).thenReturn(quotaBackendWithResource);
pushCommit();
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
+ verify(quotaBackendWithResource, times(2)).availableTokens(REPOSITORY_SIZE_GROUP);
}
@Test
public void pushWithNotSufficientTokens() throws Exception {
long availableTokens = 1L;
- expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
- .andReturn(singletonAggregation(ok(availableTokens)))
- .anyTimes();
- expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
- replay(quotaBackendWithResource);
- replay(quotaBackendWithUser);
+ when(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
+ .thenReturn(singletonAggregation(ok(availableTokens)));
+ when(quotaBackendWithUser.project(project)).thenReturn(quotaBackendWithResource);
assertThat(assertThrows(TooLargePackException.class, () -> pushCommit()).getMessage())
.contains(
String.format(
"Pack exceeds the limit of %d bytes, rejecting the pack", availableTokens));
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
}
@Test
public void errorGettingAvailableTokens() throws Exception {
String msg = "quota error";
- expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
- .andReturn(singletonAggregation(QuotaResponse.error(msg)))
- .anyTimes();
- expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
- replay(quotaBackendWithResource);
- replay(quotaBackendWithUser);
-
+ when(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
+ .thenReturn(singletonAggregation(QuotaResponse.error(msg)));
+ when(quotaBackendWithUser.project(project)).thenReturn(quotaBackendWithResource);
assertThrows(TransportException.class, () -> pushCommit());
-
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
}
private void pushCommit() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/server/quota/RestApiQuotaIT.java b/javatests/com/google/gerrit/acceptance/server/quota/RestApiQuotaIT.java
index dc082feda9..c0aab382ec 100644
--- a/javatests/com/google/gerrit/acceptance/server/quota/RestApiQuotaIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/quota/RestApiQuotaIT.java
@@ -14,30 +14,30 @@
package com.google.gerrit.acceptance.server.quota;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
+import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_TOO_MANY_REQUESTS;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.quota.QuotaBackend;
import com.google.gerrit.server.quota.QuotaResponse;
import com.google.inject.Module;
import java.util.Collections;
-import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
public class RestApiQuotaIT extends AbstractDaemonTest {
private static final QuotaBackend.WithResource quotaBackendWithResource =
- EasyMock.createStrictMock(QuotaBackend.WithResource.class);
+ mock(QuotaBackend.WithResource.class);
private static final QuotaBackend.WithUser quotaBackendWithUser =
- EasyMock.createStrictMock(QuotaBackend.WithUser.class);
+ mock(QuotaBackend.WithUser.class);
@Override
public Module createModule() {
@@ -63,79 +63,72 @@ public class RestApiQuotaIT extends AbstractDaemonTest {
@Before
public void setUp() {
- reset(quotaBackendWithResource);
- reset(quotaBackendWithUser);
+ clearInvocations(quotaBackendWithResource);
+ clearInvocations(quotaBackendWithUser);
}
@Test
public void changeDetail() throws Exception {
Change.Id changeId = retrieveChangeId();
- expect(quotaBackendWithResource.requestToken("/restapi/changes/detail:GET"))
- .andReturn(singletonAggregation(QuotaResponse.ok()));
- replay(quotaBackendWithResource);
- expect(quotaBackendWithUser.change(changeId, project)).andReturn(quotaBackendWithResource);
- replay(quotaBackendWithUser);
+ when(quotaBackendWithResource.requestToken("/restapi/changes/detail:GET"))
+ .thenReturn(singletonAggregation(QuotaResponse.ok()));
+ when(quotaBackendWithUser.change(changeId, project)).thenReturn(quotaBackendWithResource);
adminRestSession.get("/changes/" + changeId + "/detail").assertOK();
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
+ verify(quotaBackendWithResource).requestToken("/restapi/changes/detail:GET");
+ verify(quotaBackendWithUser).change(changeId, project);
}
@Test
public void revisionDetail() throws Exception {
Change.Id changeId = retrieveChangeId();
- expect(quotaBackendWithResource.requestToken("/restapi/changes/revisions/actions:GET"))
- .andReturn(singletonAggregation(QuotaResponse.ok()));
- replay(quotaBackendWithResource);
- expect(quotaBackendWithUser.change(changeId, project)).andReturn(quotaBackendWithResource);
- replay(quotaBackendWithUser);
+ when(quotaBackendWithResource.requestToken("/restapi/changes/revisions/actions:GET"))
+ .thenReturn(singletonAggregation(QuotaResponse.ok()));
+ when(quotaBackendWithUser.change(changeId, project)).thenReturn(quotaBackendWithResource);
adminRestSession.get("/changes/" + changeId + "/revisions/current/actions").assertOK();
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
+ verify(quotaBackendWithResource).requestToken("/restapi/changes/revisions/actions:GET");
+ verify(quotaBackendWithUser).change(changeId, project);
}
@Test
public void createChangePost() throws Exception {
- expect(quotaBackendWithUser.requestToken("/restapi/changes:POST"))
- .andReturn(singletonAggregation(QuotaResponse.ok()));
- replay(quotaBackendWithUser);
+ when(quotaBackendWithUser.requestToken("/restapi/changes:POST"))
+ .thenReturn(singletonAggregation(QuotaResponse.ok()));
ChangeInput changeInput = new ChangeInput(project.get(), "master", "test");
adminRestSession.post("/changes/", changeInput).assertCreated();
- verify(quotaBackendWithUser);
+ verify(quotaBackendWithUser).requestToken("/restapi/changes:POST");
}
@Test
public void accountDetail() throws Exception {
- expect(quotaBackendWithResource.requestToken("/restapi/accounts/detail:GET"))
- .andReturn(singletonAggregation(QuotaResponse.ok()));
- replay(quotaBackendWithResource);
- expect(quotaBackendWithUser.account(admin.id())).andReturn(quotaBackendWithResource);
- replay(quotaBackendWithUser);
+ when(quotaBackendWithResource.requestToken("/restapi/accounts/detail:GET"))
+ .thenReturn(singletonAggregation(QuotaResponse.ok()));
+ when(quotaBackendWithUser.account(admin.id())).thenReturn(quotaBackendWithResource);
adminRestSession.get("/accounts/self/detail").assertOK();
- verify(quotaBackendWithUser);
- verify(quotaBackendWithResource);
+ verify(quotaBackendWithResource).requestToken("/restapi/accounts/detail:GET");
+ verify(quotaBackendWithUser).account(admin.id());
}
@Test
public void config() throws Exception {
- expect(quotaBackendWithUser.requestToken("/restapi/config/version:GET"))
- .andReturn(singletonAggregation(QuotaResponse.ok()));
- replay(quotaBackendWithUser);
+ when(quotaBackendWithUser.requestToken("/restapi/config/version:GET"))
+ .thenReturn(singletonAggregation(QuotaResponse.ok()));
adminRestSession.get("/config/server/version").assertOK();
+ verify(quotaBackendWithUser).requestToken("/restapi/config/version:GET");
}
@Test
public void outOfQuotaReturnsError() throws Exception {
- expect(quotaBackendWithUser.requestToken("/restapi/config/version:GET"))
- .andReturn(singletonAggregation(QuotaResponse.error("no quota")));
- replay(quotaBackendWithUser);
- adminRestSession.get("/config/server/version").assertStatus(429);
+ when(quotaBackendWithUser.requestToken("/restapi/config/version:GET"))
+ .thenReturn(singletonAggregation(QuotaResponse.error("no quota")));
+ adminRestSession.get("/config/server/version").assertStatus(SC_TOO_MANY_REQUESTS);
+ verify(quotaBackendWithUser).requestToken("/restapi/config/version:GET");
}
private Change.Id retrieveChangeId() throws Exception {
// use REST API so that repository size quota doesn't have to be stubbed
ChangeInfo changeInfo =
gApi.changes().create(new ChangeInput(project.get(), "master", "test")).get();
- return new Change.Id(changeInfo._number);
+ return Change.id(changeInfo._number);
}
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
index 83782c912e..1c820af5d6 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.server.rules;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -22,11 +23,10 @@ import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
-import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.rules.IgnoreSelfApprovalRule;
import com.google.inject.Inject;
-import java.util.Collection;
import java.util.Map;
+import java.util.Optional;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.junit.Test;
@@ -42,11 +42,10 @@ public class IgnoreSelfApprovalRuleIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords =
- rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
- assertThat(submitRecords).hasSize(1);
- SubmitRecord result = submitRecords.iterator().next();
+ assertThat(submitRecord).isPresent();
+ SubmitRecord result = submitRecord.get();
assertThat(result.status).isEqualTo(SubmitRecord.Status.NOT_READY);
assertThat(result.labels).isNotEmpty();
assertThat(result.requirements)
@@ -69,9 +68,8 @@ public class IgnoreSelfApprovalRuleIT extends AbstractDaemonTest {
// Approve as admin
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords =
- rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
- assertThat(submitRecords).isEmpty();
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
+ assertThat(submitRecord).isEmpty();
}
@Test
@@ -81,9 +79,8 @@ public class IgnoreSelfApprovalRuleIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords =
- rule.evaluate(r.getChange(), SubmitRuleOptions.defaults());
- assertThat(submitRecords).isEmpty();
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
+ assertThat(submitRecord).isEmpty();
}
private void enableRule(String labelName, boolean newState) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
index fa13be499b..92cc396e4f 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
@@ -20,9 +20,9 @@ import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.rules.PrologOptions;
import com.google.gerrit.server.rules.PrologRuleEvaluator;
import com.google.gerrit.testing.TestChanges;
import com.google.inject.Inject;
@@ -30,8 +30,8 @@ import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.StructureTerm;
import com.googlecode.prolog_cafe.lang.Term;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class PrologRuleEvaluatorIT extends AbstractDaemonTest {
@@ -45,9 +45,9 @@ public class PrologRuleEvaluatorIT extends AbstractDaemonTest {
StructureTerm labels = new StructureTerm("label", verifiedLabel);
List<Term> terms = ImmutableList.of(makeTerm("ok", labels));
- Collection<SubmitRecord> records = evaluator.resultsToSubmitRecord(null, terms);
+ SubmitRecord record = evaluator.resultsToSubmitRecord(null, terms);
- assertThat(records).hasSize(1);
+ assertThat(record.status).isEqualTo(SubmitRecord.Status.OK);
}
/**
@@ -112,23 +112,16 @@ public class PrologRuleEvaluatorIT extends AbstractDaemonTest {
terms.add(makeTerm("not_ready", makeLabels(label3)));
// When
- List<SubmitRecord> records = evaluator.resultsToSubmitRecord(null, terms);
+ SubmitRecord record = evaluator.resultsToSubmitRecord(null, terms);
// assert that
- SubmitRecord record1Expected = new SubmitRecord();
- record1Expected.status = SubmitRecord.Status.OK;
- record1Expected.labels = new ArrayList<>();
- record1Expected.labels.add(submitRecordLabel2);
+ SubmitRecord expectedRecord = new SubmitRecord();
+ expectedRecord.status = SubmitRecord.Status.OK;
+ expectedRecord.labels = new ArrayList<>();
+ expectedRecord.labels.add(submitRecordLabel2);
+ expectedRecord.labels.add(submitRecordLabel3);
- SubmitRecord record2Expected = new SubmitRecord();
- record2Expected.status = SubmitRecord.Status.OK;
- record2Expected.labels = new ArrayList<>();
- record2Expected.labels.add(submitRecordLabel3);
-
- assertThat(records).hasSize(2);
-
- assertThat(records.get(0)).isEqualTo(record1Expected);
- assertThat(records.get(1)).isEqualTo(record2Expected);
+ assertThat(record).isEqualTo(expectedRecord);
}
private static Term makeTerm(String status, StructureTerm labels) {
@@ -149,12 +142,12 @@ public class PrologRuleEvaluatorIT extends AbstractDaemonTest {
}
private ChangeData makeChangeData() {
- ChangeData cd = ChangeData.createForTest(project, new Change.Id(1), 1);
+ ChangeData cd = ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId());
cd.setChange(TestChanges.newChange(project, admin.id()));
return cd;
}
private PrologRuleEvaluator makeEvaluator() {
- return evaluatorFactory.create(makeChangeData(), SubmitRuleOptions.defaults());
+ return evaluatorFactory.create(makeChangeData(), PrologOptions.defaultOptions());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
index 536cbbbd16..e5432d18f2 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
@@ -19,8 +19,9 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData;
@@ -40,6 +41,7 @@ public class RulesIT extends AbstractDaemonTest {
private static final String RULE_TEMPLATE =
"submit_rule(submit(W)) :- \n" + "%s,\n" + "W = label('OK', ok(user(1000000))).";
+ @Inject private ProjectOperations projectOperations;
@Inject private SubmitRuleEvaluator.Factory evaluatorFactory;
@Before
@@ -84,7 +86,7 @@ public class RulesIT extends AbstractDaemonTest {
}
private SubmitRecord.Status statusForRule() throws Exception {
- String oldHead = getRemoteHead().name();
+ String oldHead = projectOperations.project(project).getHead("master").name();
PushOneCommit.Result result1 =
pushFactory.create(user.newIdent(), testRepo).to("refs/for/master");
testRepo.reset(oldHead);
diff --git a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
index 9fb7c2d8cb..4fe0df48f6 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -20,102 +20,91 @@ import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.ChangeIndexedCounter;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.events.ChangeIndexedListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.List;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
@NoHttpd
@UseSsh
public abstract class AbstractIndexTests extends AbstractDaemonTest {
- @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
-
- private ChangeIndexedCounter changeIndexedCounter;
- private RegistrationHandle changeIndexedCounterHandle;
+ @Inject private ExtensionRegistry extensionRegistry;
/** @param injector injector */
public void configureIndex(Injector injector) {}
- @Before
- public void addChangeIndexedCounter() {
- changeIndexedCounter = new ChangeIndexedCounter();
- changeIndexedCounterHandle = changeIndexedListeners.add("gerrit", changeIndexedCounter);
- }
-
- @After
- public void removeChangeIndexedCounter() {
- if (changeIndexedCounterHandle != null) {
- changeIndexedCounterHandle.remove();
- }
- }
-
@Test
@GerritConfig(name = "index.autoReindexIfStale", value = "false")
public void indexChange() throws Exception {
- configureIndex(server.getTestInjector());
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ configureIndex(server.getTestInjector());
- PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
- String changeId = change.getChangeId();
- String changeLegacyId = change.getChange().getId().toString();
- ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
+ String changeId = change.getChangeId();
+ String changeLegacyId = change.getChange().getId().toString();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
- disableChangeIndexWrites();
- amendChange(changeId, "second test", "test2.txt", "test2");
+ disableChangeIndexWrites();
+ amendChange(changeId, "second test", "test2.txt", "test2");
- assertChangeQuery(change.getChange(), false);
- enableChangeIndexWrites();
+ assertChangeQuery(change.getChange(), false);
+ enableChangeIndexWrites();
- changeIndexedCounter.clear();
- String cmd = Joiner.on(" ").join("gerrit", "index", "changes", changeLegacyId);
- adminSshSession.exec(cmd);
- adminSshSession.assertSuccess();
+ changeIndexedCounter.clear();
+ String cmd = Joiner.on(" ").join("gerrit", "index", "changes", changeLegacyId);
+ adminSshSession.exec(cmd);
+ adminSshSession.assertSuccess();
- changeIndexedCounter.assertReindexOf(changeInfo, 1);
+ changeIndexedCounter.assertReindexOf(changeInfo, 1);
- assertChangeQuery(change.getChange(), true);
+ assertChangeQuery(change.getChange(), true);
+ }
}
@Test
@GerritConfig(name = "index.autoReindexIfStale", value = "false")
public void indexProject() throws Exception {
- configureIndex(server.getTestInjector());
-
- PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
- String changeId = change.getChangeId();
- ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(changeIndexedCounter)) {
+ configureIndex(server.getTestInjector());
- disableChangeIndexWrites();
- amendChange(changeId, "second test", "test2.txt", "test2");
+ PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
+ String changeId = change.getChangeId();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
- assertChangeQuery(change.getChange(), false);
- enableChangeIndexWrites();
+ disableChangeIndexWrites();
+ amendChange(changeId, "second test", "test2.txt", "test2");
- changeIndexedCounter.clear();
- String cmd = Joiner.on(" ").join("gerrit", "index", "changes-in-project", project.get());
- adminSshSession.exec(cmd);
- adminSshSession.assertSuccess();
+ assertChangeQuery(change.getChange(), false);
+ enableChangeIndexWrites();
- boolean indexing = true;
- while (indexing) {
- String out = adminSshSession.exec("gerrit show-queue --wide");
+ changeIndexedCounter.clear();
+ String cmd = Joiner.on(" ").join("gerrit", "index", "changes-in-project", project.get());
+ adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
- indexing = out.contains("Index all changes of project " + project.get());
- }
- changeIndexedCounter.assertReindexOf(changeInfo, 1);
+ boolean indexing = true;
+ while (indexing) {
+ String out = adminSshSession.exec("gerrit show-queue --wide");
+ adminSshSession.assertSuccess();
+ indexing = out.contains("Index all changes of project " + project.get());
+ }
+
+ changeIndexedCounter.assertReindexOf(changeInfo, 1);
- assertChangeQuery(change.getChange(), true);
+ assertChangeQuery(change.getChange(), true);
+ }
}
private void assertChangeQuery(ChangeData change, boolean assertTrue) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/ssh/CreateProjectIT.java b/javatests/com/google/gerrit/acceptance/ssh/CreateProjectIT.java
index 0ce05b0d01..39dbaa7e07 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/CreateProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/CreateProjectIT.java
@@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.UseSsh;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.project.ProjectState;
import org.junit.Test;
@@ -33,7 +33,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
adminSshSession.exec(
"gerrit create-project --branch master --owner " + newGroupName + " " + newProjectName);
adminSshSession.assertSuccess();
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
}
@@ -46,7 +46,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
adminSshSession.exec(
"gerrit create-project --branch master --owner " + wrongGroupName + " " + newProjectName);
adminSshSession.assertFailure();
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNull();
}
@@ -62,7 +62,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
+ newProjectName
+ ".git");
adminSshSession.assertSuccess();
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertThat(projectState.getName()).isEqualTo(newProjectName);
}
@@ -79,7 +79,7 @@ public class CreateProjectIT extends AbstractDaemonTest {
+ newProjectName
+ "/");
adminSshSession.assertSuccess();
- ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ ProjectState projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isNotNull();
assertThat(projectState.getName()).isEqualTo(newProjectName);
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java
new file mode 100644
index 0000000000..434071ff9c
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/CustomIndexIT.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.ssh;
+
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.testing.ConfigSuite;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Tests for a defaulted custom index configuration. This unknown type is the opposite of {@link
+ * IndexType#getKnownTypes()}.
+ */
+public class CustomIndexIT extends AbstractIndexTests {
+
+ @ConfigSuite.Default
+ public static Config customIndexType() {
+ Config config = new Config();
+ config.setString("index", null, "type", "custom");
+ return config;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 339e8aed54..f35bcb79eb 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -18,18 +18,15 @@ import static com.google.gerrit.elasticsearch.ElasticTestUtils.createAllIndexes;
import static com.google.gerrit.elasticsearch.ElasticTestUtils.getConfig;
import com.google.gerrit.elasticsearch.ElasticVersion;
+import com.google.gerrit.index.IndexType;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
+/** Tests for every supported {@link IndexType#isElasticsearch()} most recent index version. */
public class ElasticIndexIT extends AbstractIndexTests {
@ConfigSuite.Default
- public static Config elasticsearchV6() {
- return getConfig(ElasticVersion.V6_8);
- }
-
- @ConfigSuite.Config
public static Config elasticsearchV7() {
return getConfig(ElasticVersion.V7_8);
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index c23f889dce..9dcfa3f622 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -23,7 +23,7 @@ import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.GarbageCollectionResult;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GarbageCollectionQueue;
import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/ssh/IndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/IndexIT.java
index 370fb72e4b..7062b00f33 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/IndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/IndexIT.java
@@ -14,4 +14,7 @@
package com.google.gerrit.acceptance.ssh;
+import com.google.gerrit.index.IndexType;
+
+/** Tests for the default {@link IndexType#isLucene()} index configuration. */
public class IndexIT extends AbstractIndexTests {}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/PluginChangeFieldsIT.java b/javatests/com/google/gerrit/acceptance/ssh/PluginChangeFieldsIT.java
index e61e2cc407..4bf7c19d3a 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/PluginChangeFieldsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/PluginChangeFieldsIT.java
@@ -22,7 +22,7 @@ import com.google.common.io.CharStreams;
import com.google.gerrit.acceptance.AbstractPluginFieldsTest;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.query.change.OutputStreamQuery;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
diff --git a/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java b/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
index 0f47a4ab2c..78960bb329 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/QueryIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.ssh;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -294,8 +295,8 @@ public class QueryIT extends AbstractDaemonTest {
// computation while formatting the output, such as labels, reviewers etc.
merge(r);
for (ListChangesOption option : ListChangesOption.values()) {
- assertThat(gApi.changes().query(r.getChangeId()).withOption(option).get())
- .named("Option: " + option)
+ assertWithMessage("Option: " + option)
+ .that(gApi.changes().query(r.getChangeId()).withOption(option).get())
.hasSize(1);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SetReviewersIT.java b/javatests/com/google/gerrit/acceptance/ssh/SetReviewersIT.java
index 6998a0ada9..5a31bfda2a 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SetReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SetReviewersIT.java
@@ -22,7 +22,7 @@ import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.UseSsh;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.testing.ConfigSuite;
import org.eclipse.jgit.lib.Config;
import org.junit.Before;
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index 4e9c4a49e9..012e9980d0 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
@@ -29,6 +30,9 @@ import com.google.gerrit.acceptance.UseSsh;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Spliterator;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import org.junit.Test;
@NoHttpd
@@ -162,6 +166,38 @@ public class SshCommandsIT extends AbstractDaemonTest {
assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get("gerrit")).inOrder();
}
+ @Test
+ @Sandboxed
+ public void showConnections() throws Exception {
+ Spliterator<String> connectionsOutput =
+ getOutputLines(adminSshSession.exec("gerrit show-connections"));
+
+ assertThat(findConnectionsInOutput(connectionsOutput, "user")).hasSize(1);
+ }
+
+ @Test
+ @Sandboxed
+ public void cloeConnections() throws Exception {
+ List<String> connectionsOutput =
+ findConnectionsInOutput(
+ getOutputLines(adminSshSession.exec("gerrit show-connections")), "user");
+ String connectionId =
+ Splitter.on(" ")
+ .trimResults()
+ .omitEmptyStrings()
+ .split(connectionsOutput.get(0))
+ .iterator()
+ .next();
+
+ String closeConnectionOutput = adminSshSession.exec("gerrit close-connection " + connectionId);
+ assertThat(closeConnectionOutput).contains(connectionId);
+
+ assertThat(
+ findConnectionsInOutput(
+ getOutputLines(adminSshSession.exec("gerrit show-connections")), "user"))
+ .isEmpty();
+ }
+
private List<String> parseCommandsFromGerritHelpText(String helpText) {
List<String> commands = new ArrayList<>();
@@ -194,4 +230,16 @@ public class SshCommandsIT extends AbstractDaemonTest {
return commands;
}
+
+ private List<String> findConnectionsInOutput(Spliterator<String> connectionsOutput, String user) {
+ List<String> connections =
+ StreamSupport.stream(connectionsOutput, false)
+ .filter(s -> s.contains("localhost") && s.contains(user))
+ .collect(Collectors.toList());
+ return connections;
+ }
+
+ private Spliterator<String> getOutputLines(String output) throws Exception {
+ return Splitter.on("\n").trimResults().omitEmptyStrings().split(output).spliterator();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java
index 9c1e23d0a8..ae45d90338 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshTraceIT.java
@@ -16,79 +16,94 @@ package com.google.gerrit.acceptance.ssh;
import static com.google.common.truth.Truth.assertThat;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.UseSsh;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.server.logging.LoggingContext;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.PerformanceLogger;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.project.CreateProjectArgs;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
-import org.junit.After;
-import org.junit.Before;
+import java.util.ArrayList;
+import java.util.List;
import org.junit.Test;
@UseSsh
public class SshTraceIT extends AbstractDaemonTest {
- @Inject private DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
-
- private TraceValidatingProjectCreationValidationListener projectCreationListener;
- private RegistrationHandle projectCreationListenerRegistrationHandle;
-
- @Before
- public void setup() {
- projectCreationListener = new TraceValidatingProjectCreationValidationListener();
- projectCreationListenerRegistrationHandle =
- projectCreationValidationListeners.add("gerrit", projectCreationListener);
- }
-
- @After
- public void cleanup() {
- projectCreationListenerRegistrationHandle.remove();
- }
+ @Inject private ExtensionRegistry extensionRegistry;
@Test
public void sshCallWithoutTrace() throws Exception {
- adminSshSession.exec("gerrit create-project new1");
- adminSshSession.assertSuccess();
- assertThat(projectCreationListener.traceId).isNull();
- assertThat(projectCreationListener.foundTraceId).isFalse();
- assertThat(projectCreationListener.isLoggingForced).isFalse();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project new1");
+ adminSshSession.assertSuccess();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.foundTraceId).isFalse();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+ }
}
@Test
public void sshCallWithTrace() throws Exception {
- adminSshSession.exec("gerrit create-project --trace new2");
-
- // The trace ID is written to stderr.
- adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
-
- assertThat(projectCreationListener.traceId).isNotNull();
- assertThat(projectCreationListener.foundTraceId).isTrue();
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project --trace new2");
+
+ // The trace ID is written to stderr.
+ adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
+
+ assertThat(projectCreationListener.traceId).isNotNull();
+ assertThat(projectCreationListener.foundTraceId).isTrue();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
}
@Test
public void sshCallWithTraceAndProvidedTraceId() throws Exception {
- adminSshSession.exec("gerrit create-project --trace --trace-id issue/123 new3");
-
- // The trace ID is written to stderr.
- adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
-
- assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
- assertThat(projectCreationListener.foundTraceId).isTrue();
- assertThat(projectCreationListener.isLoggingForced).isTrue();
+ TraceValidatingProjectCreationValidationListener projectCreationListener =
+ new TraceValidatingProjectCreationValidationListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project --trace --trace-id issue/123 new3");
+
+ // The trace ID is written to stderr.
+ adminSshSession.assertFailure(RequestId.Type.TRACE_ID.name());
+
+ assertThat(projectCreationListener.traceId).isEqualTo("issue/123");
+ assertThat(projectCreationListener.foundTraceId).isTrue();
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ }
}
@Test
public void sshCallWithTraceIdAndWithoutTraceFails() throws Exception {
- adminSshSession.exec("gerrit create-project --trace-id issue/123 new3");
+ adminSshSession.exec("gerrit create-project --trace-id issue/123 new4");
adminSshSession.assertFailure("A trace ID can only be set if --trace was specified.");
}
+ @Test
+ public void performanceLoggingForSshCall() throws Exception {
+ TestPerformanceLogger testPerformanceLogger = new TestPerformanceLogger();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(testPerformanceLogger)) {
+ adminSshSession.exec("gerrit create-project new5");
+ adminSshSession.assertSuccess();
+ assertThat(testPerformanceLogger.logEntries()).isNotEmpty();
+ }
+ }
+
private static class TraceValidatingProjectCreationValidationListener
implements ProjectCreationValidationListener {
String traceId;
@@ -103,4 +118,28 @@ public class SshTraceIT extends AbstractDaemonTest {
this.isLoggingForced = LoggingContext.getInstance().shouldForceLogging(null, null, false);
}
}
+
+ private static class TestPerformanceLogger implements PerformanceLogger {
+ private List<PerformanceLogEntry> logEntries = new ArrayList<>();
+
+ @Override
+ public void log(String operation, long durationMs, Metadata metadata) {
+ logEntries.add(PerformanceLogEntry.create(operation, metadata));
+ }
+
+ ImmutableList<PerformanceLogEntry> logEntries() {
+ return ImmutableList.copyOf(logEntries);
+ }
+ }
+
+ @AutoValue
+ abstract static class PerformanceLogEntry {
+ static PerformanceLogEntry create(String operation, Metadata metadata) {
+ return new AutoValue_SshTraceIT_PerformanceLogEntry(operation, metadata);
+ }
+
+ abstract String operation();
+
+ abstract Metadata metadata();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java
index e0d6593fb3..96864d93db 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImplTest.java
@@ -15,19 +15,20 @@
package com.google.gerrit.acceptance.testsuite.group;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Correspondence;
-import com.google.common.truth.Correspondence.BinaryPredicate;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.inject.Inject;
import java.sql.Timestamp;
import java.util.Objects;
@@ -217,7 +218,7 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void notExistingGroupCanBeCheckedForExistence() throws Exception {
- AccountGroup.UUID notExistingGroupUuid = new AccountGroup.UUID("not-existing-group");
+ AccountGroup.UUID notExistingGroupUuid = AccountGroup.uuid("not-existing-group");
boolean exists = groupOperations.group(notExistingGroupUuid).exists();
@@ -226,10 +227,9 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void retrievingNotExistingGroupFails() throws Exception {
- AccountGroup.UUID notExistingGroupUuid = new AccountGroup.UUID("not-existing-group");
-
- exception.expect(IllegalStateException.class);
- groupOperations.group(notExistingGroupUuid).get();
+ AccountGroup.UUID notExistingGroupUuid = AccountGroup.uuid("not-existing-group");
+ assertThrows(
+ IllegalStateException.class, () -> groupOperations.group(notExistingGroupUuid).get());
}
@Test
@@ -270,7 +270,7 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
AccountGroup.NameKey groupName = groupOperations.group(groupUuid).get().nameKey();
- assertThat(groupName).isEqualTo(new AccountGroup.NameKey("ABC-789-this-name-must-be-unique"));
+ assertThat(groupName).isEqualTo(AccountGroup.nameKey("ABC-789-this-name-must-be-unique"));
}
@Test
@@ -297,7 +297,7 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void ownerGroupUuidOfExistingGroupCanBeRetrieved() throws Exception {
- AccountGroup.UUID originalOwnerGroupUuid = new AccountGroup.UUID("owner group");
+ AccountGroup.UUID originalOwnerGroupUuid = AccountGroup.uuid("owner group");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().ownerGroupUuid(originalOwnerGroupUuid).create();
@@ -314,14 +314,16 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
TestGroup visibleGroup = groupOperations.group(visibleGroupUuid).get();
TestGroup invisibleGroup = groupOperations.group(invisibleGroupUuid).get();
- assertThat(visibleGroup.visibleToAll()).named("visibility of visible group").isTrue();
- assertThat(invisibleGroup.visibleToAll()).named("visibility of invisible group").isFalse();
+ assertWithMessage("visibility of visible group").that(visibleGroup.visibleToAll()).isTrue();
+ assertWithMessage("visibility of invisible group")
+ .that(invisibleGroup.visibleToAll())
+ .isFalse();
}
@Test
public void createdOnOfExistingGroupCanBeRetrieved() throws Exception {
GroupInfo group = gApi.groups().create(createArbitraryGroupInput()).detail();
- AccountGroup.UUID groupUuid = new AccountGroup.UUID(group.id);
+ AccountGroup.UUID groupUuid = AccountGroup.uuid(group.id);
Timestamp createdOn = groupOperations.group(groupUuid).get().createdOn();
@@ -330,9 +332,9 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void membersOfExistingGroupCanBeRetrieved() throws Exception {
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
- Account.Id memberId3 = new Account.Id(3000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
+ Account.Id memberId3 = Account.id(3000);
AccountGroup.UUID groupUuid =
groupOperations.newGroup().members(memberId1, memberId2, memberId3).create();
@@ -352,9 +354,9 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void subgroupsOfExistingGroupCanBeRetrieved() throws Exception {
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
- AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
+ AccountGroup.UUID subgroupUuid3 = AccountGroup.uuid("subgroup 3");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2, subgroupUuid3).create();
@@ -428,11 +430,11 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void ownerGroupUuidCanBeUpdated() throws Exception {
- AccountGroup.UUID originalOwnerGroupUuid = new AccountGroup.UUID("original owner");
+ AccountGroup.UUID originalOwnerGroupUuid = AccountGroup.uuid("original owner");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().ownerGroupUuid(originalOwnerGroupUuid).create();
- AccountGroup.UUID updatedOwnerGroupUuid = new AccountGroup.UUID("updated owner");
+ AccountGroup.UUID updatedOwnerGroupUuid = AccountGroup.uuid("updated owner");
groupOperations.group(groupUuid).forUpdate().ownerGroupUuid(updatedOwnerGroupUuid).update();
AccountGroup.UUID currentOwnerGroupUuid =
@@ -454,8 +456,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
public void membersCanBeAdded() throws Exception {
AccountGroup.UUID groupUuid = groupOperations.newGroup().clearMembers().create();
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
groupOperations.group(groupUuid).forUpdate().addMember(memberId1).addMember(memberId2).update();
ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
@@ -464,8 +466,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void membersCanBeRemoved() throws Exception {
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
groupOperations.group(groupUuid).forUpdate().removeMember(memberId2).update();
@@ -476,11 +478,11 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void memberAdditionAndRemovalCanBeMixed() throws Exception {
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
- Account.Id memberId3 = new Account.Id(3000);
+ Account.Id memberId3 = Account.id(3000);
groupOperations
.group(groupUuid)
.forUpdate()
@@ -494,8 +496,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void membersCanBeCleared() throws Exception {
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
groupOperations.group(groupUuid).forUpdate().clearMembers().update();
@@ -506,11 +508,11 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void furtherMembersCanBeAddedAfterClearingAll() throws Exception {
- Account.Id memberId1 = new Account.Id(1000);
- Account.Id memberId2 = new Account.Id(2000);
+ Account.Id memberId1 = Account.id(1000);
+ Account.Id memberId2 = Account.id(2000);
AccountGroup.UUID groupUuid = groupOperations.newGroup().members(memberId1, memberId2).create();
- Account.Id memberId3 = new Account.Id(3000);
+ Account.Id memberId3 = Account.id(3000);
groupOperations.group(groupUuid).forUpdate().clearMembers().addMember(memberId3).update();
ImmutableSet<Account.Id> members = groupOperations.group(groupUuid).get().members();
@@ -521,8 +523,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
public void subgroupsCanBeAdded() throws Exception {
AccountGroup.UUID groupUuid = groupOperations.newGroup().clearSubgroups().create();
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
groupOperations
.group(groupUuid)
.forUpdate()
@@ -536,8 +538,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void subgroupsCanBeRemoved() throws Exception {
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
@@ -549,12 +551,12 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void subgroupAdditionAndRemovalCanBeMixed() throws Exception {
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
- AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ AccountGroup.UUID subgroupUuid3 = AccountGroup.uuid("subgroup 3");
groupOperations
.group(groupUuid)
.forUpdate()
@@ -568,8 +570,8 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void subgroupsCanBeCleared() throws Exception {
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
@@ -581,12 +583,12 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
@Test
public void furtherSubgroupsCanBeAddedAfterClearingAll() throws Exception {
- AccountGroup.UUID subgroupUuid1 = new AccountGroup.UUID("subgroup 1");
- AccountGroup.UUID subgroupUuid2 = new AccountGroup.UUID("subgroup 2");
+ AccountGroup.UUID subgroupUuid1 = AccountGroup.uuid("subgroup 1");
+ AccountGroup.UUID subgroupUuid2 = AccountGroup.uuid("subgroup 2");
AccountGroup.UUID groupUuid =
groupOperations.newGroup().subgroups(subgroupUuid1, subgroupUuid2).create();
- AccountGroup.UUID subgroupUuid3 = new AccountGroup.UUID("subgroup 3");
+ AccountGroup.UUID subgroupUuid3 = AccountGroup.uuid("subgroup 3");
groupOperations
.group(groupUuid)
.forUpdate()
@@ -610,37 +612,31 @@ public class GroupOperationsImplTest extends AbstractDaemonTest {
private AccountGroup.UUID createGroupInServer(GroupInput input) throws RestApiException {
GroupInfo group = gApi.groups().create(input).detail();
- return new AccountGroup.UUID(group.id);
+ return AccountGroup.uuid(group.id);
}
private static Correspondence<AccountInfo, Account.Id> getAccountToIdCorrespondence() {
return Correspondence.from(
- new BinaryPredicate<AccountInfo, Account.Id>() {
- @Override
- public boolean apply(AccountInfo actualAccount, Account.Id expectedId) {
- Account.Id accountId =
- Optional.ofNullable(actualAccount)
- .map(account -> account._accountId)
- .map(Account.Id::new)
- .orElse(null);
- return Objects.equals(accountId, expectedId);
- }
+ (actualAccount, expectedId) -> {
+ Account.Id accountId =
+ Optional.ofNullable(actualAccount)
+ .map(account -> account._accountId)
+ .map(Account::id)
+ .orElse(null);
+ return Objects.equals(accountId, expectedId);
},
"has ID");
}
private static Correspondence<GroupInfo, AccountGroup.UUID> getGroupToUuidCorrespondence() {
return Correspondence.from(
- new BinaryPredicate<GroupInfo, AccountGroup.UUID>() {
- @Override
- public boolean apply(GroupInfo actualGroup, AccountGroup.UUID expectedUuid) {
- AccountGroup.UUID groupUuid =
- Optional.ofNullable(actualGroup)
- .map(group -> group.id)
- .map(AccountGroup.UUID::new)
- .orElse(null);
- return Objects.equals(groupUuid, expectedUuid);
- }
+ (actualGroup, expectedUuid) -> {
+ AccountGroup.UUID groupUuid =
+ Optional.ofNullable(actualGroup)
+ .map(group -> group.id)
+ .map(AccountGroup::uuid)
+ .orElse(null);
+ return Objects.equals(groupUuid, expectedUuid);
},
"has UUID");
}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
index 3f537c077c..2a445711d5 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -15,14 +15,41 @@
package com.google.gerrit.acceptance.testsuite.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.blockLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.gerrit.common.data.GlobalCapability.QUERY_LIMIT;
+import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
+import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestPermission;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.BranchInfo;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import java.util.List;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
import org.junit.Test;
public class ProjectOperationsImplTest extends AbstractDaemonTest {
@@ -48,9 +75,526 @@ public class ProjectOperationsImplTest extends AbstractDaemonTest {
@Test
public void emptyCommit() throws Exception {
Project.NameKey key = projectOperations.newProject().create();
+
List<BranchInfo> branches = gApi.projects().name(key.get()).branches().get();
assertThat(branches).isNotEmpty();
assertThat(branches.stream().map(x -> x.ref).collect(toList()))
.isEqualTo(ImmutableList.of("HEAD", "refs/meta/config", "refs/heads/master"));
}
+
+ @Test
+ public void getProjectConfig() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ assertThat(projectOperations.project(key).getProjectConfig().getProject().getDescription())
+ .isEmpty();
+
+ ConfigInput input = new ConfigInput();
+ input.description = "my fancy project";
+ gApi.projects().name(key.get()).config(input);
+
+ assertThat(projectOperations.project(key).getProjectConfig().getProject().getDescription())
+ .isEqualTo("my fancy project");
+ }
+
+ @Test
+ public void mutatingResultOfGetProjectConfigDoesNotMutateGlobalCachedValue() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ ProjectConfig projectConfig = projectOperations.project(key).getProjectConfig();
+ ProjectState cachedProjectState1 = projectCache.checkedGet(key);
+ ProjectConfig cachedProjectConfig1 = cachedProjectState1.getConfig();
+ assertThat(cachedProjectConfig1).isNotSameInstanceAs(projectConfig);
+ assertThat(cachedProjectConfig1.getProject().getDescription()).isEmpty();
+ assertThat(projectConfig.getProject().getDescription()).isEmpty();
+ projectConfig.getProject().setDescription("my fancy project");
+
+ ProjectConfig cachedProjectConfig2 = projectCache.checkedGet(key).getConfig();
+ assertThat(cachedProjectConfig2).isNotSameInstanceAs(projectConfig);
+ assertThat(cachedProjectConfig2.getProject().getDescription()).isEmpty();
+ }
+
+ @Test
+ public void getProjectConfigNoRefsMetaConfig() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ deleteRefsMetaConfig(key);
+
+ ProjectConfig projectConfig = projectOperations.project(key).getProjectConfig();
+ assertThat(projectConfig.getName()).isEqualTo(key);
+ assertThat(projectConfig.getRevision()).isNull();
+ }
+
+ @Test
+ public void getConfig() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).isNotInstanceOf(StoredConfig.class);
+ assertThat(config).text().isEmpty();
+
+ ConfigInput input = new ConfigInput();
+ input.description = "my fancy project";
+ gApi.projects().name(key.get()).config(input);
+
+ config = projectOperations.project(key).getConfig();
+ assertThat(config).isNotInstanceOf(StoredConfig.class);
+ assertThat(config).sections().containsExactly("project");
+ assertThat(config).subsections("project").isEmpty();
+ assertThat(config).sectionValues("project").containsExactly("description", "my fancy project");
+ }
+
+ @Test
+ public void getConfigNoRefsMetaConfig() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ deleteRefsMetaConfig(key);
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).isNotInstanceOf(StoredConfig.class);
+ assertThat(config).isEmpty();
+ }
+
+ @Test
+ public void addAllowPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "group global:Registered-Users");
+ }
+
+ @Test
+ public void addDenyPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(deny(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "deny group global:Registered-Users");
+ }
+
+ @Test
+ public void addBlockPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(block(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "block group global:Registered-Users");
+ }
+
+ @Test
+ public void addAllowForcePermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS).force(true))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "+force group global:Registered-Users");
+ }
+
+ @Test
+ public void updateExclusivePermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .setExclusiveGroup(permissionKey(Permission.ABANDON).ref("refs/foo"), true)
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "abandon", "group global:Registered-Users",
+ "exclusiveGroupPermissions", "abandon");
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .setExclusiveGroup(permissionKey(Permission.ABANDON).ref("refs/foo"), false)
+ .update();
+
+ config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "group global:Registered-Users");
+ }
+
+ @Test
+ public void addMultipleExclusivePermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .setExclusiveGroup(permissionKey(Permission.ABANDON).ref("refs/foo"), true)
+ .setExclusiveGroup(permissionKey(Permission.CREATE).ref("refs/foo"), true)
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsEntry("exclusiveGroupPermissions", "abandon create");
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .setExclusiveGroup(permissionKey(Permission.ABANDON).ref("refs/foo"), false)
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsEntry("exclusiveGroupPermissions", "create");
+ }
+
+ @Test
+ public void addMultiplePermissions() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(PROJECT_OWNERS))
+ .add(allow(Permission.CREATE).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "abandon", "group global:Project-Owners",
+ "create", "group global:Registered-Users");
+ }
+
+ @Test
+ public void addDuplicatePermissions() throws Exception {
+ TestPermission permission =
+ TestProjectUpdate.allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS).build();
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations.project(key).forUpdate().add(permission).add(permission).update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "abandon", "group global:Registered-Users",
+ "abandon", "group global:Registered-Users");
+
+ projectOperations.project(key).forUpdate().add(permission).update();
+ config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "abandon", "group global:Registered-Users",
+ "abandon", "group global:Registered-Users",
+ "abandon", "group global:Registered-Users");
+ }
+
+ @Test
+ public void addAllowLabelPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/foo").group(REGISTERED_USERS).range(-1, 2))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("label-Code-Review", "-1..+2 group global:Registered-Users");
+ }
+
+ @Test
+ public void addBlockLabelPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/foo").group(REGISTERED_USERS).range(-1, 2))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("label-Code-Review", "block -1..+2 group global:Registered-Users");
+ }
+
+ @Test
+ public void addAllowExclusiveLabelPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/foo").group(REGISTERED_USERS).range(-1, 2))
+ .setExclusiveGroup(labelPermissionKey("Code-Review").ref("refs/foo"), true)
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "label-Code-Review", "-1..+2 group global:Registered-Users",
+ "exclusiveGroupPermissions", "label-Code-Review");
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .setExclusiveGroup(labelPermissionKey("Code-Review").ref("refs/foo"), false)
+ .update();
+
+ config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("label-Code-Review", "-1..+2 group global:Registered-Users");
+ }
+
+ @Test
+ public void addAllowLabelAsPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(
+ allowLabel("Code-Review")
+ .ref("refs/foo")
+ .group(REGISTERED_USERS)
+ .range(-1, 2)
+ .impersonation(true))
+ .update();
+
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).sections().containsExactly("access");
+ assertThat(config).subsections("access").containsExactly("refs/foo");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("labelAs-Code-Review", "-1..+2 group global:Registered-Users");
+ }
+
+ @Test
+ public void addAllowCapability() throws Exception {
+ Config config = projectOperations.project(allProjects).getConfig();
+ assertThat(config)
+ .sectionValues("capability")
+ .doesNotContainEntry("administrateServer", "group Registered Users");
+
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .update();
+
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("capability")
+ .containsEntry("administrateServer", "group Registered Users");
+ }
+
+ @Test
+ public void addAllowCapabilityWithRange() throws Exception {
+ Config config = projectOperations.project(allProjects).getConfig();
+ assertThat(config).sectionValues("capability").doesNotContainKey("queryLimit");
+
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(QUERY_LIMIT).group(REGISTERED_USERS).range(0, 5000))
+ .update();
+
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("capability")
+ .containsEntry("queryLimit", "+0..+5000 group Registered Users");
+ }
+
+ @Test
+ public void addAllowCapabilityWithDefaultRange() throws Exception {
+ Config config = projectOperations.project(allProjects).getConfig();
+ assertThat(config).sectionValues("capability").doesNotContainKey("queryLimit");
+
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(QUERY_LIMIT).group(REGISTERED_USERS))
+ .update();
+
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("capability")
+ .containsEntry("queryLimit", "+0..+" + DEFAULT_MAX_QUERY_LIMIT + " group Registered Users");
+ }
+
+ @Test
+ public void removePermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(PROJECT_OWNERS))
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "abandon", "group global:Registered-Users",
+ "abandon", "group global:Project-Owners");
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .remove(permissionKey(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("abandon", "group global:Project-Owners");
+ }
+
+ @Test
+ public void removeLabelPermission() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/foo").group(REGISTERED_USERS).range(-1, 2))
+ .add(allowLabel("Code-Review").ref("refs/foo").group(PROJECT_OWNERS).range(-2, 1))
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsExactly(
+ "label-Code-Review", "-1..+2 group global:Registered-Users",
+ "label-Code-Review", "-2..+1 group global:Project-Owners");
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .remove(labelPermissionKey("Code-Review").ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsExactly("label-Code-Review", "-2..+1 group global:Project-Owners");
+ }
+
+ @Test
+ public void removeCapability() throws Exception {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .add(allowCapability(ADMINISTRATE_SERVER).group(PROJECT_OWNERS))
+ .update();
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("capability")
+ .containsAtLeastEntriesIn(
+ ImmutableListMultimap.of(
+ "administrateServer", "group Registered Users",
+ "administrateServer", "group Project Owners"));
+
+ projectOperations
+ .allProjectsForUpdate()
+ .remove(capabilityKey(ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .update();
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("capability")
+ .doesNotContainEntry("administrateServer", "group Registered Users");
+ }
+
+ @Test
+ public void removeOnePermissionForAllGroupsFromOneAccessSection() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(PROJECT_OWNERS))
+ .add(allow(Permission.ABANDON).ref("refs/foo").group(REGISTERED_USERS))
+ .add(allow(Permission.CREATE).ref("refs/foo").group(REGISTERED_USERS))
+ .update();
+ assertThat(projectOperations.project(key).getConfig())
+ .subsectionValues("access", "refs/foo")
+ .containsAtLeastEntriesIn(
+ ImmutableListMultimap.of(
+ "abandon", "group global:Project-Owners",
+ "abandon", "group global:Registered-Users",
+ "create", "group global:Registered-Users"));
+
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .remove(permissionKey(Permission.ABANDON).ref("refs/foo"))
+ .update();
+ Config config = projectOperations.project(key).getConfig();
+ assertThat(config).subsectionValues("access", "refs/foo").doesNotContainKey("abandon");
+ assertThat(config)
+ .subsectionValues("access", "refs/foo")
+ .containsEntry("create", "group global:Registered-Users");
+ }
+
+ @Test
+ public void removeAllAccessSections() {
+ projectOperations.allProjectsForUpdate().removeAllAccessSections().update();
+
+ assertThat(projectOperations.project(allProjects).getConfig())
+ .sectionValues("access")
+ .isEmpty();
+ }
+
+ @Test
+ public void updatingCapabilitiesNotAllowedForNonAllProjects() throws Exception {
+ Project.NameKey key = projectOperations.newProject().create();
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .add(allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .update());
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ projectOperations
+ .project(key)
+ .forUpdate()
+ .remove(capabilityKey(ADMINISTRATE_SERVER))
+ .update());
+ }
+
+ private void deleteRefsMetaConfig(Project.NameKey key) throws Exception {
+ try (Repository repo = repoManager.openRepository(key);
+ TestRepository<Repository> tr = new TestRepository<>(repo)) {
+ tr.delete(REFS_CONFIG);
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdateTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdateTest.java
new file mode 100644
index 0000000000..8fc1677e78
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdateTest.java
@@ -0,0 +1,202 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.testsuite.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
+import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.BATCH_CHANGES_LIMIT;
+import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_BATCH_CHANGES_LIMIT;
+import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.gerrit.common.data.GlobalCapability.QUERY_LIMIT;
+import static com.google.gerrit.common.data.Permission.ABANDON;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestCapability;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestLabelPermission;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.TestPermissionKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import java.util.function.Function;
+import org.junit.Test;
+
+public class TestProjectUpdateTest {
+ private static final AllProjectsName ALL_PROJECTS_NAME = new AllProjectsName("All-Projects");
+
+ @Test
+ public void testCapabilityDisallowsZeroRangeOnCapabilityThatHasNoRange() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS).range(0, 0).build());
+ }
+
+ @Test
+ public void testCapabilityAllowsZeroRangeOnCapabilityThatHasRange() throws Exception {
+ TestCapability c = allowCapability(QUERY_LIMIT).group(REGISTERED_USERS).range(0, 0).build();
+ assertThat(c.min()).isEqualTo(0);
+ assertThat(c.max()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCapabilityDisallowsInvertedRange() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> allowCapability(QUERY_LIMIT).group(REGISTERED_USERS).range(1, 0).build());
+ }
+
+ @Test
+ public void testCapabilityDisallowsRangeIfCapabilityDoesNotSupportRange() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS).range(-1, 1).build());
+ }
+
+ @Test
+ public void testCapabilityRangeIsZeroIfCapabilityDoesNotSupportRange() throws Exception {
+ TestCapability c = allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS).build();
+ assertThat(c.min()).isEqualTo(0);
+ assertThat(c.max()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCapabilityUsesDefaultRangeIfUnspecified() throws Exception {
+ TestCapability c = allowCapability(QUERY_LIMIT).group(REGISTERED_USERS).build();
+ assertThat(c.min()).isEqualTo(0);
+ assertThat(c.max()).isEqualTo(DEFAULT_MAX_QUERY_LIMIT);
+
+ c = allowCapability(BATCH_CHANGES_LIMIT).group(REGISTERED_USERS).build();
+ assertThat(c.min()).isEqualTo(0);
+ assertThat(c.max()).isEqualTo(DEFAULT_MAX_BATCH_CHANGES_LIMIT);
+ }
+
+ @Test
+ public void testCapabilityUsesExplicitRangeIfSpecified() throws Exception {
+ TestCapability c = allowCapability(QUERY_LIMIT).group(REGISTERED_USERS).range(5, 20).build();
+ assertThat(c.min()).isEqualTo(5);
+ assertThat(c.max()).isEqualTo(20);
+ }
+
+ @Test
+ public void testLabelPermissionRequiresValidLabelName() throws Exception {
+ Function<String, TestLabelPermission.Builder> labelBuilder =
+ name -> allowLabel(name).ref("refs/*").group(REGISTERED_USERS).range(-1, 1);
+ assertThat(labelBuilder.apply("Code-Review").build().name()).isEqualTo("Code-Review");
+ assertThrows(RuntimeException.class, () -> labelBuilder.apply("not a label").build());
+ assertThrows(RuntimeException.class, () -> labelBuilder.apply("label-Code-Review").build());
+ }
+
+ @Test
+ public void testLabelPermissionDisallowsZeroRange() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(0, 0).build());
+ }
+
+ @Test
+ public void testLabelPermissionDisallowsInvertedRange() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(1, 0).build());
+ }
+
+ @Test
+ public void testPermissionKeyRequiresValidRefName() throws Exception {
+ Function<String, TestPermissionKey.Builder> keyBuilder =
+ ref -> permissionKey(ABANDON).ref(ref).group(REGISTERED_USERS);
+ assertThat(keyBuilder.apply("refs/*").build().section()).isEqualTo("refs/*");
+ assertThrows(RuntimeException.class, () -> keyBuilder.apply(null).build());
+ assertThrows(RuntimeException.class, () -> keyBuilder.apply("foo").build());
+ }
+
+ @Test
+ public void testLabelPermissionKeyRequiresValidLabelName() throws Exception {
+ Function<String, TestPermissionKey.Builder> keyBuilder =
+ label -> labelPermissionKey(label).ref("refs/*").group(REGISTERED_USERS);
+ assertThat(keyBuilder.apply("Code-Review").build().name()).isEqualTo("label-Code-Review");
+ assertThrows(RuntimeException.class, () -> keyBuilder.apply(null).build());
+ assertThrows(RuntimeException.class, () -> keyBuilder.apply("not a label").build());
+ assertThrows(RuntimeException.class, () -> keyBuilder.apply("label-Code-Review").build());
+ }
+
+ @Test
+ public void testPermissionKeyDisallowsSettingRefOnGlobalCapability() throws Exception {
+ assertThrows(RuntimeException.class, () -> capabilityKey(ADMINISTRATE_SERVER).ref("refs/*"));
+ }
+
+ @Test
+ public void testProjectUpdateDisallowsGroupOnExclusiveGroupPermissionKey() throws Exception {
+ TestPermissionKey.Builder b = permissionKey(ABANDON).ref("refs/*");
+ Function<TestPermissionKey.Builder, TestProjectUpdate.Builder> updateBuilder =
+ kb -> builder().setExclusiveGroup(kb, true);
+
+ assertThat(updateBuilder.apply(b).build().exclusiveGroupPermissions())
+ .containsExactly(b.build(), true);
+
+ b.group(REGISTERED_USERS);
+ assertThrows(RuntimeException.class, () -> updateBuilder.apply(b).build());
+ }
+
+ @Test
+ public void hasCapabilityUpdates() throws Exception {
+ assertThat(builder().build().hasCapabilityUpdates()).isFalse();
+ assertThat(
+ builder()
+ .add(allow(ABANDON).ref("refs/*").group(REGISTERED_USERS))
+ .add(allowLabel("Code-Review").ref("refs/*").group(REGISTERED_USERS).range(0, 1))
+ .remove(permissionKey(ABANDON).ref("refs/foo"))
+ .remove(labelPermissionKey("Code-Review").ref("refs/foo"))
+ .setExclusiveGroup(permissionKey(ABANDON).ref("refs/bar"), true)
+ .setExclusiveGroup(labelPermissionKey(ABANDON).ref("refs/bar"), true)
+ .build()
+ .hasCapabilityUpdates())
+ .isFalse();
+ assertThat(
+ builder(ALL_PROJECTS_NAME)
+ .add(allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS))
+ .build()
+ .hasCapabilityUpdates())
+ .isTrue();
+ assertThat(
+ builder(ALL_PROJECTS_NAME)
+ .remove(capabilityKey(ADMINISTRATE_SERVER))
+ .build()
+ .hasCapabilityUpdates())
+ .isTrue();
+ }
+
+ @Test
+ public void updatingCapabilitiesNotAllowedForNonAllProjects() throws Exception {
+ assertThrows(
+ RuntimeException.class,
+ () -> builder().add(allowCapability(ADMINISTRATE_SERVER).group(REGISTERED_USERS)).update());
+ assertThrows(
+ RuntimeException.class,
+ () -> builder().remove(capabilityKey(ADMINISTRATE_SERVER)).update());
+ }
+
+ private static TestProjectUpdate.Builder builder() {
+ return builder(Project.nameKey("test-project"));
+ }
+
+ private static TestProjectUpdate.Builder builder(Project.NameKey nameKey) {
+ return TestProjectUpdate.builder(nameKey, ALL_PROJECTS_NAME, u -> {});
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
index 5cbed1b0e1..f6421a5065 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
@@ -15,8 +15,9 @@
package com.google.gerrit.acceptance.testsuite.request;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -24,8 +25,8 @@ import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.TestAccount;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.CurrentUser.PropertyKey;
@@ -67,12 +68,9 @@ public class RequestScopeOperationsImplTest extends AbstractDaemonTest {
@Test
public void setApiUserToNonExistingUser() throws Exception {
fastCheckCurrentUser(admin.id());
- try {
- requestScopeOperations.setApiUser(new Account.Id(sequences.nextAccountId()));
- assert_().fail("expected RuntimeException");
- } catch (RuntimeException e) {
- // Expected.
- }
+ assertThrows(
+ RuntimeException.class,
+ () -> requestScopeOperations.setApiUser(Account.id(sequences.nextAccountId())));
checkCurrentUser(admin.id());
}
@@ -98,24 +96,26 @@ public class RequestScopeOperationsImplTest extends AbstractDaemonTest {
private void fastCheckCurrentUser(Account.Id expected) {
// Check current user quickly, since the full check requires creating changes and is quite slow.
- assertThat(userProvider.get().isIdentifiedUser())
- .named("user from provider is an IdentifiedUser")
+ assertWithMessage("user from provider is an IdentifiedUser")
+ .that(userProvider.get().isIdentifiedUser())
.isTrue();
- assertThat(userProvider.get().getAccountId()).named("user from provider").isEqualTo(expected);
+ assertWithMessage("user from provider")
+ .that(userProvider.get().getAccountId())
+ .isEqualTo(expected);
}
private void checkCurrentUser(Account.Id expected) throws Exception {
// Test all supported ways that an acceptance test might query the active user.
fastCheckCurrentUser(expected);
- assertThat(gApi.accounts().self().get()._accountId)
- .named("user from GerritApi")
+ assertWithMessage("user from GerritApi")
+ .that(gApi.accounts().self().get()._accountId)
.isEqualTo(expected.get());
AcceptanceTestRequestScope.Context ctx = atrScope.get();
- assertThat(ctx.getUser().isIdentifiedUser())
- .named("user from AcceptanceTestRequestScope.Context is an IdentifiedUser")
+ assertWithMessage("user from AcceptanceTestRequestScope.Context is an IdentifiedUser")
+ .that(ctx.getUser().isIdentifiedUser())
.isTrue();
- assertThat(ctx.getUser().getAccountId())
- .named("user from AcceptanceTestRequestScope.Context")
+ assertWithMessage("user from AcceptanceTestRequestScope.Context")
+ .that(ctx.getUser().getAccountId())
.isEqualTo(expected);
checkSshUser(expected);
}
@@ -131,8 +131,8 @@ public class RequestScopeOperationsImplTest extends AbstractDaemonTest {
assertThat(gApi.changes().id(changeId).get().owner._accountId).isEqualTo(expected.get());
String queryResults =
atrScope.get().getSession().exec("gerrit query owner:self change:" + changeId);
- assertThat(findDistinct(queryResults, "I[0-9a-f]{40}"))
- .named("Change-Ids in query results:\n%s", queryResults)
+ assertWithMessage("Change-Ids in query results:\n%s", queryResults)
+ .that(findDistinct(queryResults, "I[0-9a-f]{40}"))
.containsExactly(changeId);
}
diff --git a/javatests/com/google/gerrit/common/AutoValueTest.java b/javatests/com/google/gerrit/common/AutoValueTest.java
index 89d7bf4f98..947fe4aa82 100644
--- a/javatests/com/google/gerrit/common/AutoValueTest.java
+++ b/javatests/com/google/gerrit/common/AutoValueTest.java
@@ -17,10 +17,9 @@ package com.google.gerrit.common;
import static com.google.common.truth.Truth.assertThat;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class AutoValueTest extends GerritBaseTests {
+public class AutoValueTest {
@AutoValue
abstract static class Auto {
static Auto create(String val) {
diff --git a/javatests/com/google/gerrit/common/BUILD b/javatests/com/google/gerrit/common/BUILD
index 18ececd41f..c7b21a3fd3 100644
--- a/javatests/com/google/gerrit/common/BUILD
+++ b/javatests/com/google/gerrit/common/BUILD
@@ -8,7 +8,6 @@ junit_tests(
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/common:version",
"//java/com/google/gerrit/launcher",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
diff --git a/javatests/com/google/gerrit/common/data/AccessSectionTest.java b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
index faf9d6c752..e775cbcdb5 100644
--- a/javatests/com/google/gerrit/common/data/AccessSectionTest.java
+++ b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
@@ -15,16 +15,16 @@
package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
-public class AccessSectionTest extends GerritBaseTests {
+public class AccessSectionTest {
private static final String REF_PATTERN = "refs/heads/master";
private AccessSection accessSection;
@@ -57,16 +57,17 @@ public class AccessSectionTest extends GerritBaseTests {
Permission submitPermission = new Permission(Permission.SUBMIT);
accessSection.setPermissions(ImmutableList.of(submitPermission));
assertThat(accessSection.getPermissions()).containsExactly(submitPermission);
-
- exception.expect(NullPointerException.class);
- accessSection.setPermissions(null);
+ assertThrows(NullPointerException.class, () -> accessSection.setPermissions(null));
}
@Test
public void cannotSetDuplicatePermissions() {
- exception.expect(IllegalArgumentException.class);
- accessSection.setPermissions(
- ImmutableList.of(new Permission(Permission.ABANDON), new Permission(Permission.ABANDON)));
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ accessSection.setPermissions(
+ ImmutableList.of(
+ new Permission(Permission.ABANDON), new Permission(Permission.ABANDON))));
}
@Test
@@ -76,9 +77,11 @@ public class AccessSectionTest extends GerritBaseTests {
Permission abandonPermissionUpperCase =
new Permission(Permission.ABANDON.toUpperCase(Locale.US));
- exception.expect(IllegalArgumentException.class);
- accessSection.setPermissions(
- ImmutableList.of(abandonPermissionLowerCase, abandonPermissionUpperCase));
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ accessSection.setPermissions(
+ ImmutableList.of(abandonPermissionLowerCase, abandonPermissionUpperCase)));
}
@Test
@@ -92,9 +95,7 @@ public class AccessSectionTest extends GerritBaseTests {
Permission submitPermission = new Permission(Permission.SUBMIT);
accessSection.setPermissions(ImmutableList.of(submitPermission));
assertThat(accessSection.getPermission(Permission.SUBMIT)).isEqualTo(submitPermission);
-
- exception.expect(NullPointerException.class);
- accessSection.getPermission(null);
+ assertThrows(NullPointerException.class, () -> accessSection.getPermission(null));
}
@Test
@@ -112,8 +113,7 @@ public class AccessSectionTest extends GerritBaseTests {
assertThat(accessSection.getPermission(Permission.SUBMIT, true))
.isEqualTo(new Permission(Permission.SUBMIT));
- exception.expect(NullPointerException.class);
- accessSection.getPermission(null, true);
+ assertThrows(NullPointerException.class, () -> accessSection.getPermission(null, true));
}
@Test
@@ -130,9 +130,7 @@ public class AccessSectionTest extends GerritBaseTests {
assertThat(accessSection.getPermissions())
.containsExactly(abandonPermission, rebasePermission, submitPermission)
.inOrder();
-
- exception.expect(NullPointerException.class);
- accessSection.addPermission(null);
+ assertThrows(NullPointerException.class, () -> accessSection.addPermission(null));
}
@Test
@@ -166,9 +164,7 @@ public class AccessSectionTest extends GerritBaseTests {
assertThat(accessSection.getPermissions())
.containsExactly(abandonPermission, rebasePermission)
.inOrder();
-
- exception.expect(NullPointerException.class);
- accessSection.remove(null);
+ assertThrows(NullPointerException.class, () -> accessSection.remove(null));
}
@Test
@@ -187,8 +183,7 @@ public class AccessSectionTest extends GerritBaseTests {
.containsExactly(abandonPermission, rebasePermission)
.inOrder();
- exception.expect(NullPointerException.class);
- accessSection.removePermission(null);
+ assertThrows(NullPointerException.class, () -> accessSection.removePermission(null));
}
@Test
@@ -229,9 +224,7 @@ public class AccessSectionTest extends GerritBaseTests {
assertThat(accessSection1.getPermissions())
.containsExactly(abandonPermission, rebasePermission, submitPermission)
.inOrder();
-
- exception.expect(NullPointerException.class);
- accessSection.mergeFrom(null);
+ assertThrows(NullPointerException.class, () -> accessSection.mergeFrom(null));
}
@Test
diff --git a/javatests/com/google/gerrit/common/data/BUILD b/javatests/com/google/gerrit/common/data/BUILD
index 776a5e0250..f2b7d63b26 100644
--- a/javatests/com/google/gerrit/common/data/BUILD
+++ b/javatests/com/google/gerrit/common/data/BUILD
@@ -5,7 +5,7 @@ junit_tests(
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
"//lib/truth",
diff --git a/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java b/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
index 3dd2db3a5a..dcd3c054c2 100644
--- a/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
+++ b/javatests/com/google/gerrit/common/data/EncodePathSeparatorTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class EncodePathSeparatorTest extends GerritBaseTests {
+public class EncodePathSeparatorTest {
@Test
public void defaultBehaviour() {
assertThat(new GitwebType().replacePathSeparator("a/b")).isEqualTo("a/b");
diff --git a/javatests/com/google/gerrit/common/data/FilenameComparatorTest.java b/javatests/com/google/gerrit/common/data/FilenameComparatorTest.java
index 055f57d3cc..ec71e05a16 100644
--- a/javatests/com/google/gerrit/common/data/FilenameComparatorTest.java
+++ b/javatests/com/google/gerrit/common/data/FilenameComparatorTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class FilenameComparatorTest extends GerritBaseTests {
+public class FilenameComparatorTest {
private FilenameComparator comparator = FilenameComparator.INSTANCE;
@Test
diff --git a/javatests/com/google/gerrit/common/data/GroupReferenceTest.java b/javatests/com/google/gerrit/common/data/GroupReferenceTest.java
index 8cf486b118..25b55c79bb 100644
--- a/javatests/com/google/gerrit/common/data/GroupReferenceTest.java
+++ b/javatests/com/google/gerrit/common/data/GroupReferenceTest.java
@@ -15,17 +15,17 @@
package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
import org.junit.Test;
-public class GroupReferenceTest extends GerritBaseTests {
+public class GroupReferenceTest {
@Test
public void forGroupDescription() {
String name = "foo";
- AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+ AccountGroup.UUID uuid = AccountGroup.uuid("uuid-foo");
GroupReference groupReference =
GroupReference.forGroup(
new GroupDescription.Basic() {
@@ -56,7 +56,7 @@ public class GroupReferenceTest extends GerritBaseTests {
@Test
public void create() {
- AccountGroup.UUID uuid = new AccountGroup.UUID("uuid");
+ AccountGroup.UUID uuid = AccountGroup.uuid("uuid");
String name = "foo";
GroupReference groupReference = new GroupReference(uuid, name);
assertThat(groupReference.getUUID()).isEqualTo(uuid);
@@ -68,15 +68,15 @@ public class GroupReferenceTest extends GerritBaseTests {
// GroupReferences where the UUID is null are used to represent groups from project.config that
// cannot be resolved.
String name = "foo";
- GroupReference groupReference = new GroupReference(null, name);
+ GroupReference groupReference = new GroupReference(name);
assertThat(groupReference.getUUID()).isNull();
assertThat(groupReference.getName()).isEqualTo(name);
}
@Test
public void cannotCreateWithoutName() {
- exception.expect(NullPointerException.class);
- new GroupReference(new AccountGroup.UUID("uuid"), null);
+ assertThrows(
+ NullPointerException.class, () -> new GroupReference(AccountGroup.uuid("uuid"), null));
}
@Test
@@ -99,12 +99,12 @@ public class GroupReferenceTest extends GerritBaseTests {
@Test
public void getAndSetUuid() {
- AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+ AccountGroup.UUID uuid = AccountGroup.uuid("uuid-foo");
String name = "foo";
GroupReference groupReference = new GroupReference(uuid, name);
assertThat(groupReference.getUUID()).isEqualTo(uuid);
- AccountGroup.UUID uuid2 = new AccountGroup.UUID("uuid-bar");
+ AccountGroup.UUID uuid2 = AccountGroup.uuid("uuid-bar");
groupReference.setUUID(uuid2);
assertThat(groupReference.getUUID()).isEqualTo(uuid2);
@@ -116,7 +116,7 @@ public class GroupReferenceTest extends GerritBaseTests {
@Test
public void getAndSetName() {
- AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+ AccountGroup.UUID uuid = AccountGroup.uuid("uuid-foo");
String name = "foo";
GroupReference groupReference = new GroupReference(uuid, name);
assertThat(groupReference.getName()).isEqualTo(name);
@@ -125,21 +125,20 @@ public class GroupReferenceTest extends GerritBaseTests {
groupReference.setName(name2);
assertThat(groupReference.getName()).isEqualTo(name2);
- exception.expect(NullPointerException.class);
- groupReference.setName(null);
+ assertThrows(NullPointerException.class, () -> groupReference.setName(null));
}
@Test
public void toConfigValue() {
String name = "foo";
- GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-foo"), name);
+ GroupReference groupReference = new GroupReference(AccountGroup.uuid("uuid-foo"), name);
assertThat(groupReference.toConfigValue()).isEqualTo("group " + name);
}
@Test
public void testEquals() {
- AccountGroup.UUID uuid1 = new AccountGroup.UUID("uuid-foo");
- AccountGroup.UUID uuid2 = new AccountGroup.UUID("uuid-bar");
+ AccountGroup.UUID uuid1 = AccountGroup.uuid("uuid-foo");
+ AccountGroup.UUID uuid2 = AccountGroup.uuid("uuid-bar");
String name1 = "foo";
String name2 = "bar";
@@ -154,12 +153,11 @@ public class GroupReferenceTest extends GerritBaseTests {
@Test
public void testHashcode() {
- AccountGroup.UUID uuid1 = new AccountGroup.UUID("uuid1");
+ AccountGroup.UUID uuid1 = AccountGroup.uuid("uuid1");
assertThat(new GroupReference(uuid1, "foo").hashCode())
.isEqualTo(new GroupReference(uuid1, "bar").hashCode());
// Check that the following calls don't fail with an exception.
- new GroupReference(null, "bar").hashCode();
- new GroupReference(new AccountGroup.UUID(null), "bar").hashCode();
+ new GroupReference("bar").hashCode();
}
}
diff --git a/javatests/com/google/gerrit/common/data/LabelFunctionTest.java b/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
index a534a9e8f8..6f5232b7d7 100644
--- a/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
+++ b/javatests/com/google/gerrit/common/data/LabelFunctionTest.java
@@ -17,23 +17,22 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.testing.GerritBaseTests;
-import java.sql.Date;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
import org.junit.Test;
-public class LabelFunctionTest extends GerritBaseTests {
+public class LabelFunctionTest {
private static final String LABEL_NAME = "Verified";
- private static final LabelId LABEL_ID = new LabelId(LABEL_NAME);
- private static final Change.Id CHANGE_ID = new Change.Id(100);
- private static final PatchSet.Id PS_ID = new PatchSet.Id(CHANGE_ID, 1);
+ private static final LabelId LABEL_ID = LabelId.create(LABEL_NAME);
+ private static final Change.Id CHANGE_ID = Change.id(100);
+ private static final PatchSet.Id PS_ID = PatchSet.id(CHANGE_ID, 1);
private static final LabelType VERIFIED_LABEL = makeLabel();
private static final PatchSetApproval APPROVAL_2 = makeApproval(2);
private static final PatchSetApproval APPROVAL_1 = makeApproval(1);
@@ -82,7 +81,7 @@ public class LabelFunctionTest extends GerritBaseTests {
SubmitRecord.Label myLabel = LabelFunction.MAX_NO_BLOCK.check(VERIFIED_LABEL, approvals);
assertThat(myLabel.status).isEqualTo(SubmitRecord.Label.Status.OK);
- assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_2.getAccountId());
+ assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_2.accountId());
}
private static LabelType makeLabel() {
@@ -97,14 +96,11 @@ public class LabelFunctionTest extends GerritBaseTests {
}
private static PatchSetApproval makeApproval(int value) {
- Account.Id accountId = new Account.Id(10000 + value);
- PatchSetApproval.Key key = makeKey(PS_ID, accountId, LABEL_ID);
- return new PatchSetApproval(key, (short) value, Date.from(Instant.now()));
- }
-
- private static PatchSetApproval.Key makeKey(
- PatchSet.Id psId, Account.Id accountId, LabelId labelId) {
- return new PatchSetApproval.Key(psId, accountId, labelId);
+ return PatchSetApproval.builder()
+ .key(PatchSetApproval.key(PS_ID, Account.id(10000 + value), LABEL_ID))
+ .value(value)
+ .granted(Date.from(Instant.now()))
+ .build();
}
private static void checkBlockWorks(LabelFunction function) {
@@ -113,7 +109,7 @@ public class LabelFunctionTest extends GerritBaseTests {
SubmitRecord.Label myLabel = function.check(VERIFIED_LABEL, approvals);
assertThat(myLabel.status).isEqualTo(SubmitRecord.Label.Status.REJECT);
- assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_M2.getAccountId());
+ assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_M2.accountId());
}
private static void checkNothingHappens(LabelFunction function) {
@@ -144,6 +140,6 @@ public class LabelFunctionTest extends GerritBaseTests {
SubmitRecord.Label myLabel = function.check(VERIFIED_LABEL, approvals);
assertThat(myLabel.status).isEqualTo(SubmitRecord.Label.Status.OK);
- assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_2.getAccountId());
+ assertThat(myLabel.appliedBy).isEqualTo(APPROVAL_2.accountId());
}
}
diff --git a/javatests/com/google/gerrit/common/data/LabelTypeTest.java b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
index db0df2e695..6c3befb1ac 100644
--- a/javatests/com/google/gerrit/common/data/LabelTypeTest.java
+++ b/javatests/com/google/gerrit/common/data/LabelTypeTest.java
@@ -17,10 +17,9 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class LabelTypeTest extends GerritBaseTests {
+public class LabelTypeTest {
@Test
public void sortLabelValues() {
LabelValue v0 = new LabelValue((short) 0, "Zero");
diff --git a/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java b/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
index b22a51151f..b646d2bce0 100644
--- a/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
+++ b/javatests/com/google/gerrit/common/data/ParameterizedStringTest.java
@@ -17,12 +17,11 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
-public class ParameterizedStringTest extends GerritBaseTests {
+public class ParameterizedStringTest {
@Test
public void emptyString() {
ParameterizedString p = new ParameterizedString("");
diff --git a/javatests/com/google/gerrit/common/data/PermissionRuleTest.java b/javatests/com/google/gerrit/common/data/PermissionRuleTest.java
index f442f3930f..d815dbc2e5 100644
--- a/javatests/com/google/gerrit/common/data/PermissionRuleTest.java
+++ b/javatests/com/google/gerrit/common/data/PermissionRuleTest.java
@@ -15,20 +15,20 @@
package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.AccountGroup;
import org.junit.Before;
import org.junit.Test;
-public class PermissionRuleTest extends GerritBaseTests {
+public class PermissionRuleTest {
private GroupReference groupReference;
private PermissionRule permissionRule;
@Before
public void setup() {
- this.groupReference = new GroupReference(new AccountGroup.UUID("uuid"), "group");
+ this.groupReference = new GroupReference(AccountGroup.uuid("uuid"), "group");
this.permissionRule = new PermissionRule(groupReference);
}
@@ -42,8 +42,7 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void cannotSetActionToNull() {
- exception.expect(NullPointerException.class);
- permissionRule.setAction(null);
+ assertThrows(NullPointerException.class, () -> permissionRule.setAction(null));
}
@Test
@@ -131,7 +130,7 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void setGroup() {
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
assertThat(groupReference2).isNotEqualTo(groupReference);
assertThat(permissionRule.getGroup()).isEqualTo(groupReference);
@@ -142,10 +141,10 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromAnyBlock() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule1.mergeFrom(permissionRule2);
@@ -170,10 +169,10 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromAnyDeny() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule1.mergeFrom(permissionRule2);
@@ -193,10 +192,10 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromAnyBatch() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule1.mergeFrom(permissionRule2);
@@ -216,10 +215,10 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromAnyForce() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule1.mergeFrom(permissionRule2);
@@ -239,11 +238,11 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromMergeRange() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
permissionRule1.setRange(-1, 2);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule2.setRange(-2, 1);
@@ -256,10 +255,10 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void mergeFromGroupNotChanged() {
- GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+ GroupReference groupReference1 = new GroupReference(AccountGroup.uuid("uuid1"), "group1");
PermissionRule permissionRule1 = new PermissionRule(groupReference1);
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRule2 = new PermissionRule(groupReference2);
permissionRule1.mergeFrom(permissionRule2);
@@ -348,7 +347,7 @@ public class PermissionRuleTest extends GerritBaseTests {
@Test
public void testEquals() {
- GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+ GroupReference groupReference2 = new GroupReference(AccountGroup.uuid("uuid2"), "group2");
PermissionRule permissionRuleOther = new PermissionRule(groupReference2);
assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
diff --git a/javatests/com/google/gerrit/common/data/PermissionTest.java b/javatests/com/google/gerrit/common/data/PermissionTest.java
index 23380e7f79..1012eff795 100644
--- a/javatests/com/google/gerrit/common/data/PermissionTest.java
+++ b/javatests/com/google/gerrit/common/data/PermissionTest.java
@@ -17,14 +17,13 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.AccountGroup;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
-public class PermissionTest extends GerritBaseTests {
+public class PermissionTest {
private static final String PERMISSION_NAME = "foo";
private Permission permission;
@@ -155,14 +154,14 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void setAndGetRules() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
assertThat(permission.getRules()).containsExactly(permissionRule1, permissionRule2).inOrder();
PermissionRule permissionRule3 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-3"), "group3"));
permission.setRules(ImmutableList.of(permissionRule3));
assertThat(permission.getRules()).containsExactly(permissionRule3);
}
@@ -170,10 +169,10 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void cannotAddPermissionByModifyingListThatWasProvidedToAccessSection() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
- GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(AccountGroup.uuid("uuid-3"), "group3");
List<PermissionRule> rules = new ArrayList<>();
rules.add(permissionRule1);
@@ -188,14 +187,14 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void getNonExistingRule() {
- GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ GroupReference groupReference = new GroupReference(AccountGroup.uuid("uuid-1"), "group1");
assertThat(permission.getRule(groupReference)).isNull();
assertThat(permission.getRule(groupReference, false)).isNull();
}
@Test
public void getRule() {
- GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ GroupReference groupReference = new GroupReference(AccountGroup.uuid("uuid-1"), "group1");
PermissionRule permissionRule = new PermissionRule(groupReference);
permission.setRules(ImmutableList.of(permissionRule));
assertThat(permission.getRule(groupReference)).isEqualTo(permissionRule);
@@ -203,7 +202,7 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void createMissingRuleOnGet() {
- GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
+ GroupReference groupReference = new GroupReference(AccountGroup.uuid("uuid-1"), "group1");
assertThat(permission.getRule(groupReference)).isNull();
assertThat(permission.getRule(groupReference, true))
@@ -213,11 +212,11 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void addRule() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
- GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ GroupReference groupReference3 = new GroupReference(AccountGroup.uuid("uuid-3"), "group3");
assertThat(permission.getRule(groupReference3)).isNull();
PermissionRule permissionRule3 = new PermissionRule(groupReference3);
@@ -231,10 +230,10 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void removeRule() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
- GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(AccountGroup.uuid("uuid-3"), "group3");
PermissionRule permissionRule3 = new PermissionRule(groupReference3);
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2, permissionRule3));
@@ -248,10 +247,10 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void removeRuleByGroupReference() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
- GroupReference groupReference3 = new GroupReference(new AccountGroup.UUID("uuid-3"), "group3");
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
+ GroupReference groupReference3 = new GroupReference(AccountGroup.uuid("uuid-3"), "group3");
PermissionRule permissionRule3 = new PermissionRule(groupReference3);
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2, permissionRule3));
@@ -265,9 +264,9 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void clearRules() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
assertThat(permission.getRules()).isNotEmpty();
@@ -279,11 +278,11 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void mergePermissions() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
PermissionRule permissionRule3 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-3"), "group3"));
Permission permission1 = new Permission("foo");
permission1.setRules(ImmutableList.of(permissionRule1, permissionRule2));
@@ -300,9 +299,9 @@ public class PermissionTest extends GerritBaseTests {
@Test
public void testEquals() {
PermissionRule permissionRule1 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-1"), "group1"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-1"), "group1"));
PermissionRule permissionRule2 =
- new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2"));
+ new PermissionRule(new GroupReference(AccountGroup.uuid("uuid-2"), "group2"));
permission.setRules(ImmutableList.of(permissionRule1, permissionRule2));
diff --git a/javatests/com/google/gerrit/common/data/SubmitRecordTest.java b/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
index 5b9fde7cc0..5386b873e6 100644
--- a/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
+++ b/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
@@ -16,12 +16,11 @@ package com.google.gerrit.common.data;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
-public class SubmitRecordTest extends GerritBaseTests {
+public class SubmitRecordTest {
private static final SubmitRecord OK_RECORD;
private static final SubmitRecord FORCED_RECORD;
private static final SubmitRecord NOT_READY_RECORD;
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index e2a8483ea6..be35d5a552 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -12,13 +12,12 @@ java_library(
deps = [
"//java/com/google/gerrit/elasticsearch",
"//java/com/google/gerrit/index",
- "//java/com/google/gerrit/server",
"//lib:guava",
+ "//lib:jgit",
"//lib:junit",
"//lib/guice",
"//lib/httpcomponents:httpcore",
"//lib/jackson:jackson-annotations",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:api",
"//lib/testcontainers",
"//lib/testcontainers:docker-java-api",
@@ -32,7 +31,7 @@ ELASTICSEARCH_DEPS = [
"//java/com/google/gerrit/elasticsearch",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
]
HTTP_TEST_DEPS = [
@@ -51,8 +50,6 @@ TYPES = [
SUFFIX = "sTest.java"
-ELASTICSEARCH_TESTS_V6 = {i: "ElasticV6Query" + i.capitalize() + SUFFIX for i in TYPES}
-
ELASTICSEARCH_TESTS_V7 = {i: "ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
ELASTICSEARCH_TAGS = [
@@ -62,14 +59,6 @@ ELASTICSEARCH_TAGS = [
]
[junit_tests(
- name = "elasticsearch_query_%ss_test_V6" % name,
- size = "large",
- srcs = [src],
- tags = ELASTICSEARCH_TAGS,
- deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name] + HTTP_TEST_DEPS,
-) for name, src in ELASTICSEARCH_TESTS_V6.items()]
-
-[junit_tests(
name = "elasticsearch_query_%ss_test_V7" % name,
size = "large",
srcs = [src],
@@ -89,9 +78,9 @@ junit_tests(
"//java/com/google/gerrit/elasticsearch",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
"//lib/httpcomponents:httpcore",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
index 9ce1456e09..7e044c330f 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
@@ -21,17 +21,17 @@ import static com.google.gerrit.elasticsearch.ElasticConfiguration.KEY_PREFIX;
import static com.google.gerrit.elasticsearch.ElasticConfiguration.KEY_SERVER;
import static com.google.gerrit.elasticsearch.ElasticConfiguration.KEY_USERNAME;
import static com.google.gerrit.elasticsearch.ElasticConfiguration.SECTION_ELASTICSEARCH;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.ProvisionException;
import java.util.Arrays;
import org.apache.http.HttpHost;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
-public class ElasticConfigurationTest extends GerritBaseTests {
+public class ElasticConfigurationTest {
@Test
public void singleServerNoOtherConfig() throws Exception {
Config cfg = newConfig();
@@ -121,9 +121,9 @@ public class ElasticConfigurationTest extends GerritBaseTests {
.containsExactly(hostURIs);
}
- private void assertProvisionException(Config cfg) throws Exception {
- exception.expect(ProvisionException.class);
- exception.expectMessage("No valid Elasticsearch servers configured");
- new ElasticConfiguration(cfg);
+ private void assertProvisionException(Config cfg) {
+ ProvisionException thrown =
+ assertThrows(ProvisionException.class, () -> new ElasticConfiguration(cfg));
+ assertThat(thrown).hasMessageThat().contains("No valid Elasticsearch servers configured");
}
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index 48295ea5f9..8cd09e66a7 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -39,16 +39,6 @@ public class ElasticContainer extends ElasticsearchContainer {
private static String getImageName(ElasticVersion version) {
switch (version) {
- case V6_8:
- return "blacktop/elasticsearch:6.8.13";
- case V7_0:
- return "blacktop/elasticsearch:7.0.1";
- case V7_1:
- return "blacktop/elasticsearch:7.1.1";
- case V7_2:
- return "blacktop/elasticsearch:7.2.1";
- case V7_3:
- return "blacktop/elasticsearch:7.3.2";
case V7_4:
return "blacktop/elasticsearch:7.4.2";
case V7_5:
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index 0203a22df9..dcc688032a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -15,7 +15,6 @@
package com.google.gerrit.elasticsearch;
import com.google.gerrit.index.IndexDefinition;
-import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
@@ -27,7 +26,7 @@ public final class ElasticTestUtils {
public static void configure(Config config, ElasticContainer container, String prefix) {
String hostname = container.getHttpHost().getHostName();
int port = container.getHttpHost().getPort();
- config.setEnum("index", null, "type", IndexType.ELASTICSEARCH);
+ config.setString("index", null, "type", "elasticsearch");
config.setString("elasticsearch", null, "server", "http://" + hostname + ":" + port);
config.setString("elasticsearch", null, "prefix", prefix);
config.setInt("index", null, "maxLimit", 10000);
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
deleted file mode 100644
index d4d321d395..0000000000
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.elasticsearch;
-
-import com.google.gerrit.server.query.account.AbstractQueryAccountsTest;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import org.eclipse.jgit.lib.Config;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class ElasticV6QueryAccountsTest extends AbstractQueryAccountsTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
- }
-
- private static ElasticContainer container;
-
- @BeforeClass
- public static void startIndexService() {
- if (container == null) {
- // Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V6_8);
- }
- }
-
- @AfterClass
- public static void stopElasticsearchServer() {
- if (container != null) {
- container.stop();
- }
- }
-
- @Override
- protected void initAfterLifecycleStart() throws Exception {
- super.initAfterLifecycleStart();
- ElasticTestUtils.createAllIndexes(injector);
- }
-
- @Override
- protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
- }
-}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
deleted file mode 100644
index 68c4b710f9..0000000000
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.elasticsearch;
-
-import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
-import org.apache.http.impl.nio.client.HttpAsyncClients;
-import org.eclipse.jgit.lib.Config;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class ElasticV6QueryChangesTest extends AbstractQueryChangesTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
- }
-
- private static ElasticContainer container;
- private static CloseableHttpAsyncClient client;
-
- @BeforeClass
- public static void startIndexService() {
- if (container == null) {
- // Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V6_8);
- client = HttpAsyncClients.createDefault();
- client.start();
- }
- }
-
- @AfterClass
- public static void stopElasticsearchServer() {
- if (container != null) {
- container.stop();
- }
- }
-
- @After
- public void closeIndex() {
- // Close the index after each test to prevent exceeding Elasticsearch's
- // shard limit (see Issue 10120).
- client.execute(
- new HttpPost(
- String.format(
- "http://%s:%d/%s*/_close",
- container.getHttpHost().getHostName(),
- container.getHttpHost().getPort(),
- getSanitizedMethodName())),
- HttpClientContext.create(),
- null);
- }
-
- @Override
- protected void initAfterLifecycleStart() throws Exception {
- super.initAfterLifecycleStart();
- ElasticTestUtils.createAllIndexes(injector);
- }
-
- @Override
- protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
- }
-}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
deleted file mode 100644
index 99c07f4486..0000000000
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.elasticsearch;
-
-import com.google.gerrit.server.query.group.AbstractQueryGroupsTest;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import org.eclipse.jgit.lib.Config;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class ElasticV6QueryGroupsTest extends AbstractQueryGroupsTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
- }
-
- private static ElasticContainer container;
-
- @BeforeClass
- public static void startIndexService() {
- if (container == null) {
- // Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V6_8);
- }
- }
-
- @AfterClass
- public static void stopElasticsearchServer() {
- if (container != null) {
- container.stop();
- }
- }
-
- @Override
- protected void initAfterLifecycleStart() throws Exception {
- super.initAfterLifecycleStart();
- ElasticTestUtils.createAllIndexes(injector);
- }
-
- @Override
- protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
- }
-}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
deleted file mode 100644
index 89c77748b7..0000000000
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.elasticsearch;
-
-import com.google.gerrit.server.query.project.AbstractQueryProjectsTest;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import org.eclipse.jgit.lib.Config;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class ElasticV6QueryProjectsTest extends AbstractQueryProjectsTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
- }
-
- private static ElasticContainer container;
-
- @BeforeClass
- public static void startIndexService() {
- if (container == null) {
- // Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V6_8);
- }
- }
-
- @AfterClass
- public static void stopElasticsearchServer() {
- if (container != null) {
- container.stop();
- }
- }
-
- @Override
- protected void initAfterLifecycleStart() throws Exception {
- super.initAfterLifecycleStart();
- ElasticTestUtils.createAllIndexes(injector);
- }
-
- @Override
- protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
- }
-}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
index 91d366e711..48264906d4 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -57,7 +57,7 @@ public class ElasticV7QueryAccountsTest extends AbstractQueryAccountsTest {
protected Injector createInjector() {
Config elasticsearchConfig = new Config(config);
InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
+ String indicesPrefix = testName.getSanitizedMethodName();
ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
index f11dc5b97d..d9a4d2ebc2 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
@@ -14,8 +14,11 @@
package com.google.gerrit.elasticsearch;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.GerritTestName;
import com.google.gerrit.testing.InMemoryModule;
import com.google.gerrit.testing.IndexConfig;
import com.google.inject.Guice;
@@ -28,6 +31,7 @@ import org.eclipse.jgit.lib.Config;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Rule;
public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
@ConfigSuite.Default
@@ -55,19 +59,23 @@ public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
}
}
+ @Rule public final GerritTestName testName = new GerritTestName();
+
@After
- public void closeIndex() {
+ public void closeIndex() throws Exception {
// Close the index after each test to prevent exceeding Elasticsearch's
// shard limit (see Issue 10120).
- client.execute(
- new HttpPost(
- String.format(
- "http://%s:%d/%s*/_close",
- container.getHttpHost().getHostName(),
- container.getHttpHost().getPort(),
- getSanitizedMethodName())),
- HttpClientContext.create(),
- null);
+ client
+ .execute(
+ new HttpPost(
+ String.format(
+ "http://%s:%d/%s*/_close",
+ container.getHttpHost().getHostName(),
+ container.getHttpHost().getPort(),
+ testName.getSanitizedMethodName())),
+ HttpClientContext.create(),
+ null)
+ .get(5, MINUTES);
}
@Override
@@ -80,7 +88,7 @@ public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
protected Injector createInjector() {
Config elasticsearchConfig = new Config(config);
InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
+ String indicesPrefix = testName.getSanitizedMethodName();
ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
index 282763a7dd..0fc96f8388 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -57,7 +57,7 @@ public class ElasticV7QueryGroupsTest extends AbstractQueryGroupsTest {
protected Injector createInjector() {
Config elasticsearchConfig = new Config(config);
InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
+ String indicesPrefix = testName.getSanitizedMethodName();
ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
index 4d83cf572c..1e56af9556 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
@@ -57,7 +57,7 @@ public class ElasticV7QueryProjectsTest extends AbstractQueryProjectsTest {
protected Injector createInjector() {
Config elasticsearchConfig = new Config(config);
InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = getSanitizedMethodName();
+ String indicesPrefix = testName.getSanitizedMethodName();
ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 0a7a4222a6..508dc84470 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -15,28 +15,13 @@
package com.google.gerrit.elasticsearch;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class ElasticVersionTest extends GerritBaseTests {
+public class ElasticVersionTest {
@Test
public void supportedVersion() throws Exception {
- assertThat(ElasticVersion.forVersion("6.8.0")).isEqualTo(ElasticVersion.V6_8);
- assertThat(ElasticVersion.forVersion("6.8.1")).isEqualTo(ElasticVersion.V6_8);
-
- assertThat(ElasticVersion.forVersion("7.0.0")).isEqualTo(ElasticVersion.V7_0);
- assertThat(ElasticVersion.forVersion("7.0.1")).isEqualTo(ElasticVersion.V7_0);
-
- assertThat(ElasticVersion.forVersion("7.1.0")).isEqualTo(ElasticVersion.V7_1);
- assertThat(ElasticVersion.forVersion("7.1.1")).isEqualTo(ElasticVersion.V7_1);
-
- assertThat(ElasticVersion.forVersion("7.2.0")).isEqualTo(ElasticVersion.V7_2);
- assertThat(ElasticVersion.forVersion("7.2.1")).isEqualTo(ElasticVersion.V7_2);
-
- assertThat(ElasticVersion.forVersion("7.3.0")).isEqualTo(ElasticVersion.V7_3);
- assertThat(ElasticVersion.forVersion("7.3.1")).isEqualTo(ElasticVersion.V7_3);
-
assertThat(ElasticVersion.forVersion("7.4.0")).isEqualTo(ElasticVersion.V7_4);
assertThat(ElasticVersion.forVersion("7.4.1")).isEqualTo(ElasticVersion.V7_4);
@@ -55,51 +40,13 @@ public class ElasticVersionTest extends GerritBaseTests {
@Test
public void unsupportedVersion() throws Exception {
- exception.expect(ElasticVersion.UnsupportedVersion.class);
- exception.expectMessage(
- "Unsupported version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
- ElasticVersion.forVersion("4.0.0");
- }
-
- @Test
- public void atLeastMinorVersion() throws Exception {
- assertThat(ElasticVersion.V6_8.isAtLeastMinorVersion(ElasticVersion.V6_8)).isTrue();
- assertThat(ElasticVersion.V7_0.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_1.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_2.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_3.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_4.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_5.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_6.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_7.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- assertThat(ElasticVersion.V7_8.isAtLeastMinorVersion(ElasticVersion.V6_8)).isFalse();
- }
-
- @Test
- public void version6OrLater() throws Exception {
- assertThat(ElasticVersion.V6_8.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_1.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_2.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_3.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_4.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_5.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_6.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_7.isV6OrLater()).isTrue();
- assertThat(ElasticVersion.V7_8.isV6OrLater()).isTrue();
- }
-
- @Test
- public void version7OrLater() throws Exception {
- assertThat(ElasticVersion.V6_8.isV7OrLater()).isFalse();
- assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_1.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_2.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_3.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_4.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_5.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_6.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_7.isV7OrLater()).isTrue();
- assertThat(ElasticVersion.V7_8.isV7OrLater()).isTrue();
+ ElasticVersion.UnsupportedVersion thrown =
+ assertThrows(
+ ElasticVersion.UnsupportedVersion.class, () -> ElasticVersion.forVersion("4.0.0"));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "Unsupported version: [4.0.0]. Supported versions: "
+ + ElasticVersion.supportedVersions());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java b/javatests/com/google/gerrit/entities/AccountGroupTest.java
index 18a55bf77a..a9d5188cbb 100644
--- a/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java
+++ b/javatests/com/google/gerrit/entities/AccountGroupTest.java
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.AccountGroup.UUID.fromRef;
-import static com.google.gerrit.reviewdb.client.AccountGroup.UUID.fromRefPart;
+import static com.google.gerrit.entities.AccountGroup.UUID.fromRef;
+import static com.google.gerrit.entities.AccountGroup.UUID.fromRefPart;
+import static com.google.gerrit.entities.AccountGroup.uuid;
import java.sql.Timestamp;
import java.time.Instant;
@@ -70,7 +71,29 @@ public class AccountGroupTest {
assertThat(fromRefPart("ab/" + TEST_UUID)).isNull();
}
- private AccountGroup.UUID uuid(String uuid) {
- return new AccountGroup.UUID(uuid);
+ @Test
+ public void uuidToString() {
+ assertThat(uuid("foo").toString()).isEqualTo("foo");
+ assertThat(uuid("foo bar").toString()).isEqualTo("foo+bar");
+ assertThat(uuid("foo:bar").toString()).isEqualTo("foo%3Abar");
+ }
+
+ @Test
+ public void parseUuid() {
+ assertThat(AccountGroup.UUID.parse("foo")).isEqualTo(uuid("foo"));
+ assertThat(AccountGroup.UUID.parse("foo+bar")).isEqualTo(uuid("foo bar"));
+ assertThat(AccountGroup.UUID.parse("foo%3Abar")).isEqualTo(uuid("foo:bar"));
+ }
+
+ @Test
+ public void idToString() {
+ assertThat(AccountGroup.id(123).toString()).isEqualTo("123");
+ }
+
+ @Test
+ public void nameKeyToString() {
+ assertThat(AccountGroup.nameKey("foo").toString()).isEqualTo("foo");
+ assertThat(AccountGroup.nameKey("foo bar").toString()).isEqualTo("foo+bar");
+ assertThat(AccountGroup.nameKey("foo:bar").toString()).isEqualTo("foo%3Abar");
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/client/AccountTest.java b/javatests/com/google/gerrit/entities/AccountTest.java
index 11a562f8ae..e89094321a 100644
--- a/javatests/com/google/gerrit/reviewdb/client/AccountTest.java
+++ b/javatests/com/google/gerrit/entities/AccountTest.java
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.Account.Id.fromRef;
-import static com.google.gerrit.reviewdb.client.Account.Id.fromRefPart;
-import static com.google.gerrit.reviewdb.client.Account.Id.fromRefSuffix;
+import static com.google.gerrit.entities.Account.Id.fromRef;
+import static com.google.gerrit.entities.Account.Id.fromRefPart;
+import static com.google.gerrit.entities.Account.Id.fromRefSuffix;
+import static com.google.gerrit.entities.Account.id;
import org.junit.Test;
@@ -90,8 +91,4 @@ public class AccountTest {
assertThat(fromRefSuffix("12/34")).isEqualTo(id(34));
assertThat(fromRefSuffix("ab/cd")).isNull();
}
-
- private Account.Id id(int n) {
- return new Account.Id(n);
- }
}
diff --git a/javatests/com/google/gerrit/reviewdb/client/BUILD b/javatests/com/google/gerrit/entities/BUILD
index bc993d53e1..b24781a8dc 100644
--- a/javatests/com/google/gerrit/reviewdb/client/BUILD
+++ b/javatests/com/google/gerrit/entities/BUILD
@@ -1,13 +1,13 @@
load("//tools/bzl:junit.bzl", "junit_tests")
junit_tests(
- name = "client_tests",
+ name = "entities_tests",
srcs = glob(["*.java"]),
deps = [
- "//java/com/google/gerrit/reviewdb:server",
- "//java/com/google/gerrit/server/project/testing:project-test-util",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/entities/BranchTest.java b/javatests/com/google/gerrit/entities/BranchTest.java
new file mode 100644
index 0000000000..0483ebcc22
--- /dev/null
+++ b/javatests/com/google/gerrit/entities/BranchTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class BranchTest {
+ @Test
+ public void canonicalizeNameDuringConstruction() {
+ assertThat(BranchNameKey.create(new Project.NameKey("foo"), "bar").branch())
+ .isEqualTo("refs/heads/bar");
+ assertThat(BranchNameKey.create(new Project.NameKey("foo"), "refs/heads/bar").branch())
+ .isEqualTo("refs/heads/bar");
+ }
+
+ @Test
+ public void idToString() {
+ assertThat(BranchNameKey.create(new Project.NameKey("foo"), "bar").toString())
+ .isEqualTo("foo,refs/heads/bar");
+ assertThat(BranchNameKey.create(new Project.NameKey("foo bar"), "bar baz").toString())
+ .isEqualTo("foo+bar,refs/heads/bar+baz");
+ assertThat(BranchNameKey.create(new Project.NameKey("foo^bar"), "bar^baz").toString())
+ .isEqualTo("foo%5Ebar,refs/heads/bar%5Ebaz");
+ }
+}
diff --git a/javatests/com/google/gerrit/reviewdb/client/ChangeTest.java b/javatests/com/google/gerrit/entities/ChangeTest.java
index 6d1d0a6817..c75ad5a628 100644
--- a/javatests/com/google/gerrit/reviewdb/client/ChangeTest.java
+++ b/javatests/com/google/gerrit/entities/ChangeTest.java
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class ChangeTest {
@@ -122,8 +123,8 @@ public class ChangeTest {
@Test
public void toRefPrefix() {
- assertThat(new Change.Id(1).toRefPrefix()).isEqualTo("refs/changes/01/1/");
- assertThat(new Change.Id(1234).toRefPrefix()).isEqualTo("refs/changes/34/1234/");
+ assertThat(Change.id(1).toRefPrefix()).isEqualTo("refs/changes/01/1/");
+ assertThat(Change.id(1234).toRefPrefix()).isEqualTo("refs/changes/34/1234/");
}
@Test
@@ -147,8 +148,20 @@ public class ChangeTest {
assertNotRefPart("1/1");
}
+ @Test
+ public void idToString() {
+ assertThat(Change.id(3).toString()).isEqualTo("3");
+ }
+
+ @Test
+ public void keyToString() {
+ String key = "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ assertThat(ObjectId.isId(key.substring(1))).isTrue();
+ assertThat(Change.key(key).toString()).isEqualTo(key);
+ }
+
private static void assertRef(int changeId, String refName) {
- assertThat(Change.Id.fromRef(refName)).isEqualTo(new Change.Id(changeId));
+ assertThat(Change.Id.fromRef(refName)).isEqualTo(Change.id(changeId));
}
private static void assertNotRef(String refName) {
@@ -156,7 +169,7 @@ public class ChangeTest {
}
private static void assertAllUsersRef(int changeId, String refName) {
- assertThat(Change.Id.fromAllUsersRef(refName)).isEqualTo(new Change.Id(changeId));
+ assertThat(Change.Id.fromAllUsersRef(refName)).isEqualTo(Change.id(changeId));
}
private static void assertNotAllUsersRef(String refName) {
@@ -164,7 +177,7 @@ public class ChangeTest {
}
private static void assertRefPart(int changeId, String refName) {
- assertEquals(new Change.Id(changeId), Change.Id.fromRefPart(refName));
+ assertEquals(Change.id(changeId), Change.Id.fromRefPart(refName));
}
private static void assertNotRefPart(String refName) {
diff --git a/javatests/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java b/javatests/com/google/gerrit/entities/PatchSetApprovalTest.java
index 5e42ce05d1..81aa3b84bb 100644
--- a/javatests/com/google/gerrit/reviewdb/client/PatchSetApprovalTest.java
+++ b/javatests/com/google/gerrit/entities/PatchSetApprovalTest.java
@@ -12,27 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
-public class PatchSetApprovalTest extends GerritBaseTests {
+public class PatchSetApprovalTest {
@Test
public void keyEquality() {
PatchSetApproval.Key k1 =
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(1), 2), new Account.Id(3), new LabelId("My-Label"));
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(1), 2), Account.id(3), LabelId.create("My-Label"));
PatchSetApproval.Key k2 =
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(1), 2), new Account.Id(3), new LabelId("My-Label"));
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(1), 2), Account.id(3), LabelId.create("My-Label"));
PatchSetApproval.Key k3 =
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(1), 2), new Account.Id(3), new LabelId("Other-Label"));
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(1), 2), Account.id(3), LabelId.create("Other-Label"));
assertThat(k2).isEqualTo(k1);
assertThat(k3).isNotEqualTo(k1);
diff --git a/javatests/com/google/gerrit/reviewdb/client/PatchSetTest.java b/javatests/com/google/gerrit/entities/PatchSetTest.java
index 51a405f6b6..61e1b4c438 100644
--- a/javatests/com/google/gerrit/reviewdb/client/PatchSetTest.java
+++ b/javatests/com/google/gerrit/entities/PatchSetTest.java
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.PatchSet.joinGroups;
-import static com.google.gerrit.reviewdb.client.PatchSet.splitGroups;
+import static com.google.gerrit.entities.PatchSet.joinGroups;
+import static com.google.gerrit.entities.PatchSet.splitGroups;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -64,37 +65,67 @@ public class PatchSetTest {
@Test
public void testSplitGroups() {
+ assertRuntimeException(() -> splitGroups(null));
assertThat(splitGroups("")).containsExactly("");
assertThat(splitGroups("abcd")).containsExactly("abcd");
assertThat(splitGroups("ab,cd")).containsExactly("ab", "cd").inOrder();
+ assertThat(splitGroups("ab , cd")).containsExactly("ab ", " cd").inOrder();
assertThat(splitGroups("ab,")).containsExactly("ab", "").inOrder();
assertThat(splitGroups(",cd")).containsExactly("", "cd").inOrder();
}
@Test
public void testJoinGroups() {
+ assertRuntimeException(() -> joinGroups(null));
+ assertRuntimeException(() -> joinGroups(ImmutableList.of("a,", "b")));
assertThat(joinGroups(ImmutableList.of(""))).isEqualTo("");
assertThat(joinGroups(ImmutableList.of("abcd"))).isEqualTo("abcd");
assertThat(joinGroups(ImmutableList.of("ab", "cd"))).isEqualTo("ab,cd");
+ assertThat(joinGroups(ImmutableList.of("ab ", " cd"))).isEqualTo("ab , cd");
assertThat(joinGroups(ImmutableList.of("ab", ""))).isEqualTo("ab,");
assertThat(joinGroups(ImmutableList.of("", "cd"))).isEqualTo(",cd");
}
@Test
public void toRefName() {
- assertThat(new PatchSet.Id(new Change.Id(1), 23).toRefName()).isEqualTo("refs/changes/01/1/23");
- assertThat(new PatchSet.Id(new Change.Id(1234), 5).toRefName())
- .isEqualTo("refs/changes/34/1234/5");
+ assertThat(PatchSet.id(Change.id(1), 23).toRefName()).isEqualTo("refs/changes/01/1/23");
+ assertThat(PatchSet.id(Change.id(1234), 5).toRefName()).isEqualTo("refs/changes/34/1234/5");
+ }
+
+ @Test
+ public void parseId() {
+ assertThat(PatchSet.Id.parse("1,2")).isEqualTo(PatchSet.id(Change.id(1), 2));
+ assertThat(PatchSet.Id.parse("01,02")).isEqualTo(PatchSet.id(Change.id(1), 2));
+ assertInvalidId(null);
+ assertInvalidId("");
+ assertInvalidId("1");
+ assertInvalidId("1,foo.txt");
+ assertInvalidId("foo.txt,1");
+
+ String hexComma = "%" + String.format("%02x", (int) ',');
+ assertInvalidId("1" + hexComma + "2");
+ }
+
+ @Test
+ public void idToString() {
+ assertThat(PatchSet.id(Change.id(2), 3).toString()).isEqualTo("2,3");
}
private static void assertRef(int changeId, int psId, String refName) {
assertThat(PatchSet.isChangeRef(refName)).isTrue();
- assertThat(PatchSet.Id.fromRef(refName))
- .isEqualTo(new PatchSet.Id(new Change.Id(changeId), psId));
+ assertThat(PatchSet.Id.fromRef(refName)).isEqualTo(PatchSet.id(Change.id(changeId), psId));
}
private static void assertNotRef(String refName) {
assertThat(PatchSet.isChangeRef(refName)).isFalse();
assertThat(PatchSet.Id.fromRef(refName)).isNull();
}
+
+ private static void assertInvalidId(String str) {
+ assertRuntimeException(() -> PatchSet.Id.parse(str));
+ }
+
+ private static void assertRuntimeException(Runnable runnable) {
+ assertThrows(RuntimeException.class, () -> runnable.run());
+ }
}
diff --git a/javatests/com/google/gerrit/entities/PatchTest.java b/javatests/com/google/gerrit/entities/PatchTest.java
new file mode 100644
index 0000000000..9f906a92ef
--- /dev/null
+++ b/javatests/com/google/gerrit/entities/PatchTest.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import org.junit.Test;
+
+public class PatchTest {
+ @Test
+ public void isMagic() {
+ assertThat(Patch.isMagic("/COMMIT_MSG")).isTrue();
+ assertThat(Patch.isMagic("/MERGE_LIST")).isTrue();
+
+ assertThat(Patch.isMagic("/COMMIT_MSG/")).isFalse();
+ assertThat(Patch.isMagic("COMMIT_MSG")).isFalse();
+ assertThat(Patch.isMagic("/commit_msg")).isFalse();
+ }
+
+ @Test
+ public void parseKey() {
+ assertThat(Patch.Key.parse("1,2,foo.txt"))
+ .isEqualTo(Patch.key(PatchSet.id(Change.id(1), 2), "foo.txt"));
+ assertThat(Patch.Key.parse("01,02,foo.txt"))
+ .isEqualTo(Patch.key(PatchSet.id(Change.id(1), 2), "foo.txt"));
+ assertInvalidKey(null);
+ assertInvalidKey("");
+ assertInvalidKey("1,2");
+ assertInvalidKey("1, 2, foo.txt");
+ assertInvalidKey("1,foo.txt");
+ assertInvalidKey("1,foo.txt,2");
+ assertInvalidKey("foo.txt,1,2");
+
+ String hexComma = "%" + String.format("%02x", (int) ',');
+ assertInvalidKey("1" + hexComma + "2" + hexComma + "foo.txt");
+ }
+
+ private static void assertInvalidKey(String str) {
+ assertThrows(RuntimeException.class, () -> Patch.Key.parse(str));
+ }
+}
diff --git a/javatests/com/google/gerrit/entities/ProjectTest.java b/javatests/com/google/gerrit/entities/ProjectTest.java
new file mode 100644
index 0000000000..341b54fc06
--- /dev/null
+++ b/javatests/com/google/gerrit/entities/ProjectTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class ProjectTest {
+ @Test
+ public void parseId() {
+ assertThat(Project.NameKey.parse("foo")).isEqualTo(new Project.NameKey("foo"));
+ assertThat(Project.NameKey.parse("foo%20bar")).isEqualTo(new Project.NameKey("foo bar"));
+ assertThat(Project.NameKey.parse("foo+bar")).isEqualTo(new Project.NameKey("foo bar"));
+ assertThat(Project.NameKey.parse("foo%2fbar")).isEqualTo(new Project.NameKey("foo/bar"));
+ assertThat(Project.NameKey.parse("foo%2Fbar")).isEqualTo(new Project.NameKey("foo/bar"));
+ }
+
+ @Test
+ public void idToString() {
+ assertThat(Project.nameKey("foo").toString()).isEqualTo("foo");
+ assertThat(Project.nameKey("foo bar").toString()).isEqualTo("foo+bar");
+ assertThat(Project.nameKey("foo/bar").toString()).isEqualTo("foo/bar");
+ assertThat(Project.nameKey("foo^bar").toString()).isEqualTo("foo%5Ebar");
+ assertThat(Project.nameKey("foo%bar").toString()).isEqualTo("foo%25bar");
+ }
+}
diff --git a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java b/javatests/com/google/gerrit/entities/RefNamesTest.java
index fa6a722db2..1990292fe3 100644
--- a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
+++ b/javatests/com/google/gerrit/entities/RefNamesTest.java
@@ -12,29 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.client;
+package com.google.gerrit.entities;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.parseAfterShardedRefPart;
-import static com.google.gerrit.reviewdb.client.RefNames.parseRefSuffix;
-import static com.google.gerrit.reviewdb.client.RefNames.parseShardedRefPart;
-import static com.google.gerrit.reviewdb.client.RefNames.parseShardedUuidFromRefPart;
-import static com.google.gerrit.reviewdb.client.RefNames.skipShardedRefPart;
+import static com.google.gerrit.entities.RefNames.parseAfterShardedRefPart;
+import static com.google.gerrit.entities.RefNames.parseRefSuffix;
+import static com.google.gerrit.entities.RefNames.parseShardedRefPart;
+import static com.google.gerrit.entities.RefNames.parseShardedUuidFromRefPart;
+import static com.google.gerrit.entities.RefNames.skipShardedRefPart;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
public class RefNamesTest {
private static final String TEST_GROUP_UUID = "ccab3195282a8ce4f5014efa391e82d10f884c64";
private static final String TEST_SHARDED_GROUP_UUID =
TEST_GROUP_UUID.substring(0, 2) + "/" + TEST_GROUP_UUID;
-
- @Rule public ExpectedException expectedException = ExpectedException.none();
-
- private final Account.Id accountId = new Account.Id(1011123);
- private final Change.Id changeId = new Change.Id(67473);
- private final PatchSet.Id psId = new PatchSet.Id(changeId, 42);
+ private final Account.Id accountId = Account.id(1011123);
+ private final Change.Id changeId = Change.id(67473);
+ private final PatchSet.Id psId = PatchSet.id(changeId, 42);
@Test
public void fullName() throws Exception {
@@ -54,34 +50,35 @@ public class RefNamesTest {
String robotCommentsRef = RefNames.robotCommentsRef(changeId);
assertThat(robotCommentsRef).isEqualTo("refs/changes/73/67473/robot-comments");
assertThat(RefNames.isNoteDbMetaRef(robotCommentsRef)).isTrue();
+
+ String changeRefPrefix = RefNames.changeRefPrefix(changeId);
+ assertThat(changeRefPrefix).isEqualTo("refs/changes/73/67473/");
}
@Test
public void refForGroupIsSharded() throws Exception {
- AccountGroup.UUID groupUuid = new AccountGroup.UUID("ABCDEFG");
+ AccountGroup.UUID groupUuid = AccountGroup.uuid("ABCDEFG");
String groupRef = RefNames.refsGroups(groupUuid);
assertThat(groupRef).isEqualTo("refs/groups/AB/ABCDEFG");
}
@Test
public void refForGroupWithUuidLessThanTwoCharsIsRejected() throws Exception {
- AccountGroup.UUID groupUuid = new AccountGroup.UUID("A");
- expectedException.expect(IllegalArgumentException.class);
- RefNames.refsGroups(groupUuid);
+ AccountGroup.UUID groupUuid = AccountGroup.uuid("A");
+ assertThrows(IllegalArgumentException.class, () -> RefNames.refsGroups(groupUuid));
}
@Test
public void refForDeletedGroupIsSharded() throws Exception {
- AccountGroup.UUID groupUuid = new AccountGroup.UUID("ABCDEFG");
+ AccountGroup.UUID groupUuid = AccountGroup.uuid("ABCDEFG");
String groupRef = RefNames.refsDeletedGroups(groupUuid);
assertThat(groupRef).isEqualTo("refs/deleted-groups/AB/ABCDEFG");
}
@Test
public void refForDeletedGroupWithUuidLessThanTwoCharsIsRejected() throws Exception {
- AccountGroup.UUID groupUuid = new AccountGroup.UUID("A");
- expectedException.expect(IllegalArgumentException.class);
- RefNames.refsDeletedGroups(groupUuid);
+ AccountGroup.UUID groupUuid = AccountGroup.uuid("A");
+ assertThrows(IllegalArgumentException.class, () -> RefNames.refsDeletedGroups(groupUuid));
}
@Test
diff --git a/javatests/com/google/gerrit/reviewdb/converter/AccountIdProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/AccountIdProtoConverterTest.java
index 123a973ede..0e4fbc8512 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/AccountIdProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/AccountIdProtoConverterTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.protobuf.Parser;
import org.junit.Test;
@@ -30,7 +30,7 @@ public class AccountIdProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- Account.Id accountId = new Account.Id(24);
+ Account.Id accountId = Account.id(24);
Entities.Account_Id proto = accountIdProtoConverter.toProto(accountId);
@@ -40,7 +40,7 @@ public class AccountIdProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- Account.Id accountId = new Account.Id(34832);
+ Account.Id accountId = Account.id(34832);
Account.Id convertedAccountId =
accountIdProtoConverter.fromProto(accountIdProtoConverter.toProto(accountId));
@@ -61,7 +61,8 @@ public class AccountIdProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(Account.Id.class).hasFields(ImmutableMap.of("id", int.class));
+ public void methodsExistAsExpected() {
+ assertThatSerializedClass(Account.Id.class)
+ .hasAutoValueMethods(ImmutableMap.of("id", int.class));
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/BUILD b/javatests/com/google/gerrit/entities/converter/BUILD
index 9cc941c50b..6ca9871b9c 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/BUILD
+++ b/javatests/com/google/gerrit/entities/converter/BUILD
@@ -4,10 +4,12 @@ junit_tests(
name = "proto_converter_tests",
srcs = glob(["*.java"]),
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/proto/testing",
- "//java/com/google/gerrit/reviewdb:server",
"//lib:guava",
+ "//lib:jgit",
"//lib:protobuf",
+ "//lib/guice",
"//lib/truth",
"//lib/truth:truth-proto-extension",
"//proto:entities_java_proto",
diff --git a/javatests/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/BranchNameKeyProtoConverterTest.java
index 412641f6aa..0a73db8975 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/BranchNameKeyProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/BranchNameKeyProtoConverterTest.java
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import org.junit.Test;
@@ -33,23 +33,23 @@ public class BranchNameKeyProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- Branch.NameKey nameKey = new Branch.NameKey(new Project.NameKey("project-13"), "branch-72");
+ BranchNameKey nameKey = BranchNameKey.create(Project.nameKey("project-13"), "branch-72");
Entities.Branch_NameKey proto = branchNameKeyProtoConverter.toProto(nameKey);
Entities.Branch_NameKey expectedProto =
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project-13"))
- .setBranchName("refs/heads/branch-72")
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project-13"))
+ .setBranch("refs/heads/branch-72")
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- Branch.NameKey nameKey = new Branch.NameKey(new Project.NameKey("project-52"), "branch 14");
+ BranchNameKey nameKey = BranchNameKey.create(Project.nameKey("project-52"), "branch 14");
- Branch.NameKey convertedNameKey =
+ BranchNameKey convertedNameKey =
branchNameKeyProtoConverter.fromProto(branchNameKeyProtoConverter.toProto(nameKey));
assertThat(convertedNameKey).isEqualTo(nameKey);
@@ -59,8 +59,8 @@ public class BranchNameKeyProtoConverterTest {
public void protoCanBeParsedFromBytes() throws Exception {
Entities.Branch_NameKey proto =
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 1"))
- .setBranchName("branch 36")
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 1"))
+ .setBranch("branch 36")
.build();
byte[] bytes = proto.toByteArray();
@@ -72,12 +72,12 @@ public class BranchNameKeyProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(Branch.NameKey.class)
- .hasFields(
+ public void methodsExistAsExpected() {
+ assertThatSerializedClass(BranchNameKey.class)
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
- .put("projectName", Project.NameKey.class)
- .put("branchName", String.class)
+ .put("project", Project.NameKey.class)
+ .put("branch", String.class)
.build());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeIdProtoConverterTest.java
index d5ebb515b6..12f3f33253 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ChangeIdProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeIdProtoConverterTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.protobuf.Parser;
import org.junit.Test;
@@ -30,7 +30,7 @@ public class ChangeIdProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- Change.Id changeId = new Change.Id(94);
+ Change.Id changeId = Change.id(94);
Entities.Change_Id proto = changeIdProtoConverter.toProto(changeId);
@@ -40,7 +40,7 @@ public class ChangeIdProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- Change.Id changeId = new Change.Id(2903482);
+ Change.Id changeId = Change.id(2903482);
Change.Id convertedChangeId =
changeIdProtoConverter.fromProto(changeIdProtoConverter.toProto(changeId));
@@ -61,7 +61,8 @@ public class ChangeIdProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(Change.Id.class).hasFields(ImmutableMap.of("id", int.class));
+ public void methodsExistAsExpected() {
+ assertThatSerializedClass(Change.Id.class)
+ .hasAutoValueMethods(ImmutableMap.of("id", int.class));
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeKeyProtoConverterTest.java
index d94870682b..e9080b3265 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ChangeKeyProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeKeyProtoConverterTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.protobuf.Parser;
import org.junit.Test;
@@ -30,7 +30,7 @@ public class ChangeKeyProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- Change.Key changeKey = new Change.Key("change-1");
+ Change.Key changeKey = Change.key("change-1");
Entities.Change_Key proto = changeKeyProtoConverter.toProto(changeKey);
@@ -40,7 +40,7 @@ public class ChangeKeyProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- Change.Key changeKey = new Change.Key("change-52");
+ Change.Key changeKey = Change.key("change-52");
Change.Key convertedChangeKey =
changeKeyProtoConverter.fromProto(changeKeyProtoConverter.toProto(changeKey));
@@ -61,7 +61,8 @@ public class ChangeKeyProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(Change.Key.class).hasFields(ImmutableMap.of("id", String.class));
+ public void methodsExistAsExpected() {
+ assertThatSerializedClass(Change.Key.class)
+ .hasAutoValueMethods(ImmutableMap.of("key", String.class));
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverterTest.java
index c8bb2ed799..72ce89608b 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ChangeMessageKeyProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeMessageKeyProtoConverterTest.java
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import org.junit.Test;
@@ -33,7 +33,7 @@ public class ChangeMessageKeyProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- ChangeMessage.Key messageKey = new ChangeMessage.Key(new Change.Id(704), "aabbcc");
+ ChangeMessage.Key messageKey = ChangeMessage.key(Change.id(704), "aabbcc");
Entities.ChangeMessage_Key proto = messageKeyProtoConverter.toProto(messageKey);
@@ -47,7 +47,7 @@ public class ChangeMessageKeyProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- ChangeMessage.Key messageKey = new ChangeMessage.Key(new Change.Id(704), "aabbcc");
+ ChangeMessage.Key messageKey = ChangeMessage.key(Change.id(704), "aabbcc");
ChangeMessage.Key convertedMessageKey =
messageKeyProtoConverter.fromProto(messageKeyProtoConverter.toProto(messageKey));
@@ -72,9 +72,9 @@ public class ChangeMessageKeyProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
+ public void methodsExistAsExpected() {
assertThatSerializedClass(ChangeMessage.Key.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("changeId", Change.Id.class)
.put("uuid", String.class)
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeMessageProtoConverterTest.java
index 65bdfbbc3d..933ffb40e1 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ChangeMessageProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeMessageProtoConverterTest.java
@@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import java.sql.Timestamp;
@@ -38,13 +38,13 @@ public class ChangeMessageProtoConverterTest {
public void allValuesConvertedToProto() {
ChangeMessage changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"),
- new Account.Id(63),
+ ChangeMessage.key(Change.id(543), "change-message-21"),
+ Account.id(63),
new Timestamp(9876543),
- new PatchSet.Id(new Change.Id(34), 13));
+ PatchSet.id(Change.id(34), 13));
changeMessage.setMessage("This is a change message.");
changeMessage.setTag("An arbitrary tag.");
- changeMessage.setRealAuthor(new Account.Id(10003));
+ changeMessage.setRealAuthor(Account.id(10003));
Entities.ChangeMessage proto = changeMessageProtoConverter.toProto(changeMessage);
@@ -60,7 +60,7 @@ public class ChangeMessageProtoConverterTest {
.setPatchset(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(34))
- .setPatchSetId(13))
+ .setId(13))
.setTag("An arbitrary tag.")
.setRealAuthor(Entities.Account_Id.newBuilder().setId(10003))
.build();
@@ -71,10 +71,10 @@ public class ChangeMessageProtoConverterTest {
public void mainValuesConvertedToProto() {
ChangeMessage changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"),
- new Account.Id(63),
+ ChangeMessage.key(Change.id(543), "change-message-21"),
+ Account.id(63),
new Timestamp(9876543),
- new PatchSet.Id(new Change.Id(34), 13));
+ PatchSet.id(Change.id(34), 13));
Entities.ChangeMessage proto = changeMessageProtoConverter.toProto(changeMessage);
@@ -89,7 +89,7 @@ public class ChangeMessageProtoConverterTest {
.setPatchset(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(34))
- .setPatchSetId(13))
+ .setId(13))
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@@ -99,10 +99,7 @@ public class ChangeMessageProtoConverterTest {
public void realAuthorIsNotAutomaticallySetToAuthorWhenConvertedToProto() {
ChangeMessage changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"),
- new Account.Id(63),
- null,
- null);
+ ChangeMessage.key(Change.id(543), "change-message-21"), Account.id(63), null, null);
Entities.ChangeMessage proto = changeMessageProtoConverter.toProto(changeMessage);
@@ -122,8 +119,7 @@ public class ChangeMessageProtoConverterTest {
// writtenOn may not be null according to the column definition but it's optional for the
// protobuf definition. -> assume as optional and hence test null
ChangeMessage changeMessage =
- new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"), null, null, null);
+ new ChangeMessage(ChangeMessage.key(Change.id(543), "change-message-21"), null, null, null);
Entities.ChangeMessage proto = changeMessageProtoConverter.toProto(changeMessage);
@@ -141,13 +137,13 @@ public class ChangeMessageProtoConverterTest {
public void allValuesConvertedToProtoAndBackAgain() {
ChangeMessage changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"),
- new Account.Id(63),
+ ChangeMessage.key(Change.id(543), "change-message-21"),
+ Account.id(63),
new Timestamp(9876543),
- new PatchSet.Id(new Change.Id(34), 13));
+ PatchSet.id(Change.id(34), 13));
changeMessage.setMessage("This is a change message.");
changeMessage.setTag("An arbitrary tag.");
- changeMessage.setRealAuthor(new Account.Id(10003));
+ changeMessage.setRealAuthor(Account.id(10003));
ChangeMessage convertedChangeMessage =
changeMessageProtoConverter.fromProto(changeMessageProtoConverter.toProto(changeMessage));
@@ -158,10 +154,10 @@ public class ChangeMessageProtoConverterTest {
public void mainValuesConvertedToProtoAndBackAgain() {
ChangeMessage changeMessage =
new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"),
- new Account.Id(63),
+ ChangeMessage.key(Change.id(543), "change-message-21"),
+ Account.id(63),
new Timestamp(9876543),
- new PatchSet.Id(new Change.Id(34), 13));
+ PatchSet.id(Change.id(34), 13));
ChangeMessage convertedChangeMessage =
changeMessageProtoConverter.fromProto(changeMessageProtoConverter.toProto(changeMessage));
@@ -171,8 +167,7 @@ public class ChangeMessageProtoConverterTest {
@Test
public void mandatoryValuesConvertedToProtoAndBackAgain() {
ChangeMessage changeMessage =
- new ChangeMessage(
- new ChangeMessage.Key(new Change.Id(543), "change-message-21"), null, null, null);
+ new ChangeMessage(ChangeMessage.key(Change.id(543), "change-message-21"), null, null, null);
ChangeMessage convertedChangeMessage =
changeMessageProtoConverter.fromProto(changeMessageProtoConverter.toProto(changeMessage));
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ChangeProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
index 61bf105d8b..72e4a7a8ed 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ChangeProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
@@ -12,20 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import java.sql.Timestamp;
@@ -38,22 +38,22 @@ public class ChangeProtoConverterTest {
public void allValuesConvertedToProto() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch 74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch 74"),
new Timestamp(987654L));
change.setLastUpdatedOn(new Timestamp(1234567L));
change.setStatus(Change.Status.MERGED);
change.setCurrentPatchSet(
- new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", "original subject ABC");
+ PatchSet.id(Change.id(14), 23), "subject XYZ", "original subject ABC");
change.setTopic("my topic");
change.setSubmissionId("submission ID 234");
- change.setAssignee(new Account.Id(100001));
+ change.setAssignee(Account.id(100001));
change.setPrivate(true);
change.setWorkInProgress(true);
change.setReviewStarted(true);
- change.setRevertOf(new Change.Id(180));
+ change.setRevertOf(Change.id(180));
Entities.Change proto = changeProtoConverter.toProto(change);
@@ -67,8 +67,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("refs/heads/branch 74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("refs/heads/branch 74"))
.setStatus(Change.STATUS_MERGED)
.setCurrentPatchSetId(23)
.setSubject("subject XYZ")
@@ -88,10 +88,10 @@ public class ChangeProtoConverterTest {
public void mandatoryValuesConvertedToProto() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch-74"),
new Timestamp(987654L));
Entities.Change proto = changeProtoConverter.toProto(change);
@@ -106,8 +106,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("refs/heads/branch-74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("refs/heads/branch-74"))
// Default values which can't be unset.
.setCurrentPatchSetId(0)
.setRowVersion(0)
@@ -124,13 +124,13 @@ public class ChangeProtoConverterTest {
public void currentPatchSetIsAlwaysSetWhenConvertedToProto() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch-74"),
new Timestamp(987654L));
// O as ID actually means that no current patch set is present.
- change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 0), null, null);
+ change.setCurrentPatchSet(PatchSet.id(Change.id(14), 0), null, null);
Entities.Change proto = changeProtoConverter.toProto(change);
@@ -144,8 +144,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("refs/heads/branch-74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("refs/heads/branch-74"))
.setCurrentPatchSetId(0)
// Default values which can't be unset.
.setRowVersion(0)
@@ -162,12 +162,12 @@ public class ChangeProtoConverterTest {
public void originalSubjectIsNotAutomaticallySetToSubjectWhenConvertedToProto() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch-74"),
new Timestamp(987654L));
- change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 23), "subject ABC", null);
+ change.setCurrentPatchSet(PatchSet.id(Change.id(14), 23), "subject ABC", null);
Entities.Change proto = changeProtoConverter.toProto(change);
@@ -181,8 +181,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("refs/heads/branch-74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("refs/heads/branch-74"))
.setCurrentPatchSetId(23)
.setSubject("subject ABC")
// Default values which can't be unset.
@@ -199,22 +199,22 @@ public class ChangeProtoConverterTest {
public void allValuesConvertedToProtoAndBackAgain() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch-74"),
new Timestamp(987654L));
change.setLastUpdatedOn(new Timestamp(1234567L));
change.setStatus(Change.Status.MERGED);
change.setCurrentPatchSet(
- new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", "original subject ABC");
+ PatchSet.id(Change.id(14), 23), "subject XYZ", "original subject ABC");
change.setTopic("my topic");
change.setSubmissionId("submission ID 234");
- change.setAssignee(new Account.Id(100001));
+ change.setAssignee(Account.id(100001));
change.setPrivate(true);
change.setWorkInProgress(true);
change.setReviewStarted(true);
- change.setRevertOf(new Change.Id(180));
+ change.setRevertOf(Change.id(180));
Change convertedChange = changeProtoConverter.fromProto(changeProtoConverter.toProto(change));
assertEqualChange(convertedChange, change);
@@ -224,10 +224,10 @@ public class ChangeProtoConverterTest {
public void mandatoryValuesConvertedToProtoAndBackAgain() {
Change change =
new Change(
- new Change.Key("change 1"),
- new Change.Id(14),
- new Account.Id(35),
- new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
+ Change.key("change 1"),
+ Change.id(14),
+ Account.id(35),
+ BranchNameKey.create(Project.nameKey("project 67"), "branch-74"),
new Timestamp(987654L));
Change convertedChange = changeProtoConverter.fromProto(changeProtoConverter.toProto(change));
@@ -269,8 +269,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("branch 74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("branch 74"))
.build();
Change change = changeProtoConverter.fromProto(proto);
@@ -289,8 +289,8 @@ public class ChangeProtoConverterTest {
.setOwnerAccountId(Entities.Account_Id.newBuilder().setId(35))
.setDest(
Entities.Branch_NameKey.newBuilder()
- .setProjectName(Entities.Project_NameKey.newBuilder().setName("project 67"))
- .setBranchName("branch 74"))
+ .setProject(Entities.Project_NameKey.newBuilder().setName("project 67"))
+ .setBranch("branch 74"))
.setStatus(Change.STATUS_MERGED)
.setCurrentPatchSetId(23)
.setSubject("subject XYZ")
@@ -323,7 +323,7 @@ public class ChangeProtoConverterTest {
.put("createdOn", Timestamp.class)
.put("lastUpdatedOn", Timestamp.class)
.put("owner", Account.Id.class)
- .put("dest", Branch.NameKey.class)
+ .put("dest", BranchNameKey.class)
.put("status", char.class)
.put("currentPatchSetId", int.class)
.put("subject", String.class)
diff --git a/javatests/com/google/gerrit/reviewdb/converter/LabelIdProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/LabelIdProtoConverterTest.java
index 41e0f3fdb9..88b9fb651b 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/LabelIdProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/LabelIdProtoConverterTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.LabelId;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.LabelId;
import com.google.protobuf.Parser;
import org.junit.Test;
@@ -30,7 +30,7 @@ public class LabelIdProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- LabelId labelId = new LabelId("Label ID 42");
+ LabelId labelId = LabelId.create("Label ID 42");
Entities.LabelId proto = labelIdProtoConverter.toProto(labelId);
@@ -40,7 +40,7 @@ public class LabelIdProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- LabelId labelId = new LabelId("label-5");
+ LabelId labelId = LabelId.create("label-5");
LabelId convertedLabelId =
labelIdProtoConverter.fromProto(labelIdProtoConverter.toProto(labelId));
@@ -61,7 +61,8 @@ public class LabelIdProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(LabelId.class).hasFields(ImmutableMap.of("id", String.class));
+ public void methodsExistAsExpected() {
+ assertThatSerializedClass(LabelId.class)
+ .hasAutoValueMethods(ImmutableMap.of("id", String.class));
}
}
diff --git a/javatests/com/google/gerrit/entities/converter/ObjectIdProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ObjectIdProtoConverterTest.java
new file mode 100644
index 0000000000..8408b69709
--- /dev/null
+++ b/javatests/com/google/gerrit/entities/converter/ObjectIdProtoConverterTest.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities.converter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.proto.Entities;
+import com.google.gerrit.proto.testing.SerializedClassSubject;
+import com.google.protobuf.Parser;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class ObjectIdProtoConverterTest {
+ private final ObjectIdProtoConverter objectIdProtoConverter = ObjectIdProtoConverter.INSTANCE;
+
+ @Test
+ public void allValuesConvertedToProto() {
+ ObjectId objectId = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ Entities.ObjectId proto = objectIdProtoConverter.toProto(objectId);
+
+ Entities.ObjectId expectedProto =
+ Entities.ObjectId.newBuilder().setName("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef").build();
+ assertThat(proto).isEqualTo(expectedProto);
+ }
+
+ @Test
+ public void allValuesConvertedToProtoAndBackAgain() {
+ ObjectId objectId = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ ObjectId convertedObjectId =
+ objectIdProtoConverter.fromProto(objectIdProtoConverter.toProto(objectId));
+
+ assertThat(convertedObjectId).isEqualTo(objectId);
+ }
+
+ @Test
+ public void protoCanBeParsedFromBytes() throws Exception {
+ Entities.ObjectId proto =
+ Entities.ObjectId.newBuilder().setName("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef").build();
+ byte[] bytes = proto.toByteArray();
+
+ Parser<Entities.ObjectId> parser = objectIdProtoConverter.getParser();
+ Entities.ObjectId parsedProto = parser.parseFrom(bytes);
+
+ assertThat(parsedProto).isEqualTo(proto);
+ }
+
+ /** See {@link SerializedClassSubject} for background and what to do if this test fails. */
+ @Test
+ public void fieldsExistAsExpected() {
+ assertThatSerializedClass(ObjectId.class)
+ .hasFields(
+ ImmutableMap.of(
+ "w1", int.class,
+ "w2", int.class,
+ "w3", int.class,
+ "w4", int.class,
+ "w5", int.class));
+ }
+}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverterTest.java
index d1ed419849..11aac0d08a 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalKeyProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/PatchSetApprovalKeyProtoConverterTest.java
@@ -12,20 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import org.junit.Test;
@@ -37,8 +37,8 @@ public class PatchSetApprovalKeyProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
PatchSetApproval.Key key =
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14), new Account.Id(100013), new LabelId("label-8"));
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8"));
Entities.PatchSetApproval_Key proto = protoConverter.toProto(key);
@@ -47,9 +47,9 @@ public class PatchSetApprovalKeyProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8"))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8"))
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@@ -57,8 +57,8 @@ public class PatchSetApprovalKeyProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
PatchSetApproval.Key key =
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14), new Account.Id(100013), new LabelId("label-8"));
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8"));
PatchSetApproval.Key convertedKey = protoConverter.fromProto(protoConverter.toProto(key));
@@ -72,9 +72,9 @@ public class PatchSetApprovalKeyProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8"))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8"))
.build();
byte[] bytes = proto.toByteArray();
@@ -86,13 +86,13 @@ public class PatchSetApprovalKeyProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
+ public void methodsExistAsExpected() {
assertThatSerializedClass(PatchSetApproval.Key.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("patchSetId", PatchSet.Id.class)
.put("accountId", Account.Id.class)
- .put("categoryId", LabelId.class)
+ .put("labelId", LabelId.class)
.build());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverterTest.java
index 80b2cc2f4d..bca5eea9f4 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/PatchSetApprovalProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/PatchSetApprovalProtoConverterTest.java
@@ -12,24 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.inject.TypeLiteral;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.Date;
+import java.util.Optional;
import org.junit.Test;
public class PatchSetApprovalProtoConverterTest {
@@ -39,16 +41,16 @@ public class PatchSetApprovalProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
PatchSetApproval patchSetApproval =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14),
- new Account.Id(100013),
- new LabelId("label-8")),
- (short) 456,
- new Date(987654L));
- patchSetApproval.setTag("tag-21");
- patchSetApproval.setRealAccountId(new Account.Id(612));
- patchSetApproval.setPostSubmit(true);
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8")))
+ .value(456)
+ .granted(new Date(987654L))
+ .tag("tag-21")
+ .realAccountId(Account.id(612))
+ .postSubmit(true)
+ .build();
Entities.PatchSetApproval proto = protoConverter.toProto(patchSetApproval);
@@ -59,9 +61,9 @@ public class PatchSetApprovalProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8")))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8")))
.setValue(456)
.setGranted(987654L)
.setTag("tag-21")
@@ -74,13 +76,13 @@ public class PatchSetApprovalProtoConverterTest {
@Test
public void mandatoryValuesConvertedToProto() {
PatchSetApproval patchSetApproval =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14),
- new Account.Id(100013),
- new LabelId("label-8")),
- (short) 456,
- new Date(987654L));
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8")))
+ .value(456)
+ .granted(new Date(987654L))
+ .build();
Entities.PatchSetApproval proto = protoConverter.toProto(patchSetApproval);
@@ -91,9 +93,9 @@ public class PatchSetApprovalProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8")))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8")))
.setValue(456)
.setGranted(987654L)
// This value can't be unset when our entity class is given.
@@ -105,16 +107,16 @@ public class PatchSetApprovalProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
PatchSetApproval patchSetApproval =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14),
- new Account.Id(100013),
- new LabelId("label-8")),
- (short) 456,
- new Date(987654L));
- patchSetApproval.setTag("tag-21");
- patchSetApproval.setRealAccountId(new Account.Id(612));
- patchSetApproval.setPostSubmit(true);
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8")))
+ .value(456)
+ .granted(new Date(987654L))
+ .tag("tag-21")
+ .realAccountId(Account.id(612))
+ .postSubmit(true)
+ .build();
PatchSetApproval convertedPatchSetApproval =
protoConverter.fromProto(protoConverter.toProto(patchSetApproval));
@@ -124,13 +126,13 @@ public class PatchSetApprovalProtoConverterTest {
@Test
public void mandatoryValuesConvertedToProtoAndBackAgain() {
PatchSetApproval patchSetApproval =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(new Change.Id(42), 14),
- new Account.Id(100013),
- new LabelId("label-8")),
- (short) 456,
- new Date(987654L));
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(Change.id(42), 14), Account.id(100013), LabelId.create("label-8")))
+ .value(456)
+ .granted(new Date(987654L))
+ .build();
PatchSetApproval convertedPatchSetApproval =
protoConverter.fromProto(protoConverter.toProto(patchSetApproval));
@@ -148,19 +150,19 @@ public class PatchSetApprovalProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8")))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8")))
.build();
PatchSetApproval patchSetApproval = protoConverter.fromProto(proto);
- assertThat(patchSetApproval.getPatchSetId()).isEqualTo(new PatchSet.Id(new Change.Id(42), 14));
- assertThat(patchSetApproval.getAccountId()).isEqualTo(new Account.Id(100013));
- assertThat(patchSetApproval.getLabelId()).isEqualTo(new LabelId("label-8"));
+ assertThat(patchSetApproval.patchSetId()).isEqualTo(PatchSet.id(Change.id(42), 14));
+ assertThat(patchSetApproval.accountId()).isEqualTo(Account.id(100013));
+ assertThat(patchSetApproval.labelId()).isEqualTo(LabelId.create("label-8"));
// Default values for unset protobuf fields which can't be unset in the entity object.
- assertThat(patchSetApproval.getValue()).isEqualTo(0);
- assertThat(patchSetApproval.getGranted()).isEqualTo(new Timestamp(0));
- assertThat(patchSetApproval.isPostSubmit()).isEqualTo(false);
+ assertThat(patchSetApproval.value()).isEqualTo(0);
+ assertThat(patchSetApproval.granted()).isEqualTo(new Timestamp(0));
+ assertThat(patchSetApproval.postSubmit()).isEqualTo(false);
}
@Test
@@ -172,9 +174,9 @@ public class PatchSetApprovalProtoConverterTest {
.setPatchSetId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(42))
- .setPatchSetId(14))
+ .setId(14))
.setAccountId(Entities.Account_Id.newBuilder().setId(100013))
- .setCategoryId(Entities.LabelId.newBuilder().setId("label-8")))
+ .setLabelId(Entities.LabelId.newBuilder().setId("label-8")))
.setValue(456)
.setGranted(987654L)
.build();
@@ -190,14 +192,15 @@ public class PatchSetApprovalProtoConverterTest {
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(PatchSetApproval.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("key", PatchSetApproval.Key.class)
.put("value", short.class)
.put("granted", Timestamp.class)
- .put("tag", String.class)
+ .put("tag", new TypeLiteral<Optional<String>>() {}.getType())
.put("realAccountId", Account.Id.class)
.put("postSubmit", boolean.class)
+ .put("toBuilder", PatchSetApproval.Builder.class)
.build());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/PatchSetIdProtoConverterTest.java
index 1598ef2ce5..530b43150a 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/PatchSetIdProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/PatchSetIdProtoConverterTest.java
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import org.junit.Test;
@@ -33,21 +33,21 @@ public class PatchSetIdProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(103), 73);
+ PatchSet.Id patchSetId = PatchSet.id(Change.id(103), 73);
Entities.PatchSet_Id proto = patchSetIdProtoConverter.toProto(patchSetId);
Entities.PatchSet_Id expectedProto =
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(103))
- .setPatchSetId(73)
+ .setId(73)
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(20), 13);
+ PatchSet.Id patchSetId = PatchSet.id(Change.id(20), 13);
PatchSet.Id convertedPatchSetId =
patchSetIdProtoConverter.fromProto(patchSetIdProtoConverter.toProto(patchSetId));
@@ -60,7 +60,7 @@ public class PatchSetIdProtoConverterTest {
Entities.PatchSet_Id proto =
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(103))
- .setPatchSetId(73)
+ .setId(73)
.build();
byte[] bytes = proto.toByteArray();
@@ -72,12 +72,12 @@ public class PatchSetIdProtoConverterTest {
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
- public void fieldsExistAsExpected() {
+ public void methodsExistAsExpected() {
assertThatSerializedClass(PatchSet.Id.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("changeId", Change.Id.class)
- .put("patchSetId", int.class)
+ .put("id", int.class)
.build());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/PatchSetProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/PatchSetProtoConverterTest.java
index b8d2b1eb5a..2519e750cb 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/PatchSetProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/PatchSetProtoConverterTest.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
@@ -20,15 +20,17 @@ import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatS
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Truth;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.inject.TypeLiteral;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import java.sql.Timestamp;
+import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public class PatchSetProtoConverterTest {
@@ -36,13 +38,16 @@ public class PatchSetProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- PatchSet patchSet = new PatchSet(new PatchSet.Id(new Change.Id(103), 73));
- patchSet.setRevision(new RevId("aabbccddeeff"));
- patchSet.setUploader(new Account.Id(452));
- patchSet.setCreatedOn(new Timestamp(930349320L));
- patchSet.setGroups(ImmutableList.of("group1, group2"));
- patchSet.setPushCertificate("my push certificate");
- patchSet.setDescription("This is a patch set description.");
+ PatchSet patchSet =
+ PatchSet.builder()
+ .id(PatchSet.id(Change.id(103), 73))
+ .commitId(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
+ .uploader(Account.id(452))
+ .createdOn(new Timestamp(930349320L))
+ .groups(ImmutableList.of("group1", " group2"))
+ .pushCertificate("my push certificate")
+ .description("This is a patch set description.")
+ .build();
Entities.PatchSet proto = patchSetProtoConverter.toProto(patchSet);
@@ -51,8 +56,9 @@ public class PatchSetProtoConverterTest {
.setId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(103))
- .setPatchSetId(73))
- .setRevision(Entities.RevId.newBuilder().setId("aabbccddeeff"))
+ .setId(73))
+ .setCommitId(
+ Entities.ObjectId.newBuilder().setName("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
.setUploaderAccountId(Entities.Account_Id.newBuilder().setId(452))
.setCreatedOn(930349320L)
.setGroups("group1, group2")
@@ -64,7 +70,13 @@ public class PatchSetProtoConverterTest {
@Test
public void mandatoryValuesConvertedToProto() {
- PatchSet patchSet = new PatchSet(new PatchSet.Id(new Change.Id(103), 73));
+ PatchSet patchSet =
+ PatchSet.builder()
+ .id(PatchSet.id(Change.id(103), 73))
+ .commitId(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
+ .uploader(Account.id(452))
+ .createdOn(new Timestamp(930349320L))
+ .build();
Entities.PatchSet proto = patchSetProtoConverter.toProto(patchSet);
@@ -73,20 +85,27 @@ public class PatchSetProtoConverterTest {
.setId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(103))
- .setPatchSetId(73))
+ .setId(73))
+ .setCommitId(
+ Entities.ObjectId.newBuilder().setName("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
+ .setUploaderAccountId(Entities.Account_Id.newBuilder().setId(452))
+ .setCreatedOn(930349320L)
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- PatchSet patchSet = new PatchSet(new PatchSet.Id(new Change.Id(103), 73));
- patchSet.setRevision(new RevId("aabbccddeeff"));
- patchSet.setUploader(new Account.Id(452));
- patchSet.setCreatedOn(new Timestamp(930349320L));
- patchSet.setGroups(ImmutableList.of("group1, group2"));
- patchSet.setPushCertificate("my push certificate");
- patchSet.setDescription("This is a patch set description.");
+ PatchSet patchSet =
+ PatchSet.builder()
+ .id(PatchSet.id(Change.id(103), 73))
+ .commitId(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
+ .uploader(Account.id(452))
+ .createdOn(new Timestamp(930349320L))
+ .groups(ImmutableList.of("group1", " group2"))
+ .pushCertificate("my push certificate")
+ .description("This is a patch set description.")
+ .build();
PatchSet convertedPatchSet =
patchSetProtoConverter.fromProto(patchSetProtoConverter.toProto(patchSet));
@@ -95,7 +114,13 @@ public class PatchSetProtoConverterTest {
@Test
public void mandatoryValuesConvertedToProtoAndBackAgain() {
- PatchSet patchSet = new PatchSet(new PatchSet.Id(new Change.Id(103), 73));
+ PatchSet patchSet =
+ PatchSet.builder()
+ .id(PatchSet.id(Change.id(103), 73))
+ .commitId(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))
+ .uploader(Account.id(452))
+ .createdOn(new Timestamp(930349320L))
+ .build();
PatchSet convertedPatchSet =
patchSetProtoConverter.fromProto(patchSetProtoConverter.toProto(patchSet));
@@ -103,13 +128,34 @@ public class PatchSetProtoConverterTest {
}
@Test
+ public void previouslyOptionalValuesMayBeMissingFromProto() {
+ Entities.PatchSet proto =
+ Entities.PatchSet.newBuilder()
+ .setId(
+ Entities.PatchSet_Id.newBuilder()
+ .setChangeId(Entities.Change_Id.newBuilder().setId(103))
+ .setId(73))
+ .build();
+
+ PatchSet convertedPatchSet = patchSetProtoConverter.fromProto(proto);
+ Truth.assertThat(convertedPatchSet)
+ .isEqualTo(
+ PatchSet.builder()
+ .id(PatchSet.id(Change.id(103), 73))
+ .commitId(ObjectId.fromString("0000000000000000000000000000000000000000"))
+ .uploader(Account.id(0))
+ .createdOn(new Timestamp(0))
+ .build());
+ }
+
+ @Test
public void protoCanBeParsedFromBytes() throws Exception {
Entities.PatchSet proto =
Entities.PatchSet.newBuilder()
.setId(
Entities.PatchSet_Id.newBuilder()
.setChangeId(Entities.Change_Id.newBuilder().setId(103))
- .setPatchSetId(73))
+ .setId(73))
.build();
byte[] bytes = proto.toByteArray();
@@ -123,15 +169,15 @@ public class PatchSetProtoConverterTest {
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(PatchSet.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("id", PatchSet.Id.class)
- .put("revision", RevId.class)
+ .put("commitId", ObjectId.class)
.put("uploader", Account.Id.class)
.put("createdOn", Timestamp.class)
- .put("groups", String.class)
- .put("pushCertificate", String.class)
- .put("description", String.class)
+ .put("groups", new TypeLiteral<ImmutableList<String>>() {}.getType())
+ .put("pushCertificate", new TypeLiteral<Optional<String>>() {}.getType())
+ .put("description", new TypeLiteral<Optional<String>>() {}.getType())
.build());
}
}
diff --git a/javatests/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverterTest.java
index 2ad610712e..2f693e6ba4 100644
--- a/javatests/com/google/gerrit/reviewdb/converter/ProjectNameKeyProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ProjectNameKeyProtoConverterTest.java
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.reviewdb.converter;
+package com.google.gerrit.entities.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import org.junit.Test;
@@ -31,7 +31,7 @@ public class ProjectNameKeyProtoConverterTest {
@Test
public void allValuesConvertedToProto() {
- Project.NameKey nameKey = new Project.NameKey("project-72");
+ Project.NameKey nameKey = Project.nameKey("project-72");
Entities.Project_NameKey proto = projectNameKeyProtoConverter.toProto(nameKey);
@@ -42,7 +42,7 @@ public class ProjectNameKeyProtoConverterTest {
@Test
public void allValuesConvertedToProtoAndBackAgain() {
- Project.NameKey nameKey = new Project.NameKey("project-52");
+ Project.NameKey nameKey = Project.nameKey("project-52");
Project.NameKey convertedNameKey =
projectNameKeyProtoConverter.fromProto(projectNameKeyProtoConverter.toProto(nameKey));
diff --git a/javatests/com/google/gerrit/extensions/BUILD b/javatests/com/google/gerrit/extensions/BUILD
index 94e433c346..2202a1168a 100644
--- a/javatests/com/google/gerrit/extensions/BUILD
+++ b/javatests/com/google/gerrit/extensions/BUILD
@@ -7,7 +7,6 @@ junit_tests(
deps = [
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/extensions/common/testing:common-test-util",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
"//lib/guice",
"//lib/truth",
diff --git a/javatests/com/google/gerrit/extensions/api/lfs/LfsDefinitionsTest.java b/javatests/com/google/gerrit/extensions/api/lfs/LfsDefinitionsTest.java
index 86dce049d3..0be10eecdc 100644
--- a/javatests/com/google/gerrit/extensions/api/lfs/LfsDefinitionsTest.java
+++ b/javatests/com/google/gerrit/extensions/api/lfs/LfsDefinitionsTest.java
@@ -16,12 +16,11 @@ package com.google.gerrit.extensions.api.lfs;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
-public class LfsDefinitionsTest extends GerritBaseTests {
+public class LfsDefinitionsTest {
private static final String[] URL_PREFIXES = new String[] {"/", "/a/", "/p/", "/a/p/"};
@Test
diff --git a/javatests/com/google/gerrit/extensions/client/ListOptionTest.java b/javatests/com/google/gerrit/extensions/client/ListOptionTest.java
index 4bb91079cb..5e8c7b6f4e 100644
--- a/javatests/com/google/gerrit/extensions/client/ListOptionTest.java
+++ b/javatests/com/google/gerrit/extensions/client/ListOptionTest.java
@@ -21,11 +21,10 @@ import static com.google.gerrit.extensions.client.ListOptionTest.MyOption.BAZ;
import static com.google.gerrit.extensions.client.ListOptionTest.MyOption.FOO;
import com.google.common.math.IntMath;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.EnumSet;
import org.junit.Test;
-public class ListOptionTest extends GerritBaseTests {
+public class ListOptionTest {
enum MyOption implements ListOption {
FOO(0),
BAR(1),
diff --git a/javatests/com/google/gerrit/extensions/client/RangeTest.java b/javatests/com/google/gerrit/extensions/client/RangeTest.java
index 2c713b5dae..b8938aa50c 100644
--- a/javatests/com/google/gerrit/extensions/client/RangeTest.java
+++ b/javatests/com/google/gerrit/extensions/client/RangeTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.extensions.client;
import static com.google.gerrit.extensions.common.testing.RangeSubject.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class RangeTest extends GerritBaseTests {
+public class RangeTest {
@Test
public void rangeOverMultipleLinesWithSmallerEndCharacterIsValid() {
diff --git a/javatests/com/google/gerrit/extensions/conditions/BUILD b/javatests/com/google/gerrit/extensions/conditions/BUILD
index 7ad2ad3239..e2d595113f 100644
--- a/javatests/com/google/gerrit/extensions/conditions/BUILD
+++ b/javatests/com/google/gerrit/extensions/conditions/BUILD
@@ -5,7 +5,6 @@ junit_tests(
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/extensions:lib",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/extensions/conditions/BooleanConditionTest.java b/javatests/com/google/gerrit/extensions/conditions/BooleanConditionTest.java
index 81cb719e81..f9f1fa85fc 100644
--- a/javatests/com/google/gerrit/extensions/conditions/BooleanConditionTest.java
+++ b/javatests/com/google/gerrit/extensions/conditions/BooleanConditionTest.java
@@ -20,10 +20,9 @@ import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
import static com.google.gerrit.extensions.conditions.BooleanCondition.valueOf;
import static org.junit.Assert.assertEquals;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class BooleanConditionTest extends GerritBaseTests {
+public class BooleanConditionTest {
private static final BooleanCondition NO_TRIVIAL_EVALUATION =
new BooleanCondition() {
diff --git a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
index d9502247ac..0542c35d1c 100644
--- a/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
+++ b/javatests/com/google/gerrit/extensions/registration/DynamicSetTest.java
@@ -17,14 +17,13 @@ package com.google.gerrit.extensions.registration;
import static com.google.common.truth.Truth.assertThat;
import static java.util.stream.Collectors.toSet;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.util.Providers;
import java.util.Iterator;
import org.junit.Test;
-public class DynamicSetTest extends GerritBaseTests {
+public class DynamicSetTest {
// In tests for {@link DynamicSet#contains(Object)}, be sure to avoid
// {@code assertThat(ds).contains(...) @} and
// {@code assertThat(ds).DoesNotContains(...) @} as (since
diff --git a/javatests/com/google/gerrit/git/BUILD b/javatests/com/google/gerrit/git/BUILD
index d57d73fdcf..c2c9ccef99 100644
--- a/javatests/com/google/gerrit/git/BUILD
+++ b/javatests/com/google/gerrit/git/BUILD
@@ -10,11 +10,10 @@ junit_tests(
tags = ["no_windows"],
deps = [
"//java/com/google/gerrit/git",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:junit",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
],
)
@@ -30,7 +29,8 @@ junit_tests(
"//java/com/google/gerrit/git",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/git/ObjectIdsTest.java b/javatests/com/google/gerrit/git/ObjectIdsTest.java
new file mode 100644
index 0000000000..b254d6f78a
--- /dev/null
+++ b/javatests/com/google/gerrit/git/ObjectIdsTest.java
@@ -0,0 +1,156 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+
+import java.util.function.Function;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.junit.Test;
+
+public class ObjectIdsTest {
+ private static final ObjectId ID =
+ ObjectId.fromString("0000000000100000000000000000000000000000");
+ private static final ObjectId AMBIGUOUS_BLOB_ID =
+ ObjectId.fromString("0000000000b36b6aa7ea4b75318ed078f55505c3");
+ private static final ObjectId AMBIGUOUS_TREE_ID =
+ ObjectId.fromString("0000000000cdcf04beb2fab69e65622616294984");
+
+ @Test
+ public void abbreviateNameDefaultLength() throws Exception {
+ assertRuntimeException(() -> abbreviateName(null));
+ assertThat(abbreviateName(ID)).isEqualTo("0000000");
+ assertThat(abbreviateName(AMBIGUOUS_BLOB_ID)).isEqualTo(abbreviateName(ID));
+ assertThat(abbreviateName(AMBIGUOUS_TREE_ID)).isEqualTo(abbreviateName(ID));
+ }
+
+ @Test
+ public void abbreviateNameCustomLength() throws Exception {
+ assertRuntimeException(() -> abbreviateName(null, 1));
+ assertRuntimeException(() -> abbreviateName(ID, -1));
+ assertRuntimeException(() -> abbreviateName(ID, 0));
+ assertRuntimeException(() -> abbreviateName(ID, 41));
+ assertThat(abbreviateName(ID, 5)).isEqualTo("00000");
+ assertThat(abbreviateName(ID, 40)).isEqualTo(ID.name());
+ }
+
+ @Test
+ public void abbreviateNameDefaultLengthWithReader() throws Exception {
+ assertRuntimeException(() -> abbreviateName(ID, null));
+
+ ObjectReader reader = newReaderWithAmbiguousIds();
+ assertThat(abbreviateName(ID, reader)).isEqualTo("00000000001");
+ }
+
+ @Test
+ public void abbreviateNameCustomLengthWithReader() throws Exception {
+ ObjectReader reader = newReaderWithAmbiguousIds();
+ assertRuntimeException(() -> abbreviateName(ID, -1, reader));
+ assertRuntimeException(() -> abbreviateName(ID, 0, reader));
+ assertRuntimeException(() -> abbreviateName(ID, 41, reader));
+ assertRuntimeException(() -> abbreviateName(ID, 5, null));
+
+ String shortest = "00000000001";
+ assertThat(abbreviateName(ID, 1, reader)).isEqualTo(shortest);
+ assertThat(abbreviateName(ID, 7, reader)).isEqualTo(shortest);
+ assertThat(abbreviateName(ID, shortest.length(), reader)).isEqualTo(shortest);
+ assertThat(abbreviateName(ID, shortest.length() + 1, reader)).isEqualTo("000000000010");
+ }
+
+ @Test
+ public void copyOrNull() throws Exception {
+ testCopy(ObjectIds::copyOrNull);
+ assertThat(ObjectIds.copyOrNull(null)).isNull();
+ }
+
+ @Test
+ public void copyOrZero() throws Exception {
+ testCopy(ObjectIds::copyOrZero);
+ assertThat(ObjectIds.copyOrZero(null)).isEqualTo(ObjectId.zeroId());
+ }
+
+ private void testCopy(Function<AnyObjectId, ObjectId> copyFunc) {
+ MyObjectId myId = new MyObjectId(ID);
+ assertThat(myId).isEqualTo(ID);
+
+ ObjectId copy = copyFunc.apply(myId);
+ assertThat(copy).isEqualTo(myId);
+ assertThat(copy).isNotSameInstanceAs(myId);
+ assertThat(copy.getClass()).isEqualTo(ObjectId.class);
+ }
+
+ @Test
+ public void matchesAbbreviation() throws Exception {
+ assertThat(ObjectIds.matchesAbbreviation(null, "")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(null, "0")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(null, "00000")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(null, "not a SHA-1")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(null, ID.name())).isFalse();
+
+ assertThat(ObjectIds.matchesAbbreviation(ID, "")).isTrue();
+ for (int i = 1; i <= OBJECT_ID_STRING_LENGTH; i++) {
+ String prefix = ID.name().substring(0, i);
+ assertWithMessage("match %s against %s", ID.name(), prefix)
+ .that(ObjectIds.matchesAbbreviation(ID, prefix))
+ .isTrue();
+ }
+
+ assertThat(ObjectIds.matchesAbbreviation(ID, "1")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(ID, "x")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(ID, "not a SHA-1")).isFalse();
+ assertThat(ObjectIds.matchesAbbreviation(ID, AMBIGUOUS_BLOB_ID.name())).isFalse();
+ }
+
+ @FunctionalInterface
+ private interface Func {
+ void call() throws Exception;
+ }
+
+ private static void assertRuntimeException(Func func) throws Exception {
+ assertThrows(RuntimeException.class, () -> func.call());
+ }
+
+ private static ObjectReader newReaderWithAmbiguousIds() throws Exception {
+ // Recipe for creating ambiguous IDs courtesy of git core:
+ // https://github.com/git/git/blob/df799f5d99ac51d4fc791d546de3f936088582fc/t/t1512-rev-parse-disambiguation.sh
+ try (TestRepository<Repository> tr =
+ new TestRepository<>(new InMemoryRepository(new DfsRepositoryDescription("repo")))) {
+ String blobData = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n\nb1rwzyc3\n";
+ RevBlob blob = tr.blob(blobData);
+ assertThat(blob.name()).isEqualTo(AMBIGUOUS_BLOB_ID.name());
+ assertThat(tr.tree(tr.file("a0blgqsjc", blob)).name()).isEqualTo(AMBIGUOUS_TREE_ID.name());
+ return tr.getRevWalk().getObjectReader();
+ }
+ }
+
+ private static class MyObjectId extends ObjectId {
+ private static final long serialVersionUID = 1L;
+
+ MyObjectId(AnyObjectId src) {
+ super(src);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/git/RefUpdateUtilRepoTest.java b/javatests/com/google/gerrit/git/RefUpdateUtilRepoTest.java
index 59961ff3e7..60b90f3985 100644
--- a/javatests/com/google/gerrit/git/RefUpdateUtilRepoTest.java
+++ b/javatests/com/google/gerrit/git/RefUpdateUtilRepoTest.java
@@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;
-import com.google.gerrit.testing.GerritBaseTests;
import java.nio.file.Files;
import java.nio.file.Path;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -36,7 +35,7 @@ import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class RefUpdateUtilRepoTest extends GerritBaseTests {
+public class RefUpdateUtilRepoTest {
public enum RepoSetup {
LOCAL_DISK {
@Override
diff --git a/javatests/com/google/gerrit/git/RefUpdateUtilTest.java b/javatests/com/google/gerrit/git/RefUpdateUtilTest.java
index 429583a588..1d021f7ab9 100644
--- a/javatests/com/google/gerrit/git/RefUpdateUtilTest.java
+++ b/javatests/com/google/gerrit/git/RefUpdateUtilTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.git;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.util.function.Consumer;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -33,7 +32,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-public class RefUpdateUtilTest extends GerritBaseTests {
+public class RefUpdateUtilTest {
private static final Consumer<ReceiveCommand> OK = c -> c.setResult(ReceiveCommand.Result.OK);
private static final Consumer<ReceiveCommand> LOCK_FAILURE =
c -> c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
@@ -81,23 +80,18 @@ public class RefUpdateUtilTest extends GerritBaseTests {
@SafeVarargs
private static void assertIoException(Consumer<ReceiveCommand>... resultSetters) {
- try {
- RefUpdateUtil.checkResults(newBatchRefUpdate(resultSetters));
- assert_().fail("expected IOException");
- } catch (IOException e) {
- assertThat(e).isNotInstanceOf(LockFailureException.class);
- }
+ IOException thrown =
+ assertThrows(
+ IOException.class, () -> RefUpdateUtil.checkResults(newBatchRefUpdate(resultSetters)));
+ assertThat(thrown).isNotInstanceOf(LockFailureException.class);
}
@SafeVarargs
private static void assertLockFailureException(Consumer<ReceiveCommand>... resultSetters)
throws Exception {
- try {
- RefUpdateUtil.checkResults(newBatchRefUpdate(resultSetters));
- assert_().fail("expected LockFailureException");
- } catch (LockFailureException e) {
- // Expected.
- }
+ assertThrows(
+ LockFailureException.class,
+ () -> RefUpdateUtil.checkResults(newBatchRefUpdate(resultSetters)));
}
@SafeVarargs
diff --git a/javatests/com/google/gerrit/git/testing/BUILD b/javatests/com/google/gerrit/git/testing/BUILD
index 13091853b2..56e9ec29c0 100644
--- a/javatests/com/google/gerrit/git/testing/BUILD
+++ b/javatests/com/google/gerrit/git/testing/BUILD
@@ -5,7 +5,6 @@ junit_tests(
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/git/testing",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/git/testing/PushResultSubjectTest.java b/javatests/com/google/gerrit/git/testing/PushResultSubjectTest.java
index 5ab52d4e01..3bf815b211 100644
--- a/javatests/com/google/gerrit/git/testing/PushResultSubjectTest.java
+++ b/javatests/com/google/gerrit/git/testing/PushResultSubjectTest.java
@@ -18,10 +18,9 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.git.testing.PushResultSubject.parseProcessed;
import static com.google.gerrit.git.testing.PushResultSubject.trimMessages;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class PushResultSubjectTest extends GerritBaseTests {
+public class PushResultSubjectTest {
@Test
public void testTrimMessages() {
assertThat(trimMessages(null)).isNull();
diff --git a/javatests/com/google/gerrit/gpg/BUILD b/javatests/com/google/gerrit/gpg/BUILD
index f73208d8b6..f459b1a51c 100644
--- a/javatests/com/google/gerrit/gpg/BUILD
+++ b/javatests/com/google/gerrit/gpg/BUILD
@@ -5,16 +5,19 @@ junit_tests(
srcs = glob(["**/*.java"]),
tags = ["no_windows"],
visibility = ["//visibility:public"],
+ runtime_deps = ["//java/com/google/gerrit/lucene"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/gpg",
"//java/com/google/gerrit/gpg/testing:gpg-test-util",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/bouncycastle:bcpg",
"//lib/bouncycastle:bcpg-neverlink",
"//lib/bouncycastle:bcprov",
@@ -22,8 +25,6 @@ junit_tests(
"//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index 220361e205..45b34199b7 100644
--- a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -29,10 +29,10 @@ import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
import com.google.gerrit.gpg.testing.TestKey;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountManager;
@@ -41,7 +41,6 @@ import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
@@ -64,7 +63,7 @@ import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link GerritPublicKeyChecker}. */
-public class GerritPublicKeyCheckerTest extends GerritBaseTests {
+public class GerritPublicKeyCheckerTest {
@Inject @ServerInitiated private Provider<AccountsUpdate> accountsUpdateProvider;
@Inject private AccountManager accountManager;
@@ -199,7 +198,7 @@ public class GerritPublicKeyCheckerTest extends GerritBaseTests {
.update(
"Delete External IDs",
user.getAccountId(),
- (a, u) -> u.deleteExternalIds(a.getExternalIds()));
+ (a, u) -> u.deleteExternalIds(a.externalIds()));
reloadUser();
TestKey key = validKeyWithSecondUserId();
diff --git a/javatests/com/google/gerrit/gpg/PublicKeyCheckerTest.java b/javatests/com/google/gerrit/gpg/PublicKeyCheckerTest.java
index 145b9cff3f..7703fb08d5 100644
--- a/javatests/com/google/gerrit/gpg/PublicKeyCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/PublicKeyCheckerTest.java
@@ -38,7 +38,6 @@ import static org.bouncycastle.openpgp.PGPSignature.DIRECT_KEY;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testing.TestKey;
-import com.google.gerrit.testing.GerritBaseTests;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -60,7 +59,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class PublicKeyCheckerTest extends GerritBaseTests {
+public class PublicKeyCheckerTest {
private InMemoryRepository repo;
private PublicKeyStore store;
diff --git a/javatests/com/google/gerrit/gpg/PublicKeyStoreTest.java b/javatests/com/google/gerrit/gpg/PublicKeyStoreTest.java
index be657528b4..3727d38afd 100644
--- a/javatests/com/google/gerrit/gpg/PublicKeyStoreTest.java
+++ b/javatests/com/google/gerrit/gpg/PublicKeyStoreTest.java
@@ -29,7 +29,6 @@ import static org.junit.Assert.assertTrue;
import com.google.common.collect.Iterators;
import com.google.gerrit.gpg.testing.TestKey;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -51,7 +50,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
-public class PublicKeyStoreTest extends GerritBaseTests {
+public class PublicKeyStoreTest {
private TestRepository<?> tr;
private PublicKeyStore store;
diff --git a/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java b/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
index 67bf0505c3..266f8684fa 100644
--- a/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/PushCertificateCheckerTest.java
@@ -23,7 +23,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import com.google.gerrit.gpg.testing.TestKey;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
@@ -54,7 +53,7 @@ import org.eclipse.jgit.transport.SignedPushConfig;
import org.junit.Before;
import org.junit.Test;
-public class PushCertificateCheckerTest extends GerritBaseTests {
+public class PushCertificateCheckerTest {
private InMemoryRepository repo;
private PublicKeyStore store;
private SignedPushConfig signedPushConfig;
diff --git a/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java b/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
index e2c58d810f..49322485c4 100644
--- a/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
+++ b/javatests/com/google/gerrit/httpd/AllRequestFilterFilterProxyTest.java
@@ -14,14 +14,16 @@
package com.google.gerrit.httpd;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.server.plugins.Plugin;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
import com.google.inject.Key;
@@ -30,13 +32,12 @@ import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.easymock.Capture;
-import org.easymock.EasyMockSupport;
-import org.easymock.IMocksControl;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
-public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
+public class AllRequestFilterFilterProxyTest {
/**
* Set of filters for FilterProxy
*
@@ -82,16 +83,11 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
@Test
public void noFilters() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
- FilterChain chain = ems.createMock(FilterChain.class);
- chain.doFilter(req, res);
-
- ems.replayAll();
+ FilterChain chain = mock(FilterChain.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
@@ -99,25 +95,18 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
- ems.verifyAll();
+ verify(chain).doFilter(req, res);
}
@Test
public void singleFilterNoBubbling() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock("config", FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
- FilterChain chain = ems.createMock("chain", FilterChain.class);
+ FilterChain chain = mock(FilterChain.class);
- AllRequestFilter filter = ems.createStrictMock("filter", AllRequestFilter.class);
- filter.init(config);
- filter.doFilter(eq(req), eq(res), anyObject(FilterChain.class));
- filter.destroy();
-
- ems.replayAll();
+ AllRequestFilter filter = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filter);
@@ -126,63 +115,52 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
- ems.verifyAll();
+ InOrder inorder = inOrder(filter);
+ inorder.verify(filter).init(config);
+ inorder.verify(filter).doFilter(eq(req), eq(res), any(FilterChain.class));
+ inorder.verify(filter).destroy();
}
@Test
public void singleFilterBubbling() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
- IMocksControl mockControl = ems.createStrictControl();
- FilterChain chain = mockControl.createMock(FilterChain.class);
-
- Capture<FilterChain> capturedChain = new Capture<>();
+ FilterChain chain = mock(FilterChain.class);
- AllRequestFilter filter = mockControl.createMock(AllRequestFilter.class);
- filter.init(config);
- filter.doFilter(eq(req), eq(res), capture(capturedChain));
- chain.doFilter(req, res);
- filter.destroy();
+ ArgumentCaptor<FilterChain> capturedChain = ArgumentCaptor.forClass(FilterChain.class);
- ems.replayAll();
+ AllRequestFilter filter = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filter);
+ InOrder inorder = inOrder(filter, chain);
+
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
+
+ inorder.verify(filter).init(config);
+ inorder.verify(filter).doFilter(eq(req), eq(res), capturedChain.capture());
capturedChain.getValue().doFilter(req, res);
- filterProxy.destroy();
+ inorder.verify(chain).doFilter(req, res);
- ems.verifyAll();
+ filterProxy.destroy();
+ inorder.verify(filter).destroy();
}
@Test
public void twoFiltersNoBubbling() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
- IMocksControl mockControl = ems.createStrictControl();
- FilterChain chain = mockControl.createMock(FilterChain.class);
+ FilterChain chain = mock(FilterChain.class);
- AllRequestFilter filterA = mockControl.createMock(AllRequestFilter.class);
-
- AllRequestFilter filterB = mockControl.createMock(AllRequestFilter.class);
- filterA.init(config);
- filterB.init(config);
- filterA.doFilter(eq(req), eq(res), anyObject(FilterChain.class));
- filterA.destroy();
- filterB.destroy();
-
- ems.replayAll();
+ AllRequestFilter filterA = mock(AllRequestFilter.class);
+ AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
addFilter(filterB);
@@ -191,35 +169,27 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
filterProxy.doFilter(req, res, chain);
filterProxy.destroy();
- ems.verifyAll();
+ InOrder inorder = inOrder(filterA, filterB);
+ inorder.verify(filterA).init(config);
+ inorder.verify(filterB).init(config);
+ inorder.verify(filterA).doFilter(eq(req), eq(res), any(FilterChain.class));
+ inorder.verify(filterA).destroy();
+ inorder.verify(filterB).destroy();
}
@Test
public void twoFiltersBubbling() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req = new FakeHttpServletRequest();
HttpServletResponse res = new FakeHttpServletResponse();
- IMocksControl mockControl = ems.createStrictControl();
- FilterChain chain = mockControl.createMock(FilterChain.class);
-
- Capture<FilterChain> capturedChainA = new Capture<>();
- Capture<FilterChain> capturedChainB = new Capture<>();
-
- AllRequestFilter filterA = mockControl.createMock(AllRequestFilter.class);
- AllRequestFilter filterB = mockControl.createMock(AllRequestFilter.class);
+ FilterChain chain = mock(FilterChain.class);
- filterA.init(config);
- filterB.init(config);
- filterA.doFilter(eq(req), eq(res), capture(capturedChainA));
- filterB.doFilter(eq(req), eq(res), capture(capturedChainB));
- chain.doFilter(req, res);
- filterA.destroy();
- filterB.destroy();
+ ArgumentCaptor<FilterChain> capturedChainA = ArgumentCaptor.forClass(FilterChain.class);
+ ArgumentCaptor<FilterChain> capturedChainB = ArgumentCaptor.forClass(FilterChain.class);
- ems.replayAll();
+ AllRequestFilter filterA = mock(AllRequestFilter.class);
+ AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
@@ -227,70 +197,69 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
filterProxy.init(config);
filterProxy.doFilter(req, res, chain);
+
+ InOrder inorder = inOrder(filterA, filterB, chain);
+
+ inorder.verify(filterA).init(config);
+ inorder.verify(filterB).init(config);
+ inorder.verify(filterA).doFilter(eq(req), eq(res), capturedChainA.capture());
capturedChainA.getValue().doFilter(req, res);
+ inorder.verify(filterB).doFilter(eq(req), eq(res), capturedChainB.capture());
capturedChainB.getValue().doFilter(req, res);
- filterProxy.destroy();
+ inorder.verify(chain).doFilter(req, res);
- ems.verifyAll();
+ filterProxy.destroy();
+ inorder.verify(filterA).destroy();
+ inorder.verify(filterB).destroy();
}
@Test
public void postponedLoading() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req1 = new FakeHttpServletRequest();
HttpServletRequest req2 = new FakeHttpServletRequest();
HttpServletResponse res1 = new FakeHttpServletResponse();
HttpServletResponse res2 = new FakeHttpServletResponse();
- IMocksControl mockControl = ems.createStrictControl();
- FilterChain chain = mockControl.createMock("chain", FilterChain.class);
+ FilterChain chain = mock(FilterChain.class);
- Capture<FilterChain> capturedChainA1 = new Capture<>();
- Capture<FilterChain> capturedChainA2 = new Capture<>();
- Capture<FilterChain> capturedChainB = new Capture<>();
+ ArgumentCaptor<FilterChain> capturedChainA1 = ArgumentCaptor.forClass(FilterChain.class);
+ ArgumentCaptor<FilterChain> capturedChainA2 = ArgumentCaptor.forClass(FilterChain.class);
+ ArgumentCaptor<FilterChain> capturedChainB = ArgumentCaptor.forClass(FilterChain.class);
- AllRequestFilter filterA = mockControl.createMock("filterA", AllRequestFilter.class);
- AllRequestFilter filterB = mockControl.createMock("filterB", AllRequestFilter.class);
+ AllRequestFilter filterA = mock(AllRequestFilter.class);
+ AllRequestFilter filterB = mock(AllRequestFilter.class);
- filterA.init(config);
- filterA.doFilter(eq(req1), eq(res1), capture(capturedChainA1));
- chain.doFilter(req1, res1);
-
- filterA.doFilter(eq(req2), eq(res2), capture(capturedChainA2));
- filterB.init(config); // <-- This is crucial part. filterB got loaded
- // after filterProxy's init finished. Nonetheless filterB gets initialized.
- filterB.doFilter(eq(req2), eq(res2), capture(capturedChainB));
- chain.doFilter(req2, res2);
-
- filterA.destroy();
- filterB.destroy();
-
- ems.replayAll();
+ InOrder inorder = inOrder(filterA, filterB, chain);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
addFilter(filterA);
filterProxy.init(config);
filterProxy.doFilter(req1, res1, chain);
+ inorder.verify(filterA).init(config);
+ inorder.verify(filterA).doFilter(eq(req1), eq(res1), capturedChainA1.capture());
capturedChainA1.getValue().doFilter(req1, res1);
+ inorder.verify(chain).doFilter(req1, res1);
addFilter(filterB); // <-- Adds filter after filterProxy's init got called.
filterProxy.doFilter(req2, res2, chain);
+ // after filterProxy's init finished. Nonetheless filterB gets initialized.
+ inorder.verify(filterA).doFilter(eq(req2), eq(res2), capturedChainA2.capture());
capturedChainA2.getValue().doFilter(req2, res2);
+ inorder.verify(filterB).init(config); // <-- This is crucial part. filterB got loaded
+ inorder.verify(filterB).doFilter(eq(req2), eq(res2), capturedChainB.capture());
capturedChainB.getValue().doFilter(req2, res2);
+ inorder.verify(chain).doFilter(req2, res2);
filterProxy.destroy();
-
- ems.verifyAll();
+ inorder.verify(filterA).destroy();
+ inorder.verify(filterB).destroy();
}
@Test
public void dynamicUnloading() throws Exception {
- EasyMockSupport ems = new EasyMockSupport();
-
- FilterConfig config = ems.createMock(FilterConfig.class);
+ FilterConfig config = mock(FilterConfig.class);
HttpServletRequest req1 = new FakeHttpServletRequest();
HttpServletRequest req2 = new FakeHttpServletRequest();
HttpServletRequest req3 = new FakeHttpServletRequest();
@@ -298,64 +267,62 @@ public class AllRequestFilterFilterProxyTest extends GerritBaseTests {
HttpServletResponse res2 = new FakeHttpServletResponse();
HttpServletResponse res3 = new FakeHttpServletResponse();
- Plugin plugin = ems.createMock(Plugin.class);
-
- IMocksControl mockControl = ems.createStrictControl();
- FilterChain chain = mockControl.createMock("chain", FilterChain.class);
-
- Capture<FilterChain> capturedChainA1 = new Capture<>();
- Capture<FilterChain> capturedChainB1 = new Capture<>();
- Capture<FilterChain> capturedChainB2 = new Capture<>();
-
- AllRequestFilter filterA = mockControl.createMock("filterA", AllRequestFilter.class);
- AllRequestFilter filterB = mockControl.createMock("filterB", AllRequestFilter.class);
+ Plugin plugin = mock(Plugin.class);
- filterA.init(config);
- filterB.init(config);
+ FilterChain chain = mock(FilterChain.class);
- filterA.doFilter(eq(req1), eq(res1), capture(capturedChainA1));
- filterB.doFilter(eq(req1), eq(res1), capture(capturedChainB1));
- chain.doFilter(req1, res1);
+ ArgumentCaptor<FilterChain> capturedChainA1 = ArgumentCaptor.forClass(FilterChain.class);
+ ArgumentCaptor<FilterChain> capturedChainB1 = ArgumentCaptor.forClass(FilterChain.class);
+ ArgumentCaptor<FilterChain> capturedChainB2 = ArgumentCaptor.forClass(FilterChain.class);
- filterA.destroy(); // Cleaning up of filterA after it got unloaded
-
- filterB.doFilter(eq(req2), eq(res2), capture(capturedChainB2));
- chain.doFilter(req2, res2);
-
- filterB.destroy(); // Cleaning up of filterA after it got unloaded
-
- chain.doFilter(req3, res3);
-
- ems.replayAll();
+ AllRequestFilter filterA = mock(AllRequestFilter.class);
+ AllRequestFilter filterB = mock(AllRequestFilter.class);
AllRequestFilter.FilterProxy filterProxy = getFilterProxy();
ReloadableRegistrationHandle<AllRequestFilter> handleFilterA = addFilter(filterA);
ReloadableRegistrationHandle<AllRequestFilter> handleFilterB = addFilter(filterB);
+ InOrder inorder = inOrder(filterA, filterB, chain);
+
filterProxy.init(config);
+ inorder.verify(filterA).init(config);
+ inorder.verify(filterB).init(config);
+
// Request #1 with filterA and filterB
filterProxy.doFilter(req1, res1, chain);
+ inorder.verify(filterA).doFilter(eq(req1), eq(res1), capturedChainA1.capture());
capturedChainA1.getValue().doFilter(req1, res1);
+ inorder.verify(filterB).doFilter(eq(req1), eq(res1), capturedChainB1.capture());
capturedChainB1.getValue().doFilter(req1, res1);
+ inorder.verify(chain).doFilter(req1, res1);
// Unloading filterA
handleFilterA.remove();
filterProxy.onStopPlugin(plugin);
- // Request #1 only with filterB
+ inorder.verify(filterA).destroy(); // Cleaning up of filterA after it got unloaded
+
+ // Request #2 only with filterB
filterProxy.doFilter(req2, res2, chain);
- capturedChainA1.getValue().doFilter(req2, res2);
+
+ inorder.verify(filterB).doFilter(eq(req2), eq(res2), capturedChainB2.capture());
+ inorder.verify(filterA, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
+ capturedChainB2.getValue().doFilter(req2, res2);
+ inorder.verify(chain).doFilter(req2, res2);
// Unloading filterB
handleFilterB.remove();
filterProxy.onStopPlugin(plugin);
- // Request #1 with no additional filters
+ inorder.verify(filterB).destroy(); // Cleaning up of filterA after it got unloaded
+
+ // Request #3 with no additional filters
filterProxy.doFilter(req3, res3, chain);
+ inorder.verify(chain).doFilter(req3, res3);
+ inorder.verify(filterA, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
+ inorder.verify(filterB, never()).doFilter(eq(req2), eq(res2), any(FilterChain.class));
filterProxy.destroy();
-
- ems.verifyAll();
}
}
diff --git a/javatests/com/google/gerrit/httpd/AllowRenderInFrameFilterTest.java b/javatests/com/google/gerrit/httpd/AllowRenderInFrameFilterTest.java
index 0d6d482032..26798290c0 100644
--- a/javatests/com/google/gerrit/httpd/AllowRenderInFrameFilterTest.java
+++ b/javatests/com/google/gerrit/httpd/AllowRenderInFrameFilterTest.java
@@ -17,53 +17,41 @@ package com.google.gerrit.httpd;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.httpd.AllowRenderInFrameFilter.X_FRAME_OPTIONS_HEADER_NAME;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static org.easymock.EasyMock.expectLastCall;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import com.google.gerrit.httpd.AllowRenderInFrameFilter.XFrameOption;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.easymock.EasyMockSupport;
import org.eclipse.jgit.lib.Config;
-import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
-public class AllowRenderInFrameFilterTest extends GerritBaseTests {
+@RunWith(MockitoJUnitRunner.class)
+public class AllowRenderInFrameFilterTest {
- Config cfg;
- ServletRequest request;
- HttpServletResponse response;
- FilterChain filterChain;
-
- EasyMockSupport ems = new EasyMockSupport();
-
- @Before
- public void setup() throws IOException, ServletException {
- cfg = new Config();
- request = ems.createMock(ServletRequest.class);
- response = ems.createMock(HttpServletResponse.class);
- filterChain = ems.createMock(FilterChain.class);
- ems.resetAll();
- // we want to make sure that doFilter is always called
- filterChain.doFilter(request, response);
- }
+ private Config cfg = new Config();
+ @Mock ServletRequest request;
+ @Mock HttpServletResponse response;
+ @Mock FilterChain filterChain;
@Test
public void shouldDenyInFrameRenderingWhenCanRenderInFrameIsFalse()
throws IOException, ServletException {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", false);
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
}
@Test
@@ -72,14 +60,10 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", false);
cfg.setEnum("gerrit", null, "xframeOption", XFrameOption.SAMEORIGIN);
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
}
@Test
@@ -88,14 +72,10 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", false);
cfg.setEnum("gerrit", null, "xframeOption", XFrameOption.ALLOW);
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "DENY");
}
@Test
@@ -103,14 +83,10 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
throws IOException, ServletException {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", true);
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
}
@Test
@@ -118,12 +94,11 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
throws IOException, ServletException {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", true);
cfg.setEnum("gerrit", null, "xframeOption", XFrameOption.ALLOW);
- ems.replayAll();
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, never()).addHeader(eq(X_FRAME_OPTIONS_HEADER_NAME), anyString());
}
@Test
@@ -132,14 +107,10 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", true);
cfg.setEnum("gerrit", null, "xframeOption", XFrameOption.SAMEORIGIN);
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
}
@Test
@@ -147,14 +118,10 @@ public class AllowRenderInFrameFilterTest extends GerritBaseTests {
cfg.setBoolean("gerrit", null, "canLoadInIFrame", true);
cfg.setString("gerrit", null, "xframeOption", "sameOrigin");
- response.addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
- expectLastCall().times(1);
- ems.replayAll();
-
AllowRenderInFrameFilter objectUnderTest = new AllowRenderInFrameFilter(cfg);
objectUnderTest.doFilter(request, response, filterChain);
- ems.verifyAll();
+ verify(response, times(1)).addHeader(X_FRAME_OPTIONS_HEADER_NAME, "SAMEORIGIN");
}
@Test
diff --git a/javatests/com/google/gerrit/httpd/BUILD b/javatests/com/google/gerrit/httpd/BUILD
index f2c8ded82a..75f005e62a 100644
--- a/javatests/com/google/gerrit/httpd/BUILD
+++ b/javatests/com/google/gerrit/httpd/BUILD
@@ -4,23 +4,24 @@ junit_tests(
name = "httpd_tests",
srcs = glob(["**/*.java"]),
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/httpd",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
- "//java/com/google/gerrit/util/http",
"//javatests/com/google/gerrit/util/http/testutil",
"//lib:gson",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:jimfs",
"//lib:junit",
"//lib:servlet-api-without-neverlink",
"//lib:soy",
- "//lib/easymock",
"//lib/guice",
"//lib/guice:guice-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib/mockito",
"//lib/truth",
+ "//lib/truth:truth-java8-extension",
],
)
diff --git a/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java b/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java
new file mode 100644
index 0000000000..2f0fafa00b
--- /dev/null
+++ b/javatests/com/google/gerrit/httpd/ProjectBasicAuthFilterTest.java
@@ -0,0 +1,293 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
+import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectBasicAuthFilterTest {
+ private static final Base64.Encoder B64_ENC = Base64.getEncoder();
+ private static final Account.Id AUTH_ACCOUNT_ID = Account.id(1000);
+ private static final String AUTH_USER = "johndoe";
+ private static final String AUTH_USER_B64 =
+ B64_ENC.encodeToString(AUTH_USER.getBytes(StandardCharsets.UTF_8));
+ private static final String AUTH_PASSWORD = "jd123";
+ private static final String GERRIT_COOKIE_KEY = "GerritAccount";
+ private static final String AUTH_COOKIE_VALUE = "gerritcookie";
+ private static final ExternalId AUTH_USER_PASSWORD_EXTERNAL_ID =
+ ExternalId.createWithPassword(
+ ExternalId.Key.create(ExternalId.SCHEME_USERNAME, AUTH_USER),
+ AUTH_ACCOUNT_ID,
+ null,
+ AUTH_PASSWORD);
+
+ @Mock private DynamicItem<WebSession> webSessionItem;
+
+ @Mock private AccountCache accountCache;
+
+ @Mock private AccountState accountState;
+
+ @Mock private Account account;
+
+ @Mock private AccountManager accountManager;
+
+ @Mock private AuthConfig authConfig;
+
+ @Mock private FilterChain chain;
+
+ @Captor private ArgumentCaptor<HttpServletResponse> filterResponseCaptor;
+
+ @Mock private IdentifiedUser.RequestFactory userRequestFactory;
+
+ @Mock private WebSessionManager webSessionManager;
+
+ private WebSession webSession;
+ private FakeHttpServletRequest req;
+ private HttpServletResponse res;
+ private AuthResult authSuccessful;
+
+ @Before
+ public void setUp() throws Exception {
+ req = new FakeHttpServletRequest("gerrit.example.com", 80, "", "");
+ res = new FakeHttpServletResponse();
+
+ authSuccessful =
+ new AuthResult(AUTH_ACCOUNT_ID, ExternalId.Key.create("username", AUTH_USER), false);
+ doReturn(Optional.of(accountState)).when(accountCache).getByUsername(AUTH_USER);
+ doReturn(Optional.of(accountState)).when(accountCache).get(AUTH_ACCOUNT_ID);
+ doReturn(account).when(accountState).account();
+ doReturn(true).when(account).isActive();
+ doReturn(authSuccessful).when(accountManager).authenticate(any());
+
+ doReturn(new WebSessionManager.Key(AUTH_COOKIE_VALUE)).when(webSessionManager).createKey(any());
+ WebSessionManager.Val webSessionValue =
+ new WebSessionManager.Val(AUTH_ACCOUNT_ID, 0L, false, null, 0L, "", "");
+ doReturn(webSessionValue)
+ .when(webSessionManager)
+ .createVal(any(), any(), eq(false), any(), any(), any());
+ }
+
+ @Test
+ public void shouldAllowAnonymousRequest() throws Exception {
+ initMockedWebSession();
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(chain).doFilter(eq(req), filterResponseCaptor.capture());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ }
+
+ @Test
+ public void shouldRequestAuthenticationForBasicAuthRequest() throws Exception {
+ initMockedWebSession();
+ req.addHeader("Authorization", "Basic " + AUTH_USER_B64);
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(chain, never()).doFilter(any(), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+ assertThat(res.getHeader("WWW-Authenticate")).contains("Basic realm=");
+ }
+
+ @Test
+ public void shouldAuthenticateSucessfullyAgainstRealmAndReturnCookie() throws Exception {
+ initWebSessionWithoutCookie();
+ requestBasicAuth(req);
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ doReturn(true).when(account).isActive();
+ doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(accountManager).authenticate(any());
+
+ verify(chain).doFilter(eq(req), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ assertThat(res.getHeader("Set-Cookie")).contains(GERRIT_COOKIE_KEY);
+ }
+
+ @Test
+ public void shouldValidateUserPasswordAndNotReturnCookie() throws Exception {
+ initWebSessionWithoutCookie();
+ requestBasicAuth(req);
+ initMockedUsernamePasswordExternalId();
+ doReturn(GitBasicAuthPolicy.HTTP).when(authConfig).getGitBasicAuthPolicy();
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(accountManager, never()).authenticate(any());
+
+ verify(chain).doFilter(eq(req), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ assertThat(res.getHeader("Set-Cookie")).isNull();
+ }
+
+ @Test
+ public void shouldNotReauthenticateForGitPostRequest() throws Exception {
+ req.setPathInfo("/a/project.git/git-upload-pack");
+ req.setMethod("POST");
+ req.addHeader("Content-Type", "application/x-git-upload-pack-request");
+ doFilterForRequestWhenAlreadySignedIn();
+
+ verify(accountManager, never()).authenticate(any());
+ verify(chain).doFilter(eq(req), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ }
+
+ @Test
+ public void shouldReauthenticateForRegularRequestEvenIfAlreadySignedIn() throws Exception {
+ doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
+ doFilterForRequestWhenAlreadySignedIn();
+
+ verify(accountManager).authenticate(any());
+ verify(chain).doFilter(eq(req), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ }
+
+ @Test
+ public void shouldReauthenticateEvenIfHasExistingCookie() throws Exception {
+ initWebSessionWithCookie("GerritAccount=" + AUTH_COOKIE_VALUE);
+ res.setStatus(HttpServletResponse.SC_OK);
+ requestBasicAuth(req);
+ doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(accountManager).authenticate(any());
+ verify(chain).doFilter(eq(req), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+ }
+
+ @Test
+ public void shouldFailedAuthenticationAgainstRealm() throws Exception {
+ initMockedWebSession();
+ requestBasicAuth(req);
+
+ doReturn(true).when(account).isActive();
+ doThrow(new AccountException("Authentication error")).when(accountManager).authenticate(any());
+ doReturn(GitBasicAuthPolicy.LDAP).when(authConfig).getGitBasicAuthPolicy();
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+
+ verify(accountManager).authenticate(any());
+
+ verify(chain, never()).doFilter(any(), any());
+ assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+
+ private void doFilterForRequestWhenAlreadySignedIn()
+ throws IOException, ServletException, AccountException {
+ initMockedWebSession();
+ doReturn(true).when(account).isActive();
+ doReturn(true).when(webSession).isSignedIn();
+ doReturn(authSuccessful).when(accountManager).authenticate(any());
+ requestBasicAuth(req);
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ ProjectBasicAuthFilter basicAuthFilter =
+ new ProjectBasicAuthFilter(webSessionItem, accountCache, accountManager, authConfig);
+
+ basicAuthFilter.doFilter(req, res, chain);
+ }
+
+ private void initWebSessionWithCookie(String cookie) {
+ req.addHeader("Cookie", cookie);
+ initWebSessionWithoutCookie();
+ }
+
+ private void initWebSessionWithoutCookie() {
+ webSession =
+ new CacheBasedWebSession(
+ req, res, webSessionManager, authConfig, null, userRequestFactory, accountCache) {};
+ doReturn(webSession).when(webSessionItem).get();
+ }
+
+ private void initMockedWebSession() {
+ webSession = mock(WebSession.class);
+ doReturn(webSession).when(webSessionItem).get();
+ }
+
+ private void initMockedUsernamePasswordExternalId() {
+ doReturn(ImmutableSet.builder().add(AUTH_USER_PASSWORD_EXTERNAL_ID).build())
+ .when(accountState)
+ .externalIds();
+ }
+
+ private void requestBasicAuth(FakeHttpServletRequest fakeReq) {
+ fakeReq.addHeader(
+ "Authorization",
+ "Basic "
+ + B64_ENC.encodeToString(
+ (AUTH_USER + ":" + AUTH_PASSWORD).getBytes(StandardCharsets.UTF_8)));
+ }
+}
diff --git a/javatests/com/google/gerrit/httpd/RemoteUserUtilTest.java b/javatests/com/google/gerrit/httpd/RemoteUserUtilTest.java
index e19085d46e..f012ee34ac 100644
--- a/javatests/com/google/gerrit/httpd/RemoteUserUtilTest.java
+++ b/javatests/com/google/gerrit/httpd/RemoteUserUtilTest.java
@@ -17,10 +17,9 @@ package com.google.gerrit.httpd;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.httpd.RemoteUserUtil.extractUsername;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class RemoteUserUtilTest extends GerritBaseTests {
+public class RemoteUserUtilTest {
@Test
public void testExtractUsername() {
assertThat(extractUsername(null)).isNull();
diff --git a/javatests/com/google/gerrit/httpd/plugins/ContextMapperTest.java b/javatests/com/google/gerrit/httpd/plugins/ContextMapperTest.java
index 2de3788992..684a2415bd 100644
--- a/javatests/com/google/gerrit/httpd/plugins/ContextMapperTest.java
+++ b/javatests/com/google/gerrit/httpd/plugins/ContextMapperTest.java
@@ -16,12 +16,11 @@ package com.google.gerrit.httpd.plugins;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
-public class ContextMapperTest extends GerritBaseTests {
+public class ContextMapperTest {
private static final String CONTEXT = "/context";
private static final String PLUGIN_NAME = "my-plugin";
diff --git a/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java b/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java
new file mode 100644
index 0000000000..15c66eba68
--- /dev/null
+++ b/javatests/com/google/gerrit/httpd/raw/IndexHtmlUtilTest.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.raw;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.httpd.raw.IndexHtmlUtil.staticTemplateData;
+
+import com.google.template.soy.data.SanitizedContent;
+import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
+import java.util.HashMap;
+import org.junit.Test;
+
+public class IndexHtmlUtilTest {
+
+ @Test
+ public void noPathAndNoCDN() throws Exception {
+ assertThat(
+ staticTemplateData(
+ "http://example.com/", null, null, new HashMap<>(), IndexHtmlUtilTest::ordain))
+ .containsExactly("canonicalPath", "", "staticResourcePath", ordain(""));
+ }
+
+ @Test
+ public void pathAndNoCDN() throws Exception {
+ assertThat(
+ staticTemplateData(
+ "http://example.com/gerrit/",
+ null,
+ null,
+ new HashMap<>(),
+ IndexHtmlUtilTest::ordain))
+ .containsExactly("canonicalPath", "/gerrit", "staticResourcePath", ordain("/gerrit"));
+ }
+
+ @Test
+ public void noPathAndCDN() throws Exception {
+ assertThat(
+ staticTemplateData(
+ "http://example.com/",
+ "http://my-cdn.com/foo/bar/",
+ null,
+ new HashMap<>(),
+ IndexHtmlUtilTest::ordain))
+ .containsExactly(
+ "canonicalPath", "", "staticResourcePath", ordain("http://my-cdn.com/foo/bar/"));
+ }
+
+ @Test
+ public void pathAndCDN() throws Exception {
+ assertThat(
+ staticTemplateData(
+ "http://example.com/gerrit",
+ "http://my-cdn.com/foo/bar/",
+ null,
+ new HashMap<>(),
+ IndexHtmlUtilTest::ordain))
+ .containsExactly(
+ "canonicalPath", "/gerrit", "staticResourcePath", ordain("http://my-cdn.com/foo/bar/"));
+ }
+
+ private static SanitizedContent ordain(String s) {
+ return UnsafeSanitizedContentOrdainer.ordainAsSafe(
+ s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
+ }
+}
diff --git a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
index 340f690d76..77ab58b1ae 100644
--- a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
+++ b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
@@ -15,66 +15,52 @@
package com.google.gerrit.httpd.raw;
import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import com.google.gerrit.testing.GerritBaseTests;
-import java.net.URISyntaxException;
-import java.util.Map;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.accounts.Accounts;
+import com.google.gerrit.extensions.api.config.Config;
+import com.google.gerrit.extensions.api.config.Server;
+import com.google.gerrit.extensions.common.ServerInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
+import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
import org.junit.Test;
-public class IndexServletTest extends GerritBaseTests {
- static class TestIndexServlet extends IndexServlet {
- private static final long serialVersionUID = 1L;
-
- TestIndexServlet(String canonicalURL, String cdnPath, String faviconPath)
- throws URISyntaxException {
- super(canonicalURL, cdnPath, faviconPath);
- }
-
- String getIndexSource() {
- return new String(indexSource, UTF_8);
- }
- }
+public class IndexServletTest {
@Test
- public void noPathAndNoCDN() throws URISyntaxException {
- Map<String, Object> data = IndexServlet.getTemplateData("http://example.com/", null, null);
- assertThat(data.get("canonicalPath")).isEqualTo("");
- assertThat(data.get("staticResourcePath").toString()).isEqualTo("");
- }
+ public void renderTemplate() throws Exception {
+ Accounts accountsApi = mock(Accounts.class);
+ when(accountsApi.self()).thenThrow(new AuthException("user needs to be authenticated"));
- @Test
- public void pathAndNoCDN() throws URISyntaxException {
- Map<String, Object> data =
- IndexServlet.getTemplateData("http://example.com/gerrit/", null, null);
- assertThat(data.get("canonicalPath")).isEqualTo("/gerrit");
- assertThat(data.get("staticResourcePath").toString()).isEqualTo("/gerrit");
- }
+ Server serverApi = mock(Server.class);
+ when(serverApi.getVersion()).thenReturn("123");
+ when(serverApi.topMenus()).thenReturn(ImmutableList.of());
+ ServerInfo serverInfo = new ServerInfo();
+ serverInfo.defaultTheme = "my-default-theme";
+ when(serverApi.getInfo()).thenReturn(serverInfo);
- @Test
- public void noPathAndCDN() throws URISyntaxException {
- Map<String, Object> data =
- IndexServlet.getTemplateData("http://example.com/", "http://my-cdn.com/foo/bar/", null);
- assertThat(data.get("canonicalPath")).isEqualTo("");
- assertThat(data.get("staticResourcePath").toString()).isEqualTo("http://my-cdn.com/foo/bar/");
- }
+ Config configApi = mock(Config.class);
+ when(configApi.server()).thenReturn(serverApi);
- @Test
- public void pathAndCDN() throws URISyntaxException {
- Map<String, Object> data =
- IndexServlet.getTemplateData(
- "http://example.com/gerrit", "http://my-cdn.com/foo/bar/", null);
- assertThat(data.get("canonicalPath")).isEqualTo("/gerrit");
- assertThat(data.get("staticResourcePath").toString()).isEqualTo("http://my-cdn.com/foo/bar/");
- }
+ GerritApi gerritApi = mock(GerritApi.class);
+ when(gerritApi.accounts()).thenReturn(accountsApi);
+ when(gerritApi.config()).thenReturn(configApi);
- @Test
- public void renderTemplate() throws URISyntaxException {
String testCanonicalUrl = "foo-url";
String testCdnPath = "bar-cdn";
String testFaviconURL = "zaz-url";
- TestIndexServlet servlet = new TestIndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL);
- String output = servlet.getIndexSource();
+ IndexServlet servlet =
+ new IndexServlet(testCanonicalUrl, testCdnPath, testFaviconURL, gerritApi);
+
+ FakeHttpServletResponse response = new FakeHttpServletResponse();
+
+ servlet.doGet(new FakeHttpServletRequest(), response);
+
+ String output = response.getActualBodyString();
assertThat(output).contains("<!DOCTYPE html>");
assertThat(output).contains("window.CANONICAL_PATH = '" + testCanonicalUrl);
assertThat(output).contains("<link rel=\"preload\" href=\"" + testCdnPath);
@@ -84,5 +70,12 @@ public class IndexServletTest extends GerritBaseTests {
+ testCanonicalUrl
+ "/"
+ testFaviconURL);
+ assertThat(output)
+ .contains(
+ "window.INITIAL_DATA = JSON.parse("
+ + "'\\x7b\\x22\\/config\\/server\\/version\\x22: \\x22123\\x22, "
+ + "\\x22\\/config\\/server\\/info\\x22: \\x7b\\x22default_theme\\x22:"
+ + "\\x22my-default-theme\\x22\\x7d, \\x22\\/config\\/server\\/top-menus\\x22: "
+ + "\\x5b\\x5d\\x7d');</script>");
}
}
diff --git a/javatests/com/google/gerrit/httpd/raw/ResourceServletTest.java b/javatests/com/google/gerrit/httpd/raw/ResourceServletTest.java
index cfcc1d0984..dd594d61d5 100644
--- a/javatests/com/google/gerrit/httpd/raw/ResourceServletTest.java
+++ b/javatests/com/google/gerrit/httpd/raw/ResourceServletTest.java
@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.raw;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -28,7 +29,6 @@ import com.google.common.io.ByteStreams;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
import java.io.ByteArrayInputStream;
@@ -45,7 +45,7 @@ import java.util.zip.GZIPInputStream;
import org.junit.Before;
import org.junit.Test;
-public class ResourceServletTest extends GerritBaseTests {
+public class ResourceServletTest {
private static Cache<Path, Resource> newCache(int size) {
return CacheBuilder.newBuilder().maximumSize(size).recordStats().build();
}
@@ -336,8 +336,8 @@ public class ResourceServletTest extends GerritBaseTests {
}
private static void assertCacheHits(Cache<?, ?> cache, int hits, int misses) {
- assertThat(cache.stats().hitCount()).named("hits").isEqualTo(hits);
- assertThat(cache.stats().missCount()).named("misses").isEqualTo(misses);
+ assertWithMessage("hits").that(cache.stats().hitCount()).isEqualTo(hits);
+ assertWithMessage("misses").that(cache.stats().missCount()).isEqualTo(misses);
}
private static void assertCacheable(FakeHttpServletResponse res, boolean revalidate) {
diff --git a/javatests/com/google/gerrit/httpd/restapi/HttpLogRedactTest.java b/javatests/com/google/gerrit/httpd/restapi/HttpLogRedactTest.java
index fa3eaea5e2..fb1ebd9551 100644
--- a/javatests/com/google/gerrit/httpd/restapi/HttpLogRedactTest.java
+++ b/javatests/com/google/gerrit/httpd/restapi/HttpLogRedactTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.httpd.restapi;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class HttpLogRedactTest extends GerritBaseTests {
+public class HttpLogRedactTest {
@Test
public void redactAuth() {
assertThat(LogRedactUtil.redactQueryString("query=status:open")).isEqualTo("query=status:open");
diff --git a/javatests/com/google/gerrit/httpd/restapi/ParameterParserTest.java b/javatests/com/google/gerrit/httpd/restapi/ParameterParserTest.java
index 30d318b654..a550ac7c76 100644
--- a/javatests/com/google/gerrit/httpd/restapi/ParameterParserTest.java
+++ b/javatests/com/google/gerrit/httpd/restapi/ParameterParserTest.java
@@ -15,21 +15,20 @@
package com.google.gerrit.httpd.restapi;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.httpd.restapi.ParameterParser.QueryParams;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.junit.Test;
-public class ParameterParserTest extends GerritBaseTests {
+public class ParameterParserTest {
@Test
public void convertFormToJson() throws BadRequestException {
JsonObject obj =
@@ -110,35 +109,26 @@ public class ParameterParserTest extends GerritBaseTests {
public void rejectDuplicateMethod() {
FakeHttpServletRequest req = new FakeHttpServletRequest();
req.setQueryString("$m=PUT&$m=DELETE");
- try {
- ParameterParser.getQueryParams(req);
- fail("expected BadRequestException");
- } catch (BadRequestException bad) {
- assertThat(bad).hasMessageThat().isEqualTo("duplicate $m");
- }
+ BadRequestException bad =
+ assertThrows(BadRequestException.class, () -> ParameterParser.getQueryParams(req));
+ assertThat(bad).hasMessageThat().isEqualTo("duplicate $m");
}
@Test
public void rejectDuplicateContentType() {
FakeHttpServletRequest req = new FakeHttpServletRequest();
req.setQueryString("$ct=json&$ct=string");
- try {
- ParameterParser.getQueryParams(req);
- fail("expected BadRequestException");
- } catch (BadRequestException bad) {
- assertThat(bad).hasMessageThat().isEqualTo("duplicate $ct");
- }
+ BadRequestException bad =
+ assertThrows(BadRequestException.class, () -> ParameterParser.getQueryParams(req));
+ assertThat(bad).hasMessageThat().isEqualTo("duplicate $ct");
}
@Test
public void rejectInvalidMethod() {
FakeHttpServletRequest req = new FakeHttpServletRequest();
req.setQueryString("$m=CONNECT");
- try {
- ParameterParser.getQueryParams(req);
- fail("expected BadRequestException");
- } catch (BadRequestException bad) {
- assertThat(bad).hasMessageThat().isEqualTo("invalid $m");
- }
+ BadRequestException bad =
+ assertThrows(BadRequestException.class, () -> ParameterParser.getQueryParams(req));
+ assertThat(bad).hasMessageThat().isEqualTo("invalid $m");
}
}
diff --git a/javatests/com/google/gerrit/index/BUILD b/javatests/com/google/gerrit/index/BUILD
index a1f60de66b..861e768710 100644
--- a/javatests/com/google/gerrit/index/BUILD
+++ b/javatests/com/google/gerrit/index/BUILD
@@ -10,11 +10,12 @@ junit_tests(
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/index/query/testing",
+ "//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib:junit",
"//lib/antlr:java-runtime",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/index/SchemaUtilTest.java b/javatests/com/google/gerrit/index/SchemaUtilTest.java
index d6b8421c3a..698e00a54a 100644
--- a/javatests/com/google/gerrit/index/SchemaUtilTest.java
+++ b/javatests/com/google/gerrit/index/SchemaUtilTest.java
@@ -18,13 +18,13 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.index.SchemaUtil.getNameParts;
import static com.google.gerrit.index.SchemaUtil.getPersonParts;
import static com.google.gerrit.index.SchemaUtil.schema;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Map;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Test;
-public class SchemaUtilTest extends GerritBaseTests {
+public class SchemaUtilTest {
static class TestSchemas {
static final Schema<String> V1 = schema();
static final Schema<String> V2 = schema();
@@ -43,9 +43,9 @@ public class SchemaUtilTest extends GerritBaseTests {
assertThat(all.get(1)).isEqualTo(TestSchemas.V1);
assertThat(all.get(2)).isEqualTo(TestSchemas.V2);
assertThat(all.get(4)).isEqualTo(TestSchemas.V4);
-
- exception.expect(IllegalArgumentException.class);
- SchemaUtil.schemasFromClass(TestSchemas.class, Object.class);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> SchemaUtil.schemasFromClass(TestSchemas.class, Object.class));
}
@Test
diff --git a/javatests/com/google/gerrit/index/query/AndPredicateTest.java b/javatests/com/google/gerrit/index/query/AndPredicateTest.java
index 21098b315e..16828dd242 100644
--- a/javatests/com/google/gerrit/index/query/AndPredicateTest.java
+++ b/javatests/com/google/gerrit/index/query/AndPredicateTest.java
@@ -16,12 +16,12 @@ package com.google.gerrit.index.query;
import static com.google.common.collect.ImmutableList.of;
import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import java.util.List;
import org.junit.Test;
@@ -43,28 +43,13 @@ public class AndPredicateTest extends PredicateTest {
final TestPredicate b = f("author", "bob");
final Predicate<String> n = and(a, b);
- try {
- n.getChildren().clear();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().clear());
assertChildren("clear", n, of(a, b));
- try {
- n.getChildren().remove(0);
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().remove(0));
assertChildren("remove(0)", n, of(a, b));
- try {
- n.getChildren().iterator().remove();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().iterator().remove());
assertChildren("iterator().remove()", n, of(a, b));
}
diff --git a/javatests/com/google/gerrit/index/query/FieldPredicateTest.java b/javatests/com/google/gerrit/index/query/FieldPredicateTest.java
index 805f31c194..2d2c99e5ee 100644
--- a/javatests/com/google/gerrit/index/query/FieldPredicateTest.java
+++ b/javatests/com/google/gerrit/index/query/FieldPredicateTest.java
@@ -14,6 +14,8 @@
package com.google.gerrit.index.query;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
@@ -61,8 +63,9 @@ public class FieldPredicateTest extends PredicateTest {
assertSame(f, f.copy(Collections.emptyList()));
assertSame(f, f.copy(f.getChildren()));
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage("Expected 0 children");
- f.copy(Collections.singleton(f("owner", "bob")));
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class, () -> f.copy(Collections.singleton(f("owner", "bob"))));
+ assertThat(thrown).hasMessageThat().contains("Expected 0 children");
}
}
diff --git a/javatests/com/google/gerrit/index/query/LazyDataSourceTest.java b/javatests/com/google/gerrit/index/query/LazyDataSourceTest.java
new file mode 100644
index 0000000000..7064f648d4
--- /dev/null
+++ b/javatests/com/google/gerrit/index/query/LazyDataSourceTest.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.query;
+
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gerrit.server.query.change.OrSource;
+import java.util.Collection;
+import java.util.Iterator;
+import org.junit.Test;
+
+/**
+ * Tests that boolean data sources are lazy in that they don't call {@link ResultSet#toList()} or
+ * {@link ResultSet#toList()}. This is necessary because it allows Gerrit to send multiple queries
+ * to the index in parallel, have the results come in asynchronously and wait for them only when we
+ * call aforementioned methods on the {@link ResultSet}.
+ */
+public class LazyDataSourceTest {
+
+ /** Helper to avoid a mock which would be hard to create because of the type inference. */
+ static class LazyPredicate extends Predicate<ChangeData> implements ChangeDataSource {
+ @Override
+ public int getCardinality() {
+ return 1;
+ }
+
+ @Override
+ public ResultSet<ChangeData> read() {
+ return new FailingResultSet<>();
+ }
+
+ @Override
+ public ResultSet<FieldBundle> readRaw() {
+ return new FailingResultSet<>();
+ }
+
+ @Override
+ public Predicate<ChangeData> copy(Collection<? extends Predicate<ChangeData>> children) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public boolean hasChange() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+ }
+
+ /** Implementation that throws {@link AssertionError} when accessing results. */
+ static class FailingResultSet<T> implements ResultSet<T> {
+ @Override
+ public Iterator<T> iterator() {
+ throw new AssertionError(
+ "called iterator() on the result set, but shouldn't have because the data source must be lazy");
+ }
+
+ @Override
+ public ImmutableList<T> toList() {
+ throw new AssertionError(
+ "called toList() on the result set, but shouldn't have because the data source must be lazy");
+ }
+
+ @Override
+ public void close() {
+ // No-op
+ }
+ }
+
+ @Test
+ public void andSourceIsLazy() {
+ AndSource<ChangeData> and = new AndSource<>(ImmutableList.of(new LazyPredicate()));
+ ResultSet<ChangeData> resultSet = and.read();
+ assertThrows(AssertionError.class, () -> resultSet.toList());
+ }
+
+ @Test
+ public void orSourceIsLazy() {
+ OrSource or = new OrSource(ImmutableList.of(new LazyPredicate()));
+ ResultSet<ChangeData> resultSet = or.read();
+ assertThrows(AssertionError.class, () -> resultSet.toList());
+ }
+}
diff --git a/javatests/com/google/gerrit/index/query/NotPredicateTest.java b/javatests/com/google/gerrit/index/query/NotPredicateTest.java
index d10d2df498..3d1839d2a4 100644
--- a/javatests/com/google/gerrit/index/query/NotPredicateTest.java
+++ b/javatests/com/google/gerrit/index/query/NotPredicateTest.java
@@ -16,12 +16,12 @@ package com.google.gerrit.index.query;
import static com.google.gerrit.index.query.Predicate.and;
import static com.google.gerrit.index.query.Predicate.not;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.List;
@@ -50,26 +50,14 @@ public class NotPredicateTest extends PredicateTest {
final TestPredicate p = f("author", "bob");
final Predicate<String> n = not(p);
- try {
- n.getChildren().clear();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- assertOnlyChild("clear", p, n);
- }
-
- try {
- n.getChildren().remove(0);
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- assertOnlyChild("remove(0)", p, n);
- }
-
- try {
- n.getChildren().iterator().remove();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- assertOnlyChild("remove()", p, n);
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().clear());
+ assertOnlyChild("clear", p, n);
+
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().remove(0));
+ assertOnlyChild("remove(0)", p, n);
+
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().iterator().remove());
+ assertOnlyChild("remove()", p, n);
}
private static void assertOnlyChild(String o, Predicate<String> c, Predicate<String> p) {
@@ -112,18 +100,11 @@ public class NotPredicateTest extends PredicateTest {
assertNotSame(n, n.copy(sb));
assertEquals(sb, n.copy(sb).getChildren());
- try {
- n.copy(Collections.emptyList());
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- assertEquals("Expected exactly one child", e.getMessage());
- }
-
- try {
- n.copy(and(a, b).getChildren());
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- assertEquals("Expected exactly one child", e.getMessage());
- }
+ IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () -> n.copy(Collections.emptyList()));
+ assertEquals("Expected exactly one child", e.getMessage());
+
+ e = assertThrows(IllegalArgumentException.class, () -> n.copy(and(a, b).getChildren()));
+ assertEquals("Expected exactly one child", e.getMessage());
}
}
diff --git a/javatests/com/google/gerrit/index/query/OrPredicateTest.java b/javatests/com/google/gerrit/index/query/OrPredicateTest.java
index 255a3f8cce..1cbcb7567a 100644
--- a/javatests/com/google/gerrit/index/query/OrPredicateTest.java
+++ b/javatests/com/google/gerrit/index/query/OrPredicateTest.java
@@ -16,12 +16,12 @@ package com.google.gerrit.index.query;
import static com.google.common.collect.ImmutableList.of;
import static com.google.gerrit.index.query.Predicate.or;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import java.util.List;
import org.junit.Test;
@@ -43,28 +43,13 @@ public class OrPredicateTest extends PredicateTest {
final TestPredicate b = f("author", "bob");
final Predicate<String> n = or(a, b);
- try {
- n.getChildren().clear();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().clear());
assertChildren("clear", n, of(a, b));
- try {
- n.getChildren().remove(0);
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().remove(0));
assertChildren("remove(0)", n, of(a, b));
- try {
- n.getChildren().iterator().remove();
- fail("Expected UnsupportedOperationException");
- } catch (UnsupportedOperationException e) {
- // Expected
- }
+ assertThrows(UnsupportedOperationException.class, () -> n.getChildren().iterator().remove());
assertChildren("iterator().remove()", n, of(a, b));
}
diff --git a/javatests/com/google/gerrit/index/query/PredicateTest.java b/javatests/com/google/gerrit/index/query/PredicateTest.java
index 2295a60004..3ec7f1362d 100644
--- a/javatests/com/google/gerrit/index/query/PredicateTest.java
+++ b/javatests/com/google/gerrit/index/query/PredicateTest.java
@@ -14,11 +14,10 @@
package com.google.gerrit.index.query;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Ignore;
@Ignore
-public abstract class PredicateTest extends GerritBaseTests {
+public abstract class PredicateTest {
protected static final class TestPredicate extends OperatorPredicate<String> {
protected TestPredicate(String name, String value) {
super(name, value);
diff --git a/javatests/com/google/gerrit/index/query/QueryBuilderTest.java b/javatests/com/google/gerrit/index/query/QueryBuilderTest.java
index 6a397dca89..f65375995e 100644
--- a/javatests/com/google/gerrit/index/query/QueryBuilderTest.java
+++ b/javatests/com/google/gerrit/index/query/QueryBuilderTest.java
@@ -17,12 +17,11 @@ package com.google.gerrit.index.query;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.truth.ThrowableSubject;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Collection;
import java.util.Objects;
import org.junit.Test;
-public class QueryBuilderTest extends GerritBaseTests {
+public class QueryBuilderTest {
private static class TestPredicate extends Predicate<Object> {
private final String field;
private final String value;
diff --git a/javatests/com/google/gerrit/index/query/QueryParserTest.java b/javatests/com/google/gerrit/index/query/QueryParserTest.java
index b4dd1ee0c1..776a2c4bb0 100644
--- a/javatests/com/google/gerrit/index/query/QueryParserTest.java
+++ b/javatests/com/google/gerrit/index/query/QueryParserTest.java
@@ -14,7 +14,6 @@
package com.google.gerrit.index.query;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.index.query.QueryParser.AND;
import static com.google.gerrit.index.query.QueryParser.COLON;
import static com.google.gerrit.index.query.QueryParser.DEFAULT_FIELD;
@@ -22,12 +21,12 @@ import static com.google.gerrit.index.query.QueryParser.FIELD_NAME;
import static com.google.gerrit.index.query.QueryParser.SINGLE_WORD;
import static com.google.gerrit.index.query.QueryParser.parse;
import static com.google.gerrit.index.query.testing.TreeSubject.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.antlr.runtime.tree.Tree;
import org.junit.Test;
-public class QueryParserTest extends GerritBaseTests {
+public class QueryParserTest {
@Test
public void fieldNameAndValue() throws Exception {
Tree r = parse("project:tools/gerrit");
@@ -206,11 +205,6 @@ public class QueryParserTest extends GerritBaseTests {
}
private static void assertParseFails(String query) {
- try {
- parse(query);
- assert_().fail("expected parse to fail: %s", query);
- } catch (QueryParseException e) {
- // Expected.
- }
+ assertThrows(QueryParseException.class, () -> parse(query));
}
}
diff --git a/javatests/com/google/gerrit/integration/git/BUILD b/javatests/com/google/gerrit/integration/git/BUILD
index 4f1be091bf..28755af59e 100644
--- a/javatests/com/google/gerrit/integration/git/BUILD
+++ b/javatests/com/google/gerrit/integration/git/BUILD
@@ -1,6 +1,12 @@
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
acceptance_tests(
+ srcs = ["GitProtocolV2IT.java"],
+ group = "protocol-v2",
+ labels = ["git-protocol-v2"],
+)
+
+acceptance_tests(
srcs = ["UploadArchiveIT.java"],
group = "upload-archive",
labels = ["git-upload-archive"],
diff --git a/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
new file mode 100644
index 0000000000..8577c16faa
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
@@ -0,0 +1,382 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.integration.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.GerritServer.TestSshServerAddress;
+import com.google.gerrit.acceptance.GitClientVersion;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.inject.Inject;
+import java.io.File;
+import java.net.InetSocketAddress;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+@UseSsh
+public class GitProtocolV2IT extends StandaloneSiteTest {
+ private static final String ADMIN_PASSWORD = "secret";
+ private final String[] SSH_KEYGEN_CMD =
+ new String[] {"ssh-keygen", "-t", "rsa", "-q", "-P", "", "-f"};
+ private final String[] GIT_LS_REMOTE =
+ new String[] {"git", "-c", "protocol.version=2", "ls-remote", "-o", "trace=12345"};
+ private final String[] GIT_CLONE_MIRROR =
+ new String[] {"git", "-c", "protocol.version=2", "clone", "--mirror"};
+ private final String[] GIT_FETCH = new String[] {"git", "-c", "protocol.version=2", "fetch"};
+ private final String[] GIT_INIT = new String[] {"git", "init"};
+ private final String GIT_SSH_COMMAND =
+ "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i";
+
+ @Inject private GerritApi gApi;
+ @Inject private AccountCreator accountCreator;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private @TestSshServerAddress InetSocketAddress sshAddress;
+ @Inject private @GerritServerConfig Config config;
+ @Inject private AllProjectsName allProjectsName;
+
+ @BeforeClass
+ public static void assertGitClientVersion() throws Exception {
+ // Minimum required git-core version that supports wire protocol v2 is 2.18.0
+ GitClientVersion requiredGitVersion = new GitClientVersion(2, 18, 0);
+ GitClientVersion actualGitVersion =
+ new GitClientVersion(execute(ImmutableList.of("git", "version"), new File("/")));
+ // If git client version cannot be updated, consider to skip this tests. Due to
+ // an existing issue in bazel, JUnit assumption violation feature cannot be used.
+ assertThat(actualGitVersion).isAtLeast(requiredGitVersion);
+ }
+
+ @Test
+ public void testGitWireProtocolV2WithSsh() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+
+ // Create project
+ Project.NameKey project = Project.nameKey("foo");
+ gApi.projects().create(project.get());
+
+ // Set up project permission
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(deny(Permission.READ).ref("refs/*").group(SystemGroupBackend.ANONYMOUS_USERS))
+ .add(
+ allow(Permission.READ)
+ .ref("refs/heads/master")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
+
+ // Set protocol.version=2 in target repository
+ execute(
+ ImmutableList.of("git", "config", "protocol.version", "2"),
+ sitePaths.site_path.resolve("git").resolve(project.get() + Constants.DOT_GIT).toFile());
+
+ // Retrieve HTTP url
+ String url = config.getString("gerrit", null, "canonicalweburl");
+ String urlDestinationTemplate =
+ url.substring(0, 7)
+ + "%s:secret@"
+ + url.substring(7, url.length())
+ + "/a/"
+ + project.get();
+
+ // Retrieve SSH host and port
+ String sshDestinationTemplate =
+ "ssh://%s@" + sshAddress.getHostName() + ":" + sshAddress.getPort() + "/" + project.get();
+
+ // Admin user was already created by the base class
+ setUpUserAuthentication(admin.username());
+
+ // Create non-admin user
+ TestAccount user = accountCreator.user();
+ setUpUserAuthentication(user.username());
+
+ // Prepare data for new change on master branch
+ ChangeInput in = new ChangeInput(project.get(), "master", "Test public change");
+ in.newBranch = true;
+
+ // Create new change and retrieve SHA1 for the created patch set
+ String commit =
+ gApi.changes()
+ .id(gApi.changes().create(in).info().changeId)
+ .current()
+ .commit(false)
+ .commit;
+
+ // Prepare new change on secret branch
+ in = new ChangeInput(project.get(), ADMIN_PASSWORD, "Test secret change");
+ in.newBranch = true;
+
+ // Create new change and retrieve SHA1 for the created patch set
+ String secretCommit =
+ gApi.changes()
+ .id(gApi.changes().create(in).info().changeId)
+ .current()
+ .commit(false)
+ .commit;
+
+ // Read refs from target repository using git wire protocol v2 over HTTP for admin user
+ String out =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_LS_REMOTE)
+ .add(String.format(urlDestinationTemplate, admin.username()))
+ .build(),
+ ImmutableMap.of("GIT_TRACE_PACKET", "1"));
+
+ assertGitProtocolV2Refs(commit, out);
+ assertThat(out).contains(secretCommit);
+
+ // Read refs from target repository using git wire protocol v2 over SSH for admin user
+ out =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_LS_REMOTE)
+ .add(String.format(sshDestinationTemplate, admin.username()))
+ .build(),
+ ImmutableMap.of(
+ "GIT_SSH_COMMAND",
+ GIT_SSH_COMMAND
+ + sitePaths.data_dir.resolve(String.format("id_rsa_%s", admin.username())),
+ "GIT_TRACE_PACKET",
+ "1"));
+
+ assertGitProtocolV2Refs(commit, out);
+ assertThat(out).contains(secretCommit);
+
+ // Read refs from target repository using git wire protocol v2 over HTTP for non-admin user
+ out =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_LS_REMOTE)
+ .add(String.format(urlDestinationTemplate, user.username()))
+ .build(),
+ ImmutableMap.of("GIT_TRACE_PACKET", "1"));
+
+ assertGitProtocolV2Refs(commit, out);
+ assertThat(out).doesNotContain(secretCommit);
+
+ // Read refs from target repository using git wire protocol v2 over SSH for non-admin user
+ out =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_LS_REMOTE)
+ .add(String.format(sshDestinationTemplate, user.username()))
+ .build(),
+ ImmutableMap.of(
+ "GIT_SSH_COMMAND",
+ GIT_SSH_COMMAND
+ + sitePaths.data_dir.resolve(String.format("id_rsa_%s", user.username())),
+ "GIT_TRACE_PACKET",
+ "1"));
+
+ assertGitProtocolV2Refs(commit, out);
+ assertThat(out).doesNotContain(secretCommit);
+ }
+ }
+
+ @Test
+ public void testGitWireProtocolV2HidesRefMetaConfig() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+ String url = config.getString("gerrit", null, "canonicalweburl");
+
+ // Create project
+ Project.NameKey allRefsVisibleProject = Project.nameKey("all-refs-visible");
+ gApi.projects().create(allRefsVisibleProject.get());
+
+ // Set protocol.version=2 in target repository
+ execute(
+ ImmutableList.of("git", "config", "protocol.version", "2"),
+ sitePaths
+ .site_path
+ .resolve("git")
+ .resolve(allRefsVisibleProject.get() + Constants.DOT_GIT)
+ .toFile());
+
+ // Set up project permission to allow reading all refs
+ projectOperations
+ .project(allRefsVisibleProject)
+ .forUpdate()
+ .add(allow(Permission.READ).ref("refs/heads/*").group(SystemGroupBackend.ANONYMOUS_USERS))
+ .add(
+ allow(Permission.READ)
+ .ref("refs/changes/*")
+ .group(SystemGroupBackend.ANONYMOUS_USERS))
+ .update();
+
+ // Create new change and retrieve refs for the created patch set
+ ChangeInput visibleChangeIn =
+ new ChangeInput(allRefsVisibleProject.get(), "master", "Test public change");
+ visibleChangeIn.newBranch = true;
+ int visibleChangeNumber = gApi.changes().create(visibleChangeIn).info()._number;
+ Change.Id changeId = Change.id(visibleChangeNumber);
+ String visibleChangeNumberRef = RefNames.patchSetRef(PatchSet.id(changeId, 1));
+ String visibleChangeNumberMetaRef = RefNames.changeMetaRef(changeId);
+
+ // Read refs from target repository using git wire protocol v2 over HTTP anonymously
+ String outAnonymousLsRemote =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_CLONE_MIRROR)
+ .add(url + "/" + allRefsVisibleProject.get())
+ .build(),
+ ImmutableMap.of("GIT_TRACE_PACKET", "1"));
+
+ assertThat(outAnonymousLsRemote).contains("git< version 2");
+ assertThat(outAnonymousLsRemote).doesNotContain(RefNames.REFS_CONFIG);
+ assertThat(outAnonymousLsRemote).contains(visibleChangeNumberRef);
+ assertThat(outAnonymousLsRemote).contains(visibleChangeNumberMetaRef);
+ }
+ }
+
+ @Test
+ public void testGitWireProtocolV2FetchIndividualRef() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+
+ // Setup admin password
+ gApi.accounts().id(admin.username()).setHttpPassword(ADMIN_PASSWORD);
+
+ // Get authenticated Git/HTTP URL
+ String urlWithCredentials =
+ config
+ .getString("gerrit", null, "canonicalweburl")
+ .replace("http://", "http://" + admin.username() + ":" + ADMIN_PASSWORD + "@");
+
+ // Create project
+ Project.NameKey privateProject = Project.nameKey("private-project");
+ gApi.projects().create(privateProject.get());
+
+ // Set protocol.version=2 in target repository
+ execute(
+ ImmutableList.of("git", "config", "protocol.version", "2"),
+ sitePaths
+ .site_path
+ .resolve("git")
+ .resolve(privateProject.get() + Constants.DOT_GIT)
+ .toFile());
+
+ // Disallow general read permissions for anonymous users
+ projectOperations
+ .project(allProjectsName)
+ .forUpdate()
+ .add(deny(Permission.READ).ref("refs/*").group(SystemGroupBackend.ANONYMOUS_USERS))
+ .add(
+ allow(Permission.READ)
+ .ref("refs/heads/master")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
+
+ // Set up project permission to allow registered users fetching changes/*
+ projectOperations
+ .project(privateProject)
+ .forUpdate()
+ .add(
+ allow(Permission.READ)
+ .ref("refs/changes/*")
+ .group(SystemGroupBackend.REGISTERED_USERS))
+ .update();
+
+ // Create new change and retrieve refs for the created patch set
+ ChangeInput visibleChangeIn =
+ new ChangeInput(privateProject.get(), "master", "Test private change");
+ visibleChangeIn.newBranch = true;
+ int visibleChangeNumber = gApi.changes().create(visibleChangeIn).info()._number;
+ Change.Id changeId = Change.id(visibleChangeNumber);
+ String visibleChangeNumberRef = RefNames.patchSetRef(PatchSet.id(changeId, 1));
+
+ // Fetch a single ref using git wire protocol v2 over HTTP with authentication
+ execute(GIT_INIT);
+
+ String outFetchRef =
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_FETCH)
+ .add(urlWithCredentials + "/" + privateProject.get())
+ .add(visibleChangeNumberRef)
+ .build(),
+ ImmutableMap.of("GIT_TRACE_PACKET", "1"));
+
+ assertThat(outFetchRef).contains("git< version 2");
+ assertThat(outFetchRef).contains(visibleChangeNumberRef);
+ }
+ }
+
+ private void setUpUserAuthentication(String username) throws Exception {
+ // Assign HTTP password to user
+ gApi.accounts().id(username).setHttpPassword(ADMIN_PASSWORD);
+
+ // Generate private/public key for user
+ execute(
+ ImmutableList.<String>builder()
+ .add(SSH_KEYGEN_CMD)
+ .add(String.format("id_rsa_%s", username))
+ .build());
+
+ // Read the content of generated public key and add it for the user in Gerrit
+ gApi.accounts()
+ .id(username)
+ .addSshKey(
+ new String(
+ java.nio.file.Files.readAllBytes(
+ sitePaths.data_dir.resolve(String.format("id_rsa_%s.pub", username))),
+ UTF_8));
+ }
+
+ private static void assertGitProtocolV2Refs(String commit, String out) {
+ assertThat(out).contains("git< version 2");
+ assertThat(out).contains("refs/changes/01/1/1");
+ assertThat(out).contains("refs/changes/01/1/meta");
+ assertThat(out).contains(commit);
+ }
+
+ private String execute(String... cmds) throws Exception {
+ return execute(ImmutableList.<String>builder().add(cmds).build());
+ }
+
+ private String execute(ImmutableList<String> cmd) throws Exception {
+ return execute(cmd, sitePaths.data_dir.toFile(), ImmutableMap.of());
+ }
+
+ private String execute(ImmutableList<String> cmd, ImmutableMap<String, String> env)
+ throws Exception {
+ return execute(cmd, sitePaths.data_dir.toFile(), env);
+ }
+
+ private static String execute(ImmutableList<String> cmd, File dir) throws Exception {
+ return execute(cmd, dir, ImmutableMap.of());
+ }
+}
diff --git a/javatests/com/google/gerrit/integration/git/UploadArchiveIT.java b/javatests/com/google/gerrit/integration/git/UploadArchiveIT.java
index 18a78b0664..2968111f38 100644
--- a/javatests/com/google/gerrit/integration/git/UploadArchiveIT.java
+++ b/javatests/com/google/gerrit/integration/git/UploadArchiveIT.java
@@ -27,11 +27,11 @@ import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.StandaloneSiteTest;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -133,7 +133,7 @@ public class UploadArchiveIT extends StandaloneSiteTest {
private void setUpTestHarness(ServerContext ctx) throws RestApiException, Exception {
ctx.getInjector().injectMembers(this);
- project = new Project.NameKey("upload-archive-project-test");
+ project = Project.nameKey("upload-archive-project-test");
gApi.projects().create(project.get());
setUpAuthentication();
sshDestination =
diff --git a/javatests/com/google/gerrit/integration/ssh/BUILD b/javatests/com/google/gerrit/integration/ssh/BUILD
new file mode 100644
index 0000000000..72f07854ba
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/ssh/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = ["NoShellIT.java"],
+ group = "no-shell",
+ labels = ["ssh-no-shell"],
+)
diff --git a/javatests/com/google/gerrit/integration/ssh/NoShellIT.java b/javatests/com/google/gerrit/integration/ssh/NoShellIT.java
new file mode 100644
index 0000000000..2bbbf1a30e
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/ssh/NoShellIT.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.integration.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.GerritServer.TestSshServerAddress;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+
+@NoHttpd
+@UseSsh
+public class NoShellIT extends StandaloneSiteTest {
+ private static final String[] SSH_KEYGEN_CMD =
+ new String[] {"ssh-keygen", "-t", "rsa", "-q", "-P", "", "-f"};
+
+ @Inject private GerritApi gApi;
+ @Inject private @TestSshServerAddress InetSocketAddress sshAddress;
+
+ private String identityPath;
+
+ @Test(timeout = 60000)
+ public void verifyCommandsIsClosed() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ setUpTestHarness(ctx);
+
+ IOException thrown = assertThrows(IOException.class, () -> execute(cmd()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Hi Administrator, you have successfully connected over SSH.");
+ }
+ }
+
+ private void setUpTestHarness(ServerContext ctx) throws Exception {
+ ctx.getInjector().injectMembers(this);
+ setUpAuthentication();
+ identityPath = sitePaths.data_dir.resolve(String.format("id_rsa_%s", "admin")).toString();
+ }
+
+ private void setUpAuthentication() throws Exception {
+ execute(
+ ImmutableList.<String>builder()
+ .add(SSH_KEYGEN_CMD)
+ .add(String.format("id_rsa_%s", "admin"))
+ .build());
+ gApi.accounts()
+ .id("admin")
+ .addSshKey(
+ new String(
+ java.nio.file.Files.readAllBytes(
+ sitePaths.data_dir.resolve(String.format("id_rsa_%s.pub", "admin"))),
+ UTF_8));
+ }
+
+ private ImmutableList<String> cmd() {
+ return ImmutableList.<String>builder()
+ .add("ssh")
+ .add("-tt")
+ .add("-o")
+ .add("StrictHostKeyChecking=no")
+ .add("-o")
+ .add("UserKnownHostsFile=/dev/null")
+ .add("-p")
+ .add(String.valueOf(sshAddress.getPort()))
+ .add("admin@" + sshAddress.getHostName())
+ .add("-i")
+ .add(identityPath)
+ .build();
+ }
+
+ private String execute(ImmutableList<String> cmd) throws Exception {
+ return execute(cmd, sitePaths.data_dir.toFile(), ImmutableMap.of());
+ }
+}
diff --git a/javatests/com/google/gerrit/json/BUILD b/javatests/com/google/gerrit/json/BUILD
index 4894cdb466..575f575bd2 100644
--- a/javatests/com/google/gerrit/json/BUILD
+++ b/javatests/com/google/gerrit/json/BUILD
@@ -5,6 +5,9 @@ junit_tests(
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/json",
+ "//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:gson",
"//lib:guava",
"//lib/truth",
],
diff --git a/javatests/com/google/gerrit/json/JavaSqlTimestampHelperTest.java b/javatests/com/google/gerrit/json/JavaSqlTimestampHelperTest.java
index a8488a96eb..05a9cfb583 100644
--- a/javatests/com/google/gerrit/json/JavaSqlTimestampHelperTest.java
+++ b/javatests/com/google/gerrit/json/JavaSqlTimestampHelperTest.java
@@ -15,8 +15,8 @@
package com.google.gerrit.json;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.json.JavaSqlTimestampHelper.parseTimestamp;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
@@ -73,12 +73,7 @@ public class JavaSqlTimestampHelperTest {
}
private static void assertInvalid(String input) {
- try {
- parseTimestamp(input);
- assert_().fail("Expected IllegalArgumentException for: " + input);
- } catch (IllegalArgumentException e) {
- // Expected;
- }
+ assertThrows(IllegalArgumentException.class, () -> parseTimestamp(input));
}
private String reformat(String input) {
diff --git a/javatests/com/google/gerrit/json/JsonEnumMappingTest.java b/javatests/com/google/gerrit/json/JsonEnumMappingTest.java
new file mode 100644
index 0000000000..dd710f98a6
--- /dev/null
+++ b/javatests/com/google/gerrit/json/JsonEnumMappingTest.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.json;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gson.Gson;
+import org.junit.Test;
+
+public class JsonEnumMappingTest {
+
+ // Use the regular, pre-configured Gson object we use throughout the Gerrit server to ensure that
+ // the EnumTypeAdapterFactory is properly set up.
+ private final Gson gson = OutputFormat.JSON.newGson();
+
+ @Test
+ public void nullCanBeWrittenAndParsedBack() {
+ String resultingJson = gson.toJson(null, TestEnum.class);
+ TestEnum value = gson.fromJson(resultingJson, TestEnum.class);
+ assertThat(value).isNull();
+ }
+
+ @Test
+ public void enumValueCanBeWrittenAndParsedBack() {
+ String resultingJson = gson.toJson(TestEnum.ONE, TestEnum.class);
+ TestEnum value = gson.fromJson(resultingJson, TestEnum.class);
+ assertThat(value).isEqualTo(TestEnum.ONE);
+ }
+
+ @Test
+ public void enumValueCanBeParsed() {
+ TestData data = gson.fromJson("{\"value\":\"ONE\"}", TestData.class);
+ assertThat(data.value).isEqualTo(TestEnum.ONE);
+ }
+
+ @Test
+ public void mixedCaseEnumValueIsTreatedAsUnset() {
+ TestData data = gson.fromJson("{\"value\":\"oNe\"}", TestData.class);
+ assertThat(data.value).isNull();
+ }
+
+ @Test
+ public void lowerCaseEnumValueIsTreatedAsUnset() {
+ TestData data = gson.fromJson("{\"value\":\"one\"}", TestData.class);
+ assertThat(data.value).isNull();
+ }
+
+ @Test
+ public void notExistingEnumValueIsTreatedAsUnset() {
+ TestData data = gson.fromJson("{\"value\":\"FOUR\"}", TestData.class);
+ assertThat(data.value).isNull();
+ }
+
+ @Test
+ public void emptyEnumValueIsTreatedAsUnset() {
+ TestData data = gson.fromJson("{\"value\":\"\"}", TestData.class);
+ assertThat(data.value).isNull();
+ }
+
+ private static class TestData {
+ TestEnum value;
+ }
+
+ private enum TestEnum {
+ ONE,
+ TWO
+ }
+}
diff --git a/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java b/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java
new file mode 100644
index 0000000000..2699c3b793
--- /dev/null
+++ b/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.json;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gson.JsonPrimitive;
+import java.sql.Timestamp;
+import org.junit.Test;
+
+public class SqlTimestampDeserializerTest {
+
+ private final SqlTimestampDeserializer deserializer = new SqlTimestampDeserializer();
+
+ @Test
+ public void emptyStringIsDeserializedToMagicTimestamp() {
+ Timestamp timestamp = deserializer.deserialize(new JsonPrimitive(""), Timestamp.class, null);
+ assertThat(timestamp).isEqualTo(TimeUtil.never());
+ }
+}
diff --git a/javatests/com/google/gerrit/mail/AbstractParserTest.java b/javatests/com/google/gerrit/mail/AbstractParserTest.java
index bcff6a75b2..3c84420717 100644
--- a/javatests/com/google/gerrit/mail/AbstractParserTest.java
+++ b/javatests/com/google/gerrit/mail/AbstractParserTest.java
@@ -16,9 +16,8 @@ package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
@@ -26,7 +25,7 @@ import java.util.List;
import org.junit.Ignore;
@Ignore
-public class AbstractParserTest extends GerritBaseTests {
+public class AbstractParserTest {
protected static final String CHANGE_URL =
"https://gerrit-review.googlesource.com/c/project/+/123";
@@ -56,7 +55,7 @@ public class AbstractParserTest extends GerritBaseTests {
Comment c =
new Comment(
new Comment.Key(uuid, file, 1),
- new Account.Id(0),
+ Account.id(0),
new Timestamp(0L),
(short) 0,
message,
@@ -70,7 +69,7 @@ public class AbstractParserTest extends GerritBaseTests {
Comment c =
new Comment(
new Comment.Key(uuid, file, 1),
- new Account.Id(0),
+ Account.id(0),
new Timestamp(0L),
(short) 0,
message,
diff --git a/javatests/com/google/gerrit/mail/AddressTest.java b/javatests/com/google/gerrit/mail/AddressTest.java
index 53ff1fe20a..da26123bb7 100644
--- a/javatests/com/google/gerrit/mail/AddressTest.java
+++ b/javatests/com/google/gerrit/mail/AddressTest.java
@@ -15,12 +15,11 @@
package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class AddressTest extends GerritBaseTests {
+public class AddressTest {
@Test
public void parse_NameEmail1() {
final Address a = Address.parse("A U Thor <author@example.com>");
@@ -97,12 +96,9 @@ public class AddressTest extends GerritBaseTests {
}
private void assertInvalid(String in) {
- try {
- Address.parse(in);
- fail("Expected IllegalArgumentException for " + in);
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).isEqualTo("Invalid email address: " + in);
- }
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> Address.parse(in));
+ assertThat(thrown).hasMessageThat().isEqualTo("Invalid email address: " + in);
}
@Test
diff --git a/javatests/com/google/gerrit/mail/BUILD b/javatests/com/google/gerrit/mail/BUILD
index b1c97120d0..bd2c478778 100644
--- a/javatests/com/google/gerrit/mail/BUILD
+++ b/javatests/com/google/gerrit/mail/BUILD
@@ -8,15 +8,15 @@ junit_tests(
),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/mail",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:gson",
"//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/commons:codec",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
diff --git a/javatests/com/google/gerrit/mail/HtmlParserTest.java b/javatests/com/google/gerrit/mail/HtmlParserTest.java
index d630bd6290..345cb05544 100644
--- a/javatests/com/google/gerrit/mail/HtmlParserTest.java
+++ b/javatests/com/google/gerrit/mail/HtmlParserTest.java
@@ -16,7 +16,7 @@ package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/mail/MailHeaderParserTest.java b/javatests/com/google/gerrit/mail/MailHeaderParserTest.java
index cdc8d7ad6e..2d2c2ea778 100644
--- a/javatests/com/google/gerrit/mail/MailHeaderParserTest.java
+++ b/javatests/com/google/gerrit/mail/MailHeaderParserTest.java
@@ -16,14 +16,13 @@ package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import org.junit.Test;
-public class MailHeaderParserTest extends GerritBaseTests {
+public class MailHeaderParserTest {
@Test
public void parseMetadataFromHeader() {
// This tests if the metadata parser is able to parse metadata from the
diff --git a/javatests/com/google/gerrit/mail/ParserUtilTest.java b/javatests/com/google/gerrit/mail/ParserUtilTest.java
index ed40a57701..47a53673f8 100644
--- a/javatests/com/google/gerrit/mail/ParserUtilTest.java
+++ b/javatests/com/google/gerrit/mail/ParserUtilTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class ParserUtilTest extends GerritBaseTests {
+public class ParserUtilTest {
@Test
public void trimQuotationLineOnMessageWithoutQuoatationLine() throws Exception {
assertThat(ParserUtil.trimQuotation("One line")).isEqualTo("One line");
diff --git a/javatests/com/google/gerrit/mail/RawMailParserTest.java b/javatests/com/google/gerrit/mail/RawMailParserTest.java
index 9049704f0f..0ab281114b 100644
--- a/javatests/com/google/gerrit/mail/RawMailParserTest.java
+++ b/javatests/com/google/gerrit/mail/RawMailParserTest.java
@@ -23,10 +23,9 @@ import com.google.gerrit.mail.data.NonUTF8Message;
import com.google.gerrit.mail.data.QuotedPrintableHeaderMessage;
import com.google.gerrit.mail.data.RawMailMessage;
import com.google.gerrit.mail.data.SimpleTextMessage;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class RawMailParserTest extends GerritBaseTests {
+public class RawMailParserTest {
@Test
public void parseEmail() throws Exception {
RawMailMessage[] messages =
diff --git a/javatests/com/google/gerrit/mail/TextParserTest.java b/javatests/com/google/gerrit/mail/TextParserTest.java
index a5c2152711..00d5b41c62 100644
--- a/javatests/com/google/gerrit/mail/TextParserTest.java
+++ b/javatests/com/google/gerrit/mail/TextParserTest.java
@@ -16,7 +16,7 @@ package com.google.gerrit.mail;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.entities.Comment;
import java.util.List;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java b/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
index a8f5b94a92..c4737e629b 100644
--- a/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
+++ b/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
@@ -39,7 +39,7 @@ public class SimpleTextMessage extends RawMailMessage {
+ "when I try to load this change:\n"
+ "\n"
+ " Error in GET /changes/90018/detail?O=10004\n"
- + " com.google.gwtorm.OrmException: java.lang.NullPointerException\n"
+ + " com.google.gerrit.exceptions.StorageException: java.lang.NullPointerException\n"
+ "\tat com.google.gerrit.change.ChangeJson.format(ChangeJson.java:303)\n"
+ "\tat com.google.gerrit.change.ChangeJson.format(ChangeJson.java:285)\n"
+ "\tat com.google.gerrit.change.ChangeJson.format(ChangeJson.java:263)\n"
diff --git a/javatests/com/google/gerrit/metrics/dropwizard/BUILD b/javatests/com/google/gerrit/metrics/dropwizard/BUILD
index 63d4452193..98d12b2e7b 100644
--- a/javatests/com/google/gerrit/metrics/dropwizard/BUILD
+++ b/javatests/com/google/gerrit/metrics/dropwizard/BUILD
@@ -7,7 +7,6 @@ junit_tests(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/metrics/dropwizard",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/metrics/dropwizard/DropWizardMetricMakerTest.java b/javatests/com/google/gerrit/metrics/dropwizard/DropWizardMetricMakerTest.java
index d6bcb625a8..9b21bf6c01 100644
--- a/javatests/com/google/gerrit/metrics/dropwizard/DropWizardMetricMakerTest.java
+++ b/javatests/com/google/gerrit/metrics/dropwizard/DropWizardMetricMakerTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.metrics.dropwizard;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class DropWizardMetricMakerTest extends GerritBaseTests {
+public class DropWizardMetricMakerTest {
DropWizardMetricMaker metrics =
new DropWizardMetricMaker(null /* MetricRegistry unused in tests */);
diff --git a/javatests/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java b/javatests/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java
index 0a5dabf68a..33919e7ad0 100644
--- a/javatests/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java
+++ b/javatests/com/google/gerrit/metrics/proc/ProcMetricModuleTest.java
@@ -15,6 +15,8 @@
package com.google.gerrit.metrics.proc;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
@@ -30,7 +32,6 @@ import com.google.gerrit.metrics.Description.FieldOrdering;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -39,7 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
-public class ProcMetricModuleTest extends GerritBaseTests {
+public class ProcMetricModuleTest {
@Inject MetricMaker metrics;
@Inject MetricRegistry registry;
@@ -79,7 +80,9 @@ public class ProcMetricModuleTest extends GerritBaseTests {
public void counter1() {
Counter1<String> cntr =
metrics.newCounter(
- "test/count", new Description("simple test").setCumulative(), Field.ofString("action"));
+ "test/count",
+ new Description("simple test").setCumulative(),
+ Field.ofString("action", Field.ignoreMetadata()).build());
Counter total = get("test/count_total", Counter.class);
assertThat(total.getCount()).isEqualTo(0);
@@ -104,7 +107,7 @@ public class ProcMetricModuleTest extends GerritBaseTests {
new Description("simple test")
.setCumulative()
.setFieldOrdering(FieldOrdering.PREFIX_FIELDS_BASENAME),
- Field.ofString("action"));
+ Field.ofString("action", Field.ignoreMetadata()).build());
Counter total = get("test/count_total", Counter.class);
assertThat(total.getCount()).isEqualTo(0);
@@ -147,14 +150,16 @@ public class ProcMetricModuleTest extends GerritBaseTests {
@Test
public void invalidName1() {
- exception.expect(IllegalArgumentException.class);
- metrics.newCounter("invalid name", new Description("fail"));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> metrics.newCounter("invalid name", new Description("fail")));
}
@Test
public void invalidName2() {
- exception.expect(IllegalArgumentException.class);
- metrics.newCounter("invalid/ name", new Description("fail"));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> metrics.newCounter("invalid/ name", new Description("fail")));
}
@SuppressWarnings({"unchecked", "cast"})
@@ -164,8 +169,8 @@ public class ProcMetricModuleTest extends GerritBaseTests {
private <M extends Metric> M get(String name, Class<M> type) {
Metric m = registry.getMetrics().get(name);
- assertThat(m).named(name).isNotNull();
- assertThat(m).named(name).isInstanceOf(type);
+ assertWithMessage(name).that(m).isNotNull();
+ assertWithMessage(name).that(m).isInstanceOf(type);
@SuppressWarnings("unchecked")
M result = (M) m;
diff --git a/javatests/com/google/gerrit/pgm/BUILD b/javatests/com/google/gerrit/pgm/BUILD
index e82b8848a3..14457070e5 100644
--- a/javatests/com/google/gerrit/pgm/BUILD
+++ b/javatests/com/google/gerrit/pgm/BUILD
@@ -5,19 +5,17 @@ junit_tests(
name = "pgm_tests",
srcs = glob(["**/*.java"]),
deps = [
- "//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/pgm",
"//java/com/google/gerrit/pgm/init",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/securestore/testing",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:junit",
- "//lib/easymock",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib/mockito",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
index a34007edbf..e34b57897b 100644
--- a/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
+++ b/javatests/com/google/gerrit/pgm/init/api/AllProjectsConfigTest.java
@@ -15,8 +15,6 @@
package com.google.gerrit.pgm.init.api;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.replay;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.server.config.SitePaths;
@@ -37,12 +35,18 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+@RunWith(MockitoJUnitRunner.class)
public class AllProjectsConfigTest {
private static final String ALL_PROJECTS = "All-The-Projects";
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Mock ConsoleUI ui;
+
private SitePaths sitePaths;
private AllProjectsConfig allProjectsConfig;
private File allProjectsRepoFile;
@@ -70,8 +74,6 @@ public class AllProjectsConfigTest {
InMemorySecureStore secureStore = new InMemorySecureStore();
InitFlags flags = new InitFlags(sitePaths, secureStore, ImmutableList.of(), false);
- ConsoleUI ui = createStrictMock(ConsoleUI.class);
- replay(ui);
Section.Factory sections =
(name, subsection) -> new Section(flags, sitePaths, secureStore, ui, name, subsection);
allProjectsConfig =
diff --git a/javatests/com/google/gerrit/proto/ProtosTest.java b/javatests/com/google/gerrit/proto/ProtosTest.java
index 29e8fe073b..550bcc5777 100644
--- a/javatests/com/google/gerrit/proto/ProtosTest.java
+++ b/javatests/com/google/gerrit/proto/ProtosTest.java
@@ -14,17 +14,16 @@
package com.google.gerrit.proto;
-import static com.google.common.truth.Truth.assert_;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesKeyProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.protobuf.ByteString;
import java.util.Arrays;
import org.junit.Test;
-public class ProtosTest extends GerritBaseTests {
+public class ProtosTest {
@Test
public void parseUncheckedByteArrayWrongProtoType() {
ChangeNotesKeyProto proto =
@@ -34,23 +33,17 @@ public class ProtosTest extends GerritBaseTests {
.setId(ByteString.copyFromUtf8("foo"))
.build();
byte[] bytes = Protos.toByteArray(proto);
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes));
}
@Test
public void parseUncheckedByteArrayInvalidData() {
byte[] bytes = new byte[] {0x00};
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes));
}
@Test
@@ -74,23 +67,17 @@ public class ProtosTest extends GerritBaseTests {
.setId(ByteString.copyFromUtf8("foo"))
.build();
byte[] bytes = Protos.toByteArray(proto);
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes, 0, bytes.length);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes, 0, bytes.length));
}
@Test
public void parseUncheckedSegmentOfByteArrayInvalidData() {
byte[] bytes = new byte[] {0x00};
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes, 0, bytes.length);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), bytes, 0, bytes.length));
}
@Test
@@ -123,23 +110,17 @@ public class ProtosTest extends GerritBaseTests {
.setId(ByteString.copyFromUtf8("foo"))
.build();
ByteString byteString = Protos.toByteString(proto);
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), byteString);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), byteString));
}
@Test
public void parseUncheckedByteStringInvalidData() {
ByteString byteString = ByteString.copyFrom(new byte[] {0x00});
- try {
- Protos.parseUnchecked(ChangeNotesStateProto.parser(), byteString);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> Protos.parseUnchecked(ChangeNotesStateProto.parser(), byteString));
}
@Test
diff --git a/javatests/com/google/gerrit/reviewdb/converter/RevIdProtoConverterTest.java b/javatests/com/google/gerrit/reviewdb/converter/RevIdProtoConverterTest.java
deleted file mode 100644
index 2c354be94d..0000000000
--- a/javatests/com/google/gerrit/reviewdb/converter/RevIdProtoConverterTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.reviewdb.converter;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
-import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.proto.Entities;
-import com.google.gerrit.proto.testing.SerializedClassSubject;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.protobuf.Parser;
-import org.junit.Test;
-
-public class RevIdProtoConverterTest {
- private final RevIdProtoConverter revIdProtoConverter = RevIdProtoConverter.INSTANCE;
-
- @Test
- public void allValuesConvertedToProto() {
- RevId revId = new RevId("9903402f303249e");
-
- Entities.RevId proto = revIdProtoConverter.toProto(revId);
-
- Entities.RevId expectedProto = Entities.RevId.newBuilder().setId("9903402f303249e").build();
- assertThat(proto).isEqualTo(expectedProto);
- }
-
- @Test
- public void allValuesConvertedToProtoAndBackAgain() {
- RevId revId = new RevId("ff3934a320bb");
-
- RevId convertedRevId = revIdProtoConverter.fromProto(revIdProtoConverter.toProto(revId));
-
- assertThat(convertedRevId).isEqualTo(revId);
- }
-
- @Test
- public void protoCanBeParsedFromBytes() throws Exception {
- Entities.RevId proto = Entities.RevId.newBuilder().setId("9903402f303249e").build();
- byte[] bytes = proto.toByteArray();
-
- Parser<Entities.RevId> parser = revIdProtoConverter.getParser();
- Entities.RevId parsedProto = parser.parseFrom(bytes);
-
- assertThat(parsedProto).isEqualTo(proto);
- }
-
- /** See {@link SerializedClassSubject} for background and what to do if this test fails. */
- @Test
- public void fieldsExistAsExpected() {
- assertThatSerializedClass(RevId.class).hasFields(ImmutableMap.of("id", String.class));
- }
-}
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 7b8f602106..585a2727ec 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -30,30 +30,31 @@ junit_tests(
tags = ["no_windows"],
visibility = ["//visibility:public"],
runtime_deps = [
+ "//java/com/google/gerrit/lucene",
"//lib/bouncycastle:bcprov",
"//prolog:gerrit-prolog-common",
],
deps = [
":custom-truth-subjects",
+ "//java/com/google/gerrit/acceptance/testsuite/project",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/extensions/common/testing:common-test-util",
"//java/com/google/gerrit/git",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/jgit",
+ "//java/com/google/gerrit/json",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/mail",
"//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/proto",
"//java/com/google/gerrit/proto/testing",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/account/externalids/testing",
"//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/cache/testing",
- "//java/com/google/gerrit/server/group/testing",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/project/testing:project-test-util",
@@ -62,19 +63,21 @@ junit_tests(
"//java/com/google/gerrit/server/schema/testing",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/sshd",
+ "//java/com/google/gerrit/testing:assertable-executor",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/truth",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:protobuf",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/commons:codec",
"//lib/flogger:api",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib/mockito",
"//lib/truth",
"//lib/truth:truth-java8-extension",
"//lib/truth:truth-proto-extension",
diff --git a/javatests/com/google/gerrit/server/ChangeUtilTest.java b/javatests/com/google/gerrit/server/ChangeUtilTest.java
index 5cb474d34f..5f73d2cdf0 100644
--- a/javatests/com/google/gerrit/server/ChangeUtilTest.java
+++ b/javatests/com/google/gerrit/server/ChangeUtilTest.java
@@ -16,11 +16,10 @@ package com.google.gerrit.server;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.regex.Pattern;
import org.junit.Test;
-public class ChangeUtilTest extends GerritBaseTests {
+public class ChangeUtilTest {
@Test
public void changeMessageUuid() throws Exception {
Pattern pat = Pattern.compile("^[0-9a-f]{8}_[0-9a-f]{8}$");
diff --git a/javatests/com/google/gerrit/server/IdentifiedUserTest.java b/javatests/com/google/gerrit/server/IdentifiedUserTest.java
index 485de49720..1672ce139e 100644
--- a/javatests/com/google/gerrit/server/IdentifiedUserTest.java
+++ b/javatests/com/google/gerrit/server/IdentifiedUserTest.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server;
import static com.google.common.truth.Truth.assertThat;
import static com.google.inject.Scopes.SINGLETON;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.FakeRealm;
import com.google.gerrit.server.account.GroupBackend;
@@ -31,7 +31,6 @@ import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeAccountCache;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
@@ -45,7 +44,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(ConfigSuite.class)
-public class IdentifiedUserTest extends GerritBaseTests {
+public class IdentifiedUserTest {
@ConfigSuite.Parameter public Config config;
private IdentifiedUser identifiedUser;
@@ -99,8 +98,11 @@ public class IdentifiedUserTest extends GerritBaseTests {
Injector injector = Guice.createInjector(mod);
injector.injectMembers(this);
- Account account = new Account(new Account.Id(1), TimeUtil.nowTs());
- Account.Id ownerId = account.getId();
+ Account account =
+ Account.builder(Account.id(1), TimeUtil.nowTs())
+ .setMetaId("1234567812345678123456781234567812345678")
+ .build();
+ Account.Id ownerId = account.id();
identifiedUser = identifiedUserFactory.create(ownerId);
diff --git a/javatests/com/google/gerrit/server/account/AccountResolverTest.java b/javatests/com/google/gerrit/server/account/AccountResolverTest.java
index c37a9454fc..5f14d28c90 100644
--- a/javatests/com/google/gerrit/server/account/AccountResolverTest.java
+++ b/javatests/com/google/gerrit/server/account/AccountResolverTest.java
@@ -17,26 +17,24 @@ package com.google.gerrit.server.account;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AccountResolver.Result;
import com.google.gerrit.server.account.AccountResolver.Searcher;
import com.google.gerrit.server.account.AccountResolver.StringSearcher;
import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException;
-import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.Test;
-public class AccountResolverTest extends GerritBaseTests {
+public class AccountResolverTest {
private static class TestSearcher extends StringSearcher {
private final String pattern;
private final boolean shortCircuit;
@@ -86,7 +84,7 @@ public class AccountResolverTest extends GerritBaseTests {
@Override
public String toString() {
return accounts.stream()
- .map(a -> a.getAccount().getId().toString())
+ .map(a -> a.account().id().toString())
.collect(joining(",", pattern + "(", ")"));
}
}
@@ -158,7 +156,7 @@ public class AccountResolverTest extends GerritBaseTests {
// Searchers always short-circuit when finding a non-empty result list, and this one didn't
// filter out inactive results, so the second searcher never ran.
assertThat(result.asIdSet()).containsExactlyElementsIn(ids(1));
- assertThat(getOnlyElement(result.asList()).getAccount().isActive()).isFalse();
+ assertThat(getOnlyElement(result.asList()).account().isActive()).isFalse();
assertThat(filteredInactiveIds(result)).isEmpty();
}
@@ -175,7 +173,7 @@ public class AccountResolverTest extends GerritBaseTests {
// and this one didn't filter out inactive results,
// so the second searcher never ran.
assertThat(result.asIdSet()).containsExactlyElementsIn(ids(1));
- assertThat(getOnlyElement(result.asList()).getAccount().isActive()).isFalse();
+ assertThat(getOnlyElement(result.asList()).account().isActive()).isFalse();
assertThat(filteredInactiveIds(result)).isEmpty();
}
@@ -242,15 +240,14 @@ public class AccountResolverTest extends GerritBaseTests {
@Test
public void asUniqueWithNoResults() throws Exception {
- try {
- String input = "foo";
- ImmutableList<Searcher<?>> searchers = ImmutableList.of();
- Supplier<Predicate<AccountState>> visibilitySupplier = allVisible();
- search(input, searchers, visibilitySupplier).asUnique();
- assert_().fail("Expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e).hasMessageThat().isEqualTo("Account 'foo' not found");
- }
+ String input = "foo";
+ ImmutableList<Searcher<?>> searchers = ImmutableList.of();
+ Supplier<Predicate<AccountState>> visibilitySupplier = allVisible();
+ UnresolvableAccountException thrown =
+ assertThrows(
+ UnresolvableAccountException.class,
+ () -> search(input, searchers, visibilitySupplier).asUnique());
+ assertThat(thrown).hasMessageThat().isEqualTo("Account 'foo' not found");
}
@Test
@@ -258,22 +255,22 @@ public class AccountResolverTest extends GerritBaseTests {
AccountState account = newAccount(1);
ImmutableList<Searcher<?>> searchers =
ImmutableList.of(new TestSearcher("foo", false, account));
- assertThat(search("foo", searchers, allVisible()).asUnique().getAccount().getId())
- .isEqualTo(account.getAccount().getId());
+ assertThat(search("foo", searchers, allVisible()).asUnique().account().id())
+ .isEqualTo(account.account().id());
}
@Test
public void asUniqueWithMultipleResults() throws Exception {
ImmutableList<Searcher<?>> searchers =
ImmutableList.of(new TestSearcher("foo", false, newAccount(1), newAccount(2)));
- try {
- search("foo", searchers, allVisible()).asUnique();
- assert_().fail("Expected UnresolvableAccountException");
- } catch (UnresolvableAccountException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo("Account 'foo' is ambiguous:\n1: Anonymous Name (1)\n2: Anonymous Name (2)");
- }
+ UnresolvableAccountException thrown =
+ assertThrows(
+ UnresolvableAccountException.class,
+ () -> search("foo", searchers, allVisible()).asUnique());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Account 'foo' is ambiguous (at most 3 shown):\n1: Anonymous Name (1)\n2: Anonymous Name (2)");
}
@Test
@@ -315,7 +312,8 @@ public class AccountResolverTest extends GerritBaseTests {
.new Result(
"foo", ImmutableList.of(newAccount(3), newAccount(1)), ImmutableList.of())))
.hasMessageThat()
- .isEqualTo("Account 'foo' is ambiguous:\n1: Anonymous Name (1)\n3: Anonymous Name (3)");
+ .isEqualTo(
+ "Account 'foo' is ambiguous (at most 3 shown):\n1: Anonymous Name (1)\n3: Anonymous Name (3)");
}
@Test
@@ -359,17 +357,19 @@ public class AccountResolverTest extends GerritBaseTests {
private AccountState newAccount(int id) {
return AccountState.forAccount(
- new AllUsersName("All-Users"), new Account(new Account.Id(id), TimeUtil.nowTs()));
+ Account.builder(Account.id(id), TimeUtil.nowTs())
+ .setMetaId("1234567812345678123456781234567812345678")
+ .build());
}
private AccountState newInactiveAccount(int id) {
- Account a = new Account(new Account.Id(id), TimeUtil.nowTs());
+ Account.Builder a = Account.builder(Account.id(id), TimeUtil.nowTs());
a.setActive(false);
- return AccountState.forAccount(new AllUsersName("All-Users"), a);
+ return AccountState.forAccount(a.build());
}
private static ImmutableSet<Account.Id> ids(int... ids) {
- return Arrays.stream(ids).mapToObj(Account.Id::new).collect(toImmutableSet());
+ return Arrays.stream(ids).mapToObj(Account::id).collect(toImmutableSet());
}
private static Supplier<Predicate<AccountState>> allVisible() {
@@ -377,18 +377,16 @@ public class AccountResolverTest extends GerritBaseTests {
}
private Predicate<AccountState> activityPrediate() {
- return (AccountState accountState) -> accountState.getAccount().isActive();
+ return (AccountState accountState) -> accountState.account().isActive();
}
private static Supplier<Predicate<AccountState>> only(int... ids) {
ImmutableSet<Account.Id> idSet =
- Arrays.stream(ids).mapToObj(Account.Id::new).collect(toImmutableSet());
- return () -> a -> idSet.contains(a.getAccount().getId());
+ Arrays.stream(ids).mapToObj(Account::id).collect(toImmutableSet());
+ return () -> a -> idSet.contains(a.account().id());
}
private static ImmutableSet<Account.Id> filteredInactiveIds(Result result) {
- return result.filteredInactive().stream()
- .map(a -> a.getAccount().getId())
- .collect(toImmutableSet());
+ return result.filteredInactive().stream().map(a -> a.account().id()).collect(toImmutableSet());
}
}
diff --git a/javatests/com/google/gerrit/server/account/AuthorizedKeysTest.java b/javatests/com/google/gerrit/server/account/AuthorizedKeysTest.java
index 51a34f5f1a..1381c751b9 100644
--- a/javatests/com/google/gerrit/server/account/AuthorizedKeysTest.java
+++ b/javatests/com/google/gerrit/server/account/AuthorizedKeysTest.java
@@ -16,14 +16,13 @@ package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
-public class AuthorizedKeysTest extends GerritBaseTests {
+public class AuthorizedKeysTest {
private static final String KEY1 =
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCgug5VyMXQGnem2H1KVC4/HcRcD4zzBqS"
+ "uJBRWVonSSoz3RoAZ7bWXCVVGwchtXwUURD689wFYdiPecOrWOUgeeyRq754YWRhU+W28"
@@ -55,7 +54,7 @@ public class AuthorizedKeysTest extends GerritBaseTests {
+ "zRuEL5e/QOu9yGq9xkWApCmg6edpWAHG+Bx4AldU78MiZvzoB7gMMdxc9RmZ1gYj/DjxV"
+ "w== john.doe@example.com";
- private final Account.Id accountId = new Account.Id(1);
+ private final Account.Id accountId = Account.id(1);
@Test
public void test() throws Exception {
@@ -151,7 +150,7 @@ public class AuthorizedKeysTest extends GerritBaseTests {
private static void assertParse(
StringBuilder authorizedKeys, List<Optional<AccountSshKey>> expectedKeys) {
- Account.Id accountId = new Account.Id(1);
+ Account.Id accountId = Account.id(1);
List<Optional<AccountSshKey>> parsedKeys =
AuthorizedKeys.parse(accountId, authorizedKeys.toString());
assertThat(parsedKeys).containsExactlyElementsIn(expectedKeys);
@@ -171,7 +170,7 @@ public class AuthorizedKeysTest extends GerritBaseTests {
* @return the expected line for this key in the authorized_keys file
*/
private static String addKey(List<Optional<AccountSshKey>> keys, String pub) {
- AccountSshKey key = AccountSshKey.create(new Account.Id(1), keys.size() + 1, pub);
+ AccountSshKey key = AccountSshKey.create(Account.id(1), keys.size() + 1, pub);
keys.add(Optional.of(key));
return key.sshPublicKey() + "\n";
}
@@ -182,7 +181,7 @@ public class AuthorizedKeysTest extends GerritBaseTests {
* @return the expected line for this key in the authorized_keys file
*/
private static String addInvalidKey(List<Optional<AccountSshKey>> keys, String pub) {
- AccountSshKey key = AccountSshKey.createInvalid(new Account.Id(1), keys.size() + 1, pub);
+ AccountSshKey key = AccountSshKey.createInvalid(Account.id(1), keys.size() + 1, pub);
keys.add(Optional.of(key));
return AuthorizedKeys.INVALID_KEY_COMMENT_PREFIX + key.sshPublicKey() + "\n";
}
diff --git a/javatests/com/google/gerrit/server/account/DestinationListTest.java b/javatests/com/google/gerrit/server/account/DestinationListTest.java
index e51b041569..4188f39df7 100644
--- a/javatests/com/google/gerrit/server/account/DestinationListTest.java
+++ b/javatests/com/google/gerrit/server/account/DestinationListTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -27,7 +26,7 @@ import java.util.List;
import java.util.Set;
import org.junit.Test;
-public class DestinationListTest extends GerritBaseTests {
+public class DestinationListTest {
public static final String R_FOO = "refs/heads/foo";
public static final String R_BAR = "refs/heads/bar";
@@ -55,11 +54,11 @@ public class DestinationListTest extends GerritBaseTests {
public static final String LABEL = "label";
public static final String LABEL2 = "another";
- public static final Branch.NameKey B_FOO = dest(P_MY, R_FOO);
- public static final Branch.NameKey B_BAR = dest(P_SLASH, R_BAR);
- public static final Branch.NameKey B_COMPLEX = dest(P_COMPLEX, R_FOO);
+ public static final BranchNameKey B_FOO = dest(P_MY, R_FOO);
+ public static final BranchNameKey B_BAR = dest(P_SLASH, R_BAR);
+ public static final BranchNameKey B_COMPLEX = dest(P_COMPLEX, R_FOO);
- public static final Set<Branch.NameKey> D_SIMPLE = new HashSet<>();
+ public static final Set<BranchNameKey> D_SIMPLE = new HashSet<>();
static {
D_SIMPLE.clear();
@@ -67,15 +66,15 @@ public class DestinationListTest extends GerritBaseTests {
D_SIMPLE.add(B_BAR);
}
- private static Branch.NameKey dest(String project, String ref) {
- return new Branch.NameKey(new Project.NameKey(project), ref);
+ private static BranchNameKey dest(String project, String ref) {
+ return BranchNameKey.create(Project.nameKey(project), ref);
}
@Test
public void testParseSimple() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, F_SIMPLE, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
}
@@ -83,7 +82,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParseWHeader() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, HEADER + F_SIMPLE, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
}
@@ -91,7 +90,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParseWComments() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, C1 + F_SIMPLE + C2, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
}
@@ -99,7 +98,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParseFooComment() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, "#" + L_FOO + L_BAR, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).doesNotContain(B_FOO);
assertThat(branches).contains(B_BAR);
}
@@ -108,7 +107,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParsePaddedFronts() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, F_PAD_F, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
}
@@ -116,7 +115,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParsePaddedEnds() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, F_PAD_E, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
}
@@ -124,7 +123,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParseComplex() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, L_COMPLEX, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).contains(B_COMPLEX);
}
@@ -140,7 +139,7 @@ public class DestinationListTest extends GerritBaseTests {
public void testParse2Labels() throws Exception {
DestinationList dl = new DestinationList();
dl.parseLabel(LABEL, F_SIMPLE, null);
- Set<Branch.NameKey> branches = dl.getDestinations(LABEL);
+ Set<BranchNameKey> branches = dl.getDestinations(LABEL);
assertThat(branches).containsExactlyElementsIn(D_SIMPLE);
dl.parseLabel(LABEL2, L_COMPLEX, null);
diff --git a/javatests/com/google/gerrit/server/account/GroupUUIDTest.java b/javatests/com/google/gerrit/server/account/GroupUUIDTest.java
index 70887e6c38..a155d7fc32 100644
--- a/javatests/com/google/gerrit/server/account/GroupUUIDTest.java
+++ b/javatests/com/google/gerrit/server/account/GroupUUIDTest.java
@@ -16,12 +16,11 @@ package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.AccountGroup;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Test;
-public class GroupUUIDTest extends GerritBaseTests {
+public class GroupUUIDTest {
@Test
public void createdUuidsForSameInputShouldBeDifferent() {
String groupName = "Users";
diff --git a/javatests/com/google/gerrit/server/account/HashedPasswordTest.java b/javatests/com/google/gerrit/server/account/HashedPasswordTest.java
index 9a0c9cb972..34437201d3 100644
--- a/javatests/com/google/gerrit/server/account/HashedPasswordTest.java
+++ b/javatests/com/google/gerrit/server/account/HashedPasswordTest.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.common.base.Strings;
-import com.google.gerrit.testing.GerritBaseTests;
-import org.apache.commons.codec.DecoderException;
+import com.google.gerrit.server.account.HashedPassword.DecoderException;
import org.junit.Test;
-public class HashedPasswordTest extends GerritBaseTests {
+public class HashedPasswordTest {
@Test
public void encodeOneLine() throws Exception {
@@ -41,17 +40,9 @@ public class HashedPasswordTest extends GerritBaseTests {
assertThat(roundtrip.checkPassword("not the password")).isFalse();
}
- @Test(expected = DecoderException.class)
- public void invalidDecode() throws Exception {
- HashedPassword.decode("invalid");
- }
-
@Test
- public void lengthLimit() throws Exception {
- String password = Strings.repeat("1", 72);
-
- // make sure it fits in varchar(255).
- assertThat(HashedPassword.fromPassword(password).encode().length()).isLessThan(255);
+ public void invalidDecode() throws Exception {
+ assertThrows(DecoderException.class, () -> HashedPassword.decode("invalid"));
}
@Test
diff --git a/javatests/com/google/gerrit/server/account/PreferencesTest.java b/javatests/com/google/gerrit/server/account/PreferencesTest.java
new file mode 100644
index 0000000000..9866481de8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/PreferencesTest.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.client.EditPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gson.Gson;
+import org.junit.Test;
+
+public class PreferencesTest {
+
+ private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
+
+ @Test
+ public void generalPreferencesRoundTrip() {
+ GeneralPreferencesInfo original = GeneralPreferencesInfo.defaults();
+ assertThat(GSON.toJson(original))
+ .isEqualTo(GSON.toJson(Preferences.General.fromInfo(original).toInfo()));
+ }
+
+ @Test
+ public void diffPreferencesRoundTrip() {
+ DiffPreferencesInfo original = DiffPreferencesInfo.defaults();
+ assertThat(GSON.toJson(original))
+ .isEqualTo(GSON.toJson(Preferences.Diff.fromInfo(original).toInfo()));
+ }
+
+ @Test
+ public void editPreferencesRoundTrip() {
+ EditPreferencesInfo original = EditPreferencesInfo.defaults();
+ assertThat(GSON.toJson(original))
+ .isEqualTo(GSON.toJson(Preferences.Edit.fromInfo(original).toInfo()));
+ }
+}
diff --git a/javatests/com/google/gerrit/server/account/QueryListTest.java b/javatests/com/google/gerrit/server/account/QueryListTest.java
index a0876e1ad9..7d491c96e8 100644
--- a/javatests/com/google/gerrit/server/account/QueryListTest.java
+++ b/javatests/com/google/gerrit/server/account/QueryListTest.java
@@ -17,12 +17,11 @@ package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
-public class QueryListTest extends GerritBaseTests {
+public class QueryListTest {
public static final String Q_P = "project:foo";
public static final String Q_B = "branch:bar";
public static final String Q_COMPLEX = "branch:bar AND peers:'is:open\t'";
diff --git a/javatests/com/google/gerrit/server/account/StoredPreferencesTest.java b/javatests/com/google/gerrit/server/account/StoredPreferencesTest.java
new file mode 100644
index 0000000000..c39e496220
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/StoredPreferencesTest.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.server.git.ValidationError;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/** Tests for parsing user preferences from Git. */
+public class StoredPreferencesTest {
+
+ enum Unknown {
+ STATE
+ }
+
+ @Test
+ public void ignoreUnknownAccountPreferencesWhenParsing() {
+ ValidationError.Sink errorSink = Mockito.mock(ValidationError.Sink.class);
+ StoredPreferences preferences =
+ new StoredPreferences(Account.id(1), configWithUnknownEntries(), new Config(), errorSink);
+ GeneralPreferencesInfo parsedPreferences = preferences.getGeneralPreferences();
+
+ assertThat(parsedPreferences).isNotNull();
+ assertThat(parsedPreferences.expandInlineDiffs).isTrue();
+ verifyNoMoreInteractions(errorSink);
+ }
+
+ @Test
+ public void ignoreUnknownDefaultAccountPreferencesWhenParsing() {
+ ValidationError.Sink errorSink = Mockito.mock(ValidationError.Sink.class);
+ StoredPreferences preferences =
+ new StoredPreferences(Account.id(1), new Config(), configWithUnknownEntries(), errorSink);
+ GeneralPreferencesInfo parsedPreferences = preferences.getGeneralPreferences();
+
+ assertThat(parsedPreferences).isNotNull();
+ assertThat(parsedPreferences.expandInlineDiffs).isTrue();
+ verifyNoMoreInteractions(errorSink);
+ }
+
+ private static Config configWithUnknownEntries() {
+ Config cfg = new Config();
+ cfg.setBoolean("general", null, "expandInlineDiffs", true);
+ cfg.setBoolean("general", null, "unknown", true);
+ cfg.setEnum("general", null, "unknownenum", Unknown.STATE);
+ cfg.setString("general", null, "unknownstring", "bla");
+ return cfg;
+ }
+}
diff --git a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
index 334c627a96..1e3063ec03 100644
--- a/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
+++ b/javatests/com/google/gerrit/server/account/UniversalGroupBackendTest.java
@@ -17,34 +17,32 @@ package com.google.gerrit.server.account;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.getCurrentArguments;
-import static org.easymock.EasyMock.not;
-import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics;
import com.google.gerrit.server.plugincontext.PluginSetContext;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
-public class UniversalGroupBackendTest extends GerritBaseTests {
- private static final AccountGroup.UUID OTHER_UUID = new AccountGroup.UUID("other");
+public class UniversalGroupBackendTest {
+ private static final AccountGroup.UUID OTHER_UUID = AccountGroup.uuid("other");
private UniversalGroupBackend backend;
private IdentifiedUser user;
@@ -53,8 +51,7 @@ public class UniversalGroupBackendTest extends GerritBaseTests {
@Before
public void setup() {
- user = createNiceMock(IdentifiedUser.class);
- replay(user);
+ user = mock(IdentifiedUser.class);
backends = new DynamicSet<>();
backends.add("gerrit", new SystemGroupBackend(new Config()));
backend =
@@ -102,25 +99,26 @@ public class UniversalGroupBackendTest extends GerritBaseTests {
@Test
public void otherMemberships() {
- final AccountGroup.UUID handled = new AccountGroup.UUID("handled");
- final AccountGroup.UUID notHandled = new AccountGroup.UUID("not handled");
- final IdentifiedUser member = createNiceMock(IdentifiedUser.class);
- final IdentifiedUser notMember = createNiceMock(IdentifiedUser.class);
-
- GroupBackend backend = createMock(GroupBackend.class);
- expect(backend.handles(handled)).andStubReturn(true);
- expect(backend.handles(not(eq(handled)))).andStubReturn(false);
- expect(backend.membershipsOf(anyObject(IdentifiedUser.class)))
- .andStubAnswer(
- () -> {
- Object[] args = getCurrentArguments();
- GroupMembership membership = createMock(GroupMembership.class);
- expect(membership.contains(eq(handled))).andStubReturn(args[0] == member);
- expect(membership.contains(not(eq(notHandled)))).andStubReturn(false);
- replay(membership);
- return membership;
+ final AccountGroup.UUID handled = AccountGroup.uuid("handled");
+ final AccountGroup.UUID notHandled = AccountGroup.uuid("not handled");
+ final IdentifiedUser member = mock(IdentifiedUser.class);
+ final IdentifiedUser notMember = mock(IdentifiedUser.class);
+
+ GroupBackend backend = mock(GroupBackend.class);
+ when(backend.handles(eq(handled))).thenReturn(true);
+ when(backend.handles(not(eq(handled)))).thenReturn(false);
+ when(backend.membershipsOf(any(IdentifiedUser.class)))
+ .thenAnswer(
+ new Answer<GroupMembership>() {
+ @Override
+ public GroupMembership answer(InvocationOnMock invocation) {
+ GroupMembership membership = mock(GroupMembership.class);
+ when(membership.contains(eq(handled)))
+ .thenReturn(invocation.getArguments()[0] == member);
+ when(membership.contains(eq(notHandled))).thenReturn(false);
+ return membership;
+ }
});
- replay(member, notMember, backend);
backends = new DynamicSet<>();
backends.add("gerrit", backend);
diff --git a/javatests/com/google/gerrit/server/account/WatchConfigTest.java b/javatests/com/google/gerrit/server/account/WatchConfigTest.java
index 2ac7be70c5..95dbbde2f3 100644
--- a/javatests/com/google/gerrit/server/account/WatchConfigTest.java
+++ b/javatests/com/google/gerrit/server/account/WatchConfigTest.java
@@ -15,10 +15,11 @@
package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.account.ProjectWatches.NotifyValue;
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
@@ -54,12 +55,12 @@ public class WatchConfigTest implements ValidationError.Sink {
+ " notify = [NEW_PATCHSETS]\n"
+ " notify = * [NEW_PATCHSETS, ALL_COMMENTS]\n");
Map<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches =
- ProjectWatches.parse(new Account.Id(1000000), cfg, this);
+ ProjectWatches.parse(Account.id(1000000), cfg, this);
assertThat(validationErrors).isEmpty();
- Project.NameKey myProject = new Project.NameKey("myProject");
- Project.NameKey otherProject = new Project.NameKey("otherProject");
+ Project.NameKey myProject = Project.nameKey("myProject");
+ Project.NameKey otherProject = Project.nameKey("otherProject");
Map<ProjectWatchKey, Set<NotifyType>> expectedProjectWatches = new HashMap<>();
expectedProjectWatches.put(
ProjectWatchKey.create(myProject, null),
@@ -87,7 +88,7 @@ public class WatchConfigTest implements ValidationError.Sink {
+ "[project \"otherProject\"]\n"
+ " notify = [NEW_PATCHSETS]\n");
- ProjectWatches.parse(new Account.Id(1000000), cfg, this);
+ ProjectWatches.parse(Account.id(1000000), cfg, this);
assertThat(validationErrors).hasSize(1);
assertThat(validationErrors.get(0).getMessage())
.isEqualTo(
@@ -170,14 +171,14 @@ public class WatchConfigTest implements ValidationError.Sink {
private void assertParseNotifyValueFails(String notifyValue) {
assertThat(validationErrors).isEmpty();
parseNotifyValue(notifyValue);
- assertThat(validationErrors)
- .named("expected validation error for notifyValue: " + notifyValue)
+ assertWithMessage("expected validation error for notifyValue: " + notifyValue)
+ .that(validationErrors)
.isNotEmpty();
validationErrors.clear();
}
private NotifyValue parseNotifyValue(String notifyValue) {
- return NotifyValue.parse(new Account.Id(1000000), "project", notifyValue, this);
+ return NotifyValue.parse(Account.id(1000000), "project", notifyValue, this);
}
@Override
diff --git a/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
index d757f7135b..45dacd9e3e 100644
--- a/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/AllExternalIdsTest.java
@@ -21,17 +21,16 @@ import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byt
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.AllExternalIds.Serializer;
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.TypeLiteral;
import java.util.Arrays;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class AllExternalIdsTest extends GerritBaseTests {
+public class AllExternalIdsTest {
@Test
public void serializeEmptyExternalIds() throws Exception {
assertRoundTrip(allExternalIds(), AllExternalIdsProto.getDefaultInstance());
@@ -39,8 +38,8 @@ public class AllExternalIdsTest extends GerritBaseTests {
@Test
public void serializeMultipleExternalIds() throws Exception {
- Account.Id accountId1 = new Account.Id(1001);
- Account.Id accountId2 = new Account.Id(1002);
+ Account.Id accountId1 = Account.id(1001);
+ Account.Id accountId2 = Account.id(1002);
assertRoundTrip(
allExternalIds(
ExternalId.create("scheme1", "id1", accountId1),
@@ -62,7 +61,7 @@ public class AllExternalIdsTest extends GerritBaseTests {
@Test
public void serializeExternalIdWithEmail() throws Exception {
assertRoundTrip(
- allExternalIds(ExternalId.createEmail(new Account.Id(1001), "foo@example.com")),
+ allExternalIds(ExternalId.createEmail(Account.id(1001), "foo@example.com")),
AllExternalIdsProto.newBuilder()
.addExternalId(
ExternalIdProto.newBuilder()
@@ -76,7 +75,7 @@ public class AllExternalIdsTest extends GerritBaseTests {
public void serializeExternalIdWithPassword() throws Exception {
assertRoundTrip(
allExternalIds(
- ExternalId.create("scheme", "id", new Account.Id(1001), null, "hashed password")),
+ ExternalId.create("scheme", "id", Account.id(1001), null, "hashed password")),
AllExternalIdsProto.newBuilder()
.addExternalId(
ExternalIdProto.newBuilder()
@@ -91,7 +90,7 @@ public class AllExternalIdsTest extends GerritBaseTests {
assertRoundTrip(
allExternalIds(
ExternalId.create(
- ExternalId.create("scheme", "id", new Account.Id(1001)),
+ ExternalId.create("scheme", "id", Account.id(1001)),
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))),
AllExternalIdsProto.newBuilder()
.addExternalId(
diff --git a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
new file mode 100644
index 0000000000..054b1aa564
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
@@ -0,0 +1,307 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.cache.Cache;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.google.gerrit.server.account.externalids.testing.ExternalIdTestUtil;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.inject.util.Providers;
+import java.io.IOException;
+import java.util.function.Consumer;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExternalIDCacheLoaderTest {
+ private static AllUsersName ALL_USERS = new AllUsersName(AllUsersNameProvider.DEFAULT);
+
+ @Mock Cache<ObjectId, AllExternalIds> externalIdCache;
+
+ private ExternalIdCacheLoader loader;
+ private GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+ private ExternalIdReader externalIdReader;
+ private ExternalIdReader externalIdReaderSpy;
+
+ @Before
+ public void setUp() throws Exception {
+ repoManager.createRepository(ALL_USERS).close();
+ externalIdReader = new ExternalIdReader(repoManager, ALL_USERS, new DisabledMetricMaker());
+ externalIdReaderSpy = Mockito.spy(externalIdReader);
+ loader = createLoader(true);
+ }
+
+ @Test
+ public void worksOnSingleCommit() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ assertThat(loader.load(firstState)).isEqualTo(allFromGit(firstState));
+ verify(externalIdReaderSpy, times(1)).all(firstState);
+ }
+
+ @Test
+ public void reloadsSingleUpdateUsingPartialReload() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ ObjectId head = insertExternalId(2, 2);
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void reloadsMultipleUpdatesUsingPartialReload() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ insertExternalId(2, 2);
+ insertExternalId(3, 3);
+ ObjectId head = insertExternalId(4, 4);
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void reloadsAllExternalIdsWhenNoOldStateIsCached() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ ObjectId head = insertExternalId(2, 2);
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(null);
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verify(externalIdReaderSpy, times(1)).all(head);
+ }
+
+ @Test
+ public void partialReloadingDisabledAlwaysTriggersFullReload() throws Exception {
+ loader = createLoader(false);
+ insertExternalId(1, 1);
+ ObjectId head = insertExternalId(2, 2);
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verify(externalIdReaderSpy, times(1)).all(head);
+ }
+
+ @Test
+ public void fallsBackToFullReloadOnManyUpdatesOnBranch() throws Exception {
+ insertExternalId(1, 1);
+ ObjectId head = null;
+ for (int i = 2; i < 20; i++) {
+ head = insertExternalId(i, i);
+ }
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verify(externalIdReaderSpy, times(1)).all(head);
+ }
+
+ @Test
+ public void doesFullReloadWhenNoCacheStateIsFound() throws Exception {
+ ObjectId head = insertExternalId(1, 1);
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verify(externalIdReaderSpy, times(1)).all(head);
+ }
+
+ @Test
+ public void handlesDeletionInPartialReload() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ ObjectId head = deleteExternalId(1, 1);
+ assertThat(allFromGit(head).byAccount().size()).isEqualTo(0);
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void handlesModifyInPartialReload() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ ObjectId head =
+ modifyExternalId(
+ externalId(1, 1),
+ ExternalId.create("fooschema", "bar1", Account.id(1), "foo@bar.com", "password"));
+ assertThat(allFromGit(head).byAccount().size()).isEqualTo(1);
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void ignoresInvalidExternalId() throws Exception {
+ ObjectId firstState = insertExternalId(1, 1);
+ ObjectId head;
+ try (Repository repo = repoManager.openRepository(ALL_USERS);
+ RevWalk rw = new RevWalk(repo)) {
+ ExternalIdTestUtil.insertExternalIdWithKeyThatDoesntMatchNoteId(
+ repo, rw, new PersonIdent("foo", "foo@bar.com"), Account.id(2), "test");
+ head = repo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId();
+ }
+
+ when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void handlesTreePrefixesInDifferentialReload() throws Exception {
+ // Create more than 256 notes (NoteMap's current sharding limit) and check that we really have
+ // created a situation where NoteNames are sharded.
+ ObjectId oldState = inserExternalIds(257);
+ assertAllFilesHaveSlashesInPath();
+ ObjectId head = insertExternalId(500, 500);
+
+ when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ @Test
+ public void handlesReshard() throws Exception {
+ // Create 256 notes (NoteMap's current sharding limit) and check that we are not yet sharding
+ ObjectId oldState = inserExternalIds(256);
+ assertNoFilesHaveSlashesInPath();
+ // Create one more external ID and then have the Loader compute the new state
+ ObjectId head = insertExternalId(500, 500);
+ assertAllFilesHaveSlashesInPath(); // NoteMap resharded
+
+ when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+
+ assertThat(loader.load(head)).isEqualTo(allFromGit(head));
+ verifyZeroInteractions(externalIdReaderSpy);
+ }
+
+ private ExternalIdCacheLoader createLoader(boolean allowPartial) {
+ Config cfg = new Config();
+ cfg.setBoolean("cache", "external_ids_map", "enablePartialReloads", allowPartial);
+ return new ExternalIdCacheLoader(
+ repoManager,
+ ALL_USERS,
+ externalIdReaderSpy,
+ Providers.of(externalIdCache),
+ new DisabledMetricMaker(),
+ cfg);
+ }
+
+ private AllExternalIds allFromGit(ObjectId revision) throws Exception {
+ return AllExternalIds.create(externalIdReader.all(revision));
+ }
+
+ private ObjectId inserExternalIds(int numberOfIdsToInsert) throws Exception {
+ ObjectId oldState = null;
+ // Create more than 256 notes (NoteMap's current sharding limit) and check that we really have
+ // created a situation where NoteNames are sharded.
+ for (int i = 0; i < numberOfIdsToInsert; i++) {
+ oldState = insertExternalId(i, i);
+ }
+ return oldState;
+ }
+
+ private ObjectId insertExternalId(int key, int accountId) throws Exception {
+ return performExternalIdUpdate(
+ u -> {
+ try {
+ u.insert(externalId(key, accountId));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private ObjectId modifyExternalId(ExternalId oldId, ExternalId newId) throws Exception {
+ return performExternalIdUpdate(
+ u -> {
+ try {
+ u.replace(oldId, newId);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private ObjectId deleteExternalId(int key, int accountId) throws Exception {
+ return performExternalIdUpdate(u -> u.delete(externalId(key, accountId)));
+ }
+
+ private ExternalId externalId(int key, int accountId) {
+ return ExternalId.create("fooschema", "bar" + key, Account.id(accountId));
+ }
+
+ private ObjectId performExternalIdUpdate(Consumer<ExternalIdNotes> update) throws Exception {
+ try (Repository repo = repoManager.openRepository(ALL_USERS)) {
+ PersonIdent updater = new PersonIdent("Foo bar", "foo@bar.com");
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(ALL_USERS, repo);
+ update.accept(extIdNotes);
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, repo)) {
+ metaDataUpdate.getCommitBuilder().setAuthor(updater);
+ metaDataUpdate.getCommitBuilder().setCommitter(updater);
+ return extIdNotes.commit(metaDataUpdate).getId();
+ }
+ }
+ }
+
+ private void assertAllFilesHaveSlashesInPath() throws Exception {
+ assertThat(allFilesInExternalIdRef().stream().allMatch(f -> f.contains("/"))).isTrue();
+ }
+
+ private void assertNoFilesHaveSlashesInPath() throws Exception {
+ assertThat(allFilesInExternalIdRef().stream().noneMatch(f -> f.contains("/"))).isTrue();
+ }
+
+ private ImmutableList<String> allFilesInExternalIdRef() throws Exception {
+ try (Repository repo = repoManager.openRepository(ALL_USERS);
+ TreeWalk treeWalk = new TreeWalk(repo);
+ RevWalk rw = new RevWalk(repo)) {
+ treeWalk.reset(
+ rw.parseCommit(repo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId()).getTree());
+ treeWalk.setRecursive(true);
+ ImmutableList.Builder<String> allPaths = ImmutableList.builder();
+ while (treeWalk.next()) {
+ allPaths.add(treeWalk.getPathString());
+ }
+ return allPaths.build();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java b/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java
index 13de3e7ad0..ba40d8c211 100644
--- a/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java
+++ b/javatests/com/google/gerrit/server/auth/ldap/LdapRealmTest.java
@@ -24,8 +24,8 @@ import static com.google.gerrit.server.auth.ldap.LdapModule.PARENT_GROUPS_CACHE;
import static com.google.gerrit.server.auth.ldap.LdapModule.USERNAME_CACHE;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.testing.InMemoryModule;
@@ -64,7 +64,7 @@ public final class LdapRealmTest {
}
private ExternalId id(String scheme, String id) {
- return ExternalId.create(scheme, id, new Account.Id(1000));
+ return ExternalId.create(scheme, id, Account.id(1000));
}
private boolean accountBelongsToRealm(ExternalId... ids) {
diff --git a/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java b/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java
index 7a0661a59b..dc62a61017 100644
--- a/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java
+++ b/javatests/com/google/gerrit/server/auth/oauth/OAuthRealmTest.java
@@ -19,7 +19,7 @@ import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_EXT
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
-import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Guice;
@@ -39,7 +39,7 @@ public final class OAuthRealmTest {
}
private ExternalId id(String scheme, String id) {
- return ExternalId.create(scheme, id, new Account.Id(1000));
+ return ExternalId.create(scheme, id, Account.id(1000));
}
private boolean accountBelongsToRealm(ExternalId... ids) {
diff --git a/javatests/com/google/gerrit/server/cache/BUILD b/javatests/com/google/gerrit/server/cache/BUILD
index 0c3bf3ed40..346c578d7d 100644
--- a/javatests/com/google/gerrit/server/cache/BUILD
+++ b/javatests/com/google/gerrit/server/cache/BUILD
@@ -7,10 +7,10 @@ junit_tests(
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//javatests/com/google/gerrit/util/http/testutil",
+ "//lib:jgit",
"//lib:junit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
- "@jgit-lib//jar",
"@servlet-api//jar",
],
)
diff --git a/javatests/com/google/gerrit/server/cache/PerThreadCacheTest.java b/javatests/com/google/gerrit/server/cache/PerThreadCacheTest.java
index 2e7d8eb75a..35a28c08b2 100644
--- a/javatests/com/google/gerrit/server/cache/PerThreadCacheTest.java
+++ b/javatests/com/google/gerrit/server/cache/PerThreadCacheTest.java
@@ -21,7 +21,6 @@ import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.server.cache.PerThreadCache.Key;
import com.google.gerrit.server.cache.PerThreadCache.ReadonlyRequestWindow;
import com.google.gerrit.server.git.RefCache;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import java.io.IOException;
import java.util.Optional;
@@ -32,7 +31,7 @@ import javax.servlet.http.HttpServletRequestWrapper;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class PerThreadCacheTest extends GerritBaseTests {
+public class PerThreadCacheTest {
@Test
public void key_respectsClass() {
assertThat(PerThreadCache.Key.create(String.class))
@@ -96,9 +95,9 @@ public class PerThreadCacheTest extends GerritBaseTests {
@Test
public void doubleInstantiationFails() {
try (PerThreadCache ignored = PerThreadCache.create(null)) {
- exception.expect(IllegalStateException.class);
- exception.expectMessage("called create() twice on the same request");
- PerThreadCache.create(null);
+ IllegalStateException thrown =
+ assertThrows(IllegalStateException.class, () -> PerThreadCache.create(null));
+ assertThat(thrown).hasMessageThat().contains("called create() twice on the same request");
}
}
diff --git a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
index 147aeeb58c..69c2799f2d 100644
--- a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
+++ b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.cache.h2;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -66,22 +67,22 @@ public class H2CacheTest {
return "bar";
}))
.isEqualTo("bar");
- assertThat(called.get()).named("Callable was called").isTrue();
- assertThat(impl.getIfPresent("foo")).named("in-memory value").isEqualTo("bar");
+ assertWithMessage("Callable was called").that(called.get()).isTrue();
+ assertWithMessage("in-memory value").that(impl.getIfPresent("foo")).isEqualTo("bar");
mem.invalidate("foo");
- assertThat(impl.getIfPresent("foo")).named("persistent value").isEqualTo("bar");
+ assertWithMessage("persistent value").that(impl.getIfPresent("foo")).isEqualTo("bar");
called.set(false);
- assertThat(
+ assertWithMessage("cached value")
+ .that(
impl.get(
"foo",
() -> {
called.set(true);
return "baz";
}))
- .named("cached value")
.isEqualTo("bar");
- assertThat(called.get()).named("Callable was called").isFalse();
+ assertWithMessage("Callable was called").that(called.get()).isFalse();
}
@Test
diff --git a/javatests/com/google/gerrit/server/cache/serialize/BUILD b/javatests/com/google/gerrit/server/cache/serialize/BUILD
index a0d5ea6767..ce5f273a38 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/BUILD
+++ b/javatests/com/google/gerrit/server/cache/serialize/BUILD
@@ -4,16 +4,16 @@ junit_tests(
name = "tests",
srcs = glob(["*.java"]),
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/cache/testing",
"//java/com/google/gerrit/testing:gerrit-test-util",
- "//java/com/google/gwtorm",
"//lib:guava",
+ "//lib:jgit",
"//lib:junit",
"//lib:protobuf",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
"//lib/truth:truth-proto-extension",
"//proto:cache_java_proto",
diff --git a/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
index c634a785bc..ebd7d5553f 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/BooleanCacheSerializerTest.java
@@ -15,14 +15,12 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.protobuf.TextFormat;
import org.junit.Test;
-public class BooleanCacheSerializerTest extends GerritBaseTests {
+public class BooleanCacheSerializerTest {
@Test
public void serialize() throws Exception {
assertThat(BooleanCacheSerializer.INSTANCE.serialize(true))
@@ -53,11 +51,6 @@ public class BooleanCacheSerializerTest extends GerritBaseTests {
}
private static void assertDeserializeFails(byte[] in) {
- try {
- BooleanCacheSerializer.INSTANCE.deserialize(in);
- assert_().fail("expected deserialization to fail for \"%s\"", TextFormat.escapeBytes(in));
- } catch (RuntimeException e) {
- // Expected.
- }
+ assertThrows(RuntimeException.class, () -> BooleanCacheSerializer.INSTANCE.deserialize(in));
}
}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/CacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/CacheSerializerTest.java
new file mode 100644
index 0000000000..819189f96c
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/serialize/CacheSerializerTest.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache.serialize;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Converter;
+import org.junit.Test;
+
+public class CacheSerializerTest {
+ @AutoValue
+ abstract static class MyAutoValue {
+ static MyAutoValue create(int val) {
+ return new AutoValue_CacheSerializerTest_MyAutoValue(val);
+ }
+
+ abstract int val();
+ }
+
+ private static final CacheSerializer<MyAutoValue> SERIALIZER =
+ CacheSerializer.convert(
+ IntegerCacheSerializer.INSTANCE, Converter.from(MyAutoValue::val, MyAutoValue::create));
+
+ @Test
+ public void serialize() throws Exception {
+ MyAutoValue v = MyAutoValue.create(1234);
+ byte[] serialized = SERIALIZER.serialize(v);
+ assertThat(serialized).isEqualTo(new byte[] {-46, 9});
+ assertThat(SERIALIZER.deserialize(serialized).val()).isEqualTo(1234);
+ }
+
+ @Test
+ public void deserializeNullFails() throws Exception {
+ assertThrows(RuntimeException.class, () -> SERIALIZER.deserialize(null));
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
index c6efc21513..7bfcc590cb 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/EnumCacheSerializerTest.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class EnumCacheSerializerTest extends GerritBaseTests {
+public class EnumCacheSerializerTest {
@Test
public void serialize() throws Exception {
assertRoundTrip(MyEnum.FOO);
@@ -50,11 +49,6 @@ public class EnumCacheSerializerTest extends GerritBaseTests {
private static void assertDeserializeFails(byte[] in) {
CacheSerializer<MyEnum> s = new EnumCacheSerializer<>(MyEnum.class);
- try {
- s.deserialize(in);
- assert_().fail("expected RuntimeException");
- } catch (RuntimeException e) {
- // Expected.
- }
+ assertThrows(RuntimeException.class, () -> s.deserialize(in));
}
}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java
deleted file mode 100644
index 56dd6ad5a2..0000000000
--- a/javatests/com/google/gerrit/server/cache/serialize/IntKeyCacheSerializerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache.serialize;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gwtorm.client.IntKey;
-import com.google.gwtorm.client.Key;
-import org.junit.Test;
-
-public class IntKeyCacheSerializerTest extends GerritBaseTests {
-
- private static class MyIntKey extends IntKey<Key<?>> {
- private static final long serialVersionUID = 1L;
-
- private int val;
-
- MyIntKey(int val) {
- this.val = val;
- }
-
- @Override
- public int get() {
- return val;
- }
-
- @Override
- protected void set(int newValue) {
- this.val = newValue;
- }
- }
-
- private static final IntKeyCacheSerializer<MyIntKey> SERIALIZER =
- new IntKeyCacheSerializer<>(MyIntKey::new);
-
- @Test
- public void serialize() throws Exception {
- MyIntKey k = new MyIntKey(1234);
- byte[] serialized = SERIALIZER.serialize(k);
- assertThat(serialized).isEqualTo(new byte[] {-46, 9});
- assertThat(SERIALIZER.deserialize(serialized).get()).isEqualTo(1234);
- }
-
- @Test
- public void deserializeNullFails() throws Exception {
- try {
- SERIALIZER.deserialize(null);
- assert_().fail("expected RuntimeException");
- } catch (RuntimeException e) {
- // Expected.
- }
- }
-}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
index 1d540100ae..40ff0acd11 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/IntegerCacheSerializerTest.java
@@ -14,16 +14,15 @@
package com.google.gerrit.server.cache.serialize;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.protobuf.TextFormat;
import org.junit.Test;
-public class IntegerCacheSerializerTest extends GerritBaseTests {
+public class IntegerCacheSerializerTest {
@Test
public void serialize() throws Exception {
for (int i :
@@ -49,17 +48,12 @@ public class IntegerCacheSerializerTest extends GerritBaseTests {
private static void assertRoundTrip(int i) throws Exception {
byte[] serialized = IntegerCacheSerializer.INSTANCE.serialize(i);
int result = IntegerCacheSerializer.INSTANCE.deserialize(serialized);
- assertThat(result)
- .named("round-trip of %s via \"%s\"", i, TextFormat.escapeBytes(serialized))
+ assertWithMessage("round-trip of %s via \"%s\"", i, TextFormat.escapeBytes(serialized))
+ .that(result)
.isEqualTo(i);
}
private static void assertDeserializeFails(byte[] in) {
- try {
- IntegerCacheSerializer.INSTANCE.deserialize(in);
- assert_().fail("expected RuntimeException");
- } catch (RuntimeException e) {
- // Expected.
- }
+ assertThrows(RuntimeException.class, () -> IntegerCacheSerializer.INSTANCE.deserialize(in));
}
}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
index 9fcb8a4688..effc801c54 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/JavaCacheSerializerTest.java
@@ -17,11 +17,11 @@ package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Project;
import java.io.Serializable;
import org.junit.Test;
-public class JavaCacheSerializerTest extends GerritBaseTests {
+public class JavaCacheSerializerTest {
@Test
public void builtInTypes() throws Exception {
assertRoundTrip("foo");
@@ -34,6 +34,11 @@ public class JavaCacheSerializerTest extends GerritBaseTests {
assertRoundTrip(new AutoValue_JavaCacheSerializerTest_MyType(123, "four five six"));
}
+ @Test
+ public void gerritEntities() throws Exception {
+ assertRoundTrip(Project.nameKey("foo"));
+ }
+
@AutoValue
abstract static class MyType implements Serializable {
private static final long serialVersionUID = 1L;
diff --git a/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java
index 257be54bdf..7d6647a09c 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdCacheSerializerTest.java
@@ -15,14 +15,13 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteArray;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class ObjectIdCacheSerializerTest extends GerritBaseTests {
+public class ObjectIdCacheSerializerTest {
@Test
public void serialize() {
ObjectId id = ObjectId.fromString("aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
@@ -47,11 +46,7 @@ public class ObjectIdCacheSerializerTest extends GerritBaseTests {
}
private void assertDeserializeFails(byte[] bytes) {
- try {
- ObjectIdCacheSerializer.INSTANCE.deserialize(bytes);
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class, () -> ObjectIdCacheSerializer.INSTANCE.deserialize(bytes));
}
}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/ObjectIdConverterTest.java b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdConverterTest.java
index c5ea2ea2c3..f6d6c8ab3c 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/ObjectIdConverterTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/ObjectIdConverterTest.java
@@ -15,15 +15,14 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.protobuf.ByteString;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class ObjectIdConverterTest extends GerritBaseTests {
+public class ObjectIdConverterTest {
@Test
public void objectIdFromByteString() {
ObjectIdConverter idConverter = ObjectIdConverter.create();
@@ -43,12 +42,9 @@ public class ObjectIdConverterTest extends GerritBaseTests {
@Test
public void objectIdFromByteStringWrongSize() {
- try {
- ObjectIdConverter.create().fromByteString(ByteString.copyFromUtf8("foo"));
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ObjectIdConverter.create().fromByteString(ByteString.copyFromUtf8("foo")));
}
@Test
diff --git a/javatests/com/google/gerrit/server/cache/serialize/ProtobufSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/ProtobufSerializerTest.java
index 845da9b700..04d2f73800 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/ProtobufSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/ProtobufSerializerTest.java
@@ -17,10 +17,9 @@ package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.proto.testing.Test.SerializableProto;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class ProtobufSerializerTest extends GerritBaseTests {
+public class ProtobufSerializerTest {
@Test
public void requiredAndOptionalTypes() {
assertRoundTrip(SerializableProto.newBuilder().setId(123));
diff --git a/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
index ff0cf9af04..dc228050d6 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/StringCacheSerializerTest.java
@@ -15,14 +15,13 @@
package com.google.gerrit.server.cache.serialize;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
-public class StringCacheSerializerTest extends GerritBaseTests {
+public class StringCacheSerializerTest {
@Test
public void serialize() {
assertThat(StringCacheSerializer.INSTANCE.serialize("")).isEmpty();
@@ -35,12 +34,11 @@ public class StringCacheSerializerTest extends GerritBaseTests {
@Test
public void serializeInvalidChar() {
// Can't use UTF-8 for the test, since it can encode all Unicode code points.
- try {
- StringCacheSerializer.serialize(StandardCharsets.US_ASCII, "\u1234");
- assert_().fail("expected IllegalStateException");
- } catch (IllegalStateException expected) {
- assertThat(expected).hasCauseThat().isInstanceOf(CharacterCodingException.class);
- }
+ IllegalStateException thrown =
+ assertThrows(
+ IllegalStateException.class,
+ () -> StringCacheSerializer.serialize(StandardCharsets.US_ASCII, "\u1234"));
+ assertThat(thrown).hasCauseThat().isInstanceOf(CharacterCodingException.class);
}
@Test
@@ -56,11 +54,10 @@ public class StringCacheSerializerTest extends GerritBaseTests {
@Test
public void deserializeInvalidChar() {
- try {
- StringCacheSerializer.INSTANCE.deserialize(new byte[] {(byte) 0xff});
- assert_().fail("expected IllegalStateException");
- } catch (IllegalStateException expected) {
- assertThat(expected).hasCauseThat().isInstanceOf(CharacterCodingException.class);
- }
+ IllegalStateException thrown =
+ assertThrows(
+ IllegalStateException.class,
+ () -> StringCacheSerializer.INSTANCE.deserialize(new byte[] {(byte) 0xff}));
+ assertThat(thrown).hasCauseThat().isInstanceOf(CharacterCodingException.class);
}
}
diff --git a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
index fffb1da213..20813f6834 100644
--- a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
+++ b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
@@ -24,11 +24,10 @@ import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.change.ChangeKindCacheImpl.Key;
-import com.google.gerrit.testing.GerritBaseTests;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class ChangeKindCacheImplTest extends GerritBaseTests {
+public class ChangeKindCacheImplTest {
@Test
public void keySerializer() throws Exception {
ChangeKindCacheImpl.Key key =
diff --git a/javatests/com/google/gerrit/server/change/HashtagsTest.java b/javatests/com/google/gerrit/server/change/HashtagsTest.java
index 49d295263c..780ac71929 100644
--- a/javatests/com/google/gerrit/server/change/HashtagsTest.java
+++ b/javatests/com/google/gerrit/server/change/HashtagsTest.java
@@ -17,10 +17,9 @@ package com.google.gerrit.server.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Sets;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class HashtagsTest extends GerritBaseTests {
+public class HashtagsTest {
@Test
public void emptyCommitMessage() throws Exception {
assertThat(HashtagsUtil.extractTags("")).isEmpty();
diff --git a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
index 0cfe483d0e..b69a894b14 100644
--- a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -15,19 +15,22 @@
package com.google.gerrit.server.change;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS;
+import static com.google.gerrit.entities.RefNames.REFS_TAGS;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.common.truth.Correspondence;
+import com.google.gerrit.truth.NullAwareCorrespondence;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.junit.Before;
import org.junit.Test;
-public class IncludedInResolverTest extends GerritBaseTests {
+public class IncludedInResolverTest {
// Branch names
private static final String BRANCH_MASTER = "master";
private static final String BRANCH_1_0 = "rel-1.0";
@@ -113,8 +116,12 @@ public class IncludedInResolverTest extends GerritBaseTests {
IncludedInResolver.Result detail = resolve(commit_v2_5);
// Check that only tags and branches which refer the tip are returned
- assertThat(detail.tags()).containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
- assertThat(detail.branches()).containsExactly(BRANCH_2_5);
+ assertThat(detail.tags())
+ .comparingElementsUsing(hasShortName())
+ .containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
+ assertThat(detail.branches())
+ .comparingElementsUsing(hasShortName())
+ .containsExactly(BRANCH_2_5);
}
@Test
@@ -124,6 +131,7 @@ public class IncludedInResolverTest extends GerritBaseTests {
// Check whether all tags and branches are returned
assertThat(detail.tags())
+ .comparingElementsUsing(hasShortName())
.containsExactly(
TAG_1_0,
TAG_1_0_1,
@@ -134,6 +142,7 @@ public class IncludedInResolverTest extends GerritBaseTests {
TAG_2_5_ANNOTATED,
TAG_2_5_ANNOTATED_TWICE);
assertThat(detail.branches())
+ .comparingElementsUsing(hasShortName())
.containsExactly(BRANCH_MASTER, BRANCH_1_0, BRANCH_1_3, BRANCH_2_0, BRANCH_2_5);
}
@@ -144,8 +153,11 @@ public class IncludedInResolverTest extends GerritBaseTests {
// Check whether all succeeding tags and branches are returned
assertThat(detail.tags())
+ .comparingElementsUsing(hasShortName())
.containsExactly(TAG_1_3, TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
- assertThat(detail.branches()).containsExactly(BRANCH_1_3, BRANCH_2_5);
+ assertThat(detail.branches())
+ .comparingElementsUsing(hasShortName())
+ .containsExactly(BRANCH_1_3, BRANCH_2_5);
}
private IncludedInResolver.Result resolve(RevCommit commit) throws Exception {
@@ -155,4 +167,9 @@ public class IncludedInResolverTest extends GerritBaseTests {
private RevTag tag(String name, RevObject dest) throws Exception {
return tr.update(REFS_TAGS + name, tr.tag(name, dest));
}
+
+ private static Correspondence<Ref, String> hasShortName() {
+ return NullAwareCorrespondence.transforming(
+ ref -> Repository.shortenRefName(ref.getName()), "has short name");
+ }
}
diff --git a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
index 6e02d61713..c259e6012b 100644
--- a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
+++ b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
@@ -14,24 +14,25 @@
package com.google.gerrit.server.change;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.common.data.Permission.forLabel;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.allow;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
@@ -45,7 +46,6 @@ import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
@@ -57,7 +57,7 @@ import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link LabelNormalizer}. */
-public class LabelNormalizerTest extends GerritBaseTests {
+public class LabelNormalizerTest {
@Inject private AccountManager accountManager;
@Inject private AllProjectsName allProjects;
@Inject private GitRepositoryManager repoManager;
@@ -70,6 +70,7 @@ public class LabelNormalizerTest extends GerritBaseTests {
@Inject private ChangeNotes.Factory changeNotesFactory;
@Inject private ProjectConfig.Factory projectConfigFactory;
@Inject private GerritApi gApi;
+ @Inject private ProjectOperations projectOperations;
private LifecycleManager lifecycle;
private Account.Id userId;
@@ -103,7 +104,7 @@ public class LabelNormalizerTest extends GerritBaseTests {
}
}
LabelType lt =
- category("Verified", value(1, "Verified"), value(0, "No score"), value(-1, "Fails"));
+ label("Verified", value(1, "Verified"), value(0, "No score"), value(-1, "Fails"));
pc.getLabelSections().put(lt.getName(), lt);
save(pc);
}
@@ -115,7 +116,7 @@ public class LabelNormalizerTest extends GerritBaseTests {
input.newBranch = true;
input.subject = "Test change";
ChangeInfo info = gApi.changes().create(input).get();
- notes = changeNotesFactory.createChecked(allProjects, new Change.Id(info._number));
+ notes = changeNotesFactory.createChecked(allProjects, Change.id(info._number));
change = notes.getChange();
}
@@ -129,10 +130,11 @@ public class LabelNormalizerTest extends GerritBaseTests {
@Test
public void noNormalizeByPermission() throws Exception {
- ProjectConfig pc = loadAllProjects();
- allow(pc, forLabel("Code-Review"), -1, 1, REGISTERED_USERS, "refs/heads/*");
- allow(pc, forLabel("Verified"), -1, 1, REGISTERED_USERS, "refs/heads/*");
- save(pc);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-1, 1))
+ .add(allowLabel("Verified").ref("refs/heads/*").group(REGISTERED_USERS).range(-1, 1))
+ .update();
PatchSetApproval cr = psa(userId, "Code-Review", 2);
PatchSetApproval v = psa(userId, "Verified", 1);
@@ -141,10 +143,11 @@ public class LabelNormalizerTest extends GerritBaseTests {
@Test
public void normalizeByType() throws Exception {
- ProjectConfig pc = loadAllProjects();
- allow(pc, forLabel("Code-Review"), -5, 5, REGISTERED_USERS, "refs/heads/*");
- allow(pc, forLabel("Verified"), -5, 5, REGISTERED_USERS, "refs/heads/*");
- save(pc);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-5, 5))
+ .add(allowLabel("Verified").ref("refs/heads/*").group(REGISTERED_USERS).range(-5, 5))
+ .update();
PatchSetApproval cr = psa(userId, "Code-Review", 5);
PatchSetApproval v = psa(userId, "Verified", 5);
@@ -162,9 +165,10 @@ public class LabelNormalizerTest extends GerritBaseTests {
@Test
public void explicitZeroVoteOnNonEmptyRangeIsPresent() throws Exception {
- ProjectConfig pc = loadAllProjects();
- allow(pc, forLabel("Code-Review"), -1, 1, REGISTERED_USERS, "refs/heads/*");
- save(pc);
+ projectOperations
+ .allProjectsForUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-1, 1))
+ .update();
PatchSetApproval cr = psa(userId, "Code-Review", 0);
PatchSetApproval v = psa(userId, "Verified", 0);
@@ -187,16 +191,15 @@ public class LabelNormalizerTest extends GerritBaseTests {
}
private PatchSetApproval psa(Account.Id accountId, String label, int value) {
- return new PatchSetApproval(
- new PatchSetApproval.Key(change.currentPatchSetId(), accountId, new LabelId(label)),
- (short) value,
- TimeUtil.nowTs());
+ return PatchSetApproval.builder()
+ .key(PatchSetApproval.key(change.currentPatchSetId(), accountId, LabelId.create(label)))
+ .value(value)
+ .granted(TimeUtil.nowTs())
+ .build();
}
private PatchSetApproval copy(PatchSetApproval src, int newValue) {
- PatchSetApproval result = new PatchSetApproval(src.getKey().getParentKey(), src);
- result.setValue((short) newValue);
- return result;
+ return src.toBuilder().value(newValue).build();
}
private static List<PatchSetApproval> list(PatchSetApproval... psas) {
diff --git a/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
index 46ddbc27e0..19c8998516 100644
--- a/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
+++ b/javatests/com/google/gerrit/server/change/MergeabilityCacheImplTest.java
@@ -23,11 +23,10 @@ import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
-import com.google.gerrit.testing.GerritBaseTests;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class MergeabilityCacheImplTest extends GerritBaseTests {
+public class MergeabilityCacheImplTest {
@Test
public void keySerializer() throws Exception {
MergeabilityCacheImpl.EntryKey key =
diff --git a/javatests/com/google/gerrit/server/change/WalkSorterTest.java b/javatests/com/google/gerrit/server/change/WalkSorterTest.java
index 189dfbcbe8..2c4c98fb5e 100644
--- a/javatests/com/google/gerrit/server/change/WalkSorterTest.java
+++ b/javatests/com/google/gerrit/server/change/WalkSorterTest.java
@@ -19,14 +19,12 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.change.WalkSorter.PatchSetData;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
import com.google.gerrit.testing.TestChanges;
@@ -39,13 +37,13 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
-public class WalkSorterTest extends GerritBaseTests {
+public class WalkSorterTest {
private Account.Id userId;
private InMemoryRepositoryManager repoManager;
@Before
public void setUp() {
- userId = new Account.Id(1);
+ userId = Account.id(1);
repoManager = new InMemoryRepositoryManager();
}
@@ -280,7 +278,7 @@ public class WalkSorterTest extends GerritBaseTests {
// If we restrict to PS1 of each change, the sorter uses that commit.
sorter.includePatchSets(
- ImmutableSet.of(new PatchSet.Id(cd1.getId(), 1), new PatchSet.Id(cd2.getId(), 1)));
+ ImmutableSet.of(PatchSet.id(cd1.getId(), 1), PatchSet.id(cd2.getId(), 1)));
assertSorted(
sorter, changes, ImmutableList.of(patchSetData(cd2, 1, c2_1), patchSetData(cd1, 1, c1_1)));
}
@@ -297,8 +295,7 @@ public class WalkSorterTest extends GerritBaseTests {
List<ChangeData> changes = ImmutableList.of(cd1, cd2);
WalkSorter sorter =
- new WalkSorter(repoManager)
- .includePatchSets(ImmutableSet.of(cd1.currentPatchSet().getId()));
+ new WalkSorter(repoManager).includePatchSets(ImmutableSet.of(cd1.currentPatchSet().id()));
assertSorted(sorter, changes, ImmutableList.of(patchSetData(cd1, c1)));
}
@@ -335,17 +332,15 @@ public class WalkSorterTest extends GerritBaseTests {
private ChangeData newChange(TestRepository<Repo> tr, ObjectId id) throws Exception {
Project.NameKey project = tr.getRepository().getDescription().getProject();
Change c = TestChanges.newChange(project, userId);
- ChangeData cd = ChangeData.createForTest(project, c.getId(), 1);
+ ChangeData cd = ChangeData.createForTest(project, c.getId(), 1, id);
cd.setChange(c);
- cd.currentPatchSet().setRevision(new RevId(id.name()));
cd.setPatchSets(ImmutableList.of(cd.currentPatchSet()));
return cd;
}
private PatchSet addPatchSet(ChangeData cd, ObjectId id) throws Exception {
TestChanges.incrementPatchSet(cd.change());
- PatchSet ps = new PatchSet(cd.change().currentPatchSetId());
- ps.setRevision(new RevId(id.name()));
+ PatchSet ps = TestChanges.newPatchSet(cd.change().currentPatchSetId(), id.name(), userId);
List<PatchSet> patchSets = new ArrayList<>(cd.patchSets());
patchSets.add(ps);
cd.setPatchSets(patchSets);
@@ -353,7 +348,7 @@ public class WalkSorterTest extends GerritBaseTests {
}
private TestRepository<Repo> newRepo(String name) throws Exception {
- return new TestRepository<>(repoManager.createRepository(new Project.NameKey(name)));
+ return new TestRepository<>(repoManager.createRepository(Project.nameKey(name)));
}
private static PatchSetData patchSetData(ChangeData cd, RevCommit commit) throws Exception {
@@ -362,7 +357,7 @@ public class WalkSorterTest extends GerritBaseTests {
private static PatchSetData patchSetData(ChangeData cd, int psId, RevCommit commit)
throws Exception {
- return PatchSetData.create(cd, cd.patchSet(new PatchSet.Id(cd.getId(), psId)), commit);
+ return PatchSetData.create(cd, cd.patchSet(PatchSet.id(cd.getId(), psId)), commit);
}
private static void assertSorted(
diff --git a/javatests/com/google/gerrit/server/config/AllProjectsNameTest.java b/javatests/com/google/gerrit/server/config/AllProjectsNameTest.java
new file mode 100644
index 0000000000..64a5f834cf
--- /dev/null
+++ b/javatests/com/google/gerrit/server/config/AllProjectsNameTest.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import org.junit.Test;
+
+public class AllProjectsNameTest {
+ @Test
+ public void equalToProjectNameKey() {
+ String name = "a-project";
+ AllProjectsName allProjectsName = new AllProjectsName(name);
+ Project.NameKey projectName = Project.nameKey(name);
+ assertThat(allProjectsName.get()).isEqualTo(projectName.get());
+ assertThat(allProjectsName).isEqualTo(projectName);
+ }
+
+ @Test
+ public void equalToAllUsersName() {
+ String name = "a-project";
+ AllProjectsName allProjectsName = new AllProjectsName(name);
+ AllUsersName allUsersName = new AllUsersName(name);
+ assertThat(allProjectsName.get()).isEqualTo(allUsersName.get());
+ assertThat(allProjectsName).isEqualTo(allUsersName);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/config/AllUsersNameTest.java b/javatests/com/google/gerrit/server/config/AllUsersNameTest.java
new file mode 100644
index 0000000000..d46b7a640a
--- /dev/null
+++ b/javatests/com/google/gerrit/server/config/AllUsersNameTest.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import org.junit.Test;
+
+public class AllUsersNameTest {
+ @Test
+ public void equalToProjectNameKey() {
+ String name = "a-project";
+ AllUsersName allUsersName = new AllUsersName(name);
+ Project.NameKey projectName = Project.nameKey(name);
+ assertThat(allUsersName.get()).isEqualTo(projectName.get());
+ assertThat(allUsersName).isEqualTo(projectName);
+ }
+
+ @Test
+ public void equalToAllProjectsName() {
+ String name = "a-project";
+ AllUsersName allUsersName = new AllUsersName(name);
+ AllProjectsName allProjectsName = new AllProjectsName(name);
+ assertThat(allUsersName.get()).isEqualTo(allProjectsName.get());
+ assertThat(allUsersName).isEqualTo(allProjectsName);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/config/ConfigUtilTest.java b/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
index b1378ad265..035878caf9 100644
--- a/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/javatests/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -15,20 +15,20 @@
package com.google.gerrit.server.config;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
-public class ConfigUtilTest extends GerritBaseTests {
+public class ConfigUtilTest {
private static final String SECT = "foo";
private static final String SUB = "bar";
@@ -85,17 +85,17 @@ public class ConfigUtilTest extends GerritBaseTests {
Config cfg = new Config();
ConfigUtil.storeSection(cfg, SECT, SUB, in, d);
- assertThat(cfg.getString(SECT, SUB, "CONSTANT")).isNull();
- assertThat(cfg.getString(SECT, SUB, "missing")).isNull();
- assertThat(cfg.getBoolean(SECT, SUB, "b", false)).isEqualTo(in.b);
- assertThat(cfg.getBoolean(SECT, SUB, "bb", false)).isEqualTo(in.bb);
- assertThat(cfg.getInt(SECT, SUB, "i", 0)).isEqualTo(0);
- assertThat(cfg.getInt(SECT, SUB, "ii", 0)).isEqualTo(in.ii);
- assertThat(cfg.getLong(SECT, SUB, "l", 0L)).isEqualTo(0L);
- assertThat(cfg.getLong(SECT, SUB, "ll", 0L)).isEqualTo(in.ll);
- assertThat(cfg.getString(SECT, SUB, "s")).isEqualTo(in.s);
- assertThat(cfg.getString(SECT, SUB, "sd")).isNull();
- assertThat(cfg.getString(SECT, SUB, "nd")).isNull();
+ assertThat(cfg).stringValue(SECT, SUB, "CONSTANT").isNull();
+ assertThat(cfg).stringValue(SECT, SUB, "missing").isNull();
+ assertThat(cfg).booleanValue(SECT, SUB, "b", false).isEqualTo(in.b);
+ assertThat(cfg).booleanValue(SECT, SUB, "bb", false).isEqualTo(in.bb);
+ assertThat(cfg).intValue(SECT, SUB, "i", 0).isEqualTo(0);
+ assertThat(cfg).intValue(SECT, SUB, "ii", 0).isEqualTo(in.ii);
+ assertThat(cfg).longValue(SECT, SUB, "l", 0L).isEqualTo(0L);
+ assertThat(cfg).longValue(SECT, SUB, "ll", 0L).isEqualTo(in.ll);
+ assertThat(cfg).stringValue(SECT, SUB, "s").isEqualTo(in.s);
+ assertThat(cfg).stringValue(SECT, SUB, "sd").isNull();
+ assertThat(cfg).stringValue(SECT, SUB, "nd").isNull();
SectionInfo out = new SectionInfo();
ConfigUtil.loadSection(cfg, SECT, SUB, out, d, null);
diff --git a/javatests/com/google/gerrit/server/config/GitwebConfigTest.java b/javatests/com/google/gerrit/server/config/GitwebConfigTest.java
index bf7e4fd237..cb6de34b76 100644
--- a/javatests/com/google/gerrit/server/config/GitwebConfigTest.java
+++ b/javatests/com/google/gerrit/server/config/GitwebConfigTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.config;
import static com.google.common.truth.Truth.assertWithMessage;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class GitwebConfigTest extends GerritBaseTests {
+public class GitwebConfigTest {
private static final String VALID_CHARACTERS = "*()";
private static final String SOME_INVALID_CHARACTERS = "09AZaz$-_.+!',";
diff --git a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
index 30fabdc521..708e2474e4 100644
--- a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
+++ b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -18,16 +18,15 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.PluginProjectPermissionDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.restapi.config.ListCapabilities;
import com.google.gerrit.server.restapi.config.ListCapabilities.CapabilityInfo;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -36,7 +35,7 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
-public class ListCapabilitiesTest extends GerritBaseTests {
+public class ListCapabilitiesTest {
private Injector injector;
@Before
@@ -74,7 +73,7 @@ public class ListCapabilitiesTest extends GerritBaseTests {
@Test
public void list() throws Exception {
Map<String, CapabilityInfo> m =
- injector.getInstance(ListCapabilities.class).apply(new ConfigResource());
+ injector.getInstance(ListCapabilities.class).apply(new ConfigResource()).value();
for (String id : GlobalCapability.getAllNames()) {
assertThat(m).containsKey(id);
assertThat(m.get(id).id).isEqualTo(id);
diff --git a/javatests/com/google/gerrit/server/config/RepositoryConfigTest.java b/javatests/com/google/gerrit/server/config/RepositoryConfigTest.java
index 2a473f42e5..d7aae6a0fe 100644
--- a/javatests/com/google/gerrit/server/config/RepositoryConfigTest.java
+++ b/javatests/com/google/gerrit/server/config/RepositoryConfigTest.java
@@ -18,9 +18,8 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.testing.GerritBaseTests;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -28,7 +27,7 @@ import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
-public class RepositoryConfigTest extends GerritBaseTests {
+public class RepositoryConfigTest {
private Config cfg;
private RepositoryConfig repoCfg;
@@ -42,35 +41,35 @@ public class RepositoryConfigTest extends GerritBaseTests {
@Test
public void defaultSubmitTypeWhenNotConfigured() {
// Check expected value explicitly rather than depending on constant.
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.INHERIT);
}
@Test
public void defaultSubmitTypeForStarFilter() {
configureDefaultSubmitType("*", SubmitType.CHERRY_PICK);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.CHERRY_PICK);
configureDefaultSubmitType("*", SubmitType.FAST_FORWARD_ONLY);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.FAST_FORWARD_ONLY);
configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.REBASE_IF_NECESSARY);
configureDefaultSubmitType("*", SubmitType.REBASE_ALWAYS);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.REBASE_ALWAYS);
}
@Test
public void defaultSubmitTypeForSpecificFilter() {
configureDefaultSubmitType("someProject", SubmitType.CHERRY_PICK);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someOtherProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someOtherProject")))
.isEqualTo(RepositoryConfig.DEFAULT_SUBMIT_TYPE);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.CHERRY_PICK);
}
@@ -80,13 +79,13 @@ public class RepositoryConfigTest extends GerritBaseTests {
configureDefaultSubmitType("somePath/*", SubmitType.CHERRY_PICK);
configureDefaultSubmitType("*", SubmitType.MERGE_ALWAYS);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("someProject")))
.isEqualTo(SubmitType.MERGE_ALWAYS);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("somePath/someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("somePath/someProject")))
.isEqualTo(SubmitType.CHERRY_PICK);
- assertThat(repoCfg.getDefaultSubmitType(new Project.NameKey("somePath/somePath/someProject")))
+ assertThat(repoCfg.getDefaultSubmitType(Project.nameKey("somePath/somePath/someProject")))
.isEqualTo(SubmitType.REBASE_IF_NECESSARY);
}
@@ -100,14 +99,14 @@ public class RepositoryConfigTest extends GerritBaseTests {
@Test
public void ownerGroupsWhenNotConfigured() {
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("someProject"))).isEmpty();
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("someProject"))).isEmpty();
}
@Test
public void ownerGroupsForStarFilter() {
ImmutableList<String> ownerGroups = ImmutableList.of("group1", "group2");
configureOwnerGroups("*", ownerGroups);
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("someProject")))
.containsExactlyElementsIn(ownerGroups);
}
@@ -115,8 +114,8 @@ public class RepositoryConfigTest extends GerritBaseTests {
public void ownerGroupsForSpecificFilter() {
ImmutableList<String> ownerGroups = ImmutableList.of("group1", "group2");
configureOwnerGroups("someProject", ownerGroups);
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("someOtherProject"))).isEmpty();
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("someOtherProject"))).isEmpty();
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("someProject")))
.containsExactlyElementsIn(ownerGroups);
}
@@ -130,13 +129,13 @@ public class RepositoryConfigTest extends GerritBaseTests {
configureOwnerGroups("somePath/*", ownerGroups2);
configureOwnerGroups("somePath/somePath/*", ownerGroups3);
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("someProject")))
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("someProject")))
.containsExactlyElementsIn(ownerGroups1);
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("somePath/someProject")))
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("somePath/someProject")))
.containsExactlyElementsIn(ownerGroups2);
- assertThat(repoCfg.getOwnerGroups(new Project.NameKey("somePath/somePath/someProject")))
+ assertThat(repoCfg.getOwnerGroups(Project.nameKey("somePath/somePath/someProject")))
.containsExactlyElementsIn(ownerGroups3);
}
@@ -150,24 +149,22 @@ public class RepositoryConfigTest extends GerritBaseTests {
@Test
public void basePathWhenNotConfigured() {
- assertThat(repoCfg.getBasePath(new Project.NameKey("someProject"))).isNull();
+ assertThat(repoCfg.getBasePath(Project.nameKey("someProject"))).isNull();
}
@Test
public void basePathForStarFilter() {
String basePath = "/someAbsolutePath/someDirectory";
configureBasePath("*", basePath);
- assertThat(repoCfg.getBasePath(new Project.NameKey("someProject")).toString())
- .isEqualTo(basePath);
+ assertThat(repoCfg.getBasePath(Project.nameKey("someProject")).toString()).isEqualTo(basePath);
}
@Test
public void basePathForSpecificFilter() {
String basePath = "/someAbsolutePath/someDirectory";
configureBasePath("someProject", basePath);
- assertThat(repoCfg.getBasePath(new Project.NameKey("someOtherProject"))).isNull();
- assertThat(repoCfg.getBasePath(new Project.NameKey("someProject")).toString())
- .isEqualTo(basePath);
+ assertThat(repoCfg.getBasePath(Project.nameKey("someOtherProject"))).isNull();
+ assertThat(repoCfg.getBasePath(Project.nameKey("someProject")).toString()).isEqualTo(basePath);
}
@Test
@@ -182,14 +179,12 @@ public class RepositoryConfigTest extends GerritBaseTests {
configureBasePath("project/*", basePath3);
configureBasePath("*", basePath4);
- assertThat(repoCfg.getBasePath(new Project.NameKey("project1")).toString())
- .isEqualTo(basePath1);
- assertThat(repoCfg.getBasePath(new Project.NameKey("project/project/someProject")).toString())
+ assertThat(repoCfg.getBasePath(Project.nameKey("project1")).toString()).isEqualTo(basePath1);
+ assertThat(repoCfg.getBasePath(Project.nameKey("project/project/someProject")).toString())
.isEqualTo(basePath2);
- assertThat(repoCfg.getBasePath(new Project.NameKey("project/someProject")).toString())
+ assertThat(repoCfg.getBasePath(Project.nameKey("project/someProject")).toString())
.isEqualTo(basePath3);
- assertThat(repoCfg.getBasePath(new Project.NameKey("someProject")).toString())
- .isEqualTo(basePath4);
+ assertThat(repoCfg.getBasePath(Project.nameKey("someProject")).toString()).isEqualTo(basePath4);
}
@Test
diff --git a/javatests/com/google/gerrit/server/config/ScheduleConfigTest.java b/javatests/com/google/gerrit/server/config/ScheduleConfigTest.java
index 69260523a7..55f0374b56 100644
--- a/javatests/com/google/gerrit/server/config/ScheduleConfigTest.java
+++ b/javatests/com/google/gerrit/server/config/ScheduleConfigTest.java
@@ -22,7 +22,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.gerrit.server.config.ScheduleConfig.Schedule;
-import com.google.gerrit.testing.GerritBaseTests;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
@@ -32,7 +31,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
-public class ScheduleConfigTest extends GerritBaseTests {
+public class ScheduleConfigTest {
// Friday June 13, 2014 10:00 UTC
private static final ZonedDateTime NOW =
diff --git a/javatests/com/google/gerrit/server/config/SitePathsTest.java b/javatests/com/google/gerrit/server/config/SitePathsTest.java
index b4cde14e7e..1e5f41d228 100644
--- a/javatests/com/google/gerrit/server/config/SitePathsTest.java
+++ b/javatests/com/google/gerrit/server/config/SitePathsTest.java
@@ -16,9 +16,9 @@ package com.google.gerrit.server.config;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.server.ioutil.HostPlatform;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
@@ -26,7 +26,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
-public class SitePathsTest extends GerritBaseTests {
+public class SitePathsTest {
@Test
public void create_NotExisting() throws IOException {
final Path root = random();
@@ -72,8 +72,8 @@ public class SitePathsTest extends GerritBaseTests {
final Path root = random();
try {
Files.createFile(root);
- exception.expect(NotDirectoryException.class);
- new SitePaths(root);
+ assertThrows(NotDirectoryException.class, () -> new SitePaths(root));
+
} finally {
Files.delete(root);
}
diff --git a/javatests/com/google/gerrit/server/edit/ChangeEditTest.java b/javatests/com/google/gerrit/server/edit/ChangeEditTest.java
index 4c0b5a18c3..2bad8152cd 100644
--- a/javatests/com/google/gerrit/server/edit/ChangeEditTest.java
+++ b/javatests/com/google/gerrit/server/edit/ChangeEditTest.java
@@ -16,19 +16,18 @@ package com.google.gerrit.server.edit;
import static org.junit.Assert.assertEquals;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.RefNames;
import org.junit.Test;
-public class ChangeEditTest extends GerritBaseTests {
+public class ChangeEditTest {
@Test
public void changeEditRef() throws Exception {
- Account.Id accountId = new Account.Id(1000042);
- Change.Id changeId = new Change.Id(56414);
- PatchSet.Id psId = new PatchSet.Id(changeId, 50);
+ Account.Id accountId = Account.id(1000042);
+ Change.Id changeId = Change.id(56414);
+ PatchSet.Id psId = PatchSet.id(changeId, 50);
String refName = RefNames.refsEdit(accountId, changeId, psId);
assertEquals("refs/users/42/1000042/edit-56414/50", refName);
}
diff --git a/javatests/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java b/javatests/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
index 265b24e46e..a618c9ee1a 100644
--- a/javatests/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
+++ b/javatests/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
@@ -25,31 +25,38 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-public class ChangeFileContentModificationSubject
- extends Subject<ChangeFileContentModificationSubject, ChangeFileContentModification> {
+public class ChangeFileContentModificationSubject extends Subject {
public static ChangeFileContentModificationSubject assertThat(
ChangeFileContentModification modification) {
- return assertAbout(ChangeFileContentModificationSubject::new).that(modification);
+ return assertAbout(modifications()).that(modification);
}
+ public static Factory<ChangeFileContentModificationSubject, ChangeFileContentModification>
+ modifications() {
+ return ChangeFileContentModificationSubject::new;
+ }
+
+ private final ChangeFileContentModification modification;
+
private ChangeFileContentModificationSubject(
FailureMetadata failureMetadata, ChangeFileContentModification modification) {
super(failureMetadata, modification);
+ this.modification = modification;
}
public StringSubject filePath() {
isNotNull();
- return check("filePath()").that(actual().getFilePath());
+ return check("getFilePath()").that(modification.getFilePath());
}
public StringSubject newContent() throws IOException {
isNotNull();
- RawInput newContent = actual().getNewContent();
- check("newContent()").that(newContent).isNotNull();
+ RawInput newContent = modification.getNewContent();
+ check("getNewContent()").that(newContent).isNotNull();
String contentString =
CharStreams.toString(
new InputStreamReader(newContent.getInputStream(), StandardCharsets.UTF_8));
- return check("newContent()").that(contentString);
+ return check("getNewContent()").that(contentString);
}
}
diff --git a/javatests/com/google/gerrit/server/edit/tree/TreeModificationSubject.java b/javatests/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
index bd9d4df862..d5b70bb7a3 100644
--- a/javatests/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
+++ b/javatests/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
@@ -21,7 +21,7 @@ import com.google.common.truth.Subject;
import com.google.gerrit.truth.ListSubject;
import java.util.List;
-public class TreeModificationSubject extends Subject<TreeModificationSubject, TreeModification> {
+public class TreeModificationSubject extends Subject {
public static TreeModificationSubject assertThat(TreeModification treeModification) {
return assertAbout(treeModifications()).that(treeModification);
@@ -33,19 +33,21 @@ public class TreeModificationSubject extends Subject<TreeModificationSubject, Tr
public static ListSubject<TreeModificationSubject, TreeModification> assertThatList(
List<TreeModification> treeModifications) {
- return assertAbout(ListSubject.elements())
- .thatCustom(treeModifications, treeModifications())
- .named("treeModifications");
+ return ListSubject.assertThat(treeModifications, treeModifications());
}
+ private final TreeModification treeModification;
+
private TreeModificationSubject(
FailureMetadata failureMetadata, TreeModification treeModification) {
super(failureMetadata, treeModification);
+ this.treeModification = treeModification;
}
public ChangeFileContentModificationSubject asChangeFileContentModification() {
isInstanceOf(ChangeFileContentModification.class);
- return ChangeFileContentModificationSubject.assertThat(
- (ChangeFileContentModification) actual());
+ return check("asChangeFileContentModification()")
+ .about(ChangeFileContentModificationSubject.modifications())
+ .that((ChangeFileContentModification) treeModification);
}
}
diff --git a/javatests/com/google/gerrit/server/events/BUILD b/javatests/com/google/gerrit/server/events/BUILD
new file mode 100644
index 0000000000..eed83c8ac8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/events/BUILD
@@ -0,0 +1,15 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+ name = "events_tests",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:gson",
+ "//lib:guava",
+ "//lib/truth",
+ ],
+)
diff --git a/javatests/com/google/gerrit/server/events/EventDeserializerTest.java b/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
index eac8d0dcab..e0223e4885 100644
--- a/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
+++ b/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
@@ -18,38 +18,32 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ChangeAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
+import java.sql.Timestamp;
import org.junit.Test;
-public class EventDeserializerTest extends GerritBaseTests {
+public class EventDeserializerTest {
+ private final Gson gson = new EventGsonProvider().get();
@Test
public void refUpdatedEvent() {
- RefUpdatedEvent refUpdatedEvent = new RefUpdatedEvent();
-
+ RefUpdatedEvent orig = new RefUpdatedEvent();
RefUpdateAttribute refUpdatedAttribute = new RefUpdateAttribute();
refUpdatedAttribute.refName = "refs/heads/master";
- refUpdatedEvent.refUpdate = createSupplier(refUpdatedAttribute);
+ orig.refUpdate = createSupplier(refUpdatedAttribute);
AccountAttribute accountAttribute = new AccountAttribute();
accountAttribute.email = "some.user@domain.com";
- refUpdatedEvent.submitter = createSupplier(accountAttribute);
-
- Gson gsonSerializer =
- new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
- String serializedEvent = gsonSerializer.toJson(refUpdatedEvent);
+ orig.submitter = createSupplier(accountAttribute);
- Gson gsonDeserializer =
- new GsonBuilder()
- .registerTypeAdapter(Event.class, new EventDeserializer())
- .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
- .create();
-
- RefUpdatedEvent e = (RefUpdatedEvent) gsonDeserializer.fromJson(serializedEvent, Event.class);
+ RefUpdatedEvent e = roundTrip(orig);
assertThat(e).isNotNull();
assertThat(e.refUpdate).isInstanceOf(Supplier.class);
@@ -58,7 +52,271 @@ public class EventDeserializerTest extends GerritBaseTests {
assertThat(e.submitter.get().email).isEqualTo(accountAttribute.email);
}
+ @Test
+ public void patchSetCreatedEvent() {
+ Change change = newChange();
+ PatchSetCreatedEvent orig = new PatchSetCreatedEvent(change);
+ orig.change = asChangeAttribute(change);
+ orig.uploader = newAccount("uploader");
+
+ PatchSetCreatedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ assertSameAccount(e.uploader, orig.uploader);
+ }
+
+ @Test
+ public void assigneeChangedEvent() {
+ Change change = newChange();
+ AssigneeChangedEvent orig = new AssigneeChangedEvent(change);
+ orig.change = asChangeAttribute(change);
+ orig.changer = newAccount("changer");
+ orig.oldAssignee = newAccount("oldAssignee");
+
+ AssigneeChangedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ assertSameAccount(e.changer, orig.changer);
+ assertSameAccount(e.oldAssignee, orig.oldAssignee);
+ }
+
+ @Test
+ public void changeDeletedEvent() {
+ Change change = newChange();
+ ChangeDeletedEvent orig = new ChangeDeletedEvent(change);
+ orig.change = asChangeAttribute(change);
+ orig.deleter = newAccount("deleter");
+
+ ChangeDeletedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ assertSameAccount(e.deleter, orig.deleter);
+ }
+
+ @Test
+ public void hashtagsChangedEvent() {
+ Change change = newChange();
+ HashtagsChangedEvent orig = new HashtagsChangedEvent(change);
+ orig.change = asChangeAttribute(change);
+ orig.editor = newAccount("editor");
+ orig.added = new String[] {"added"};
+ orig.removed = new String[] {"removed"};
+ orig.hashtags = new String[] {"hashtags"};
+
+ HashtagsChangedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ assertSameAccount(e.editor, orig.editor);
+ assertThat(e.added).isEqualTo(orig.added);
+ assertThat(e.removed).isEqualTo(orig.removed);
+ assertThat(e.hashtags).isEqualTo(orig.hashtags);
+ }
+
+ @Test
+ public void changeAbandonedEvent() {
+ Change change = newChange();
+ ChangeAbandonedEvent orig = new ChangeAbandonedEvent(change);
+ orig.change = asChangeAttribute(change);
+ orig.abandoner = newAccount("abandoner");
+ orig.reason = "some reason";
+
+ ChangeAbandonedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ assertSameAccount(e.abandoner, orig.abandoner);
+ assertThat(e.reason).isEqualTo(orig.reason);
+ }
+
+ @Test
+ public void changeMergedEvent() {
+ Change change = newChange();
+ ChangeMergedEvent orig = new ChangeMergedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ ChangeMergedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void changeRestoredEvent() {
+ Change change = newChange();
+ ChangeRestoredEvent orig = new ChangeRestoredEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ ChangeRestoredEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void commentAddedEvent() {
+ Change change = newChange();
+ CommentAddedEvent orig = new CommentAddedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ CommentAddedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void privateStateChangedEvent() {
+ Change change = newChange();
+ PrivateStateChangedEvent orig = new PrivateStateChangedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ PrivateStateChangedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void reviewerAddedEvent() {
+ Change change = newChange();
+ ReviewerAddedEvent orig = new ReviewerAddedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ ReviewerAddedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void reviewerDeletedEvent() {
+ Change change = newChange();
+ ReviewerDeletedEvent orig = new ReviewerDeletedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ ReviewerDeletedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void voteDeletedEvent() {
+ Change change = newChange();
+ VoteDeletedEvent orig = new VoteDeletedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ VoteDeletedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void workinProgressStateChangedEvent() {
+ Change change = newChange();
+ WorkInProgressStateChangedEvent orig = new WorkInProgressStateChangedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ WorkInProgressStateChangedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
+ @Test
+ public void topicChangedEvent() {
+ Change change = newChange();
+ TopicChangedEvent orig = new TopicChangedEvent(change);
+ orig.change = asChangeAttribute(change);
+
+ TopicChangedEvent e = roundTrip(orig);
+
+ assertThat(e).isNotNull();
+ assertSameChangeEvent(e, orig);
+ }
+
private <T> Supplier<T> createSupplier(T value) {
return Suppliers.memoize(() -> value);
}
+
+ private Change newChange() {
+ Change change =
+ new Change(
+ Change.key("Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
+ Change.id(1000),
+ Account.id(1000),
+ BranchNameKey.create(Project.nameKey("myproject"), "mybranch"),
+ new Timestamp(System.currentTimeMillis()));
+ return change;
+ }
+
+ private Supplier<AccountAttribute> newAccount(String name) {
+ AccountAttribute account = new AccountAttribute();
+ account.name = name;
+ account.email = name + "@somewhere.com";
+ account.username = name;
+ return Suppliers.ofInstance(account);
+ }
+
+ private void assertSameChangeEvent(ChangeEvent current, ChangeEvent expected) {
+ assertThat(current.changeKey.get()).isEqualTo(expected.changeKey.get());
+ assertThat(current.refName).isEqualTo(expected.refName);
+ assertThat(current.project).isEqualTo(expected.project);
+ assertSameChange(current.change, expected.change);
+ }
+
+ private void assertSameChange(
+ Supplier<ChangeAttribute> currentSupplier, Supplier<ChangeAttribute> expectedSupplier) {
+ ChangeAttribute current = currentSupplier.get();
+ ChangeAttribute expected = expectedSupplier.get();
+ assertThat(current.project).isEqualTo(expected.project);
+ assertThat(current.branch).isEqualTo(expected.branch);
+ assertThat(current.topic).isEqualTo(expected.topic);
+ assertThat(current.id).isEqualTo(expected.id);
+ assertThat(current.number).isEqualTo(expected.number);
+ assertThat(current.subject).isEqualTo(expected.subject);
+ assertThat(current.commitMessage).isEqualTo(expected.commitMessage);
+ assertThat(current.url).isEqualTo(expected.url);
+ assertThat(current.status).isEqualTo(expected.status);
+ assertThat(current.createdOn).isEqualTo(expected.createdOn);
+ assertThat(current.wip).isEqualTo(expected.wip);
+ assertThat(current.isPrivate).isEqualTo(expected.isPrivate);
+ }
+
+ private void assertSameAccount(
+ Supplier<AccountAttribute> currentSupplier, Supplier<AccountAttribute> expectedSupplier) {
+ AccountAttribute current = currentSupplier.get();
+ AccountAttribute expected = expectedSupplier.get();
+ assertThat(current.name).isEqualTo(expected.name);
+ assertThat(current.email).isEqualTo(expected.email);
+ assertThat(current.username).isEqualTo(expected.username);
+ }
+
+ public Supplier<ChangeAttribute> asChangeAttribute(Change change) {
+ ChangeAttribute a = new ChangeAttribute();
+ a.project = change.getProject().get();
+ a.branch = change.getDest().shortName();
+ a.topic = change.getTopic();
+ a.id = change.getKey().get();
+ a.number = change.getId().get();
+ a.subject = change.getSubject();
+ a.commitMessage = "This is a test commit message";
+ a.url = "http://somewhere.com";
+ a.status = change.getStatus();
+ a.createdOn = change.getCreatedOn().getTime() / 1000L;
+ a.wip = change.isWorkInProgress() ? true : null;
+ a.isPrivate = change.isPrivate() ? true : null;
+ return Suppliers.ofInstance(a);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <E extends Event> E roundTrip(E event) {
+ String json = gson.toJson(event);
+ return (E) gson.fromJson(json, event.getClass());
+ }
}
diff --git a/javatests/com/google/gerrit/server/events/EventJsonTest.java b/javatests/com/google/gerrit/server/events/EventJsonTest.java
index b59641d70e..6163ea7f3c 100644
--- a/javatests/com/google/gerrit/server/events/EventJsonTest.java
+++ b/javatests/com/google/gerrit/server/events/EventJsonTest.java
@@ -15,32 +15,30 @@
package com.google.gerrit.server.events;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
+import static com.google.gerrit.entities.Change.Status.NEW;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.MapSubject;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.data.ChangeAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
-public class EventJsonTest extends GerritBaseTests {
+public class EventJsonTest {
private static final String BRANCH = "mybranch";
private static final String CHANGE_ID = "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
private static final int CHANGE_NUM = 1000;
@@ -52,12 +50,7 @@ public class EventJsonTest extends GerritBaseTests {
private static final double TS2 = 1.254344401E9;
private static final String URL = "http://somewhere.com";
- // Must match StreamEvents#gson. (In master, the definition is refactored to be hared.)
- private final Gson gson =
- new GsonBuilder()
- .registerTypeAdapter(Supplier.class, new SupplierSerializer())
- .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeySerializer())
- .create();
+ private final Gson gson = new EventGsonProvider().get();
@Before
public void setTimeForTesting() {
@@ -580,7 +573,7 @@ public class EventJsonTest extends GerritBaseTests {
Change.key(CHANGE_ID),
Change.id(CHANGE_NUM),
Account.id(9999),
- Branch.nameKey(Project.nameKey(PROJECT), BRANCH),
+ BranchNameKey.create(Project.nameKey(PROJECT), BRANCH),
TimeUtil.nowTs());
}
@@ -591,7 +584,7 @@ public class EventJsonTest extends GerritBaseTests {
private Supplier<ChangeAttribute> asChangeAttribute(Change change) {
ChangeAttribute a = new ChangeAttribute();
a.project = change.getProject().get();
- a.branch = change.getDest().getShortName();
+ a.branch = change.getDest().shortName();
a.topic = change.getTopic();
a.id = change.getKey().get();
a.number = change.getId().get();
@@ -609,8 +602,9 @@ public class EventJsonTest extends GerritBaseTests {
// Parse JSON into a raw Java map:
// * Doesn't depend on field iteration order.
// * Avoids excessively long string literals in asserts.
+ String json = gson.toJson(src);
Map<Object, Object> map =
- gson.fromJson(gson.toJson(src), new TypeToken<Map<Object, Object>>() {}.getType());
+ gson.fromJson(json, new TypeToken<Map<Object, Object>>() {}.getType());
return assertThat(map);
}
diff --git a/javatests/com/google/gerrit/server/events/EventTypesTest.java b/javatests/com/google/gerrit/server/events/EventTypesTest.java
index dd5c7f91f1..c822d6c68c 100644
--- a/javatests/com/google/gerrit/server/events/EventTypesTest.java
+++ b/javatests/com/google/gerrit/server/events/EventTypesTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.events;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class EventTypesTest extends GerritBaseTests {
+public class EventTypesTest {
public static class TestEvent extends Event {
private static final String TYPE = "test-event";
diff --git a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
index 4a1f47cd85..2485613641 100644
--- a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
+++ b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
@@ -15,14 +15,17 @@
package com.google.gerrit.server.extensions.webui;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -32,16 +35,14 @@ import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendCondition;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
-import org.easymock.EasyMock;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
-public class UiActionsTest extends GerritBaseTests {
+public class UiActionsTest {
private static class FakeForProject extends ForProject {
private boolean allowValueQueries = true;
@@ -112,7 +113,7 @@ public class UiActionsTest extends GerritBaseTests {
@Override
public Account.Id getAccountId() {
- return new Account.Id(1);
+ return Account.id(1);
}
};
}
@@ -132,9 +133,7 @@ public class UiActionsTest extends GerritBaseTests {
// Set up the Mock to expect a call of bulkEvaluateTest to only contain cond{1,2} since cond3
// needs to be identified as duplicate and not called out explicitly.
- PermissionBackend permissionBackendMock = EasyMock.createMock(PermissionBackend.class);
- permissionBackendMock.bulkEvaluateTest(ImmutableSet.of(cond1, cond2));
- EasyMock.replay(permissionBackendMock);
+ PermissionBackend permissionBackendMock = mock(PermissionBackend.class);
UiActions.evaluatePermissionBackendConditions(
permissionBackendMock, ImmutableList.of(cond1, cond2, cond3));
@@ -143,6 +142,8 @@ public class UiActionsTest extends GerritBaseTests {
// the value of cond1 and issues no additional call to PermissionBackend.
forProject.disallowValueQueries();
+ verify(permissionBackendMock, only()).bulkEvaluateTest(ImmutableSet.of(cond1, cond2));
+
// Assert the values of all conditions
assertThat(cond1.value()).isFalse();
assertThat(cond2.value()).isTrue();
diff --git a/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java b/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
index cc648bfa7a..8b5705b1fd 100644
--- a/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
+++ b/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
@@ -15,32 +15,31 @@
package com.google.gerrit.server.fixes;
import static com.google.gerrit.server.edit.tree.TreeModificationSubject.assertThatList;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.replay;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Comment.Range;
+import com.google.gerrit.entities.FixReplacement;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Comment.Range;
-import com.google.gerrit.reviewdb.client.FixReplacement;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.edit.tree.TreeModification;
import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
-import org.easymock.EasyMock;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
-public class FixReplacementInterpreterTest extends GerritBaseTests {
- private final FileContentUtil fileContentUtil = createMock(FileContentUtil.class);
- private final Repository repository = createMock(Repository.class);
- private final ProjectState projectState = createMock(ProjectState.class);
- private final ObjectId patchSetCommitId = createMock(ObjectId.class);
+public class FixReplacementInterpreterTest {
+ private final FileContentUtil fileContentUtil = mock(FileContentUtil.class);
+ private final Repository repository = mock(Repository.class);
+ private final ProjectState projectState = mock(ProjectState.class);
+ private final ObjectId patchSetCommitId = mock(ObjectId.class);
private final String filePath1 = "an/arbitrary/file.txt";
private final String filePath2 = "another/arbitrary/file.txt";
@@ -68,7 +67,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath2, new Range(2, 0, 3, 0), "Another modified content");
mockFileContent(filePath2, "1st line\n2nd line\n3rd line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications =
toTreeModifications(fixReplacement, fixReplacement3, fixReplacement2);
List<TreeModification> sortedTreeModifications = getSortedCopy(treeModifications);
@@ -99,7 +97,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(2, 0, 3, 0), "");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
assertThatList(treeModifications)
.onlyElement()
@@ -114,7 +111,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(2, 0, 2, 0), "A new line\n");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
assertThatList(treeModifications)
.onlyElement()
@@ -128,7 +124,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(1, 6, 3, 1), "and t");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
assertThatList(treeModifications)
.onlyElement()
@@ -144,7 +139,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(2, 7, 2, 11), "modification");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications =
toTreeModifications(fixReplacement1, fixReplacement2);
assertThatList(treeModifications)
@@ -154,6 +148,22 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
.isEqualTo("First line\nA modification\nThird line\n");
}
+ @Test()
+ public void startAfterEndOfLineMarkThrowsAnException() throws Exception {
+ FixReplacement fixReplacement =
+ new FixReplacement(filePath1, new Range(1, 11, 2, 6), "A modification");
+ mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
+ }
+
+ @Test()
+ public void endAfterEndOfLineMarkThrowsAnException() throws Exception {
+ FixReplacement fixReplacement =
+ new FixReplacement(filePath1, new Range(2, 0, 2, 12), "A modification");
+ mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
+ }
+
@Test
public void replacementsMayTouch() throws Exception {
FixReplacement fixReplacement1 =
@@ -162,7 +172,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(2, 7, 3, 5), "content");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications =
toTreeModifications(fixReplacement1, fixReplacement2);
assertThatList(treeModifications)
@@ -178,7 +187,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(4, 0, 4, 0), "New content");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
assertThatList(treeModifications)
.onlyElement()
@@ -188,6 +196,34 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
}
@Test
+ public void replacementsCanChangeLastLine() throws Exception {
+ FixReplacement fixReplacement =
+ new FixReplacement(filePath1, new Range(3, 0, 4, 0), "New content\n");
+ mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+
+ List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
+ assertThatList(treeModifications)
+ .onlyElement()
+ .asChangeFileContentModification()
+ .newContent()
+ .isEqualTo("First line\nSecond line\nNew content\n");
+ }
+
+ @Test
+ public void replacementsCanChangeLastLineWithoutEOLMark() throws Exception {
+ FixReplacement fixReplacement =
+ new FixReplacement(filePath1, new Range(3, 0, 3, 10), "New content\n");
+ mockFileContent(filePath1, "First line\nSecond line\nThird line");
+
+ List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
+ assertThatList(treeModifications)
+ .onlyElement()
+ .asChangeFileContentModification()
+ .newContent()
+ .isEqualTo("First line\nSecond line\nNew content\n");
+ }
+
+ @Test
public void replacementsCanModifySeveralFilesInAnyOrder() throws Exception {
FixReplacement fixReplacement1 =
new FixReplacement(filePath1, new Range(1, 1, 3, 2), "Modified content");
@@ -198,7 +234,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath2, new Range(3, 0, 4, 0), "Second modification\n");
mockFileContent(filePath2, "1st line\n2nd line\n3rd line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications =
toTreeModifications(fixReplacement3, fixReplacement1, fixReplacement2);
List<TreeModification> sortedTreeModifications = getSortedCopy(treeModifications);
@@ -219,7 +254,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
FixReplacement fixReplacement = new FixReplacement(filePath1, new Range(2, 11, 3, 0), "\r");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
assertThatList(treeModifications)
.onlyElement()
@@ -238,7 +272,6 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(4, 0, 5, 0), "3rd modification\n");
mockFileContent(filePath1, "First line\nSecond line\nThird line\nFourth line\nFifth line\n");
- replay(fileContentUtil);
List<TreeModification> treeModifications =
toTreeModifications(fixReplacement2, fixReplacement1, fixReplacement3);
assertThatList(treeModifications)
@@ -255,10 +288,7 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(5, 0, 5, 0), "A new line\n");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
-
- exception.expect(ResourceConflictException.class);
- toTreeModifications(fixReplacement);
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
}
@Test
@@ -267,10 +297,7 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(0, 0, 0, 0), "A new line\n");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
-
- exception.expect(ResourceConflictException.class);
- toTreeModifications(fixReplacement);
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
}
@Test
@@ -279,10 +306,7 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(1, 0, 1, 11), "modified");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
-
- exception.expect(ResourceConflictException.class);
- toTreeModifications(fixReplacement);
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
}
@Test
@@ -291,10 +315,7 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(3, 0, 3, 11), "modified");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
-
- exception.expect(ResourceConflictException.class);
- toTreeModifications(fixReplacement);
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
}
@Test
@@ -303,16 +324,12 @@ public class FixReplacementInterpreterTest extends GerritBaseTests {
new FixReplacement(filePath1, new Range(1, -1, 1, 5), "modified");
mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
- replay(fileContentUtil);
-
- exception.expect(ResourceConflictException.class);
- toTreeModifications(fixReplacement);
+ assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
}
private void mockFileContent(String filePath, String fileContent) throws Exception {
- EasyMock.expect(
- fileContentUtil.getContent(repository, projectState, patchSetCommitId, filePath))
- .andReturn(BinaryResult.create(fileContent));
+ when(fileContentUtil.getContent(repository, projectState, patchSetCommitId, filePath))
+ .thenReturn(BinaryResult.create(fileContent));
}
private List<TreeModification> toTreeModifications(FixReplacement... fixReplacements)
diff --git a/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java b/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java
index 309f726ed2..ba80c02189 100644
--- a/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java
+++ b/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java
@@ -15,25 +15,27 @@
package com.google.gerrit.server.fixes;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class LineIdentifierTest extends GerritBaseTests {
+public class LineIdentifierTest {
@Test
public void lineNumberMustBePositive() {
LineIdentifier lineIdentifier = new LineIdentifier("First line\nSecond line");
- exception.expect(StringIndexOutOfBoundsException.class);
- exception.expectMessage("positive");
- lineIdentifier.getStartIndexOfLine(0);
+ StringIndexOutOfBoundsException thrown =
+ assertThrows(
+ StringIndexOutOfBoundsException.class, () -> lineIdentifier.getStartIndexOfLine(0));
+ assertThat(thrown).hasMessageThat().contains("positive");
}
@Test
public void lineNumberMustIndicateAnAvailableLine() {
LineIdentifier lineIdentifier = new LineIdentifier("First line\nSecond line");
- exception.expect(StringIndexOutOfBoundsException.class);
- exception.expectMessage("Line 3 isn't available");
- lineIdentifier.getStartIndexOfLine(3);
+ StringIndexOutOfBoundsException thrown =
+ assertThrows(
+ StringIndexOutOfBoundsException.class, () -> lineIdentifier.getStartIndexOfLine(3));
+ assertThat(thrown).hasMessageThat().contains("Line 3 isn't available");
}
@Test
diff --git a/javatests/com/google/gerrit/server/fixes/StringModifierTest.java b/javatests/com/google/gerrit/server/fixes/StringModifierTest.java
index 185b58c21c..34472489af 100644
--- a/javatests/com/google/gerrit/server/fixes/StringModifierTest.java
+++ b/javatests/com/google/gerrit/server/fixes/StringModifierTest.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.fixes;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Before;
import org.junit.Test;
-public class StringModifierTest extends GerritBaseTests {
+public class StringModifierTest {
private final String originalString = "This is the original, unmodified string.";
private StringModifier stringModifier;
@@ -63,20 +63,20 @@ public class StringModifierTest extends GerritBaseTests {
@Test
public void replacedPartsMustNotOverlap() {
stringModifier.replace(0, 9, "");
- exception.expect(StringIndexOutOfBoundsException.class);
- stringModifier.replace(8, 32, "The modified");
+ assertThrows(
+ StringIndexOutOfBoundsException.class, () -> stringModifier.replace(8, 32, "The modified"));
}
@Test
public void startIndexMustNotBeGreaterThanEndIndex() {
- exception.expect(StringIndexOutOfBoundsException.class);
- stringModifier.replace(10, 9, "something");
+ assertThrows(
+ StringIndexOutOfBoundsException.class, () -> stringModifier.replace(10, 9, "something"));
}
@Test
public void startIndexMustNotBeNegative() {
- exception.expect(StringIndexOutOfBoundsException.class);
- stringModifier.replace(-1, 9, "something");
+ assertThrows(
+ StringIndexOutOfBoundsException.class, () -> stringModifier.replace(-1, 9, "something"));
}
@Test
@@ -90,13 +90,17 @@ public class StringModifierTest extends GerritBaseTests {
@Test
public void startIndexMustNotBeGreaterThanLengthOfString() {
- exception.expect(StringIndexOutOfBoundsException.class);
- stringModifier.replace(originalString.length() + 1, originalString.length() + 1, "something");
+ assertThrows(
+ StringIndexOutOfBoundsException.class,
+ () ->
+ stringModifier.replace(
+ originalString.length() + 1, originalString.length() + 1, "something"));
}
@Test
public void endIndexMustNotBeGreaterThanLengthOfString() {
- exception.expect(StringIndexOutOfBoundsException.class);
- stringModifier.replace(8, originalString.length() + 1, "something");
+ assertThrows(
+ StringIndexOutOfBoundsException.class,
+ () -> stringModifier.replace(8, originalString.length() + 1, "something"));
}
}
diff --git a/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java
index b278bfea45..3a8d7e46f2 100644
--- a/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java
+++ b/javatests/com/google/gerrit/server/git/DeleteZombieCommentsRefsTest.java
@@ -18,11 +18,11 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.notedb.DeleteZombieCommentsRefs;
import com.google.gerrit.server.util.time.TimeUtil;
@@ -50,7 +50,7 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class DeleteZombieCommentsRefsTest {
private InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
- private Project.NameKey allUsersProject = new Project.NameKey("All-Users");
+ private Project.NameKey allUsersProject = Project.nameKey("All-Users");
@Test
public void cleanZombieDraftsSmall() throws Exception {
@@ -169,8 +169,8 @@ public class DeleteZombieCommentsRefsTest {
}
private static String getRefName(int changeId, int userId) {
- Change.Id cId = new Change.Id(changeId);
- Account.Id aId = new Account.Id(userId);
+ Change.Id cId = Change.id(changeId);
+ Account.Id aId = Account.id(userId);
return RefNames.refsDraftComments(cId, aId);
}
diff --git a/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
index ea4a199c71..7832bec143 100644
--- a/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/GitRepositoryManagerTest.java
@@ -15,16 +15,13 @@ package com.google.gerrit.server.git;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
-import com.google.gerrit.testing.GerritBaseTests;
-import java.io.IOException;
+import com.google.gerrit.entities.Project.NameKey;
import java.util.SortedSet;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
-public class GitRepositoryManagerTest extends GerritBaseTests {
+public class GitRepositoryManagerTest {
private GitRepositoryManager objectUnderTest;
@@ -40,13 +37,12 @@ public class GitRepositoryManagerTest extends GerritBaseTests {
private static class TestGitRepositoryManager implements GitRepositoryManager {
@Override
- public Repository openRepository(NameKey name) throws RepositoryNotFoundException, IOException {
+ public Repository openRepository(NameKey name) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
- public Repository createRepository(NameKey name)
- throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException {
+ public Repository createRepository(NameKey name) {
throw new UnsupportedOperationException("Not implemented");
}
diff --git a/javatests/com/google/gerrit/server/git/GroupCollectorTest.java b/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
index f6942999e5..6175385cb2 100644
--- a/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
+++ b/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
@@ -19,9 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SortedSetMultimap;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
@@ -32,7 +31,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
-public class GroupCollectorTest extends GerritBaseTests {
+public class GroupCollectorTest {
private TestRepository<?> tr;
@Before
@@ -285,7 +284,7 @@ public class GroupCollectorTest extends GerritBaseTests {
// TODO(dborowitz): Tests for octopus merges.
private static PatchSet.Id psId(int c, int p) {
- return new PatchSet.Id(new Change.Id(c), p);
+ return PatchSet.id(Change.id(c), p);
}
private RevWalk newWalk(ObjectId start, ObjectId branchTip) throws Exception {
diff --git a/javatests/com/google/gerrit/server/git/JGitConfigTest.java b/javatests/com/google/gerrit/server/git/JGitConfigTest.java
new file mode 100644
index 0000000000..9f6b47e7ae
--- /dev/null
+++ b/javatests/com/google/gerrit/server/git/JGitConfigTest.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.server.config.SitePaths;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class JGitConfigTest {
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private SitePaths site;
+ private Path gitPath;
+
+ @Before
+ public void setUp() throws IOException {
+ site = new SitePaths(temporaryFolder.newFolder().toPath());
+ Files.createDirectories(site.etc_dir);
+ gitPath = Files.createDirectories(site.resolve("git"));
+
+ Files.write(
+ site.jgit_config, "[core]\n trustFolderStat = false\n".getBytes(StandardCharsets.UTF_8));
+ new SystemReaderInstaller(site).start();
+ }
+
+ @Test
+ public void test() throws IOException {
+ try (Repository repo = new FileRepository(gitPath.resolve("foo").toFile())) {
+ assertThat(repo.getConfig().getString("core", null, "trustFolderStat")).isEqualTo("false");
+ }
+ }
+
+ @Test
+ public void openSystemConfigRespectsParent() throws Exception {
+ Config parent = new Config();
+ parent.setString("foo", null, "bar", "value");
+ FileBasedConfig system = SystemReader.getInstance().openSystemConfig(parent, FS.DETECTED);
+ system.load();
+ assertThat(system.getString("core", null, "trustFolderStat")).isEqualTo("false");
+ assertThat(system.getString("foo", null, "bar")).isEqualTo("value");
+ }
+
+ @Test
+ public void openSystemConfigReturnsDifferentInstances() throws Exception {
+ FileBasedConfig system1 = SystemReader.getInstance().openSystemConfig(null, FS.DETECTED);
+ system1.load();
+ assertThat(system1.getString("core", null, "trustFolderStat")).isEqualTo("false");
+
+ FileBasedConfig system2 = SystemReader.getInstance().openSystemConfig(null, FS.DETECTED);
+ system2.load();
+ assertThat(system2.getString("core", null, "trustFolderStat")).isEqualTo("false");
+
+ system1.setString("core", null, "trustFolderStat", "true");
+ assertThat(system1.getString("core", null, "trustFolderStat")).isEqualTo("true");
+ assertThat(system2.getString("core", null, "trustFolderStat")).isEqualTo("false");
+ }
+}
diff --git a/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
index 4948a12f66..febb1427f6 100644
--- a/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/LocalDiskRepositoryManagerTest.java
@@ -16,11 +16,11 @@ package com.google.gerrit.server.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.ioutil.HostPlatform;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -36,7 +36,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-public class LocalDiskRepositoryManagerTest extends GerritBaseTests {
+public class LocalDiskRepositoryManagerTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private Config cfg;
@@ -52,14 +52,15 @@ public class LocalDiskRepositoryManagerTest extends GerritBaseTests {
repoManager = new LocalDiskRepositoryManager(site, cfg);
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testThatNullBasePathThrowsAnException() {
- new LocalDiskRepositoryManager(site, new Config());
+ assertThrows(
+ IllegalStateException.class, () -> new LocalDiskRepositoryManager(site, new Config()));
}
@Test
public void projectCreation() throws Exception {
- Project.NameKey projectA = new Project.NameKey("projectA");
+ Project.NameKey projectA = Project.nameKey("projectA");
try (Repository repo = repoManager.createRepository(projectA)) {
assertThat(repo).isNotNull();
}
@@ -69,112 +70,149 @@ public class LocalDiskRepositoryManagerTest extends GerritBaseTests {
assertThat(repoManager.list()).containsExactly(projectA);
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithEmptyName() throws Exception {
- repoManager.createRepository(new Project.NameKey(""));
+ assertThrows(
+ RepositoryNotFoundException.class, () -> repoManager.createRepository(Project.nameKey("")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithTrailingSlash() throws Exception {
- repoManager.createRepository(new Project.NameKey("projectA/"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("projectA/")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithBackSlash() throws Exception {
- repoManager.createRepository(new Project.NameKey("a\\projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("a\\projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationAbsolutePath() throws Exception {
- repoManager.createRepository(new Project.NameKey("/projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("/projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationStartingWithDotDot() throws Exception {
- repoManager.createRepository(new Project.NameKey("../projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("../projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationContainsDotDot() throws Exception {
- repoManager.createRepository(new Project.NameKey("a/../projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("a/../projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationDotPathSegment() throws Exception {
- repoManager.createRepository(new Project.NameKey("a/./projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("a/./projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithTwoSlashes() throws Exception {
- repoManager.createRepository(new Project.NameKey("a//projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("a//projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithPathSegmentEndingByDotGit() throws Exception {
- repoManager.createRepository(new Project.NameKey("a/b.git/projectA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("a/b.git/projectA")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithQuestionMark() throws Exception {
- repoManager.createRepository(new Project.NameKey("project?A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project?A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithPercentageSign() throws Exception {
- repoManager.createRepository(new Project.NameKey("project%A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project%A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithWidlcard() throws Exception {
- repoManager.createRepository(new Project.NameKey("project*A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project*A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithColon() throws Exception {
- repoManager.createRepository(new Project.NameKey("project:A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project:A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithLessThatSign() throws Exception {
- repoManager.createRepository(new Project.NameKey("project<A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project<A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithGreaterThatSign() throws Exception {
- repoManager.createRepository(new Project.NameKey("project>A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project>A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithPipe() throws Exception {
- repoManager.createRepository(new Project.NameKey("project|A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project|A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithDollarSign() throws Exception {
- repoManager.createRepository(new Project.NameKey("project$A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project$A")));
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testProjectCreationWithCarriageReturn() throws Exception {
- repoManager.createRepository(new Project.NameKey("project\\rA"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.createRepository(Project.nameKey("project\\rA")));
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testProjectRecreation() throws Exception {
- repoManager.createRepository(new Project.NameKey("a"));
- repoManager.createRepository(new Project.NameKey("a"));
+ repoManager.createRepository(Project.nameKey("a"));
+ assertThrows(
+ IllegalStateException.class, () -> repoManager.createRepository(Project.nameKey("a")));
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testProjectRecreationAfterRestart() throws Exception {
- repoManager.createRepository(new Project.NameKey("a"));
+ repoManager.createRepository(Project.nameKey("a"));
LocalDiskRepositoryManager newRepoManager = new LocalDiskRepositoryManager(site, cfg);
- newRepoManager.createRepository(new Project.NameKey("a"));
+ assertThrows(
+ IllegalStateException.class, () -> newRepoManager.createRepository(Project.nameKey("a")));
}
@Test
public void openRepositoryCreatedDirectlyOnDisk() throws Exception {
- Project.NameKey projectA = new Project.NameKey("projectA");
+ Project.NameKey projectA = Project.nameKey("projectA");
createRepository(repoManager.getBasePath(projectA), projectA.get());
try (Repository repo = repoManager.openRepository(projectA)) {
assertThat(repo).isNotNull();
@@ -182,30 +220,36 @@ public class LocalDiskRepositoryManagerTest extends GerritBaseTests {
assertThat(repoManager.list()).containsExactly(projectA);
}
- @Test(expected = RepositoryCaseMismatchException.class)
+ @Test
public void testNameCaseMismatch() throws Exception {
assume().that(HostPlatform.isWin32() || HostPlatform.isMac()).isTrue();
- repoManager.createRepository(new Project.NameKey("a"));
- repoManager.createRepository(new Project.NameKey("A"));
+ repoManager.createRepository(Project.nameKey("a"));
+ assertThrows(
+ RepositoryCaseMismatchException.class,
+ () -> repoManager.createRepository(Project.nameKey("A")));
}
- @Test(expected = RepositoryCaseMismatchException.class)
+ @Test
public void testNameCaseMismatchWithSymlink() throws Exception {
assume().that(HostPlatform.isWin32() || HostPlatform.isMac()).isTrue();
- Project.NameKey name = new Project.NameKey("a");
+ Project.NameKey name = Project.nameKey("a");
repoManager.createRepository(name);
createSymLink(name, "b.git");
- repoManager.createRepository(new Project.NameKey("B"));
+ assertThrows(
+ RepositoryCaseMismatchException.class,
+ () -> repoManager.createRepository(Project.nameKey("B")));
}
- @Test(expected = RepositoryCaseMismatchException.class)
+ @Test
public void testNameCaseMismatchAfterRestart() throws Exception {
assume().that(HostPlatform.isWin32() || HostPlatform.isMac()).isTrue();
- Project.NameKey name = new Project.NameKey("a");
+ Project.NameKey name = Project.nameKey("a");
repoManager.createRepository(name);
LocalDiskRepositoryManager newRepoManager = new LocalDiskRepositoryManager(site, cfg);
- newRepoManager.createRepository(new Project.NameKey("A"));
+ assertThrows(
+ RepositoryCaseMismatchException.class,
+ () -> newRepoManager.createRepository(Project.nameKey("A")));
}
@Test
@@ -220,20 +264,22 @@ public class LocalDiskRepositoryManagerTest extends GerritBaseTests {
Files.createSymbolicLink(symlink, projectDir);
}
- @Test(expected = RepositoryNotFoundException.class)
+ @Test
public void testOpenRepositoryInvalidName() throws Exception {
- repoManager.openRepository(new Project.NameKey("project%?|<>A"));
+ assertThrows(
+ RepositoryNotFoundException.class,
+ () -> repoManager.openRepository(Project.nameKey("project%?|<>A")));
}
@Test
public void list() throws Exception {
- Project.NameKey projectA = new Project.NameKey("projectA");
+ Project.NameKey projectA = Project.nameKey("projectA");
createRepository(repoManager.getBasePath(projectA), projectA.get());
- Project.NameKey projectB = new Project.NameKey("path/projectB");
+ Project.NameKey projectB = Project.nameKey("path/projectB");
createRepository(repoManager.getBasePath(projectB), projectB.get());
- Project.NameKey projectC = new Project.NameKey("anotherPath/path/projectC");
+ Project.NameKey projectC = Project.nameKey("anotherPath/path/projectC");
createRepository(repoManager.getBasePath(projectC), projectC.get());
// create an invalid git repo named only .git
repoManager.getBasePath(null).resolve(".git").toFile().mkdir();
diff --git a/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
index fc79a6de5f..4902830b86 100644
--- a/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
@@ -15,16 +15,14 @@
package com.google.gerrit.server.git;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.RepositoryConfig;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -41,7 +39,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-public class MultiBaseLocalDiskRepositoryManagerTest extends GerritBaseTests {
+public class MultiBaseLocalDiskRepositoryManagerTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private Config cfg;
@@ -55,16 +53,15 @@ public class MultiBaseLocalDiskRepositoryManagerTest extends GerritBaseTests {
site.resolve("git").toFile().mkdir();
cfg = new Config();
cfg.setString("gerrit", null, "basePath", "git");
- configMock = createNiceMock(RepositoryConfig.class);
- expect(configMock.getAllBasePaths()).andReturn(ImmutableList.of()).anyTimes();
- replay(configMock);
+ configMock = mock(RepositoryConfig.class);
+ when(configMock.getAllBasePaths()).thenReturn(ImmutableList.of());
repoManager = new MultiBaseLocalDiskRepositoryManager(site, cfg, configMock);
}
@Test
public void defaultRepositoryLocation()
throws RepositoryCaseMismatchException, RepositoryNotFoundException, IOException {
- Project.NameKey someProjectKey = new Project.NameKey("someProject");
+ Project.NameKey someProjectKey = Project.nameKey("someProject");
Repository repo = repoManager.createRepository(someProjectKey);
assertThat(repo.getDirectory()).isNotNull();
assertThat(repo.getDirectory().exists()).isTrue();
@@ -89,21 +86,21 @@ public class MultiBaseLocalDiskRepositoryManagerTest extends GerritBaseTests {
@Test
public void alternateRepositoryLocation() throws IOException {
Path alternateBasePath = temporaryFolder.newFolder().toPath();
- Project.NameKey someProjectKey = new Project.NameKey("someProject");
- reset(configMock);
- expect(configMock.getBasePath(someProjectKey)).andReturn(alternateBasePath).anyTimes();
- expect(configMock.getAllBasePaths()).andReturn(ImmutableList.of(alternateBasePath)).anyTimes();
- replay(configMock);
+ Project.NameKey someProjectKey = Project.nameKey("someProject");
+ when(configMock.getBasePath(someProjectKey)).thenReturn(alternateBasePath);
+ when(configMock.getAllBasePaths()).thenReturn(ImmutableList.of(alternateBasePath));
Repository repo = repoManager.createRepository(someProjectKey);
assertThat(repo.getDirectory()).isNotNull();
assertThat(repo.getDirectory().exists()).isTrue();
- assertThat(repo.getDirectory().getParent()).isEqualTo(alternateBasePath.toString());
+ assertThat(repo.getDirectory().getParent())
+ .isEqualTo(alternateBasePath.toRealPath().toString());
repo = repoManager.openRepository(someProjectKey);
assertThat(repo.getDirectory()).isNotNull();
assertThat(repo.getDirectory().exists()).isTrue();
- assertThat(repo.getDirectory().getParent()).isEqualTo(alternateBasePath.toString());
+ assertThat(repo.getDirectory().getParent())
+ .isEqualTo(alternateBasePath.toRealPath().toString());
assertThat(repoManager.getBasePath(someProjectKey).toAbsolutePath().toString())
.isEqualTo(alternateBasePath.toString());
@@ -116,18 +113,16 @@ public class MultiBaseLocalDiskRepositoryManagerTest extends GerritBaseTests {
@Test
public void listReturnRepoFromProperLocation() throws IOException {
- Project.NameKey basePathProject = new Project.NameKey("basePathProject");
- Project.NameKey altPathProject = new Project.NameKey("altPathProject");
- Project.NameKey misplacedProject1 = new Project.NameKey("misplacedProject1");
- Project.NameKey misplacedProject2 = new Project.NameKey("misplacedProject2");
+ Project.NameKey basePathProject = Project.nameKey("basePathProject");
+ Project.NameKey altPathProject = Project.nameKey("altPathProject");
+ Project.NameKey misplacedProject1 = Project.nameKey("misplacedProject1");
+ Project.NameKey misplacedProject2 = Project.nameKey("misplacedProject2");
Path alternateBasePath = temporaryFolder.newFolder().toPath();
- reset(configMock);
- expect(configMock.getBasePath(altPathProject)).andReturn(alternateBasePath).anyTimes();
- expect(configMock.getBasePath(misplacedProject2)).andReturn(alternateBasePath).anyTimes();
- expect(configMock.getAllBasePaths()).andReturn(ImmutableList.of(alternateBasePath)).anyTimes();
- replay(configMock);
+ when(configMock.getBasePath(altPathProject)).thenReturn(alternateBasePath);
+ when(configMock.getBasePath(misplacedProject2)).thenReturn(alternateBasePath);
+ when(configMock.getAllBasePaths()).thenReturn(ImmutableList.of(alternateBasePath));
repoManager.createRepository(basePathProject);
repoManager.createRepository(altPathProject);
@@ -150,11 +145,14 @@ public class MultiBaseLocalDiskRepositoryManagerTest extends GerritBaseTests {
}
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testRelativeAlternateLocation() {
- configMock = createNiceMock(RepositoryConfig.class);
- expect(configMock.getAllBasePaths()).andReturn(ImmutableList.of(Paths.get("repos"))).anyTimes();
- replay(configMock);
- repoManager = new MultiBaseLocalDiskRepositoryManager(site, cfg, configMock);
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ configMock = mock(RepositoryConfig.class);
+ when(configMock.getAllBasePaths()).thenReturn(ImmutableList.of(Paths.get("repos")));
+ repoManager = new MultiBaseLocalDiskRepositoryManager(site, cfg, configMock);
+ });
}
}
diff --git a/javatests/com/google/gerrit/server/git/PureRevertCacheKeyTest.java b/javatests/com/google/gerrit/server/git/PureRevertCacheKeyTest.java
index 8c1707556e..a12a8f750d 100644
--- a/javatests/com/google/gerrit/server/git/PureRevertCacheKeyTest.java
+++ b/javatests/com/google/gerrit/server/git/PureRevertCacheKeyTest.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteArray;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.cache.proto.Cache;
import com.google.protobuf.ByteString;
import org.eclipse.jgit.lib.ObjectId;
@@ -36,8 +36,7 @@ public class PureRevertCacheKeyTest {
0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb);
- Cache.PureRevertKeyProto key =
- PureRevertCache.key(new Project.NameKey("test"), revert, original);
+ Cache.PureRevertKeyProto key = PureRevertCache.key(Project.nameKey("test"), revert, original);
assertThat(key)
.isEqualTo(
Cache.PureRevertKeyProto.newBuilder()
diff --git a/javatests/com/google/gerrit/server/git/RepoRefCacheTest.java b/javatests/com/google/gerrit/server/git/RepoRefCacheTest.java
index 9ad8ffbf58..99618db2f8 100644
--- a/javatests/com/google/gerrit/server/git/RepoRefCacheTest.java
+++ b/javatests/com/google/gerrit/server/git/RepoRefCacheTest.java
@@ -325,5 +325,10 @@ public class RepoRefCacheTest {
throw new IllegalStateException("Repository is not open (refCounter=" + refCounter + ")");
}
}
+
+ @Override
+ public String getIdentifier() {
+ return "foo";
+ }
}
}
diff --git a/javatests/com/google/gerrit/server/git/TagSetHolderTest.java b/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
index 87ddc7582a..4fa09039d0 100644
--- a/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
+++ b/javatests/com/google/gerrit/server/git/TagSetHolderTest.java
@@ -19,15 +19,14 @@ import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class TagSetHolderTest extends GerritBaseTests {
+public class TagSetHolderTest {
@Test
public void serializerWithTagSet() throws Exception {
- TagSetHolder holder = new TagSetHolder(new Project.NameKey("project"));
+ TagSetHolder holder = new TagSetHolder(Project.nameKey("project"));
holder.setTagSet(new TagSet(holder.getProjectName()));
byte[] serialized = TagSetHolder.Serializer.INSTANCE.serialize(holder);
@@ -46,7 +45,7 @@ public class TagSetHolderTest extends GerritBaseTests {
@Test
public void serializerWithoutTagSet() throws Exception {
- TagSetHolder holder = new TagSetHolder(new Project.NameKey("project"));
+ TagSetHolder holder = new TagSetHolder(Project.nameKey("project"));
byte[] serialized = TagSetHolder.Serializer.INSTANCE.serialize(holder);
assertThat(TagSetHolderProto.parseFrom(serialized))
diff --git a/javatests/com/google/gerrit/server/git/TagSetTest.java b/javatests/com/google/gerrit/server/git/TagSetTest.java
index 3ac72be63e..4a3c930464 100644
--- a/javatests/com/google/gerrit/server/git/TagSetTest.java
+++ b/javatests/com/google/gerrit/server/git/TagSetTest.java
@@ -24,13 +24,12 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.CachedRefProto;
import com.google.gerrit.server.cache.proto.Cache.TagSetHolderProto.TagSetProto.TagProto;
import com.google.gerrit.server.git.TagSet.CachedRef;
import com.google.gerrit.server.git.TagSet.Tag;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Type;
import java.util.Arrays;
@@ -43,7 +42,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
import org.junit.Test;
-public class TagSetTest extends GerritBaseTests {
+public class TagSetTest {
@Test
public void roundTripToProto() {
HashMap<String, CachedRef> refs = new HashMap<>();
@@ -60,7 +59,7 @@ public class TagSetTest extends GerritBaseTests {
tags.add(
new Tag(
ObjectId.fromString("dddddddddddddddddddddddddddddddddddddddd"), newBitSet(2, 4, 6)));
- TagSet tagSet = new TagSet(new Project.NameKey("project"), refs, tags);
+ TagSet tagSet = new TagSet(Project.nameKey("project"), refs, tags);
TagSetProto proto = tagSet.toProto();
assertThat(proto)
@@ -156,22 +155,24 @@ public class TagSetTest extends GerritBaseTests {
Map<String, CachedRef> aRefs = a.getRefsForTesting();
Map<String, CachedRef> bRefs = b.getRefsForTesting();
- assertThat(ImmutableSortedSet.copyOf(aRefs.keySet()))
- .named("ref name set")
+ assertWithMessage("ref name set")
+ .that(ImmutableSortedSet.copyOf(aRefs.keySet()))
.isEqualTo(ImmutableSortedSet.copyOf(bRefs.keySet()));
for (String name : aRefs.keySet()) {
CachedRef aRef = aRefs.get(name);
CachedRef bRef = bRefs.get(name);
- assertThat(aRef.get()).named("value of ref %s", name).isEqualTo(bRef.get());
- assertThat(aRef.flag).named("flag of ref %s", name).isEqualTo(bRef.flag);
+ assertWithMessage("value of ref %s", name).that(aRef.get()).isEqualTo(bRef.get());
+ assertWithMessage("flag of ref %s", name).that(aRef.flag).isEqualTo(bRef.flag);
}
ObjectIdOwnerMap<Tag> aTags = a.getTagsForTesting();
ObjectIdOwnerMap<Tag> bTags = b.getTagsForTesting();
- assertThat(getTagIds(aTags)).named("tag ID set").isEqualTo(getTagIds(bTags));
+ assertWithMessage("tag ID set").that(getTagIds(aTags)).isEqualTo(getTagIds(bTags));
for (Tag aTag : aTags) {
Tag bTag = bTags.get(aTag);
- assertThat(aTag.refFlags).named("flags for tag %s", aTag.name()).isEqualTo(bTag.refFlags);
+ assertWithMessage("flags for tag %s", aTag.name())
+ .that(aTag.refFlags)
+ .isEqualTo(bTag.refFlags);
}
}
diff --git a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
index dedccc23ab..690a5ccfc3 100644
--- a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
+++ b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
@@ -22,12 +22,11 @@ import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.VersionedMetaData.BatchMetaDataUpdate;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.TestTimeUtil;
import java.io.IOException;
import java.util.Arrays;
@@ -51,7 +50,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class VersionedMetaDataTest extends GerritBaseTests {
+public class VersionedMetaDataTest {
// If you're considering fleshing out this test and making it more comprehensive, please consider
// instead coming up with a replacement interface for
// VersionedMetaData/BatchMetaDataUpdate/MetaDataUpdate that is easier to use correctly.
@@ -65,7 +64,7 @@ public class VersionedMetaDataTest extends GerritBaseTests {
@Before
public void setUp() {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
- project = new Project.NameKey("repo");
+ project = Project.nameKey("repo");
repo = new InMemoryRepository(new DfsRepositoryDescription(project.get()));
}
diff --git a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
index 9fc6da1874..c749b77549 100644
--- a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
@@ -18,17 +18,16 @@ import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.asse
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import java.io.IOException;
import java.sql.Timestamp;
@@ -44,7 +43,7 @@ import org.junit.Before;
import org.junit.Ignore;
@Ignore
-public class AbstractGroupTest extends GerritBaseTests {
+public class AbstractGroupTest {
protected static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
protected static final String SERVER_ID = "server-id";
protected static final String SERVER_NAME = "Gerrit Server";
@@ -65,9 +64,9 @@ public class AbstractGroupTest extends GerritBaseTests {
allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
repoManager = new InMemoryRepositoryManager();
allUsersRepo = repoManager.createRepository(allUsersName);
- serverAccountId = new Account.Id(SERVER_ACCOUNT_NUMBER);
+ serverAccountId = Account.id(SERVER_ACCOUNT_NUMBER);
serverIdent = new PersonIdent(SERVER_NAME, SERVER_EMAIL, TimeUtil.nowTs(), TZ);
- userId = new Account.Id(USER_ACCOUNT_NUMBER);
+ userId = Account.id(USER_ACCOUNT_NUMBER);
userIdent = newPersonIdent(userId, serverIdent);
}
@@ -124,9 +123,9 @@ public class AbstractGroupTest extends GerritBaseTests {
}
private static Optional<Account> getAccount(Account.Id id) {
- Account account = new Account(id, TimeUtil.nowTs());
+ Account.Builder account = Account.builder(id, TimeUtil.nowTs());
account.setFullName("Account " + id);
- return Optional.of(account);
+ return Optional.of(account.build());
}
private Optional<GroupDescription.Basic> getGroup(AccountGroup.UUID uuid) {
diff --git a/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java b/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
index 120f026b27..e54ab5d242 100644
--- a/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AuditLogReaderTest.java
@@ -18,10 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.group.InternalGroup;
import java.sql.Timestamp;
@@ -37,7 +37,7 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
@Before
public void setUp() throws Exception {
- auditLogReader = new AuditLogReader(SERVER_ID, allUsersName);
+ auditLogReader = new AuditLogReader(allUsersName);
}
@Test
@@ -66,7 +66,7 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit1);
// User adds account 100002 to the group.
- Account.Id id = new Account.Id(100002);
+ Account.Id id = Account.id(100002);
addMembers(uuid, ImmutableSet.of(id));
AccountGroupMemberAudit expAudit2 =
@@ -78,7 +78,7 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
// User removes account 100002 from the group.
removeMembers(uuid, ImmutableSet.of(id));
- expAudit2.removed(userId, getTipTimestamp(uuid));
+ expAudit2 = expAudit2.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
.inOrder();
@@ -94,8 +94,8 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
createExpMemberAudit(groupId, userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit1);
- Account.Id id1 = new Account.Id(100002);
- Account.Id id2 = new Account.Id(100003);
+ Account.Id id1 = Account.id(100002);
+ Account.Id id2 = Account.id(100003);
addMembers(uuid, ImmutableSet.of(id1, id2));
AccountGroupMemberAudit expAudit2 =
@@ -118,13 +118,13 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
addSubgroups(uuid, ImmutableSet.of(subgroupUuid));
- AccountGroupByIdAud expAudit =
+ AccountGroupByIdAudit expAudit =
createExpGroupAudit(group.getId(), subgroupUuid, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid)).containsExactly(expAudit);
removeSubgroups(uuid, ImmutableSet.of(subgroupUuid));
- expAudit.removed(userId, getTipTimestamp(uuid));
+ expAudit = expAudit.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid)).containsExactly(expAudit);
}
@@ -140,9 +140,9 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1, subgroupUuid2));
- AccountGroupByIdAud expAudit1 =
+ AccountGroupByIdAudit expAudit1 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
- AccountGroupByIdAud expAudit2 =
+ AccountGroupByIdAudit expAudit2 =
createExpGroupAudit(group.getId(), subgroupUuid2, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
@@ -158,9 +158,9 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
createExpMemberAudit(groupId, userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expMemberAudit);
- Account.Id id1 = new Account.Id(100002);
- Account.Id id2 = new Account.Id(100003);
- Account.Id id3 = new Account.Id(100004);
+ Account.Id id1 = Account.id(100002);
+ Account.Id id2 = Account.id(100003);
+ Account.Id id3 = Account.id(100004);
InternalGroup subgroup1 = createGroupAsUser(2, "test-group-2");
InternalGroup subgroup2 = createGroupAsUser(3, "test-group-3");
InternalGroup subgroup3 = createGroupAsUser(4, "test-group-4");
@@ -180,23 +180,23 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
// Add one subgroup.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1));
- AccountGroupByIdAud expGroupAudit1 =
+ AccountGroupByIdAudit expGroupAudit1 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1);
// Remove one account.
removeMembers(uuid, ImmutableSet.of(id2));
- expMemberAudit2.removed(userId, getTipTimestamp(uuid));
+ expMemberAudit2 = expMemberAudit2.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expMemberAudit, expMemberAudit1, expMemberAudit2)
.inOrder();
// Add two subgroups.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid2, subgroupUuid3));
- AccountGroupByIdAud expGroupAudit2 =
+ AccountGroupByIdAudit expGroupAudit2 =
createExpGroupAudit(group.getId(), subgroupUuid2, userId, getTipTimestamp(uuid));
- AccountGroupByIdAud expGroupAudit3 =
+ AccountGroupByIdAudit expGroupAudit3 =
createExpGroupAudit(group.getId(), subgroupUuid3, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3)
@@ -215,15 +215,15 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
// Remove two subgroups.
removeSubgroups(uuid, ImmutableSet.of(subgroupUuid1, subgroupUuid3));
- expGroupAudit1.removed(userId, getTipTimestamp(uuid));
- expGroupAudit3.removed(userId, getTipTimestamp(uuid));
+ expGroupAudit1 = expGroupAudit1.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
+ expGroupAudit3 = expGroupAudit3.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3)
.inOrder();
// Add back one removed subgroup.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1));
- AccountGroupByIdAud expGroupAudit4 =
+ AccountGroupByIdAudit expGroupAudit4 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3, expGroupAudit4)
@@ -239,8 +239,8 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
InternalGroupCreation groupCreation =
InternalGroupCreation.builder()
.setGroupUUID(GroupUUID.make(groupName, serverIdent))
- .setNameKey(new AccountGroup.NameKey(groupName))
- .setId(new AccountGroup.Id(next))
+ .setNameKey(AccountGroup.nameKey(groupName))
+ .setId(AccountGroup.id(next))
.build();
InternalGroupUpdate groupUpdate =
authorIdent.equals(serverIdent)
@@ -303,12 +303,21 @@ public final class AuditLogReaderTest extends AbstractGroupTest {
private static AccountGroupMemberAudit createExpMemberAudit(
AccountGroup.Id groupId, Account.Id id, Account.Id addedBy, Timestamp addedOn) {
- return new AccountGroupMemberAudit(
- new AccountGroupMemberAudit.Key(id, groupId, addedOn), addedBy);
+ return AccountGroupMemberAudit.builder()
+ .groupId(groupId)
+ .memberId(id)
+ .addedOn(addedOn)
+ .addedBy(addedBy)
+ .build();
}
- private static AccountGroupByIdAud createExpGroupAudit(
+ private static AccountGroupByIdAudit createExpGroupAudit(
AccountGroup.Id groupId, AccountGroup.UUID uuid, Account.Id addedBy, Timestamp addedOn) {
- return new AccountGroupByIdAud(new AccountGroupByIdAud.Key(groupId, uuid, addedOn), addedBy);
+ return AccountGroupByIdAudit.builder()
+ .groupId(groupId)
+ .includeUuid(uuid)
+ .addedOn(addedOn)
+ .addedBy(addedBy)
+ .build();
}
}
diff --git a/javatests/com/google/gerrit/server/group/db/BUILD b/javatests/com/google/gerrit/server/group/db/BUILD
index b4652c9b6b..33033386d9 100644
--- a/javatests/com/google/gerrit/server/group/db/BUILD
+++ b/javatests/com/google/gerrit/server/group/db/BUILD
@@ -8,11 +8,11 @@ junit_tests(
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/common/data/testing:common-data-test-util",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/exceptions",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/extensions/common/testing:common-test-util",
"//java/com/google/gerrit/git",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/group/db/testing",
"//java/com/google/gerrit/server/group/testing",
@@ -20,8 +20,8 @@ junit_tests(
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/truth",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
index c9ba72ea2d..b7fe23d339 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
@@ -17,23 +17,24 @@ package com.google.gerrit.server.group.db;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.server.group.testing.InternalGroupSubject.internalGroups;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.testing.InternalGroupSubject;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.truth.OptionalSubject;
import java.io.IOException;
import java.sql.Timestamp;
@@ -55,20 +56,20 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
import org.junit.Test;
-public class GroupConfigTest extends GerritBaseTests {
+public class GroupConfigTest {
private Project.NameKey projectName;
private Repository repository;
private TestRepository<?> testRepository;
- private final AccountGroup.UUID groupUuid = new AccountGroup.UUID("users-XYZ");
- private final AccountGroup.NameKey groupName = new AccountGroup.NameKey("users");
- private final AccountGroup.Id groupId = new AccountGroup.Id(123);
+ private final AccountGroup.UUID groupUuid = AccountGroup.uuid("users-XYZ");
+ private final AccountGroup.NameKey groupName = AccountGroup.nameKey("users");
+ private final AccountGroup.Id groupId = AccountGroup.id(123);
private final AuditLogFormatter auditLogFormatter =
AuditLogFormatter.createBackedBy(ImmutableSet.of(), ImmutableSet.of(), "server-id");
private final TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
@Before
public void setUp() throws Exception {
- projectName = new Project.NameKey("Test Repository");
+ projectName = Project.nameKey("Test Repository");
repository = new InMemoryRepository(new DfsRepositoryDescription("Test Repository"));
testRepository = new TestRepository<>(repository);
}
@@ -95,7 +96,7 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void nameOfGroupUpdateOverridesGroupCreation() throws Exception {
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("Another name");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("Another name");
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setNameKey(groupName).build();
@@ -109,26 +110,13 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void nameOfNewGroupMustNotBeEmpty() throws Exception {
InternalGroupCreation groupCreation =
- getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey("")).build();
+ getPrefilledGroupCreationBuilder().setNameKey(AccountGroup.nameKey("")).build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Name of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
- }
- }
-
- @Test
- public void nameOfNewGroupMustNotBeNull() throws Exception {
- InternalGroupCreation groupCreation =
- getPrefilledGroupCreationBuilder().setNameKey(new AccountGroup.NameKey(null)).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
-
- try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Name of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
+ Throwable thrown = assertThrows(Throwable.class, () -> groupConfig.commit(metaDataUpdate));
+ assertThat(thrown.getCause(), instanceOf(ConfigInvalidException.class));
+ assertThat(thrown).hasMessageThat().contains("Name of the group " + groupUuid);
}
}
@@ -144,13 +132,13 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void idOfNewGroupMustNotBeNegative() throws Exception {
InternalGroupCreation groupCreation =
- getPrefilledGroupCreationBuilder().setId(new AccountGroup.Id(-2)).build();
+ getPrefilledGroupCreationBuilder().setId(AccountGroup.id(-2)).build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("ID of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
+ Throwable thrown = assertThrows(Throwable.class, () -> groupConfig.commit(metaDataUpdate));
+ assertThat(thrown.getCause(), instanceOf(ConfigInvalidException.class));
+ assertThat(thrown).hasMessageThat().contains("ID of the group " + groupUuid);
}
}
@@ -207,7 +195,7 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void specifiedOwnerGroupUuidIsRespectedForNewGroup() throws Exception {
- AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID("anotherOwnerUuid");
+ AccountGroup.UUID ownerGroupUuid = AccountGroup.uuid("anotherOwnerUuid");
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
@@ -219,32 +207,17 @@ public class GroupConfigTest extends GerritBaseTests {
}
@Test
- public void ownerGroupUuidOfNewGroupMustNotBeNull() throws Exception {
- InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
- InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID(null)).build();
- GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
- groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
-
- try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Owner UUID of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
- }
- }
-
- @Test
public void ownerGroupUuidOfNewGroupMustNotBeEmpty() throws Exception {
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID("")).build();
+ InternalGroupUpdate.builder().setOwnerGroupUUID(AccountGroup.uuid("")).build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Owner UUID of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
+ Throwable thrown = assertThrows(Throwable.class, () -> groupConfig.commit(metaDataUpdate));
+ assertThat(thrown.getCause(), instanceOf(ConfigInvalidException.class));
+ assertThat(thrown).hasMessageThat().contains("Owner UUID of the group " + groupUuid);
}
}
@@ -303,8 +276,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void specifiedMembersAreRespectedForNewGroup() throws Exception {
- Account.Id member1 = new Account.Id(1);
- Account.Id member2 = new Account.Id(2);
+ Account.Id member1 = Account.id(1);
+ Account.Id member2 = Account.id(2);
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
@@ -319,8 +292,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void specifiedSubgroupsAreRespectedForNewGroup() throws Exception {
- AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroup1");
- AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroup2");
+ AccountGroup.UUID subgroup1 = AccountGroup.uuid("subgroup1");
+ AccountGroup.UUID subgroup2 = AccountGroup.uuid("subgroup2");
InternalGroupCreation groupCreation = getPrefilledGroupCreationBuilder().build();
InternalGroupUpdate groupUpdate =
@@ -353,9 +326,11 @@ public class GroupConfigTest extends GerritBaseTests {
public void idInConfigMustBeDefined() throws Exception {
populateGroupConfig(groupUuid, "[group]\n\tname = users\n\townerGroupUuid = owners\n");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage("ID of the group " + groupUuid);
- GroupConfig.loadForGroup(projectName, repository, groupUuid);
+ ConfigInvalidException thrown =
+ assertThrows(
+ ConfigInvalidException.class,
+ () -> GroupConfig.loadForGroup(projectName, repository, groupUuid));
+ assertThat(thrown).hasMessageThat().contains("ID of the group " + groupUuid);
}
@Test
@@ -363,9 +338,11 @@ public class GroupConfigTest extends GerritBaseTests {
populateGroupConfig(
groupUuid, "[group]\n\tname = users\n\tid = -5\n\townerGroupUuid = owners\n");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage("ID of the group " + groupUuid);
- GroupConfig.loadForGroup(projectName, repository, groupUuid);
+ ConfigInvalidException thrown =
+ assertThrows(
+ ConfigInvalidException.class,
+ () -> GroupConfig.loadForGroup(projectName, repository, groupUuid));
+ assertThat(thrown).hasMessageThat().contains("ID of the group " + groupUuid);
}
@Test
@@ -389,9 +366,11 @@ public class GroupConfigTest extends GerritBaseTests {
public void ownerGroupUuidInConfigMustBeDefined() throws Exception {
populateGroupConfig(groupUuid, "[group]\n\tname = users\n\tid = 42\n");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage("Owner UUID of the group " + groupUuid);
- GroupConfig.loadForGroup(projectName, repository, groupUuid);
+ ConfigInvalidException thrown =
+ assertThrows(
+ ConfigInvalidException.class,
+ () -> GroupConfig.loadForGroup(projectName, repository, groupUuid));
+ assertThat(thrown).hasMessageThat().contains("Owner UUID of the group " + groupUuid);
}
@Test
@@ -430,12 +409,12 @@ public class GroupConfigTest extends GerritBaseTests {
.value()
.members()
.containsExactly(
- new Account.Id(1),
- new Account.Id(2),
- new Account.Id(3),
- new Account.Id(4),
- new Account.Id(5),
- new Account.Id(6));
+ Account.id(1),
+ Account.id(2),
+ Account.id(3),
+ Account.id(4),
+ Account.id(5),
+ Account.id(6));
}
@Test
@@ -443,9 +422,9 @@ public class GroupConfigTest extends GerritBaseTests {
populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n");
populateMembersFile(groupUuid, "One");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage("Invalid file members");
- loadGroup(groupUuid);
+ ConfigInvalidException thrown =
+ assertThrows(ConfigInvalidException.class, () -> loadGroup(groupUuid));
+ assertThat(thrown).hasMessageThat().contains("Invalid file members");
}
@Test
@@ -453,9 +432,9 @@ public class GroupConfigTest extends GerritBaseTests {
populateGroupConfig(groupUuid, "[group]\n\tname=users\n\tid = 42\n\townerGroupUuid = owners\n");
populateMembersFile(groupUuid, "1\t2");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage("Invalid file members");
- loadGroup(groupUuid);
+ ConfigInvalidException thrown =
+ assertThrows(ConfigInvalidException.class, () -> loadGroup(groupUuid));
+ assertThat(thrown).hasMessageThat().contains("Invalid file members");
}
@Test
@@ -494,12 +473,12 @@ public class GroupConfigTest extends GerritBaseTests {
.value()
.subgroups()
.containsExactly(
- new AccountGroup.UUID("1"),
- new AccountGroup.UUID("2"),
- new AccountGroup.UUID("3"),
- new AccountGroup.UUID("4"),
- new AccountGroup.UUID("5"),
- new AccountGroup.UUID("6"));
+ AccountGroup.uuid("1"),
+ AccountGroup.uuid("2"),
+ AccountGroup.uuid("3"),
+ AccountGroup.uuid("4"),
+ AccountGroup.uuid("5"),
+ AccountGroup.uuid("6"));
}
@Test
@@ -508,7 +487,7 @@ public class GroupConfigTest extends GerritBaseTests {
populateSubgroupsFile(groupUuid, "1\t2 3");
Optional<InternalGroup> group = loadGroup(groupUuid);
- assertThatGroup(group).value().subgroups().containsExactly(new AccountGroup.UUID("1\t2 3"));
+ assertThatGroup(group).value().subgroups().containsExactly(AccountGroup.uuid("1\t2 3"));
}
@Test
@@ -520,13 +499,13 @@ public class GroupConfigTest extends GerritBaseTests {
assertThatGroup(group)
.value()
.subgroups()
- .containsExactly(new AccountGroup.UUID("1\t2"), new AccountGroup.UUID("3"));
+ .containsExactly(AccountGroup.uuid("1\t2"), AccountGroup.uuid("3"));
}
@Test
public void nameCanBeUpdated() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.NameKey newName = new AccountGroup.NameKey("New name");
+ AccountGroup.NameKey newName = AccountGroup.nameKey("New name");
InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().setName(newName).build();
updateGroup(groupUuid, groupUpdate);
@@ -536,41 +515,25 @@ public class GroupConfigTest extends GerritBaseTests {
}
@Test
- public void nameCannotBeUpdatedToNull() throws Exception {
- createArbitraryGroup(groupUuid);
-
- GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
- InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey(null)).build();
- groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
-
- try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Name of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
- }
- }
-
- @Test
public void nameCannotBeUpdatedToEmptyString() throws Exception {
createArbitraryGroup(groupUuid);
GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("")).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Name of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
+ Throwable thrown = assertThrows(Throwable.class, () -> groupConfig.commit(metaDataUpdate));
+ assertThat(thrown.getCause(), instanceOf(ConfigInvalidException.class));
+ assertThat(thrown).hasMessageThat().contains("Name of the group " + groupUuid);
}
}
@Test
public void nameCanBeUpdatedToEmptyStringIfExplicitlySpecified() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.NameKey emptyName = new AccountGroup.NameKey("");
+ AccountGroup.NameKey emptyName = AccountGroup.nameKey("");
GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
groupConfig.setAllowSaveEmptyName();
@@ -608,7 +571,7 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void ownerGroupUuidCanBeUpdated() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.UUID newOwnerGroupUuid = new AccountGroup.UUID("New owner");
+ AccountGroup.UUID newOwnerGroupUuid = AccountGroup.uuid("New owner");
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setOwnerGroupUUID(newOwnerGroupUuid).build();
@@ -619,34 +582,18 @@ public class GroupConfigTest extends GerritBaseTests {
}
@Test
- public void ownerGroupUuidCannotBeUpdatedToNull() throws Exception {
- createArbitraryGroup(groupUuid);
-
- GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
- InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID(null)).build();
- groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
-
- try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Owner UUID of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
- }
- }
-
- @Test
public void ownerGroupUuidCannotBeUpdatedToEmptyString() throws Exception {
createArbitraryGroup(groupUuid);
GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setOwnerGroupUUID(new AccountGroup.UUID("")).build();
+ InternalGroupUpdate.builder().setOwnerGroupUUID(AccountGroup.uuid("")).build();
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Owner UUID of the group " + groupUuid);
- groupConfig.commit(metaDataUpdate);
+ Throwable thrown = assertThrows(Throwable.class, () -> groupConfig.commit(metaDataUpdate));
+ assertThat(thrown.getCause(), instanceOf(ConfigInvalidException.class));
+ assertThat(thrown).hasMessageThat().contains("Owner UUID of the group " + groupUuid);
}
}
@@ -675,7 +622,7 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate laterGroupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(updatedOn)
.build();
Optional<InternalGroup> group = updateGroup(groupCreation.getGroupUUID(), laterGroupUpdate);
@@ -688,8 +635,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void membersCanBeAdded() throws Exception {
createArbitraryGroup(groupUuid);
- Account.Id member1 = new Account.Id(1);
- Account.Id member2 = new Account.Id(2);
+ Account.Id member1 = Account.id(1);
+ Account.Id member2 = Account.id(2);
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
@@ -710,8 +657,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void membersCanBeDeleted() throws Exception {
createArbitraryGroup(groupUuid);
- Account.Id member1 = new Account.Id(1);
- Account.Id member2 = new Account.Id(2);
+ Account.Id member1 = Account.id(1);
+ Account.Id member2 = Account.id(2);
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
@@ -732,8 +679,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void subgroupsCanBeAdded() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroups1");
- AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroups2");
+ AccountGroup.UUID subgroup1 = AccountGroup.uuid("subgroups1");
+ AccountGroup.UUID subgroup2 = AccountGroup.uuid("subgroups2");
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
@@ -754,8 +701,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void subgroupsCanBeDeleted() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.UUID subgroup1 = new AccountGroup.UUID("subgroups1");
- AccountGroup.UUID subgroup2 = new AccountGroup.UUID("subgroups2");
+ AccountGroup.UUID subgroup1 = AccountGroup.uuid("subgroups1");
+ AccountGroup.UUID subgroup2 = AccountGroup.uuid("subgroups2");
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
@@ -798,13 +745,12 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
.setDescription("A test group")
- .setOwnerGroupUUID(new AccountGroup.UUID("another owner"))
+ .setOwnerGroupUUID(AccountGroup.uuid("another owner"))
.setVisibleToAll(true)
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(new Timestamp(92900892))
- .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2)))
- .setSubgroupModification(
- subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup")))
+ .setMemberModification(members -> ImmutableSet.of(Account.id(1), Account.id(2)))
+ .setSubgroupModification(subgroups -> ImmutableSet.of(AccountGroup.uuid("subgroup")))
.build();
Optional<InternalGroup> createdGroup = createGroup(groupCreation, groupUpdate);
@@ -820,13 +766,12 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
.setDescription("A test group")
- .setOwnerGroupUUID(new AccountGroup.UUID("another owner"))
+ .setOwnerGroupUUID(AccountGroup.uuid("another owner"))
.setVisibleToAll(true)
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(new Timestamp(92900892))
- .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2)))
- .setSubgroupModification(
- subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup")))
+ .setMemberModification(members -> ImmutableSet.of(Account.id(1), Account.id(2)))
+ .setSubgroupModification(subgroups -> ImmutableSet.of(AccountGroup.uuid("subgroup")))
.build();
Optional<InternalGroup> updatedGroup = updateGroup(groupUuid, groupUpdate);
@@ -843,19 +788,18 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate initialGroupUpdate =
InternalGroupUpdate.builder()
.setDescription("A test group")
- .setOwnerGroupUUID(new AccountGroup.UUID("another owner"))
+ .setOwnerGroupUUID(AccountGroup.uuid("another owner"))
.setVisibleToAll(true)
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(new Timestamp(92900892))
- .setMemberModification(members -> ImmutableSet.of(new Account.Id(1), new Account.Id(2)))
- .setSubgroupModification(
- subgroups -> ImmutableSet.of(new AccountGroup.UUID("subgroup")))
+ .setMemberModification(members -> ImmutableSet.of(Account.id(1), Account.id(2)))
+ .setSubgroupModification(subgroups -> ImmutableSet.of(AccountGroup.uuid("subgroup")))
.build();
createGroup(groupCreation, initialGroupUpdate);
// Only update one of the properties.
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Another name")).build();
Optional<InternalGroup> updatedGroup = updateGroup(groupCreation.getGroupUUID(), groupUpdate);
Optional<InternalGroup> reloadedGroup = loadGroup(groupCreation.getGroupUUID());
@@ -870,7 +814,7 @@ public class GroupConfigTest extends GerritBaseTests {
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
commit(groupConfig);
- AccountGroup.NameKey name = new AccountGroup.NameKey("Robots");
+ AccountGroup.NameKey name = AccountGroup.nameKey("Robots");
InternalGroupUpdate groupUpdate1 = InternalGroupUpdate.builder().setName(name).build();
groupConfig.setGroupUpdate(groupUpdate1, auditLogFormatter);
commit(groupConfig);
@@ -907,7 +851,7 @@ public class GroupConfigTest extends GerritBaseTests {
RevCommit commitAfterCreation = getLatestCommitForGroup(groupUuid);
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Another name")).build();
updateGroup(groupUuid, groupUpdate);
RevCommit commitAfterUpdate = getLatestCommitForGroup(groupUuid);
@@ -990,9 +934,7 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder()
- .setOwnerGroupUUID(new AccountGroup.UUID("Another owner"))
- .build();
+ InternalGroupUpdate.builder().setOwnerGroupUUID(AccountGroup.uuid("Another owner")).build();
updateGroup(groupUuid, groupUpdate);
RevCommit commitBeforeUpdate = getLatestCommitForGroup(groupUuid);
@@ -1008,8 +950,7 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setMemberModification(
- members -> Sets.union(members, ImmutableSet.of(new Account.Id(10))))
+ .setMemberModification(members -> Sets.union(members, ImmutableSet.of(Account.id(10))))
.build();
updateGroup(groupUuid, groupUpdate);
@@ -1027,8 +968,7 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
.setSubgroupModification(
- subgroups ->
- Sets.union(subgroups, ImmutableSet.of(new AccountGroup.UUID("subgroup"))))
+ subgroups -> Sets.union(subgroups, ImmutableSet.of(AccountGroup.uuid("subgroup"))))
.build();
updateGroup(groupUuid, groupUpdate);
@@ -1045,7 +985,7 @@ public class GroupConfigTest extends GerritBaseTests {
getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build();
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Another name")).build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, auditLogFormatter);
@@ -1128,7 +1068,7 @@ public class GroupConfigTest extends GerritBaseTests {
.build();
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(createdOn)
.build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
@@ -1161,7 +1101,7 @@ public class GroupConfigTest extends GerritBaseTests {
.build();
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(createdOn)
.build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
@@ -1187,7 +1127,7 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Another name")).build();
updateGroup(groupUuid, groupUpdate);
RevCommit revCommit = getLatestCommitForGroup(groupUuid);
@@ -1202,7 +1142,7 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(new Timestamp(updatedOnAsSecondsSinceEpoch * 1000))
.build();
updateGroup(groupUuid, groupUpdate);
@@ -1220,7 +1160,7 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(updatedOn)
.build();
GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
@@ -1248,7 +1188,7 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Another name"))
+ .setName(AccountGroup.nameKey("Another name"))
.setUpdatedOn(updatedOn)
.build();
GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repository, groupUuid);
@@ -1281,14 +1221,14 @@ public class GroupConfigTest extends GerritBaseTests {
public void groupCanBeLoadedAtASpecificRevision() throws Exception {
createArbitraryGroup(groupUuid);
- AccountGroup.NameKey firstName = new AccountGroup.NameKey("Bots");
+ AccountGroup.NameKey firstName = AccountGroup.nameKey("Bots");
InternalGroupUpdate groupUpdate1 = InternalGroupUpdate.builder().setName(firstName).build();
updateGroup(groupUuid, groupUpdate1);
RevCommit commitAfterUpdate1 = getLatestCommitForGroup(groupUuid);
InternalGroupUpdate groupUpdate2 =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Robots")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Robots")).build();
updateGroup(groupUuid, groupUpdate2);
GroupConfig groupConfig =
@@ -1315,7 +1255,7 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupCreation groupCreation =
getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build();
InternalGroupUpdate groupUpdate =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Another name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Another name")).build();
createGroup(groupCreation, groupUpdate);
RevCommit revCommit = getLatestCommitForGroup(groupUuid);
@@ -1324,8 +1264,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfNewGroupWithMembersContainsFooters() throws Exception {
- Account account13 = createAccount(new Account.Id(13), "John");
- Account account7 = createAccount(new Account.Id(7), "Jane");
+ Account account13 = createAccount(Account.id(13), "John");
+ Account account7 = createAccount(Account.id(7), "Jane");
ImmutableSet<Account> accounts = ImmutableSet.of(account13, account7);
AuditLogFormatter auditLogFormatter =
@@ -1335,7 +1275,7 @@ public class GroupConfigTest extends GerritBaseTests {
getPrefilledGroupCreationBuilder().setGroupUUID(groupUuid).build();
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId()))
+ .setMemberModification(members -> ImmutableSet.of(account13.id(), account7.id()))
.build();
GroupConfig groupConfig = GroupConfig.createForNewGroup(projectName, repository, groupCreation);
@@ -1349,8 +1289,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfNewGroupWithSubgroupsContainsFooters() throws Exception {
- GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots");
- GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers");
+ GroupDescription.Basic group1 = createGroup(AccountGroup.uuid("129403"), "Bots");
+ GroupDescription.Basic group2 = createGroup(AccountGroup.uuid("8903493"), "Verifiers");
ImmutableSet<GroupDescription.Basic> groups = ImmutableSet.of(group1, group2);
AuditLogFormatter auditLogFormatter =
@@ -1374,8 +1314,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfMemberAdditionContainsFooters() throws Exception {
- Account account13 = createAccount(new Account.Id(13), "John");
- Account account7 = createAccount(new Account.Id(7), "Jane");
+ Account account13 = createAccount(Account.id(13), "John");
+ Account account7 = createAccount(Account.id(7), "Jane");
ImmutableSet<Account> accounts = ImmutableSet.of(account13, account7);
createArbitraryGroup(groupUuid);
@@ -1385,7 +1325,7 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder()
- .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId()))
+ .setMemberModification(members -> ImmutableSet.of(account13.id(), account7.id()))
.build();
updateGroup(groupUuid, groupUpdate, auditLogFormatter);
@@ -1396,8 +1336,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfMemberRemovalContainsFooters() throws Exception {
- Account account13 = createAccount(new Account.Id(13), "John");
- Account account7 = createAccount(new Account.Id(7), "Jane");
+ Account account13 = createAccount(Account.id(13), "John");
+ Account account7 = createAccount(Account.id(7), "Jane");
ImmutableSet<Account> accounts = ImmutableSet.of(account13, account7);
createArbitraryGroup(groupUuid);
@@ -1407,13 +1347,13 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
- .setMemberModification(members -> ImmutableSet.of(account13.getId(), account7.getId()))
+ .setMemberModification(members -> ImmutableSet.of(account13.id(), account7.id()))
.build();
updateGroup(groupUuid, groupUpdate1, auditLogFormatter);
InternalGroupUpdate groupUpdate2 =
InternalGroupUpdate.builder()
- .setMemberModification(members -> ImmutableSet.of(account7.getId()))
+ .setMemberModification(members -> ImmutableSet.of(account7.id()))
.build();
updateGroup(groupUuid, groupUpdate2, auditLogFormatter);
@@ -1423,8 +1363,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfSubgroupAdditionContainsFooters() throws Exception {
- GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots");
- GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers");
+ GroupDescription.Basic group1 = createGroup(AccountGroup.uuid("129403"), "Bots");
+ GroupDescription.Basic group2 = createGroup(AccountGroup.uuid("8903493"), "Verifiers");
ImmutableSet<GroupDescription.Basic> groups = ImmutableSet.of(group1, group2);
createArbitraryGroup(groupUuid);
@@ -1446,8 +1386,8 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageOfSubgroupRemovalContainsFooters() throws Exception {
- GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots");
- GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers");
+ GroupDescription.Basic group1 = createGroup(AccountGroup.uuid("129403"), "Bots");
+ GroupDescription.Basic group2 = createGroup(AccountGroup.uuid("8903493"), "Verifiers");
ImmutableSet<GroupDescription.Basic> groups = ImmutableSet.of(group1, group2);
createArbitraryGroup(groupUuid);
@@ -1478,11 +1418,11 @@ public class GroupConfigTest extends GerritBaseTests {
createArbitraryGroup(groupUuid);
InternalGroupUpdate groupUpdate1 =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("Old name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("Old name")).build();
updateGroup(groupUuid, groupUpdate1);
InternalGroupUpdate groupUpdate2 =
- InternalGroupUpdate.builder().setName(new AccountGroup.NameKey("New name")).build();
+ InternalGroupUpdate.builder().setName(AccountGroup.nameKey("New name")).build();
updateGroup(groupUuid, groupUpdate2);
RevCommit revCommit = getLatestCommitForGroup(groupUuid);
@@ -1492,11 +1432,11 @@ public class GroupConfigTest extends GerritBaseTests {
@Test
public void commitMessageFootersCanBeMixed() throws Exception {
- Account account13 = createAccount(new Account.Id(13), "John");
- Account account7 = createAccount(new Account.Id(7), "Jane");
+ Account account13 = createAccount(Account.id(13), "John");
+ Account account7 = createAccount(Account.id(7), "Jane");
ImmutableSet<Account> accounts = ImmutableSet.of(account13, account7);
- GroupDescription.Basic group1 = createGroup(new AccountGroup.UUID("129403"), "Bots");
- GroupDescription.Basic group2 = createGroup(new AccountGroup.UUID("8903493"), "Verifiers");
+ GroupDescription.Basic group1 = createGroup(AccountGroup.uuid("129403"), "Bots");
+ GroupDescription.Basic group2 = createGroup(AccountGroup.uuid("8903493"), "Verifiers");
ImmutableSet<GroupDescription.Basic> groups = ImmutableSet.of(group1, group2);
createArbitraryGroup(groupUuid);
@@ -1506,16 +1446,16 @@ public class GroupConfigTest extends GerritBaseTests {
InternalGroupUpdate groupUpdate1 =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("Old name"))
- .setMemberModification(members -> ImmutableSet.of(account7.getId()))
+ .setName(AccountGroup.nameKey("Old name"))
+ .setMemberModification(members -> ImmutableSet.of(account7.id()))
.setSubgroupModification(subgroups -> ImmutableSet.of(group2.getGroupUUID()))
.build();
updateGroup(groupUuid, groupUpdate1, auditLogFormatter);
InternalGroupUpdate groupUpdate2 =
InternalGroupUpdate.builder()
- .setName(new AccountGroup.NameKey("New name"))
- .setMemberModification(members -> ImmutableSet.of(account13.getId()))
+ .setName(AccountGroup.nameKey("New name"))
+ .setMemberModification(members -> ImmutableSet.of(account13.id()))
.setSubgroupModification(subgroups -> ImmutableSet.of(group1.getGroupUUID()))
.build();
updateGroup(groupUuid, groupUpdate2, auditLogFormatter);
@@ -1623,7 +1563,7 @@ public class GroupConfigTest extends GerritBaseTests {
MetaDataUpdate metaDataUpdate =
new MetaDataUpdate(
- GitReferenceUpdated.DISABLED, new Project.NameKey("Test Repository"), repository);
+ GitReferenceUpdated.DISABLED, Project.nameKey("Test Repository"), repository);
metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
return metaDataUpdate;
@@ -1641,9 +1581,9 @@ public class GroupConfigTest extends GerritBaseTests {
}
private static Account createAccount(Account.Id id, String name) {
- Account account = new Account(id, TimeUtil.nowTs());
+ Account.Builder account = Account.builder(id, TimeUtil.nowTs());
account.setFullName(name);
- return account;
+ return account.build();
}
private static GroupDescription.Basic createGroup(AccountGroup.UUID uuid, String name) {
diff --git a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
index 2a8d55162e..df97e8809d 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
@@ -15,11 +15,11 @@
package com.google.gerrit.server.group.db;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.common.data.testing.GroupReferenceSubject.groupReferences;
+import static com.google.gerrit.entities.RefNames.REFS_GROUPNAMES;
import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.commits;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_GROUPNAMES;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -27,19 +27,18 @@ import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.testing.GroupReferenceSubject;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.testing.CommitInfoSubject;
import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.GitTestUtil;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.gerrit.truth.ListSubject;
@@ -69,13 +68,13 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class GroupNameNotesTest extends GerritBaseTests {
+public class GroupNameNotesTest {
private static final String SERVER_NAME = "Gerrit Server";
private static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
- private final AccountGroup.UUID groupUuid = new AccountGroup.UUID("users-XYZ");
- private final AccountGroup.NameKey groupName = new AccountGroup.NameKey("users");
+ private final AccountGroup.UUID groupUuid = AccountGroup.uuid("users-XYZ");
+ private final AccountGroup.NameKey groupName = AccountGroup.nameKey("users");
private AtomicInteger idCounter;
private AllUsersName allUsersName;
@@ -105,19 +104,21 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void uuidOfNewGroupMustNotBeNull() throws Exception {
- exception.expect(NullPointerException.class);
- GroupNameNotes.forNewGroup(allUsersName, repo, null, groupName);
+ assertThrows(
+ NullPointerException.class,
+ () -> GroupNameNotes.forNewGroup(allUsersName, repo, null, groupName));
}
@Test
public void nameOfNewGroupMustNotBeNull() throws Exception {
- exception.expect(NullPointerException.class);
- GroupNameNotes.forNewGroup(allUsersName, repo, groupUuid, null);
+ assertThrows(
+ NullPointerException.class,
+ () -> GroupNameNotes.forNewGroup(allUsersName, repo, groupUuid, null));
}
@Test
public void nameOfNewGroupMayBeEmpty() throws Exception {
- AccountGroup.NameKey emptyName = new AccountGroup.NameKey("");
+ AccountGroup.NameKey emptyName = AccountGroup.nameKey("");
createGroup(groupUuid, emptyName);
Optional<GroupReference> groupReference = loadGroup(emptyName);
@@ -128,17 +129,19 @@ public class GroupNameNotesTest extends GerritBaseTests {
public void newGroupMustNotReuseNameOfAnotherGroup() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("AnotherGroup");
- exception.expect(DuplicateKeyException.class);
- exception.expectMessage(groupName.get());
- GroupNameNotes.forNewGroup(allUsersName, repo, anotherGroupUuid, groupName);
+ AccountGroup.UUID anotherGroupUuid = AccountGroup.uuid("AnotherGroup");
+ DuplicateKeyException thrown =
+ assertThrows(
+ DuplicateKeyException.class,
+ () -> GroupNameNotes.forNewGroup(allUsersName, repo, anotherGroupUuid, groupName));
+ assertThat(thrown).hasMessageThat().contains(groupName.get());
}
@Test
public void newGroupMayReuseUuidOfAnotherGroup() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
createGroup(groupUuid, anotherName);
Optional<GroupReference> group1 = loadGroup(groupName);
@@ -151,7 +154,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
public void groupCanBeRenamed() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
renameGroup(groupUuid, groupName, anotherName);
Optional<GroupReference> groupReference = loadGroup(anotherName);
@@ -163,7 +166,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
public void previousNameOfGroupCannotBeUsedAfterRename() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
renameGroup(groupUuid, groupName, anotherName);
Optional<GroupReference> group = loadGroup(groupName);
@@ -173,61 +176,75 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void groupCannotBeRenamedToNull() throws Exception {
createGroup(groupUuid, groupName);
-
- exception.expect(NullPointerException.class);
- GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, null);
+ assertThrows(
+ NullPointerException.class,
+ () -> GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, null));
}
@Test
public void oldNameOfGroupMustBeSpecifiedForRename() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
- exception.expect(NullPointerException.class);
- GroupNameNotes.forRename(allUsersName, repo, groupUuid, null, anotherName);
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
+ assertThrows(
+ NullPointerException.class,
+ () -> GroupNameNotes.forRename(allUsersName, repo, groupUuid, null, anotherName));
}
@Test
public void groupCannotBeRenamedWhenOldNameIsWrong() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherOldName = new AccountGroup.NameKey("contributors");
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage(anotherOldName.get());
- GroupNameNotes.forRename(allUsersName, repo, groupUuid, anotherOldName, anotherName);
+ AccountGroup.NameKey anotherOldName = AccountGroup.nameKey("contributors");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
+ ConfigInvalidException thrown =
+ assertThrows(
+ ConfigInvalidException.class,
+ () ->
+ GroupNameNotes.forRename(
+ allUsersName, repo, groupUuid, anotherOldName, anotherName));
+ assertThat(thrown).hasMessageThat().contains(anotherOldName.get());
}
@Test
public void groupCannotBeRenamedToNameOfAnotherGroup() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
- AccountGroup.NameKey anotherGroupName = new AccountGroup.NameKey("admins");
+ AccountGroup.UUID anotherGroupUuid = AccountGroup.uuid("admins-ABC");
+ AccountGroup.NameKey anotherGroupName = AccountGroup.nameKey("admins");
createGroup(anotherGroupUuid, anotherGroupName);
- exception.expect(DuplicateKeyException.class);
- exception.expectMessage(anotherGroupName.get());
- GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, anotherGroupName);
+ DuplicateKeyException thrown =
+ assertThrows(
+ DuplicateKeyException.class,
+ () ->
+ GroupNameNotes.forRename(
+ allUsersName, repo, groupUuid, groupName, anotherGroupName));
+ assertThat(thrown).hasMessageThat().contains(anotherGroupName.get());
}
@Test
public void groupCannotBeRenamedWithoutSpecifiedUuid() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
- exception.expect(NullPointerException.class);
- GroupNameNotes.forRename(allUsersName, repo, null, groupName, anotherName);
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
+ assertThrows(
+ NullPointerException.class,
+ () -> GroupNameNotes.forRename(allUsersName, repo, null, groupName, anotherName));
}
@Test
public void groupCannotBeRenamedWhenUuidIsWrong() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
- exception.expect(ConfigInvalidException.class);
- exception.expectMessage(groupUuid.get());
- GroupNameNotes.forRename(allUsersName, repo, anotherGroupUuid, groupName, anotherName);
+ AccountGroup.UUID anotherGroupUuid = AccountGroup.uuid("admins-ABC");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
+ ConfigInvalidException thrown =
+ assertThrows(
+ ConfigInvalidException.class,
+ () ->
+ GroupNameNotes.forRename(
+ allUsersName, repo, anotherGroupUuid, groupName, anotherName));
+ assertThat(thrown).hasMessageThat().contains(groupUuid.get());
}
@Test
@@ -248,8 +265,8 @@ public class GroupNameNotesTest extends GerritBaseTests {
createGroup(groupUuid, groupName);
ImmutableList<CommitInfo> commitsAfterCreation = log();
- AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.UUID anotherGroupUuid = AccountGroup.uuid("admins-ABC");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
createGroup(anotherGroupUuid, anotherName);
ImmutableList<CommitInfo> commitsAfterFurtherGroup = log();
@@ -262,7 +279,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
createGroup(groupUuid, groupName);
ImmutableList<CommitInfo> commitsAfterCreation = log();
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
renameGroup(groupUuid, groupName, anotherName);
ImmutableList<CommitInfo> commitsAfterRename = log();
@@ -298,7 +315,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
public void newCommitIsNotCreatedWhenCommittingGroupRenamingTwice() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
GroupNameNotes groupNameNotes =
GroupNameNotes.forRename(allUsersName, repo, groupUuid, groupName, anotherName);
@@ -323,7 +340,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
public void commitMessageMentionsGroupRenaming() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherName = AccountGroup.nameKey("admins");
renameGroup(groupUuid, groupName, anotherName);
ImmutableList<CommitInfo> commits = log();
@@ -341,18 +358,18 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void nonExistentGroupCannotBeLoaded() throws Exception {
- createGroup(new AccountGroup.UUID("contributors-MN"), new AccountGroup.NameKey("contributors"));
+ createGroup(AccountGroup.uuid("contributors-MN"), AccountGroup.nameKey("contributors"));
createGroup(groupUuid, groupName);
- Optional<GroupReference> group = loadGroup(new AccountGroup.NameKey("admins"));
+ Optional<GroupReference> group = loadGroup(AccountGroup.nameKey("admins"));
assertThatGroup(group).isAbsent();
}
@Test
public void specificGroupCanBeLoaded() throws Exception {
- createGroup(new AccountGroup.UUID("contributors-MN"), new AccountGroup.NameKey("contributors"));
+ createGroup(AccountGroup.uuid("contributors-MN"), AccountGroup.nameKey("contributors"));
createGroup(groupUuid, groupName);
- createGroup(new AccountGroup.UUID("admins-ABC"), new AccountGroup.NameKey("admins"));
+ createGroup(AccountGroup.uuid("admins-ABC"), AccountGroup.nameKey("admins"));
Optional<GroupReference> group = loadGroup(groupName);
assertThatGroup(group).value().groupUuid().isEqualTo(groupUuid);
@@ -367,11 +384,11 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void allGroupsCanBeLoaded() throws Exception {
- AccountGroup.UUID groupUuid1 = new AccountGroup.UUID("contributors-MN");
- AccountGroup.NameKey groupName1 = new AccountGroup.NameKey("contributors");
+ AccountGroup.UUID groupUuid1 = AccountGroup.uuid("contributors-MN");
+ AccountGroup.NameKey groupName1 = AccountGroup.nameKey("contributors");
createGroup(groupUuid1, groupName1);
- AccountGroup.UUID groupUuid2 = new AccountGroup.UUID("admins-ABC");
- AccountGroup.NameKey groupName2 = new AccountGroup.NameKey("admins");
+ AccountGroup.UUID groupUuid2 = AccountGroup.uuid("admins-ABC");
+ AccountGroup.NameKey groupName2 = AccountGroup.nameKey("admins");
createGroup(groupUuid2, groupName2);
ImmutableList<GroupReference> allGroups = GroupNameNotes.loadAllGroups(repo);
@@ -384,7 +401,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void loadedGroupsContainGroupsWithDuplicateGroupUuids() throws Exception {
createGroup(groupUuid, groupName);
- AccountGroup.NameKey anotherGroupName = new AccountGroup.NameKey("admins");
+ AccountGroup.NameKey anotherGroupName = AccountGroup.nameKey("admins");
createGroup(groupUuid, anotherGroupName);
ImmutableList<GroupReference> allGroups = GroupNameNotes.loadAllGroups(repo);
@@ -424,10 +441,10 @@ public class GroupNameNotesTest extends GerritBaseTests {
GroupReference g1 = newGroup("a");
GroupReference g2 = newGroup("b");
- try (TestRepository<?> tr = new TestRepository<>(repo)) {
+ try (TestRepository<Repository> tr = new TestRepository<>(repo)) {
ObjectId k1 = getNoteKey(g1);
ObjectId k2 = getNoteKey(g2);
- ObjectId k3 = GroupNameNotes.getNoteKey(new AccountGroup.NameKey("c"));
+ ObjectId k3 = GroupNameNotes.getNoteKey(AccountGroup.nameKey("c"));
PersonIdent ident = newPersonIdent();
ObjectId origCommitId =
tr.branch(REFS_GROUPNAMES)
@@ -481,14 +498,14 @@ public class GroupNameNotesTest extends GerritBaseTests {
@Test
public void updateGroupNamesRejectsNonOneToOneGroupReferences() throws Exception {
assertIllegalArgument(
- new GroupReference(new AccountGroup.UUID("uuid1"), "name1"),
- new GroupReference(new AccountGroup.UUID("uuid1"), "name2"));
+ new GroupReference(AccountGroup.uuid("uuid1"), "name1"),
+ new GroupReference(AccountGroup.uuid("uuid1"), "name2"));
assertIllegalArgument(
- new GroupReference(new AccountGroup.UUID("uuid1"), "name1"),
- new GroupReference(new AccountGroup.UUID("uuid2"), "name1"));
+ new GroupReference(AccountGroup.uuid("uuid1"), "name1"),
+ new GroupReference(AccountGroup.uuid("uuid2"), "name1"));
assertIllegalArgument(
- new GroupReference(new AccountGroup.UUID("uuid1"), "name1"),
- new GroupReference(new AccountGroup.UUID("uuid1"), "name1"));
+ new GroupReference(AccountGroup.uuid("uuid1"), "name1"),
+ new GroupReference(AccountGroup.uuid("uuid1"), "name1"));
}
@Test
@@ -529,8 +546,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
PersonIdent serverIdent = newPersonIdent();
MetaDataUpdate metaDataUpdate =
- new MetaDataUpdate(
- GitReferenceUpdated.DISABLED, new Project.NameKey("Test Repository"), repo);
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, Project.nameKey("Test Repository"), repo);
metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
return metaDataUpdate;
@@ -538,7 +554,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
private GroupReference newGroup(String name) {
int id = idCounter.incrementAndGet();
- return new GroupReference(new AccountGroup.UUID(name + "-" + id), name);
+ return new GroupReference(AccountGroup.uuid(name + "-" + id), name);
}
private static PersonIdent newPersonIdent() {
@@ -546,7 +562,7 @@ public class GroupNameNotesTest extends GerritBaseTests {
}
private static ObjectId getNoteKey(GroupReference g) {
- return GroupNameNotes.getNoteKey(new AccountGroup.NameKey(g.getName()));
+ return GroupNameNotes.getNoteKey(AccountGroup.nameKey(g.getName()));
}
private void updateAllGroups(PersonIdent ident, GroupReference... groupRefs) throws Exception {
@@ -562,12 +578,13 @@ public class GroupNameNotesTest extends GerritBaseTests {
try (ObjectInserter inserter = repo.newObjectInserter()) {
BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
PersonIdent ident = newPersonIdent();
- try {
- GroupNameNotes.updateAllGroups(repo, inserter, bru, Arrays.asList(groupRefs), ident);
- assert_().fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().isEqualTo(GroupNameNotes.UNIQUE_REF_ERROR);
- }
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ GroupNameNotes.updateAllGroups(
+ repo, inserter, bru, Arrays.asList(groupRefs), ident));
+ assertThat(thrown).hasMessageThat().isEqualTo(GroupNameNotes.UNIQUE_REF_ERROR);
}
}
diff --git a/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java b/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
index a5b04eebc8..9025691c58 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
@@ -17,9 +17,9 @@ package com.google.gerrit.server.group.db;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.warning;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.group.db.testing.GroupTestUtil;
import java.util.List;
import org.junit.Test;
@@ -30,7 +30,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
public void groupNamesRefIsMissing() throws Exception {
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(warning("Group with name 'g-1' doesn't exist in the list of all names"));
}
@@ -40,7 +40,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-2", "[group]\n\tuuid = uuid-2\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(warning("Group with name 'g-1' doesn't exist in the list of all names"));
}
@@ -50,7 +50,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-1\n\tname = g-1\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems).isEmpty();
}
@@ -59,7 +59,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-2\n\tname = g-1\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
@@ -72,7 +72,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-1\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(warning("group note of name 'g-1' claims to represent name of 'g-2'"));
}
@@ -82,7 +82,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-2\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
@@ -97,7 +97,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
updateGroupNamesRef("g-1", "[invalid");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, AccountGroup.nameKey("g-1"), AccountGroup.uuid("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
@@ -105,7 +105,7 @@ public class GroupsNoteDbConsistencyCheckerTest extends AbstractGroupTest {
}
private void updateGroupNamesRef(String groupName, String content) throws Exception {
- String nameKey = GroupNameNotes.getNoteKey(new AccountGroup.NameKey(groupName)).getName();
+ String nameKey = GroupNameNotes.getNoteKey(AccountGroup.nameKey(groupName)).getName();
GroupTestUtil.updateGroupFile(
allUsersRepo, serverIdent, RefNames.REFS_GROUPNAMES, nameKey, content);
}
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index c69fa200e1..92a5fbed13 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -21,37 +21,39 @@ import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class AccountFieldTest extends GerritBaseTests {
+public class AccountFieldTest {
@Test
public void refStateFieldValues() throws Exception {
AllUsersName allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
- Account account = new Account(new Account.Id(1), TimeUtil.nowTs());
+ Account.Builder account = Account.builder(Account.id(1), TimeUtil.nowTs());
String metaId = "0e39795bb25dc914118224995c53c5c36923a461";
account.setMetaId(metaId);
List<String> values =
- toStrings(AccountField.REF_STATE.get(AccountState.forAccount(allUsersName, account)));
+ toStrings(AccountField.REF_STATE.get(AccountState.forAccount(account.build())));
assertThat(values).hasSize(1);
String expectedValue =
- allUsersName.get() + ":" + RefNames.refsUsers(account.getId()) + ":" + metaId;
+ allUsersName.get() + ":" + RefNames.refsUsers(account.id()) + ":" + metaId;
assertThat(Iterables.getOnlyElement(values)).isEqualTo(expectedValue);
}
@Test
public void externalIdStateFieldValues() throws Exception {
- Account.Id id = new Account.Id(1);
- Account account = new Account(id, TimeUtil.nowTs());
+ Account.Id id = Account.id(1);
+ Account account =
+ Account.builder(id, TimeUtil.nowTs())
+ .setMetaId("1234567812345678123456781234567812345678")
+ .build();
ExternalId extId1 =
ExternalId.create(
ExternalId.Key.create(ExternalId.SCHEME_MAILTO, "foo.bar@example.com"),
@@ -69,7 +71,7 @@ public class AccountFieldTest extends GerritBaseTests {
List<String> values =
toStrings(
AccountField.EXTERNAL_ID_STATE.get(
- AccountState.forAccount(null, account, ImmutableSet.of(extId1, extId2))));
+ AccountState.forAccount(account, ImmutableSet.of(extId1, extId2))));
String expectedValue1 = extId1.key().sha1().name() + ":" + extId1.blobId().name();
String expectedValue2 = extId2.key().sha1().name() + ":" + extId2.blobId().name();
assertThat(values).containsExactly(expectedValue1, expectedValue2);
diff --git a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
index 758c304822..b817b80741 100644
--- a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.index.change;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
@@ -23,12 +24,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.TestTimeUtil;
import java.sql.Timestamp;
import java.util.Collections;
@@ -38,7 +38,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class ChangeFieldTest extends GerritBaseTests {
+public class ChangeFieldTest {
@Before
public void setUp() {
TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
@@ -53,9 +53,9 @@ public class ChangeFieldTest extends GerritBaseTests {
public void reviewerFieldValues() {
Table<ReviewerStateInternal, Account.Id, Timestamp> t = HashBasedTable.create();
Timestamp t1 = TimeUtil.nowTs();
- t.put(ReviewerStateInternal.REVIEWER, new Account.Id(1), t1);
+ t.put(ReviewerStateInternal.REVIEWER, Account.id(1), t1);
Timestamp t2 = TimeUtil.nowTs();
- t.put(ReviewerStateInternal.CC, new Account.Id(2), t2);
+ t.put(ReviewerStateInternal.CC, Account.id(2), t2);
ReviewerSet reviewers = ReviewerSet.fromTable(t);
List<String> values = ChangeField.getReviewerFieldValues(reviewers);
@@ -63,7 +63,7 @@ public class ChangeFieldTest extends GerritBaseTests {
.containsExactly(
"REVIEWER,1", "REVIEWER,1," + t1.getTime(), "CC,2", "CC,2," + t2.getTime());
- assertThat(ChangeField.parseReviewerFieldValues(new Change.Id(1), values)).isEqualTo(reviewers);
+ assertThat(ChangeField.parseReviewerFieldValues(Change.id(1), values)).isEqualTo(reviewers);
}
@Test
@@ -75,7 +75,7 @@ public class ChangeFieldTest extends GerritBaseTests {
SubmitRecord.Status.OK,
label(SubmitRecord.Label.Status.MAY, "Label-1", null),
label(SubmitRecord.Label.Status.OK, "Label-2", 1))),
- new Account.Id(1)))
+ Account.id(1)))
.containsExactly("OK", "MAY,label-1", "OK,label-2", "OK,label-2,0", "OK,label-2,1");
}
@@ -142,7 +142,7 @@ public class ChangeFieldTest extends GerritBaseTests {
l.status = status;
l.label = label;
if (appliedBy != null) {
- l.appliedBy = new Account.Id(appliedBy);
+ l.appliedBy = Account.id(appliedBy);
}
return l;
}
@@ -153,8 +153,8 @@ public class ChangeFieldTest extends GerritBaseTests {
ChangeField.storedSubmitRecords(recordList).stream()
.map(s -> new String(s, UTF_8))
.collect(toList());
- assertThat(ChangeField.parseSubmitRecords(stored))
- .named("JSON %s" + stored)
+ assertWithMessage("JSON %s" + stored)
+ .that(ChangeField.parseSubmitRecords(stored))
.isEqualTo(recordList);
}
}
diff --git a/javatests/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java b/javatests/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
index fd23da3679..f5d3bf73a0 100644
--- a/javatests/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
+++ b/javatests/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
@@ -16,32 +16,32 @@ package com.google.gerrit.server.index.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.gerrit.entities.Change.Status.MERGED;
+import static com.google.gerrit.entities.Change.Status.NEW;
import static com.google.gerrit.index.query.Predicate.and;
import static com.google.gerrit.index.query.Predicate.or;
-import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
-import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
import static com.google.gerrit.server.index.change.IndexedChangeQuery.convertOptions;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.AndChangeSource;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
import com.google.gerrit.server.query.change.OrSource;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
-public class ChangeIndexRewriterTest extends GerritBaseTests {
+public class ChangeIndexRewriterTest {
private static final IndexConfig CONFIG = IndexConfig.createDefault();
private FakeChangeIndex index;
@@ -196,9 +196,8 @@ public class ChangeIndexRewriterTest extends GerritBaseTests {
indexes.setSearchIndex(new FakeChangeIndex(FakeChangeIndex.V1));
- exception.expect(QueryParseException.class);
- exception.expectMessage("Unsupported index predicate: file:a");
- rewrite(in);
+ QueryParseException thrown = assertThrows(QueryParseException.class, () -> rewrite(in));
+ assertThat(thrown).hasMessageThat().contains("Unsupported index predicate: file:a");
}
@Test
@@ -207,9 +206,9 @@ public class ChangeIndexRewriterTest extends GerritBaseTests {
Predicate<ChangeData> in = parse(q);
assertEquals(query(in), rewrite(in));
- exception.expect(QueryParseException.class);
- exception.expectMessage("too many terms in query");
- rewrite(parse(q + " OR file:d"));
+ QueryParseException thrown =
+ assertThrows(QueryParseException.class, () -> rewrite(parse(q + " OR file:d")));
+ assertThat(thrown).hasMessageThat().contains("too many terms in query");
}
@Test
diff --git a/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java b/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
index 34c5717f10..a23ccaba9a 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeChangeIndex.java
@@ -15,23 +15,24 @@
package com.google.gerrit.server.index.change;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import org.junit.Ignore;
@Ignore
public class FakeChangeIndex implements ChangeIndex {
- static final Schema<ChangeData> V1 = new Schema<>(1, ImmutableList.of(ChangeField.STATUS));
+ static final Schema<ChangeData> V1 = new Schema<>(1, false, ImmutableList.of(ChangeField.STATUS));
static final Schema<ChangeData> V2 =
- new Schema<>(2, ImmutableList.of(ChangeField.STATUS, ChangeField.PATH, ChangeField.UPDATED));
+ new Schema<>(
+ 2, false, ImmutableList.of(ChangeField.STATUS, ChangeField.PATH, ChangeField.UPDATED));
private static class Source implements ChangeDataSource {
private final Predicate<ChangeData> p;
diff --git a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
index a38eabe036..c887875d6a 100644
--- a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
+++ b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
@@ -15,20 +15,19 @@
package com.google.gerrit.server.index.change;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.server.index.change.StalenessChecker.refsAreStale;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ListMultimap;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.index.RefState;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import java.util.stream.Stream;
import org.eclipse.jgit.junit.TestRepository;
@@ -37,14 +36,14 @@ import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
-public class StalenessCheckerTest extends GerritBaseTests {
+public class StalenessCheckerTest {
private static final String SHA1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
private static final String SHA2 = "badc0feebadc0feebadc0feebadc0feebadc0fee";
- private static final Project.NameKey P1 = new Project.NameKey("project1");
- private static final Project.NameKey P2 = new Project.NameKey("project2");
+ private static final Project.NameKey P1 = Project.nameKey("project1");
+ private static final Project.NameKey P2 = Project.nameKey("project2");
- private static final Change.Id C = new Change.Id(1234);
+ private static final Change.Id C = Change.id(1234);
private GitRepositoryManager repoManager;
private Repository r1;
@@ -83,12 +82,7 @@ public class StalenessCheckerTest extends GerritBaseTests {
}
private static void assertInvalidState(String state) {
- try {
- RefState.parseStates(byteArrays(state));
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(IllegalArgumentException.class, () -> RefState.parseStates(byteArrays(state)));
}
@Test
@@ -155,12 +149,8 @@ public class StalenessCheckerTest extends GerritBaseTests {
}
private static void assertInvalidPattern(String state) {
- try {
- StalenessChecker.parsePatterns(byteArrays(state));
- assert_().fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(
+ IllegalArgumentException.class, () -> StalenessChecker.parsePatterns(byteArrays(state)));
}
@Test
diff --git a/javatests/com/google/gerrit/server/ioutil/BUILD b/javatests/com/google/gerrit/server/ioutil/BUILD
index ef0224397e..ac9530f6fa 100644
--- a/javatests/com/google/gerrit/server/ioutil/BUILD
+++ b/javatests/com/google/gerrit/server/ioutil/BUILD
@@ -10,7 +10,6 @@ junit_tests(
visibility = ["//visibility:public"],
deps = [
"//java/com/google/gerrit/server/ioutil",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
"//lib/truth",
"//lib/truth:truth-java8-extension",
diff --git a/javatests/com/google/gerrit/server/ioutil/BasicSerializationTest.java b/javatests/com/google/gerrit/server/ioutil/BasicSerializationTest.java
index c4f32c73b3..fae8559c31 100644
--- a/javatests/com/google/gerrit/server/ioutil/BasicSerializationTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/BasicSerializationTest.java
@@ -23,14 +23,13 @@ import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
-public class BasicSerializationTest extends GerritBaseTests {
+public class BasicSerializationTest {
@Test
public void testReadVarInt32() throws IOException {
assertEquals(0x00000000, readVarInt32(r(b(0))));
diff --git a/javatests/com/google/gerrit/server/ioutil/ColumnFormatterTest.java b/javatests/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
index 9f5e60a3bf..fe642ba945 100644
--- a/javatests/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
@@ -14,13 +14,12 @@
package com.google.gerrit.server.ioutil;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.junit.Assert;
import org.junit.Test;
-public class ColumnFormatterTest extends GerritBaseTests {
+public class ColumnFormatterTest {
/**
* Holds an in-memory {@link java.io.PrintWriter} object and allows comparisons of its contents to
* a supplied string via an assert statement.
diff --git a/javatests/com/google/gerrit/server/ioutil/HexFormatTest.java b/javatests/com/google/gerrit/server/ioutil/HexFormatTest.java
index 40fd71f616..9bb6951501 100644
--- a/javatests/com/google/gerrit/server/ioutil/HexFormatTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/HexFormatTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.ioutil;
import static org.junit.Assert.assertEquals;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class HexFormatTest extends GerritBaseTests {
+public class HexFormatTest {
@Test
public void fromInt() {
diff --git a/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
index 33b1c4f27e..048d59d00a 100644
--- a/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
@@ -18,11 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.List;
import org.junit.Test;
-public class RegexListSearcherTest extends GerritBaseTests {
+public class RegexListSearcherTest {
private static final ImmutableList<String> EMPTY = ImmutableList.of();
@Test
@@ -58,7 +57,7 @@ public class RegexListSearcherTest extends GerritBaseTests {
}
private void assertSearchReturns(List<?> expected, String re, List<String> inputs) {
- assertThat(inputs).isOrdered();
+ assertThat(inputs).isInOrder();
assertThat(RegexListSearcher.ofStrings(re).search(inputs))
.containsExactlyElementsIn(expected)
.inOrder();
diff --git a/javatests/com/google/gerrit/server/ioutil/StringUtilTest.java b/javatests/com/google/gerrit/server/ioutil/StringUtilTest.java
index 817b317b5e..04f806d804 100644
--- a/javatests/com/google/gerrit/server/ioutil/StringUtilTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/StringUtilTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.ioutil;
import static org.junit.Assert.assertEquals;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class StringUtilTest extends GerritBaseTests {
+public class StringUtilTest {
/** Test the boundary condition that the first character of a string should be escaped. */
@Test
public void escapeFirstChar() {
diff --git a/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java b/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java
index 463decf8cd..733d784775 100644
--- a/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java
+++ b/javatests/com/google/gerrit/server/logging/LoggingContextAwareExecutorServiceTest.java
@@ -17,26 +17,71 @@ package com.google.gerrit.server.logging;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.truth.Expect;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-public class LoggingContextAwareExecutorServiceTest extends GerritBaseTests {
+public class LoggingContextAwareExecutorServiceTest {
@Rule public final Expect expect = Expect.create();
+ @Inject @GerritServerConfig private Config config;
+ @Inject private DynamicSet<PerformanceLogger> performanceLoggers;
+
+ private PerformanceLogger testPerformanceLogger;
+ private RegistrationHandle performanceLoggerRegistrationHandle;
+
+ @Before
+ public void setup() {
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+
+ testPerformanceLogger =
+ new PerformanceLogger() {
+ @Override
+ public void log(String operation, long durationMs, Metadata metadata) {
+ // do nothing
+ }
+ };
+ performanceLoggerRegistrationHandle = performanceLoggers.add("gerrit", testPerformanceLogger);
+ }
+
+ @After
+ public void cleanup() {
+ performanceLoggerRegistrationHandle.remove();
+ }
+
@Test
public void loggingContextPropagationToBackgroundThread() throws Exception {
assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
assertForceLogging(false);
- try (TraceContext traceContext = TraceContext.open().forceLogging().addTag("foo", "bar")) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (TraceContext traceContext = TraceContext.open().forceLogging().addTag("foo", "bar");
+ PerformanceLogContext performanceLogContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ // Create a performance log record.
+ TraceContext.newTimer("test").close();
+
SortedMap<String, SortedSet<Object>> tagMap = LoggingContext.getInstance().getTags().asMap();
assertThat(tagMap.keySet()).containsExactly("foo");
assertThat(tagMap.get("foo")).containsExactly("bar");
assertForceLogging(true);
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(1);
ExecutorService executor =
new LoggingContextAwareExecutorService(Executors.newFixedThreadPool(1));
@@ -52,17 +97,32 @@ public class LoggingContextAwareExecutorServiceTest extends GerritBaseTests {
expect
.that(LoggingContext.getInstance().shouldForceLogging(null, null, false))
.isTrue();
+ expect.that(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+ expect.that(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(1);
+
+ // Create another performance log record. We expect this to be visible in the outer
+ // thread.
+ TraceContext.newTimer("test2").close();
+ expect.that(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(2);
})
.get();
- // Verify that tags and force logging flag in the outer thread are still set.
+ // Verify that logging context values in the outer thread are still set.
tagMap = LoggingContext.getInstance().getTags().asMap();
assertThat(tagMap.keySet()).containsExactly("foo");
assertThat(tagMap.get("foo")).containsExactly("bar");
assertForceLogging(true);
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ // The performance log record that was added in the inner thread is available in addition to
+ // the performance log record that was created in the outer thread.
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(2);
}
+
assertThat(LoggingContext.getInstance().getTags().isEmpty()).isTrue();
assertForceLogging(false);
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
}
private void assertForceLogging(boolean expected) {
diff --git a/javatests/com/google/gerrit/server/logging/MetadataTest.java b/javatests/com/google/gerrit/server/logging/MetadataTest.java
new file mode 100644
index 0000000000..f9ae2c122b
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/MetadataTest.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class MetadataTest {
+
+ @Test
+ public void stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresent() {
+ Metadata metadata = Metadata.builder().accountId(1000001).branchName("refs/heads/foo").build();
+ assertThat(metadata.toStringForLoggingLazy().evaluate())
+ .isEqualTo("Metadata{accountId=1000001, branchName=refs/heads/foo, pluginMetadata=[]}");
+ }
+
+ @Test
+ public void
+ stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresentNoFieldsSet() {
+ Metadata metadata = Metadata.builder().build();
+ assertThat(metadata.toStringForLoggingLazy().evaluate())
+ .isEqualTo("Metadata{pluginMetadata=[]}");
+ }
+}
diff --git a/javatests/com/google/gerrit/server/logging/MutableTagsTest.java b/javatests/com/google/gerrit/server/logging/MutableTagsTest.java
index 113f26c3d6..f6f3b46044 100644
--- a/javatests/com/google/gerrit/server/logging/MutableTagsTest.java
+++ b/javatests/com/google/gerrit/server/logging/MutableTagsTest.java
@@ -15,19 +15,18 @@
package com.google.gerrit.server.logging;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import org.junit.Before;
import org.junit.Test;
-public class MutableTagsTest extends GerritBaseTests {
+public class MutableTagsTest {
private MutableTags tags;
@Before
@@ -167,11 +166,7 @@ public class MutableTagsTest extends GerritBaseTests {
}
private void assertNullPointerException(String expectedMessage, Runnable r) {
- try {
- r.run();
- assert_().fail("expected NullPointerException");
- } catch (NullPointerException e) {
- assertThat(e.getMessage()).isEqualTo(expectedMessage);
- }
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> r.run());
+ assertThat(thrown).hasMessageThat().isEqualTo(expectedMessage);
}
}
diff --git a/javatests/com/google/gerrit/server/logging/PerformanceLogContextTest.java b/javatests/com/google/gerrit/server/logging/PerformanceLogContextTest.java
new file mode 100644
index 0000000000..ed4325db06
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/PerformanceLogContextTest.java
@@ -0,0 +1,382 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
+import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer2;
+import com.google.gerrit.metrics.Timer3;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PerformanceLogContextTest {
+ @Inject @GerritServerConfig private Config config;
+ @Inject private DynamicSet<PerformanceLogger> performanceLoggers;
+
+ // In this test setup this gets the DisabledMetricMaker injected. This means it doesn't record any
+ // metric, but performance log records are still created.
+ @Inject private MetricMaker metricMaker;
+
+ private TestPerformanceLogger testPerformanceLogger;
+ private RegistrationHandle performanceLoggerRegistrationHandle;
+
+ @Before
+ public void setup() {
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+
+ testPerformanceLogger = new TestPerformanceLogger();
+ performanceLoggerRegistrationHandle = performanceLoggers.add("gerrit", testPerformanceLogger);
+ }
+
+ @After
+ public void cleanup() {
+ performanceLoggerRegistrationHandle.remove();
+
+ LoggingContext.getInstance().clearPerformanceLogEntries();
+ LoggingContext.getInstance().performanceLogging(false);
+ }
+
+ @Test
+ public void traceTimersInsidePerformanceLogContextCreatePerformanceLog() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ TraceContext.newTimer("test1").close();
+ TraceContext.newTimer("test2", Metadata.builder().accountId(1000000).changeId(123).build())
+ .close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(2);
+ }
+
+ assertThat(testPerformanceLogger.logEntries())
+ .containsExactly(
+ PerformanceLogEntry.create("test1", Metadata.empty()),
+ PerformanceLogEntry.create(
+ "test2", Metadata.builder().accountId(1000000).changeId(123).build()))
+ .inOrder();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ @Test
+ public void traceTimersOutsidePerformanceLogContextDoNotCreatePerformanceLog() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ TraceContext.newTimer("test1").close();
+ TraceContext.newTimer("test2", Metadata.builder().accountId(1000000).changeId(123).build())
+ .close();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ assertThat(testPerformanceLogger.logEntries()).isEmpty();
+ }
+
+ @Test
+ public void
+ traceTimersInsidePerformanceLogContextDoNotCreatePerformanceLogIfNoPerformanceLoggers() {
+ // Remove test performance logger so that there are no registered performance loggers.
+ performanceLoggerRegistrationHandle.remove();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+
+ TraceContext.newTimer("test1").close();
+ TraceContext.newTimer("test2", Metadata.builder().accountId(1000000).changeId(123).build())
+ .close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ assertThat(testPerformanceLogger.logEntries()).isEmpty();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ @Test
+ public void timerMetricsInsidePerformanceLogContextCreatePerformanceLog() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ Timer0 timer0 =
+ metricMaker.newTimer("test1/latency", new Description("Latency metric for testing"));
+ timer0.start().close();
+
+ Timer1<Integer> timer1 =
+ metricMaker.newTimer(
+ "test2/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build());
+ timer1.start(1000000).close();
+
+ Timer2<Integer, Integer> timer2 =
+ metricMaker.newTimer(
+ "test3/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build());
+ timer2.start(1000000, 123).close();
+
+ Timer3<Integer, Integer, String> timer3 =
+ metricMaker.newTimer(
+ "test4/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build(),
+ Field.ofString("project", Metadata.Builder::projectName).build());
+ timer3.start(1000000, 123, "foo/bar").close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(4);
+ }
+
+ assertThat(testPerformanceLogger.logEntries())
+ .containsExactly(
+ PerformanceLogEntry.create("test1/latency", Metadata.empty()),
+ PerformanceLogEntry.create(
+ "test2/latency", Metadata.builder().accountId(1000000).build()),
+ PerformanceLogEntry.create(
+ "test3/latency", Metadata.builder().accountId(1000000).changeId(123).build()),
+ PerformanceLogEntry.create(
+ "test4/latency",
+ Metadata.builder().accountId(1000000).changeId(123).projectName("foo/bar").build()))
+ .inOrder();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ @Test
+ public void timerMetricsInsidePerformanceLogContextCreatePerformanceLogNullValuesAllowed() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ Timer1<String> timer1 =
+ metricMaker.newTimer(
+ "test1/latency",
+ new Description("Latency metric for testing"),
+ Field.ofString("project", Metadata.Builder::projectName).build());
+ timer1.start(null).close();
+
+ Timer2<String, String> timer2 =
+ metricMaker.newTimer(
+ "test2/latency",
+ new Description("Latency metric for testing"),
+ Field.ofString("project", Metadata.Builder::projectName).build(),
+ Field.ofString("branch", Metadata.Builder::branchName).build());
+ timer2.start(null, null).close();
+
+ Timer3<String, String, String> timer3 =
+ metricMaker.newTimer(
+ "test3/latency",
+ new Description("Latency metric for testing"),
+ Field.ofString("project", Metadata.Builder::projectName).build(),
+ Field.ofString("branch", Metadata.Builder::branchName).build(),
+ Field.ofString("revision", Metadata.Builder::revision).build());
+ timer3.start(null, null, null).close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(3);
+ }
+
+ assertThat(testPerformanceLogger.logEntries())
+ .containsExactly(
+ PerformanceLogEntry.create("test1/latency", Metadata.empty()),
+ PerformanceLogEntry.create("test2/latency", Metadata.empty()),
+ PerformanceLogEntry.create("test3/latency", Metadata.empty()))
+ .inOrder();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ @Test
+ public void timerMetricsOutsidePerformanceLogContextDoNotCreatePerformanceLog() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ Timer0 timer0 =
+ metricMaker.newTimer("test1/latency", new Description("Latency metric for testing"));
+ timer0.start().close();
+
+ Timer1<Integer> timer1 =
+ metricMaker.newTimer(
+ "test2/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build());
+ timer1.start(1000000).close();
+
+ Timer2<Integer, Integer> timer2 =
+ metricMaker.newTimer(
+ "test3/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build());
+ timer2.start(1000000, 123).close();
+
+ Timer3<Integer, Integer, String> timer3 =
+ metricMaker.newTimer(
+ "test4/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build(),
+ Field.ofString("project", Metadata.Builder::projectName).build());
+ timer3.start(1000000, 123, "value3").close();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ assertThat(testPerformanceLogger.logEntries()).isEmpty();
+ }
+
+ @Test
+ public void
+ timerMetricssInsidePerformanceLogContextDoNotCreatePerformanceLogIfNoPerformanceLoggers() {
+ // Remove test performance logger so that there are no registered performance loggers.
+ performanceLoggerRegistrationHandle.remove();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+
+ Timer0 timer0 =
+ metricMaker.newTimer("test1/latency", new Description("Latency metric for testing"));
+ timer0.start().close();
+
+ Timer1<Integer> timer1 =
+ metricMaker.newTimer(
+ "test2/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("accoutn", Metadata.Builder::accountId).build());
+ timer1.start(1000000).close();
+
+ Timer2<Integer, Integer> timer2 =
+ metricMaker.newTimer(
+ "test3/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build());
+ timer2.start(1000000, 123).close();
+
+ Timer3<Integer, Integer, String> timer3 =
+ metricMaker.newTimer(
+ "test4/latency",
+ new Description("Latency metric for testing"),
+ Field.ofInteger("account", Metadata.Builder::accountId).build(),
+ Field.ofInteger("change", Metadata.Builder::changeId).build(),
+ Field.ofString("project", Metadata.Builder::projectName).build());
+ timer3.start(1000000, 123, "foo/bar").close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ assertThat(testPerformanceLogger.logEntries()).isEmpty();
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ @Test
+ public void nestingPerformanceLogContextsIsPossible() {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+
+ try (PerformanceLogContext traceContext1 =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ TraceContext.newTimer("test1").close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(1);
+
+ try (PerformanceLogContext traceContext2 =
+ new PerformanceLogContext(config, performanceLoggers)) {
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+
+ TraceContext.newTimer("test2").close();
+ TraceContext.newTimer("test3").close();
+
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(2);
+ }
+
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isTrue();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).hasSize(1);
+ }
+ assertThat(LoggingContext.getInstance().isPerformanceLogging()).isFalse();
+ assertThat(LoggingContext.getInstance().getPerformanceLogRecords()).isEmpty();
+ }
+
+ private static class TestPerformanceLogger implements PerformanceLogger {
+ private List<PerformanceLogEntry> logEntries = new ArrayList<>();
+
+ @Override
+ public void log(String operation, long durationMs, Metadata metadata) {
+ logEntries.add(PerformanceLogEntry.create(operation, metadata));
+ }
+
+ ImmutableList<PerformanceLogEntry> logEntries() {
+ return ImmutableList.copyOf(logEntries);
+ }
+ }
+
+ @AutoValue
+ abstract static class PerformanceLogEntry {
+ static PerformanceLogEntry create(String operation, Metadata metadata) {
+ return new AutoValue_PerformanceLogContextTest_PerformanceLogEntry(operation, metadata);
+ }
+
+ abstract String operation();
+
+ abstract Metadata metadata();
+ }
+}
diff --git a/javatests/com/google/gerrit/server/logging/TraceContextTest.java b/javatests/com/google/gerrit/server/logging/TraceContextTest.java
index 19b2eeb322..13f2035aef 100644
--- a/javatests/com/google/gerrit/server/logging/TraceContextTest.java
+++ b/javatests/com/google/gerrit/server/logging/TraceContextTest.java
@@ -15,18 +15,18 @@
package com.google.gerrit.server.logging;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.server.logging.TraceContext.TraceIdConsumer;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import org.junit.After;
import org.junit.Test;
-public class TraceContextTest extends GerritBaseTests {
+public class TraceContextTest {
@After
public void cleanup() {
LoggingContext.getInstance().clearTags();
@@ -237,6 +237,22 @@ public class TraceContextTest extends GerritBaseTests {
}
}
+ @Test
+ public void operationForTraceTimerCannotBeNull() throws Exception {
+ assertThrows(NullPointerException.class, () -> TraceContext.newTimer(null));
+ assertThrows(NullPointerException.class, () -> TraceContext.newTimer(null, Metadata.empty()));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ TraceContext.newTimer(
+ null, Metadata.builder().accountId(1000000).changeId(123).build()));
+ }
+
+ @Test
+ public void metadataForTraceTimerCannotBeNull() throws Exception {
+ assertThrows(NullPointerException.class, () -> TraceContext.newTimer("test", null));
+ }
+
private void assertTags(ImmutableMap<String, ImmutableSet<String>> expectedTagMap) {
SortedMap<String, SortedSet<Object>> actualTagMap =
LoggingContext.getInstance().getTags().asMap();
diff --git a/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java b/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
index f8a613aa37..9dcb08c4b6 100644
--- a/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
+++ b/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
@@ -18,11 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.mail.Address;
import com.google.gerrit.mail.MailMessage;
-import com.google.gerrit.testing.GerritBaseTests;
import java.time.Instant;
import org.junit.Test;
-public class AutoReplyMailFilterTest extends GerritBaseTests {
+public class AutoReplyMailFilterTest {
private AutoReplyMailFilter autoReplyMailFilter = new AutoReplyMailFilter();
diff --git a/javatests/com/google/gerrit/server/mail/send/CommentFormatterTest.java b/javatests/com/google/gerrit/server/mail/send/CommentFormatterTest.java
index 78116edf29..f4fbc7846e 100644
--- a/javatests/com/google/gerrit/server/mail/send/CommentFormatterTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/CommentFormatterTest.java
@@ -20,11 +20,10 @@ import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.PARA
import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.PRE_FORMATTED;
import static com.google.gerrit.server.mail.send.CommentFormatter.BlockType.QUOTE;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.List;
import org.junit.Test;
-public class CommentFormatterTest extends GerritBaseTests {
+public class CommentFormatterTest {
private void assertBlock(
List<CommentFormatter.Block> list, int index, CommentFormatter.BlockType type, String text) {
CommentFormatter.Block block = list.get(index);
diff --git a/javatests/com/google/gerrit/server/mail/send/CommentSenderTest.java b/javatests/com/google/gerrit/server/mail/send/CommentSenderTest.java
index 0682bb3fd5..78cefdf70f 100644
--- a/javatests/com/google/gerrit/server/mail/send/CommentSenderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/CommentSenderTest.java
@@ -16,11 +16,10 @@ package com.google.gerrit.server.mail.send;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Collections;
import org.junit.Test;
-public class CommentSenderTest extends GerritBaseTests {
+public class CommentSenderTest {
private static class TestSender extends CommentSender {
TestSender() {
super(null, null, null, null, null);
diff --git a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index 537ebff86b..74f44a1258 100644
--- a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -15,20 +15,17 @@
package com.google.gerrit.server.mail.send;
import static com.google.common.truth.Truth.assertThat;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import com.google.gerrit.entities.Account;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -37,7 +34,7 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Before;
import org.junit.Test;
-public class FromAddressGeneratorProviderTest extends GerritBaseTests {
+public class FromAddressGeneratorProviderTest {
private Config config;
private PersonIdent ident;
private AccountCache accountCache;
@@ -46,7 +43,7 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
public void setUp() throws Exception {
config = new Config();
ident = new PersonIdent("NAME", "e@email", 0, 0);
- accountCache = createStrictMock(AccountCache.class);
+ accountCache = mock(AccountCache.class);
}
private FromAddressGenerator create() {
@@ -86,12 +83,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name);
assertThat(r.getEmail()).isEqualTo(email);
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -101,12 +97,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(null, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isNull();
assertThat(r.getEmail()).isEqualTo(email);
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -116,23 +111,21 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String name = "A U. Thor";
final Account.Id user = user(name, null);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name + " (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
public void USER_NullUser() {
setFrom("USER");
- replay(accountCache);
final Address r = create().from(null);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(ident.getName());
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyZeroInteractions(accountCache);
}
@Test
@@ -143,12 +136,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name);
assertThat(r.getEmail()).isEqualTo(email);
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -159,12 +151,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name + " (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -176,12 +167,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name);
assertThat(r.getEmail()).isEqualTo(email);
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -193,12 +183,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name + " (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -209,12 +198,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name);
assertThat(r.getEmail()).isEqualTo(email);
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -237,23 +225,21 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = userNoLookup(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(ident.getName());
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyZeroInteractions(accountCache);
}
@Test
public void SERVER_NullUser() {
setFrom("SERVER");
- replay(accountCache);
final Address r = create().from(null);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(ident.getName());
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyZeroInteractions(accountCache);
}
@Test
@@ -276,12 +262,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name + " (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -291,12 +276,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(null, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo("Anonymous Coward (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -306,23 +290,21 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String name = "A U. Thor";
final Account.Id user = user(name, null);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(name + " (Code Review)");
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
public void MIXED_NullUser() {
setFrom("MIXED");
- replay(accountCache);
final Address r = create().from(null);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(ident.getName());
assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
- verify(accountCache);
+ verifyZeroInteractions(accountCache);
}
@Test
@@ -333,12 +315,11 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(name, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo("A " + name + " B");
assertThat(r.getEmail()).isEqualTo("my.server@email.address");
- verify(accountCache);
+ verifyAccountCacheGet(user);
}
@Test
@@ -348,42 +329,42 @@ public class FromAddressGeneratorProviderTest extends GerritBaseTests {
final String email = "a.u.thor@test.example.com";
final Account.Id user = user(null, email);
- replay(accountCache);
final Address r = create().from(user);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo("A Anonymous Coward B");
assertThat(r.getEmail()).isEqualTo("my.server@email.address");
- verify(accountCache);
}
@Test
public void CUSTOM_NullUser() {
setFrom("A ${user} B <my.server@email.address>");
- replay(accountCache);
final Address r = create().from(null);
assertThat(r).isNotNull();
assertThat(r.getName()).isEqualTo(ident.getName());
assertThat(r.getEmail()).isEqualTo("my.server@email.address");
- verify(accountCache);
}
private Account.Id user(String name, String email) {
final AccountState s = makeUser(name, email);
- expect(accountCache.get(eq(s.getAccount().getId()))).andReturn(Optional.of(s));
- return s.getAccount().getId();
+ when(accountCache.get(eq(s.account().id()))).thenReturn(Optional.of(s));
+ return s.account().id();
+ }
+
+ private void verifyAccountCacheGet(Account.Id id) {
+ verify(accountCache).get(eq(id));
}
private Account.Id userNoLookup(String name, String email) {
final AccountState s = makeUser(name, email);
- return s.getAccount().getId();
+ return s.account().id();
}
private AccountState makeUser(String name, String email) {
- final Account.Id userId = new Account.Id(42);
- final Account account = new Account(userId, TimeUtil.nowTs());
+ final Account.Id userId = Account.id(42);
+ final Account.Builder account = Account.builder(userId, TimeUtil.nowTs());
account.setFullName(name);
account.setPreferredEmail(email);
- return AccountState.forAccount(new AllUsersName(AllUsersNameProvider.DEFAULT), account);
+ return AccountState.forAccount(account.build());
}
}
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index a1f318f8a5..7192c55f23 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -19,16 +19,17 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.CommentRange;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.CommentRange;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.FanOutExecutor;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
@@ -51,9 +52,9 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.testing.AssertableExecutorService;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeAccountCache;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.TestChanges;
import com.google.gerrit.testing.TestTimeUtil;
@@ -63,9 +64,11 @@ import com.google.inject.Injector;
import com.google.inject.util.Providers;
import java.sql.Timestamp;
import java.util.TimeZone;
+import java.util.concurrent.ExecutorService;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
@@ -75,7 +78,7 @@ import org.junit.runner.RunWith;
@Ignore
@RunWith(ConfigSuite.class)
-public abstract class AbstractChangeNotesTest extends GerritBaseTests {
+public abstract class AbstractChangeNotesTest {
private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
@ConfigSuite.Parameter public Config testConfig;
@@ -91,6 +94,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
protected Project.NameKey project;
protected RevWalk rw;
protected TestRepository<InMemoryRepository> tr;
+ protected AssertableExecutorService assertableFanOutExecutor;
@Inject protected IdentifiedUser.GenericFactory userFactory;
@@ -110,20 +114,21 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
setTimeForTesting();
serverIdent = new PersonIdent("Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
- project = new Project.NameKey("test-project");
+ project = Project.nameKey("test-project");
repoManager = new InMemoryRepositoryManager();
repo = repoManager.createRepository(project);
tr = new TestRepository<>(repo);
rw = tr.getRevWalk();
accountCache = new FakeAccountCache();
- Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
+ Account.Builder co = Account.builder(Account.id(1), TimeUtil.nowTs());
co.setFullName("Change Owner");
co.setPreferredEmail("change@owner.com");
- accountCache.put(co);
- Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
+ accountCache.put(co.build());
+ Account.Builder ou = Account.builder(Account.id(2), TimeUtil.nowTs());
ou.setFullName("Other Account");
ou.setPreferredEmail("other@account.com");
- accountCache.put(ou);
+ accountCache.put(ou.build());
+ assertableFanOutExecutor = new AssertableExecutorService();
injector =
Guice.createInjector(
@@ -156,13 +161,16 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
.toInstance(serverIdent);
bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
bind(MetricMaker.class).to(DisabledMetricMaker.class);
+ bind(ExecutorService.class)
+ .annotatedWith(FanOutExecutor.class)
+ .toInstance(assertableFanOutExecutor);
}
});
injector.injectMembers(this);
repoManager.createRepository(allUsers);
- changeOwner = userFactory.create(co.getId());
- otherUser = userFactory.create(ou.getId());
+ changeOwner = userFactory.create(co.id());
+ otherUser = userFactory.create(ou.id());
otherUserId = otherUser.getAccountId();
internalUser = new InternalUser();
}
@@ -182,7 +190,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
Change c = TestChanges.newChange(project, changeOwner.getAccountId());
ChangeUpdate u = newUpdateForNewChange(c, changeOwner);
u.setChangeId(c.getKey().get());
- u.setBranch(c.getDest().get());
+ u.setBranch(c.getDest().branch());
u.setWorkInProgress(workInProgress);
u.commit();
return c;
@@ -247,7 +255,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
Timestamp t,
String message,
short side,
- String commitSHA1,
+ ObjectId commitId,
boolean unresolved) {
Comment c =
new Comment(
@@ -260,7 +268,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
unresolved);
c.lineNbr = line;
c.parentUuid = parentUUID;
- c.revId = commitSHA1;
+ c.setCommitId(commitId);
c.setRange(range);
return c;
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
index b4d9738a51..9112756cef 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesCacheTest.java
@@ -20,8 +20,8 @@ import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatS
import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesKeyProto;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -31,8 +31,8 @@ public final class ChangeNotesCacheTest {
public void keySerializer() throws Exception {
ChangeNotesCache.Key key =
ChangeNotesCache.Key.create(
- new Project.NameKey("project"),
- new Change.Id(1234),
+ Project.nameKey("project"),
+ Change.id(1234),
ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
byte[] serialized = ChangeNotesCache.Key.Serializer.INSTANCE.serialize(key);
assertThat(ChangeNotesKeyProto.parseFrom(serialized))
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
index 79dcd5bed3..013939a400 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -15,9 +15,9 @@
package com.google.gerrit.server.notedb;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
import com.google.gerrit.server.util.time.TimeUtil;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -214,6 +214,26 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
}
@Test
+ public void parseAssignee() throws Exception {
+ assertParseSucceeds(
+ "Update change\n"
+ + "\n"
+ + "Branch: refs/heads/master\n"
+ + "Change-id: I577fb248e474018276351785930358ec0450e9f7\n"
+ + "Patch-set: 1\n"
+ + "Assignee: Change Owner <1@gerrit>\n"
+ + "Subject: This is a test change\n");
+ assertParseSucceeds(
+ "Update change\n"
+ + "\n"
+ + "Branch: refs/heads/master\n"
+ + "Change-id: I577fb248e474018276351785930358ec0450e9f7\n"
+ + "Patch-set: 2\n"
+ + "Assignee:\n"
+ + "Subject: This is a test change\n");
+ }
+
+ @Test
public void parseTopic() throws Exception {
assertParseSucceeds(
"Update change\n"
@@ -545,19 +565,12 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
}
private void assertParseFails(RevCommit commit) throws Exception {
- try {
- newParser(commit).parseAll();
- fail("Expected parse to fail:\n" + commit.getFullMessage());
- } catch (ConfigInvalidException e) {
- // Expected
- }
+ assertThrows(ConfigInvalidException.class, () -> newParser(commit).parseAll());
}
private ChangeNotesParser newParser(ObjectId tip) throws Exception {
walk.reset();
ChangeNoteJson changeNoteJson = injector.getInstance(ChangeNoteJson.class);
- LegacyChangeNoteRead reader = injector.getInstance(LegacyChangeNoteRead.class);
- return new ChangeNotesParser(
- newChange().getId(), tip, walk, changeNoteJson, reader, args.metrics);
+ return new ChangeNotesParser(newChange().getId(), tip, walk, changeNoteJson, args.metrics);
}
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 2931b1706c..6ece894645 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -27,22 +27,23 @@ import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.converter.ChangeMessageProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.entities.converter.PatchSetProtoConverter;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.converter.ChangeMessageProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
+import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
+import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
@@ -50,23 +51,25 @@ import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.Reviewer
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.notedb.ChangeNotesState.ChangeColumns;
import com.google.gerrit.server.notedb.ChangeNotesState.Serializer;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.TypeLiteral;
import com.google.protobuf.ByteString;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Before;
import org.junit.Test;
-public class ChangeNotesStateTest extends GerritBaseTests {
- private static final Change.Id ID = new Change.Id(123);
+public class ChangeNotesStateTest {
+ private static final Change.Id ID = Change.id(123);
private static final ObjectId SHA =
ObjectId.fromString("1234567812345678123456781234567812345678");
private static final ByteString SHA_BYTES = ObjectIdConverter.create().toByteString(SHA);
private static final String CHANGE_KEY = "Iabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
+ private static final String DEFAULT_SERVER_ID = UUID.randomUUID().toString();
private ChangeColumns cols;
private ChangeColumnsProto colsProto;
@@ -75,10 +78,10 @@ public class ChangeNotesStateTest extends GerritBaseTests {
public void setUp() throws Exception {
cols =
ChangeColumns.builder()
- .changeKey(new Change.Key(CHANGE_KEY))
+ .changeKey(Change.key(CHANGE_KEY))
.createdOn(new Timestamp(123456L))
.lastUpdatedOn(new Timestamp(234567L))
- .owner(new Account.Id(1000))
+ .owner(Account.id(1000))
.branch("refs/heads/master")
.subject("Test change")
.isPrivate(false)
@@ -98,7 +101,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
newBuilder()
.columns(
cols.toBuilder()
- .changeKey(new Change.Key("Ieeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"))
+ .changeKey(Change.key("Ieeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"))
.build())
.build(),
ChangeNotesStateProto.newBuilder()
@@ -134,7 +137,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void serializeOwner() throws Exception {
assertRoundTrip(
- newBuilder().columns(cols.toBuilder().owner(new Account.Id(7777)).build()).build(),
+ newBuilder().columns(cols.toBuilder().owner(Account.id(7777)).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
@@ -168,7 +171,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
public void serializeCurrentPatchSetId() throws Exception {
assertRoundTrip(
newBuilder()
- .columns(cols.toBuilder().currentPatchSetId(new PatchSet.Id(ID, 2)).build())
+ .columns(cols.toBuilder().currentPatchSetId(PatchSet.id(ID, 2)).build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
@@ -241,17 +244,6 @@ public class ChangeNotesStateTest extends GerritBaseTests {
}
@Test
- public void serializeAssignee() throws Exception {
- assertRoundTrip(
- newBuilder().columns(cols.toBuilder().assignee(new Account.Id(2000)).build()).build(),
- ChangeNotesStateProto.newBuilder()
- .setMetaId(SHA_BYTES)
- .setChangeId(ID.get())
- .setColumns(colsProto.toBuilder().setAssignee(2000).setHasAssignee(true))
- .build());
- }
-
- @Test
public void serializeStatus() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().status(Change.Status.MERGED).build()).build(),
@@ -298,7 +290,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void serializeRevertOf() throws Exception {
assertRoundTrip(
- newBuilder().columns(cols.toBuilder().revertOf(new Change.Id(999)).build()).build(),
+ newBuilder().columns(cols.toBuilder().revertOf(Change.id(999)).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
@@ -307,21 +299,6 @@ public class ChangeNotesStateTest extends GerritBaseTests {
}
@Test
- public void serializePastAssignees() throws Exception {
- assertRoundTrip(
- newBuilder()
- .pastAssignees(ImmutableSet.of(new Account.Id(2002), new Account.Id(2001)))
- .build(),
- ChangeNotesStateProto.newBuilder()
- .setMetaId(SHA_BYTES)
- .setChangeId(ID.get())
- .setColumns(colsProto)
- .addPastAssignee(2002)
- .addPastAssignee(2001)
- .build());
- }
-
- @Test
public void serializeHashtags() throws Exception {
assertRoundTrip(
newBuilder().hashtags(ImmutableSet.of("tag2", "tag1")).build(),
@@ -336,25 +313,29 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void serializePatchSets() throws Exception {
- PatchSet ps1 = new PatchSet(new PatchSet.Id(ID, 1));
- ps1.setUploader(new Account.Id(2000));
- ps1.setRevision(new RevId("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
- ps1.setCreatedOn(cols.createdOn());
+ PatchSet ps1 =
+ PatchSet.builder()
+ .id(PatchSet.id(ID, 1))
+ .commitId(ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+ .uploader(Account.id(2000))
+ .createdOn(cols.createdOn())
+ .build();
ByteString ps1Bytes = toByteString(ps1, PatchSetProtoConverter.INSTANCE);
assertThat(ps1Bytes.size()).isEqualTo(66);
- PatchSet ps2 = new PatchSet(new PatchSet.Id(ID, 2));
- ps2.setUploader(new Account.Id(3000));
- ps2.setRevision(new RevId("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
- ps2.setCreatedOn(cols.lastUpdatedOn());
+ PatchSet ps2 =
+ PatchSet.builder()
+ .id(PatchSet.id(ID, 2))
+ .commitId(ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"))
+ .uploader(Account.id(3000))
+ .createdOn(cols.lastUpdatedOn())
+ .build();
ByteString ps2Bytes = toByteString(ps2, PatchSetProtoConverter.INSTANCE);
assertThat(ps2Bytes.size()).isEqualTo(66);
assertThat(ps2Bytes).isNotEqualTo(ps1Bytes);
assertRoundTrip(
- newBuilder()
- .patchSets(ImmutableMap.of(ps2.getId(), ps2, ps1.getId(), ps1).entrySet())
- .build(),
+ newBuilder().patchSets(ImmutableMap.of(ps2.id(), ps2, ps1.id(), ps1).entrySet()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
@@ -367,28 +348,31 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void serializeApprovals() throws Exception {
PatchSetApproval a1 =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(ID, 1), new Account.Id(2001), new LabelId("Code-Review")),
- (short) 1,
- new Timestamp(1212L));
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(ID, 1), Account.id(2001), LabelId.create("Code-Review")))
+ .value(1)
+ .granted(new Timestamp(1212L))
+ .build();
ByteString a1Bytes = toByteString(a1, PatchSetApprovalProtoConverter.INSTANCE);
assertThat(a1Bytes.size()).isEqualTo(43);
PatchSetApproval a2 =
- new PatchSetApproval(
- new PatchSetApproval.Key(
- new PatchSet.Id(ID, 1), new Account.Id(2002), new LabelId("Verified")),
- (short) -1,
- new Timestamp(3434L));
+ PatchSetApproval.builder()
+ .key(
+ PatchSetApproval.key(
+ PatchSet.id(ID, 1), Account.id(2002), LabelId.create("Verified")))
+ .value(-1)
+ .granted(new Timestamp(3434L))
+ .build();
ByteString a2Bytes = toByteString(a2, PatchSetApprovalProtoConverter.INSTANCE);
assertThat(a2Bytes.size()).isEqualTo(49);
assertThat(a2Bytes).isNotEqualTo(a1Bytes);
assertRoundTrip(
newBuilder()
- .approvals(
- ImmutableListMultimap.of(a2.getPatchSetId(), a2, a1.getPatchSetId(), a1).entries())
+ .approvals(ImmutableListMultimap.of(a2.patchSetId(), a2, a1.patchSetId(), a1).entries())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
@@ -406,11 +390,8 @@ public class ChangeNotesStateTest extends GerritBaseTests {
.reviewers(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
- .put(ReviewerStateInternal.CC, new Account.Id(2001), new Timestamp(1212L))
- .put(
- ReviewerStateInternal.REVIEWER,
- new Account.Id(2002),
- new Timestamp(3434L))
+ .put(ReviewerStateInternal.CC, Account.id(2001), new Timestamp(1212L))
+ .put(ReviewerStateInternal.REVIEWER, Account.id(2002), new Timestamp(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
@@ -503,11 +484,8 @@ public class ChangeNotesStateTest extends GerritBaseTests {
.pendingReviewers(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
- .put(ReviewerStateInternal.CC, new Account.Id(2001), new Timestamp(1212L))
- .put(
- ReviewerStateInternal.REVIEWER,
- new Account.Id(2002),
- new Timestamp(3434L))
+ .put(ReviewerStateInternal.CC, Account.id(2001), new Timestamp(1212L))
+ .put(ReviewerStateInternal.REVIEWER, Account.id(2002), new Timestamp(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
@@ -564,9 +542,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void serializeAllPastReviewers() throws Exception {
assertRoundTrip(
- newBuilder()
- .allPastReviewers(ImmutableList.of(new Account.Id(2002), new Account.Id(2001)))
- .build(),
+ newBuilder().allPastReviewers(ImmutableList.of(Account.id(2002), Account.id(2001))).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
@@ -584,13 +560,13 @@ public class ChangeNotesStateTest extends GerritBaseTests {
ImmutableList.of(
ReviewerStatusUpdate.create(
new Timestamp(1212L),
- new Account.Id(1000),
- new Account.Id(2002),
+ Account.id(1000),
+ Account.id(2002),
ReviewerStateInternal.CC),
ReviewerStatusUpdate.create(
new Timestamp(3434L),
- new Account.Id(1000),
- new Account.Id(2001),
+ Account.id(1000),
+ Account.id(2001),
ReviewerStateInternal.REVIEWER)))
.build(),
ChangeNotesStateProto.newBuilder()
@@ -613,6 +589,35 @@ public class ChangeNotesStateTest extends GerritBaseTests {
}
@Test
+ public void serializeAssigneeUpdates() throws Exception {
+ assertRoundTrip(
+ newBuilder()
+ .assigneeUpdates(
+ ImmutableList.of(
+ AssigneeStatusUpdate.create(
+ new Timestamp(1212L), Account.id(1000), Optional.of(Account.id(2001))),
+ AssigneeStatusUpdate.create(
+ new Timestamp(3434L), Account.id(1000), Optional.empty())))
+ .build(),
+ ChangeNotesStateProto.newBuilder()
+ .setMetaId(SHA_BYTES)
+ .setChangeId(ID.get())
+ .setColumns(colsProto)
+ .addAssigneeUpdate(
+ AssigneeStatusUpdateProto.newBuilder()
+ .setDate(1212L)
+ .setUpdatedBy(1000)
+ .setCurrentAssignee(2001)
+ .setHasCurrentAssignee(true))
+ .addAssigneeUpdate(
+ AssigneeStatusUpdateProto.newBuilder()
+ .setDate(3434L)
+ .setUpdatedBy(1000)
+ .setHasCurrentAssignee(false))
+ .build());
+ }
+
+ @Test
public void serializeSubmitRecords() throws Exception {
SubmitRecord sr1 = new SubmitRecord();
sr1.status = SubmitRecord.Status.OK;
@@ -635,19 +640,19 @@ public class ChangeNotesStateTest extends GerritBaseTests {
public void serializeChangeMessages() throws Exception {
ChangeMessage m1 =
new ChangeMessage(
- new ChangeMessage.Key(ID, "uuid1"),
- new Account.Id(1000),
+ ChangeMessage.key(ID, "uuid1"),
+ Account.id(1000),
new Timestamp(1212L),
- new PatchSet.Id(ID, 1));
+ PatchSet.id(ID, 1));
ByteString m1Bytes = toByteString(m1, ChangeMessageProtoConverter.INSTANCE);
assertThat(m1Bytes.size()).isEqualTo(35);
ChangeMessage m2 =
new ChangeMessage(
- new ChangeMessage.Key(ID, "uuid2"),
- new Account.Id(2000),
+ ChangeMessage.key(ID, "uuid2"),
+ Account.id(2000),
new Timestamp(3434L),
- new PatchSet.Id(ID, 2));
+ PatchSet.id(ID, 2));
ByteString m2Bytes = toByteString(m2, ChangeMessageProtoConverter.INSTANCE);
assertThat(m2Bytes.size()).isEqualTo(35);
assertThat(m2Bytes).isNotEqualTo(m1Bytes);
@@ -668,31 +673,30 @@ public class ChangeNotesStateTest extends GerritBaseTests {
Comment c1 =
new Comment(
new Comment.Key("uuid1", "file1", 1),
- new Account.Id(1001),
+ Account.id(1001),
new Timestamp(1212L),
(short) 1,
"message 1",
"serverId",
false);
- c1.setRevId(new RevId("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+ c1.setCommitId(ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
String c1Json = Serializer.GSON.toJson(c1);
Comment c2 =
new Comment(
new Comment.Key("uuid2", "file2", 2),
- new Account.Id(1002),
+ Account.id(1002),
new Timestamp(3434L),
(short) 2,
"message 2",
"serverId",
true);
- c2.setRevId(new RevId("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
+ c2.setCommitId(ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
String c2Json = Serializer.GSON.toJson(c2);
assertRoundTrip(
newBuilder()
- .publishedComments(
- ImmutableListMultimap.of(new RevId(c2.revId), c2, new RevId(c1.revId), c1))
+ .publishedComments(ImmutableListMultimap.of(c2.getCommitId(), c2, c1.getCommitId(), c1))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
@@ -704,14 +708,26 @@ public class ChangeNotesStateTest extends GerritBaseTests {
}
@Test
+ public void serializeUpdateCount() throws Exception {
+ assertRoundTrip(
+ newBuilder().updateCount(234).build(),
+ ChangeNotesStateProto.newBuilder()
+ .setMetaId(SHA_BYTES)
+ .setChangeId(ID.get())
+ .setColumns(colsProto)
+ .setUpdateCount(234)
+ .build());
+ }
+
+ @Test
public void changeNotesStateMethods() throws Exception {
assertThatSerializedClass(ChangeNotesState.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("metaId", ObjectId.class)
.put("changeId", Change.Id.class)
+ .put("serverId", String.class)
.put("columns", ChangeColumns.class)
- .put("pastAssignees", new TypeLiteral<ImmutableSet<Account.Id>>() {}.getType())
.put("hashtags", new TypeLiteral<ImmutableSet<String>>() {}.getType())
.put(
"patchSets",
@@ -728,11 +744,15 @@ public class ChangeNotesStateTest extends GerritBaseTests {
.put(
"reviewerUpdates",
new TypeLiteral<ImmutableList<ReviewerStatusUpdate>>() {}.getType())
+ .put(
+ "assigneeUpdates",
+ new TypeLiteral<ImmutableList<AssigneeStatusUpdate>>() {}.getType())
.put("submitRecords", new TypeLiteral<ImmutableList<SubmitRecord>>() {}.getType())
.put("changeMessages", new TypeLiteral<ImmutableList<ChangeMessage>>() {}.getType())
.put(
"publishedComments",
- new TypeLiteral<ImmutableListMultimap<RevId, Comment>>() {}.getType())
+ new TypeLiteral<ImmutableListMultimap<ObjectId, Comment>>() {}.getType())
+ .put("updateCount", int.class)
.build());
}
@@ -751,7 +771,6 @@ public class ChangeNotesStateTest extends GerritBaseTests {
.put("topic", String.class)
.put("originalSubject", String.class)
.put("submissionId", String.class)
- .put("assignee", Account.Id.class)
.put("status", Change.Status.class)
.put("isPrivate", boolean.class)
.put("workInProgress", boolean.class)
@@ -764,36 +783,37 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void patchSetFields() throws Exception {
assertThatSerializedClass(PatchSet.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("id", PatchSet.Id.class)
- .put("revision", RevId.class)
+ .put("commitId", ObjectId.class)
.put("uploader", Account.Id.class)
.put("createdOn", Timestamp.class)
- .put("groups", String.class)
- .put("pushCertificate", String.class)
- .put("description", String.class)
+ .put("groups", new TypeLiteral<ImmutableList<String>>() {}.getType())
+ .put("pushCertificate", new TypeLiteral<Optional<String>>() {}.getType())
+ .put("description", new TypeLiteral<Optional<String>>() {}.getType())
.build());
}
@Test
public void patchSetApprovalFields() throws Exception {
assertThatSerializedClass(PatchSetApproval.Key.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("patchSetId", PatchSet.Id.class)
.put("accountId", Account.Id.class)
- .put("categoryId", LabelId.class)
+ .put("labelId", LabelId.class)
.build());
assertThatSerializedClass(PatchSetApproval.class)
- .hasFields(
+ .hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("key", PatchSetApproval.Key.class)
.put("value", short.class)
.put("granted", Timestamp.class)
- .put("tag", String.class)
+ .put("tag", new TypeLiteral<Optional<String>>() {}.getType())
.put("realAccountId", Account.Id.class)
.put("postSubmit", boolean.class)
+ .put("toBuilder", PatchSetApproval.Builder.class)
.build());
}
@@ -832,6 +852,19 @@ public class ChangeNotesStateTest extends GerritBaseTests {
}
@Test
+ public void assigneeStatusUpdateMethods() throws Exception {
+ assertThatSerializedClass(AssigneeStatusUpdate.class)
+ .hasAutoValueMethods(
+ ImmutableMap.of(
+ "date",
+ Timestamp.class,
+ "updatedBy",
+ Account.Id.class,
+ "currentAssignee",
+ new TypeLiteral<Optional<Account.Id>>() {}.getType()));
+ }
+
+ @Test
public void submitRecordFields() throws Exception {
assertThatSerializedClass(SubmitRecord.class)
.hasFields(
@@ -861,7 +894,7 @@ public class ChangeNotesStateTest extends GerritBaseTests {
@Test
public void changeMessageFields() throws Exception {
assertThatSerializedClass(ChangeMessage.Key.class)
- .hasFields(ImmutableMap.of("changeId", Change.Id.class, "uuid", String.class));
+ .hasAutoValueMethods(ImmutableMap.of("changeId", Change.Id.class, "uuid", String.class));
assertThatSerializedClass(ChangeMessage.class)
.hasFields(
ImmutableMap.<String, Type>builder()
@@ -905,10 +938,22 @@ public class ChangeNotesStateTest extends GerritBaseTests {
.put("revId", String.class)
.put("serverId", String.class)
.put("unresolved", boolean.class)
- .put("legacyFormat", boolean.class)
.build());
}
+ @Test
+ public void serializeServerId() throws Exception {
+ assertRoundTrip(
+ newBuilder().serverId(DEFAULT_SERVER_ID).build(),
+ ChangeNotesStateProto.newBuilder()
+ .setMetaId(SHA_BYTES)
+ .setChangeId(ID.get())
+ .setServerId(DEFAULT_SERVER_ID)
+ .setHasServerId(true)
+ .setColumns(colsProto.toBuilder())
+ .build());
+ }
+
private static ChangeNotesStateProto toProto(ChangeNotesState state) throws Exception {
return ChangeNotesStateProto.parseFrom(Serializer.INSTANCE.serialize(state));
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index a6c0224465..145e914e7e 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -16,15 +16,17 @@ package com.google.gerrit.server.notedb;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
+import static com.google.gerrit.entities.RefNames.refsDraftComments;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REMOVED;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.junit.Assert.fail;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
@@ -35,18 +37,17 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.ChangeMessage;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.CommentRange;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.CommentRange;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ReviewerSet;
@@ -73,7 +74,6 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
@Inject private DraftCommentNotes.Factory draftNotesFactory;
@Inject private ChangeNoteJson changeNoteJson;
- @Inject private LegacyChangeNoteRead legacyChangeNoteRead;
@Inject private @GerritServerId String serverId;
@@ -101,7 +101,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getCurrentPatchSet().getDescription()).isEqualTo(description);
+ assertThat(notes.getCurrentPatchSet().description()).hasValue(description);
description = "new, now more descriptive!";
update = newUpdate(c, changeOwner);
@@ -109,7 +109,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.commit();
notes = newNotes(c);
- assertThat(notes.getCurrentPatchSet().getDescription()).isEqualTo(description);
+ assertThat(notes.getCurrentPatchSet().description()).hasValue(description);
}
@Test
@@ -119,7 +119,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
RevCommit commit = tr.commit().message("PS2").create();
ChangeUpdate update = newUpdate(c, changeOwner);
update.putComment(
- Status.PUBLISHED,
+ Comment.Status.PUBLISHED,
newComment(
c.currentPatchSetId(),
"a.txt",
@@ -131,14 +131,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"Comment",
(short) 1,
- commit.name(),
+ commit,
false));
update.setTag(tag);
update.commit();
ChangeNotes notes = newNotes(c);
- ImmutableListMultimap<RevId, Comment> comments = notes.getComments();
+ ImmutableListMultimap<ObjectId, Comment> comments = notes.getComments();
assertThat(comments).hasSize(1);
assertThat(comments.entries().asList().get(0).getValue().tag).isEqualTo(tag);
}
@@ -162,7 +162,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals = notes.getApprovals();
assertThat(approvals).hasSize(1);
- assertThat(approvals.entries().asList().get(0).getValue().getTag()).isEqualTo(tag2);
+ assertThat(approvals.entries().asList().get(0).getValue().tag()).hasValue(tag2);
}
@Test
@@ -181,7 +181,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
RevCommit commit = tr.commit().message("PS2").create();
update = newUpdate(c, changeOwner);
update.putComment(
- Status.PUBLISHED,
+ Comment.Status.PUBLISHED,
newComment(
c.currentPatchSetId(),
"a.txt",
@@ -193,7 +193,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"Comment",
(short) 1,
- commit.name(),
+ commit,
false));
update.setChangeMessage("coverage verification");
update.setTag(coverageTag);
@@ -209,10 +209,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals = notes.getApprovals();
assertThat(approvals).hasSize(1);
PatchSetApproval approval = approvals.entries().asList().get(0).getValue();
- assertThat(approval.getTag()).isEqualTo(integrationTag);
- assertThat(approval.getValue()).isEqualTo(-1);
+ assertThat(approval.tag()).hasValue(integrationTag);
+ assertThat(approval.value()).isEqualTo(-1);
- ImmutableListMultimap<RevId, Comment> comments = notes.getComments();
+ ImmutableListMultimap<ObjectId, Comment> comments = notes.getComments();
assertThat(comments).hasSize(1);
assertThat(comments.entries().asList().get(0).getValue().tag).isEqualTo(coverageTag);
@@ -236,17 +236,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
List<PatchSetApproval> psas = notes.getApprovals().get(c.currentPatchSetId());
assertThat(psas).hasSize(2);
- assertThat(psas.get(0).getPatchSetId()).isEqualTo(c.currentPatchSetId());
- assertThat(psas.get(0).getAccountId().get()).isEqualTo(1);
- assertThat(psas.get(0).getLabel()).isEqualTo("Code-Review");
- assertThat(psas.get(0).getValue()).isEqualTo((short) -1);
- assertThat(psas.get(0).getGranted()).isEqualTo(truncate(after(c, 2000)));
+ assertThat(psas.get(0).patchSetId()).isEqualTo(c.currentPatchSetId());
+ assertThat(psas.get(0).accountId().get()).isEqualTo(1);
+ assertThat(psas.get(0).label()).isEqualTo("Code-Review");
+ assertThat(psas.get(0).value()).isEqualTo((short) -1);
+ assertThat(psas.get(0).granted()).isEqualTo(truncate(after(c, 2000)));
- assertThat(psas.get(1).getPatchSetId()).isEqualTo(c.currentPatchSetId());
- assertThat(psas.get(1).getAccountId().get()).isEqualTo(1);
- assertThat(psas.get(1).getLabel()).isEqualTo("Verified");
- assertThat(psas.get(1).getValue()).isEqualTo((short) 1);
- assertThat(psas.get(1).getGranted()).isEqualTo(psas.get(0).getGranted());
+ assertThat(psas.get(1).patchSetId()).isEqualTo(c.currentPatchSetId());
+ assertThat(psas.get(1).accountId().get()).isEqualTo(1);
+ assertThat(psas.get(1).label()).isEqualTo("Verified");
+ assertThat(psas.get(1).value()).isEqualTo((short) 1);
+ assertThat(psas.get(1).granted()).isEqualTo(psas.get(0).granted());
}
@Test
@@ -268,18 +268,18 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(psas).hasSize(2);
PatchSetApproval psa1 = Iterables.getOnlyElement(psas.get(ps1));
- assertThat(psa1.getPatchSetId()).isEqualTo(ps1);
- assertThat(psa1.getAccountId().get()).isEqualTo(1);
- assertThat(psa1.getLabel()).isEqualTo("Code-Review");
- assertThat(psa1.getValue()).isEqualTo((short) -1);
- assertThat(psa1.getGranted()).isEqualTo(truncate(after(c, 2000)));
+ assertThat(psa1.patchSetId()).isEqualTo(ps1);
+ assertThat(psa1.accountId().get()).isEqualTo(1);
+ assertThat(psa1.label()).isEqualTo("Code-Review");
+ assertThat(psa1.value()).isEqualTo((short) -1);
+ assertThat(psa1.granted()).isEqualTo(truncate(after(c, 2000)));
PatchSetApproval psa2 = Iterables.getOnlyElement(psas.get(ps2));
- assertThat(psa2.getPatchSetId()).isEqualTo(ps2);
- assertThat(psa2.getAccountId().get()).isEqualTo(1);
- assertThat(psa2.getLabel()).isEqualTo("Code-Review");
- assertThat(psa2.getValue()).isEqualTo((short) +1);
- assertThat(psa2.getGranted()).isEqualTo(truncate(after(c, 4000)));
+ assertThat(psa2.patchSetId()).isEqualTo(ps2);
+ assertThat(psa2.accountId().get()).isEqualTo(1);
+ assertThat(psa2.label()).isEqualTo("Code-Review");
+ assertThat(psa2.value()).isEqualTo((short) +1);
+ assertThat(psa2.granted()).isEqualTo(truncate(after(c, 4000)));
}
@Test
@@ -292,8 +292,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
PatchSetApproval psa =
Iterables.getOnlyElement(notes.getApprovals().get(c.currentPatchSetId()));
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getValue()).isEqualTo((short) -1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.value()).isEqualTo((short) -1);
update = newUpdate(c, changeOwner);
update.putApproval("Code-Review", (short) 1);
@@ -301,8 +301,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
notes = newNotes(c);
psa = Iterables.getOnlyElement(notes.getApprovals().get(c.currentPatchSetId()));
- assertThat(psa.getLabel()).isEqualTo("Code-Review");
- assertThat(psa.getValue()).isEqualTo((short) 1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.value()).isEqualTo((short) 1);
}
@Test
@@ -321,17 +321,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
List<PatchSetApproval> psas = notes.getApprovals().get(c.currentPatchSetId());
assertThat(psas).hasSize(2);
- assertThat(psas.get(0).getPatchSetId()).isEqualTo(c.currentPatchSetId());
- assertThat(psas.get(0).getAccountId().get()).isEqualTo(1);
- assertThat(psas.get(0).getLabel()).isEqualTo("Code-Review");
- assertThat(psas.get(0).getValue()).isEqualTo((short) -1);
- assertThat(psas.get(0).getGranted()).isEqualTo(truncate(after(c, 2000)));
+ assertThat(psas.get(0).patchSetId()).isEqualTo(c.currentPatchSetId());
+ assertThat(psas.get(0).accountId().get()).isEqualTo(1);
+ assertThat(psas.get(0).label()).isEqualTo("Code-Review");
+ assertThat(psas.get(0).value()).isEqualTo((short) -1);
+ assertThat(psas.get(0).granted()).isEqualTo(truncate(after(c, 2000)));
- assertThat(psas.get(1).getPatchSetId()).isEqualTo(c.currentPatchSetId());
- assertThat(psas.get(1).getAccountId().get()).isEqualTo(2);
- assertThat(psas.get(1).getLabel()).isEqualTo("Code-Review");
- assertThat(psas.get(1).getValue()).isEqualTo((short) 1);
- assertThat(psas.get(1).getGranted()).isEqualTo(truncate(after(c, 3000)));
+ assertThat(psas.get(1).patchSetId()).isEqualTo(c.currentPatchSetId());
+ assertThat(psas.get(1).accountId().get()).isEqualTo(2);
+ assertThat(psas.get(1).label()).isEqualTo("Code-Review");
+ assertThat(psas.get(1).value()).isEqualTo((short) 1);
+ assertThat(psas.get(1).granted()).isEqualTo(truncate(after(c, 3000)));
}
@Test
@@ -344,9 +344,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
PatchSetApproval psa =
Iterables.getOnlyElement(notes.getApprovals().get(c.currentPatchSetId()));
- assertThat(psa.getAccountId().get()).isEqualTo(1);
- assertThat(psa.getLabel()).isEqualTo("Not-For-Long");
- assertThat(psa.getValue()).isEqualTo((short) 1);
+ assertThat(psa.accountId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Not-For-Long");
+ assertThat(psa.value()).isEqualTo((short) 1);
update = newUpdate(c, changeOwner);
update.removeApproval("Not-For-Long");
@@ -356,8 +356,12 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(notes.getApprovals())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- psa.getPatchSetId(),
- new PatchSetApproval(psa.getKey(), (short) 0, update.getWhen())));
+ psa.patchSetId(),
+ PatchSetApproval.builder()
+ .key(psa.key())
+ .value(0)
+ .granted(update.getWhen())
+ .build()));
}
@Test
@@ -370,9 +374,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
PatchSetApproval psa =
Iterables.getOnlyElement(notes.getApprovals().get(c.currentPatchSetId()));
- assertThat(psa.getAccountId()).isEqualTo(otherUserId);
- assertThat(psa.getLabel()).isEqualTo("Not-For-Long");
- assertThat(psa.getValue()).isEqualTo((short) 1);
+ assertThat(psa.accountId()).isEqualTo(otherUserId);
+ assertThat(psa.label()).isEqualTo("Not-For-Long");
+ assertThat(psa.value()).isEqualTo((short) 1);
update = newUpdate(c, changeOwner);
update.removeApprovalFor(otherUserId, "Not-For-Long");
@@ -382,8 +386,12 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(notes.getApprovals())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- psa.getPatchSetId(),
- new PatchSetApproval(psa.getKey(), (short) 0, update.getWhen())));
+ psa.patchSetId(),
+ PatchSetApproval.builder()
+ .key(psa.key())
+ .value(0)
+ .granted(update.getWhen())
+ .build()));
// Add back approval on same label.
update = newUpdate(c, otherUser);
@@ -392,9 +400,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
notes = newNotes(c);
psa = Iterables.getOnlyElement(notes.getApprovals().get(c.currentPatchSetId()));
- assertThat(psa.getAccountId()).isEqualTo(otherUserId);
- assertThat(psa.getLabel()).isEqualTo("Not-For-Long");
- assertThat(psa.getValue()).isEqualTo((short) 2);
+ assertThat(psa.accountId()).isEqualTo(otherUserId);
+ assertThat(psa.label()).isEqualTo("Not-For-Long");
+ assertThat(psa.value()).isEqualTo((short) 2);
}
@Test
@@ -408,17 +416,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
ImmutableList<PatchSetApproval> approvals =
notes.getApprovals().get(c.currentPatchSetId()).stream()
- .sorted(comparing(a -> a.getAccountId().get()))
+ .sorted(comparing(a -> a.accountId().get()))
.collect(toImmutableList());
assertThat(approvals).hasSize(2);
- assertThat(approvals.get(0).getAccountId()).isEqualTo(changeOwner.getAccountId());
- assertThat(approvals.get(0).getLabel()).isEqualTo("Code-Review");
- assertThat(approvals.get(0).getValue()).isEqualTo((short) 1);
+ assertThat(approvals.get(0).accountId()).isEqualTo(changeOwner.getAccountId());
+ assertThat(approvals.get(0).label()).isEqualTo("Code-Review");
+ assertThat(approvals.get(0).value()).isEqualTo((short) 1);
- assertThat(approvals.get(1).getAccountId()).isEqualTo(otherUser.getAccountId());
- assertThat(approvals.get(1).getLabel()).isEqualTo("Code-Review");
- assertThat(approvals.get(1).getValue()).isEqualTo((short) -1);
+ assertThat(approvals.get(1).accountId()).isEqualTo(otherUser.getAccountId());
+ assertThat(approvals.get(1).label()).isEqualTo("Code-Review");
+ assertThat(approvals.get(1).value()).isEqualTo((short) -1);
}
@Test
@@ -448,12 +456,12 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
List<PatchSetApproval> approvals = Lists.newArrayList(notes.getApprovals().values());
assertThat(approvals).hasSize(2);
- assertThat(approvals.get(0).getLabel()).isEqualTo("Verified");
- assertThat(approvals.get(0).getValue()).isEqualTo((short) 1);
- assertThat(approvals.get(0).isPostSubmit()).isFalse();
- assertThat(approvals.get(1).getLabel()).isEqualTo("Code-Review");
- assertThat(approvals.get(1).getValue()).isEqualTo((short) 2);
- assertThat(approvals.get(1).isPostSubmit()).isTrue();
+ assertThat(approvals.get(0).label()).isEqualTo("Verified");
+ assertThat(approvals.get(0).value()).isEqualTo((short) 1);
+ assertThat(approvals.get(0).postSubmit()).isFalse();
+ assertThat(approvals.get(1).label()).isEqualTo("Code-Review");
+ assertThat(approvals.get(1).value()).isEqualTo((short) 2);
+ assertThat(approvals.get(1).postSubmit()).isTrue();
}
@Test
@@ -488,26 +496,26 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
List<PatchSetApproval> approvals = Lists.newArrayList(notes.getApprovals().values());
assertThat(approvals).hasSize(3);
- assertThat(approvals.get(0).getAccountId()).isEqualTo(ownerId);
- assertThat(approvals.get(0).getLabel()).isEqualTo("Verified");
- assertThat(approvals.get(0).getValue()).isEqualTo(1);
- assertThat(approvals.get(0).isPostSubmit()).isFalse();
- assertThat(approvals.get(1).getAccountId()).isEqualTo(ownerId);
- assertThat(approvals.get(1).getLabel()).isEqualTo("Code-Review");
- assertThat(approvals.get(1).getValue()).isEqualTo(2);
- assertThat(approvals.get(1).isPostSubmit()).isFalse(); // During submit.
- assertThat(approvals.get(2).getAccountId()).isEqualTo(otherId);
- assertThat(approvals.get(2).getLabel()).isEqualTo("Other-Label");
- assertThat(approvals.get(2).getValue()).isEqualTo(2);
- assertThat(approvals.get(2).isPostSubmit()).isTrue();
+ assertThat(approvals.get(0).accountId()).isEqualTo(ownerId);
+ assertThat(approvals.get(0).label()).isEqualTo("Verified");
+ assertThat(approvals.get(0).value()).isEqualTo(1);
+ assertThat(approvals.get(0).postSubmit()).isFalse();
+ assertThat(approvals.get(1).accountId()).isEqualTo(ownerId);
+ assertThat(approvals.get(1).label()).isEqualTo("Code-Review");
+ assertThat(approvals.get(1).value()).isEqualTo(2);
+ assertThat(approvals.get(1).postSubmit()).isFalse(); // During submit.
+ assertThat(approvals.get(2).accountId()).isEqualTo(otherId);
+ assertThat(approvals.get(2).label()).isEqualTo("Other-Label");
+ assertThat(approvals.get(2).value()).isEqualTo(2);
+ assertThat(approvals.get(2).postSubmit()).isTrue();
}
@Test
public void multipleReviewers() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().id(), REVIEWER);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -516,8 +524,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
.isEqualTo(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
- .put(REVIEWER, new Account.Id(1), ts)
- .put(REVIEWER, new Account.Id(2), ts)
+ .put(REVIEWER, Account.id(1), ts)
+ .put(REVIEWER, Account.id(2), ts)
.build()));
}
@@ -525,8 +533,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void reviewerTypes() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.putReviewer(otherUser.getAccount().getId(), CC);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().id(), CC);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -535,8 +543,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
.isEqualTo(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Timestamp>builder()
- .put(REVIEWER, new Account.Id(1), ts)
- .put(CC, new Account.Id(2), ts)
+ .put(REVIEWER, Account.id(1), ts)
+ .put(CC, Account.id(2), ts)
.build()));
}
@@ -544,29 +552,29 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void oneReviewerMultipleTypes() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().id(), REVIEWER);
update.commit();
ChangeNotes notes = newNotes(c);
Timestamp ts = new Timestamp(update.getWhen().getTime());
assertThat(notes.getReviewers())
- .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(REVIEWER, new Account.Id(2), ts)));
+ .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(REVIEWER, Account.id(2), ts)));
update = newUpdate(c, otherUser);
- update.putReviewer(otherUser.getAccount().getId(), CC);
+ update.putReviewer(otherUser.getAccount().id(), CC);
update.commit();
notes = newNotes(c);
ts = new Timestamp(update.getWhen().getTime());
assertThat(notes.getReviewers())
- .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(CC, new Account.Id(2), ts)));
+ .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(CC, Account.id(2), ts)));
}
@Test
public void removeReviewer() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().id(), REVIEWER);
update.commit();
update = newUpdate(c, changeOwner);
@@ -580,17 +588,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
List<PatchSetApproval> psas = notes.getApprovals().get(c.currentPatchSetId());
assertThat(psas).hasSize(2);
- assertThat(psas.get(0).getAccountId()).isEqualTo(changeOwner.getAccount().getId());
- assertThat(psas.get(1).getAccountId()).isEqualTo(otherUser.getAccount().getId());
+ assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id());
+ assertThat(psas.get(1).accountId()).isEqualTo(otherUser.getAccount().id());
update = newUpdate(c, changeOwner);
- update.removeReviewer(otherUser.getAccount().getId());
+ update.removeReviewer(otherUser.getAccount().id());
update.commit();
notes = newNotes(c);
psas = notes.getApprovals().get(c.currentPatchSetId());
assertThat(psas).hasSize(1);
- assertThat(psas.get(0).getAccountId()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id());
}
@Test
@@ -736,6 +744,35 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
}
@Test
+ public void assigneeStatusUpdateChangeNotes() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setAssignee(otherUserId);
+ update.commit();
+
+ update = newUpdate(c, changeOwner);
+ update.removeAssignee();
+ update.commit();
+
+ update = newUpdate(c, changeOwner);
+ update.setAssignee(changeOwner.getAccountId());
+ update.commit();
+
+ update = newUpdate(c, changeOwner);
+ update.setAssignee(otherUserId);
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ ImmutableList<AssigneeStatusUpdate> statusUpdates = notes.getAssigneeUpdates();
+ assertThat(statusUpdates).hasSize(4);
+ assertThat(statusUpdates.get(3).updatedBy()).isEqualTo(otherUserId);
+ assertThat(statusUpdates.get(3).currentAssignee()).hasValue(otherUserId);
+ assertThat(statusUpdates.get(2).currentAssignee()).isEmpty();
+ assertThat(statusUpdates.get(1).currentAssignee()).hasValue(changeOwner.getAccountId());
+ assertThat(statusUpdates.get(0).currentAssignee()).hasValue(otherUserId);
+ }
+
+ @Test
public void hashtagCommit() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -819,14 +856,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
// Trying to set another Change-Id fails
String otherChangeId = "I577fb248e474018276351785930358ec0450e9f7";
- update = newUpdate(c, changeOwner);
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(
- "The Change-Id was already set to "
- + c.getKey()
- + ", so we cannot set this Change-Id: "
- + otherChangeId);
- update.setChangeId(otherChangeId);
+ ChangeUpdate failingUpdate = newUpdate(c, changeOwner);
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class, () -> failingUpdate.setChangeId(otherChangeId));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(
+ "The Change-Id was already set to "
+ + c.getKey()
+ + ", so we cannot set this Change-Id: "
+ + otherChangeId);
}
@Test
@@ -834,7 +874,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeNotes notes = newNotes(c);
- Branch.NameKey expectedBranch = new Branch.NameKey(project, "refs/heads/master");
+ BranchNameKey expectedBranch = BranchNameKey.create(project, "refs/heads/master");
assertThat(notes.getChange().getDest()).isEqualTo(expectedBranch);
// An update doesn't affect the branch
@@ -849,7 +889,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.setBranch(otherBranch);
update.commit();
assertThat(newNotes(c).getChange().getDest())
- .isEqualTo(new Branch.NameKey(project, otherBranch));
+ .isEqualTo(BranchNameKey.create(project, otherBranch));
}
@Test
@@ -956,7 +996,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
c.setCurrentPatchSet(c.currentPatchSetId(), " " + trimmedSubj, c.getOriginalSubject());
ChangeUpdate update = newUpdateForNewChange(c, changeOwner);
update.setChangeId(c.getKey().get());
- update.setBranch(c.getDest().get());
+ update.setBranch(c.getDest().branch());
update.commit();
ChangeNotes notes = newNotes(c);
@@ -968,7 +1008,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
c.setCurrentPatchSet(c.currentPatchSetId(), tabSubj, c.getOriginalSubject());
update = newUpdateForNewChange(c, changeOwner);
update.setChangeId(c.getKey().get());
- update.setBranch(c.getDest().get());
+ update.setBranch(c.getDest().branch());
update.commit();
notes = newNotes(c);
@@ -977,7 +1017,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
@Test
public void commitChangeNotesUnique() throws Exception {
- // PatchSetId -> RevId must be a one to one mapping
+ // PatchSetId -> ObjectId must be a one to one mapping
Change c = newChange();
ChangeNotes notes = newNotes(c);
@@ -990,19 +1030,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.setCommit(rw, commit);
update.commit();
- try {
- newNotes(c);
- fail("Expected IOException");
- } catch (StorageException e) {
- assertCause(
- e,
- ConfigInvalidException.class,
- "Multiple revisions parsed for patch set 1:"
- + " RevId{"
- + commit.name()
- + "} and "
- + ps.getRevision().get());
- }
+ StorageException e = assertThrows(StorageException.class, () -> newNotes(c));
+ assertCause(
+ e,
+ ConfigInvalidException.class,
+ "Multiple revisions parsed for patch set 1:"
+ + " "
+ + commit.name()
+ + " and "
+ + ps.commitId().name());
}
@Test
@@ -1012,32 +1048,32 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
// ps1 created by newChange()
ChangeNotes notes = newNotes(c);
PatchSet ps1 = notes.getCurrentPatchSet();
- assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps1.getId());
+ assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps1.id());
assertThat(notes.getChange().getSubject()).isEqualTo("Change subject");
assertThat(notes.getChange().getOriginalSubject()).isEqualTo("Change subject");
- assertThat(ps1.getId()).isEqualTo(new PatchSet.Id(c.getId(), 1));
- assertThat(ps1.getUploader()).isEqualTo(changeOwner.getAccountId());
+ assertThat(ps1.id()).isEqualTo(PatchSet.id(c.getId(), 1));
+ assertThat(ps1.uploader()).isEqualTo(changeOwner.getAccountId());
// ps2 by other user
RevCommit commit = incrementPatchSet(c, otherUser);
notes = newNotes(c);
PatchSet ps2 = notes.getCurrentPatchSet();
- assertThat(ps2.getId()).isEqualTo(new PatchSet.Id(c.getId(), 2));
+ assertThat(ps2.id()).isEqualTo(PatchSet.id(c.getId(), 2));
assertThat(notes.getChange().getSubject()).isEqualTo("PS2");
assertThat(notes.getChange().getOriginalSubject()).isEqualTo("Change subject");
- assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.getId());
- assertThat(ps2.getRevision().get()).isNotEqualTo(ps1.getRevision());
- assertThat(ps2.getRevision().get()).isEqualTo(commit.name());
- assertThat(ps2.getUploader()).isEqualTo(otherUser.getAccountId());
- assertThat(ps2.getCreatedOn()).isEqualTo(notes.getChange().getLastUpdatedOn());
+ assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.id());
+ assertThat(ps2.commitId()).isNotEqualTo(ps1.commitId());
+ assertThat(ps2.commitId()).isEqualTo(commit);
+ assertThat(ps2.uploader()).isEqualTo(otherUser.getAccountId());
+ assertThat(ps2.createdOn()).isEqualTo(notes.getChange().getLastUpdatedOn());
// comment on ps1, current patch set is still ps2
ChangeUpdate update = newUpdate(c, changeOwner);
- update.setPatchSetId(ps1.getId());
+ update.setPatchSetId(ps1.id());
update.setChangeMessage("Comment on old patch set.");
update.commit();
notes = newNotes(c);
- assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.getId());
+ assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.id());
}
@Test
@@ -1053,7 +1089,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.putApproval("Code-Review", (short) 1);
update.setChangeMessage("This is a message");
update.putComment(
- Status.PUBLISHED,
+ Comment.Status.PUBLISHED,
newComment(
c.currentPatchSetId(),
"a.txt",
@@ -1065,7 +1101,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"Comment",
(short) 1,
- commit.name(),
+ commit,
false));
update.commit();
@@ -1098,14 +1134,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
PatchSet.Id psId1 = c.currentPatchSetId();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getPatchSets().get(psId1).getGroups()).isEmpty();
+ assertThat(notes.getPatchSets().get(psId1).groups()).isEmpty();
// ps1
ChangeUpdate update = newUpdate(c, changeOwner);
update.setGroups(ImmutableList.of("a", "b"));
update.commit();
notes = newNotes(c);
- assertThat(notes.getPatchSets().get(psId1).getGroups()).containsExactly("a", "b").inOrder();
+ assertThat(notes.getPatchSets().get(psId1).groups()).containsExactly("a", "b").inOrder();
incrementCurrentPatchSetFieldOnly(c);
PatchSet.Id psId2 = c.currentPatchSetId();
@@ -1114,8 +1150,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.setGroups(ImmutableList.of("d"));
update.commit();
notes = newNotes(c);
- assertThat(notes.getPatchSets().get(psId2).getGroups()).containsExactly("d");
- assertThat(notes.getPatchSets().get(psId1).getGroups()).containsExactly("a", "b").inOrder();
+ assertThat(notes.getPatchSets().get(psId2).groups()).containsExactly("d");
+ assertThat(notes.getPatchSets().get(psId1).groups()).containsExactly("a", "b").inOrder();
}
@Test
@@ -1144,8 +1180,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
readNote(notes, commit);
Map<PatchSet.Id, PatchSet> patchSets = notes.getPatchSets();
- assertThat(patchSets.get(psId1).getPushCertificate()).isNull();
- assertThat(patchSets.get(psId2).getPushCertificate()).isEqualTo(pushCert);
+ assertThat(patchSets.get(psId1).pushCertificate()).isEmpty();
+ assertThat(patchSets.get(psId2).pushCertificate()).hasValue(pushCert);
assertThat(notes.getComments()).isEmpty();
// comment on ps2
@@ -1153,7 +1189,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update.setPatchSetId(psId2);
Timestamp ts = TimeUtil.nowTs();
update.putComment(
- Status.PUBLISHED,
+ Comment.Status.PUBLISHED,
newComment(
psId2,
"a.txt",
@@ -1165,15 +1201,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ts,
"Comment",
(short) 1,
- commit.name(),
+ commit,
false));
update.commit();
notes = newNotes(c);
patchSets = notes.getPatchSets();
- assertThat(patchSets.get(psId1).getPushCertificate()).isNull();
- assertThat(patchSets.get(psId2).getPushCertificate()).isEqualTo(pushCert);
+ assertThat(patchSets.get(psId1).pushCertificate()).isEmpty();
+ assertThat(patchSets.get(psId2).pushCertificate()).hasValue(pushCert);
assertThat(notes.getComments()).isNotEmpty();
}
@@ -1203,13 +1239,13 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
List<PatchSetApproval> psas = notes.getApprovals().get(c.currentPatchSetId());
assertThat(psas).hasSize(2);
- assertThat(psas.get(0).getAccountId()).isEqualTo(changeOwner.getAccount().getId());
- assertThat(psas.get(0).getLabel()).isEqualTo("Verified");
- assertThat(psas.get(0).getValue()).isEqualTo((short) 1);
+ assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id());
+ assertThat(psas.get(0).label()).isEqualTo("Verified");
+ assertThat(psas.get(0).value()).isEqualTo((short) 1);
- assertThat(psas.get(1).getAccountId()).isEqualTo(otherUser.getAccount().getId());
- assertThat(psas.get(1).getLabel()).isEqualTo("Code-Review");
- assertThat(psas.get(1).getValue()).isEqualTo((short) 2);
+ assertThat(psas.get(1).accountId()).isEqualTo(otherUser.getAccount().id());
+ assertThat(psas.get(1).label()).isEqualTo("Code-Review");
+ assertThat(psas.get(1).value()).isEqualTo((short) 2);
}
@Test
@@ -1235,10 +1271,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time1,
message1,
(short) 0,
- "abcd1234abcd1234abcd1234abcd1234abcd1234",
+ ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"),
false);
update1.setPatchSetId(psId);
- update1.putComment(Status.PUBLISHED, comment1);
+ update1.putComment(Comment.Status.PUBLISHED, comment1);
updateManager.add(update1);
ChangeUpdate update2 = newUpdate(c, otherUser);
@@ -1260,12 +1296,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
ChangeNotesParser notesWithComments =
new ChangeNotesParser(
- c.getId(),
- commitWithComments.copy(),
- rw,
- changeNoteJson,
- legacyChangeNoteRead,
- args.metrics);
+ c.getId(), commitWithComments.copy(), rw, changeNoteJson, args.metrics);
ChangeNotesState state = notesWithComments.parseAll();
assertThat(state.approvals()).isEmpty();
assertThat(state.publishedComments()).hasSize(1);
@@ -1274,12 +1305,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
ChangeNotesParser notesWithApprovals =
new ChangeNotesParser(
- c.getId(),
- commitWithApprovals.copy(),
- rw,
- changeNoteJson,
- legacyChangeNoteRead,
- args.metrics);
+ c.getId(), commitWithApprovals.copy(), rw, changeNoteJson, args.metrics);
ChangeNotesState state = notesWithApprovals.parseAll();
assertThat(state.approvals()).hasSize(1);
@@ -1317,25 +1343,25 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
PatchSetApproval approval1 =
newNotes(c1).getApprovals().get(c1.currentPatchSetId()).iterator().next();
- assertThat(approval1.getLabel()).isEqualTo("Verified");
+ assertThat(approval1.label()).isEqualTo("Verified");
PatchSetApproval approval2 =
newNotes(c2).getApprovals().get(c2.currentPatchSetId()).iterator().next();
- assertThat(approval2.getLabel()).isEqualTo("Code-Review");
+ assertThat(approval2.label()).isEqualTo("Code-Review");
}
@Test
public void changeMessageOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.setChangeMessage("Just a little code change.\n");
update.commit();
ChangeNotes notes = newNotes(c);
ChangeMessage cm = Iterables.getOnlyElement(notes.getChangeMessages());
assertThat(cm.getMessage()).isEqualTo("Just a little code change.\n");
- assertThat(cm.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm.getAuthor()).isEqualTo(changeOwner.getAccount().id());
assertThat(cm.getPatchSetId()).isEqualTo(c.currentPatchSetId());
}
@@ -1343,7 +1369,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void noChangeMessage() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1360,7 +1386,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeNotes notes = newNotes(c);
ChangeMessage cm1 = Iterables.getOnlyElement(notes.getChangeMessages());
assertThat(cm1.getMessage()).isEqualTo("Testing trailing double newline\n\n");
- assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id());
}
@Test
@@ -1379,14 +1405,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
+ "Testing paragraph 2\n"
+ "\n"
+ "Testing paragraph 3");
- assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id());
}
@Test
public void changeMessagesMultiplePatchSets() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.setChangeMessage("This is the change message for the first PS.");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
@@ -1404,12 +1430,12 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeMessage cm1 = notes.getChangeMessages().get(0);
assertThat(cm1.getPatchSetId()).isEqualTo(ps1);
assertThat(cm1.getMessage()).isEqualTo("This is the change message for the first PS.");
- assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id());
ChangeMessage cm2 = notes.getChangeMessages().get(1);
assertThat(cm2.getPatchSetId()).isEqualTo(ps2);
assertThat(cm2.getMessage()).isEqualTo("This is the change message for the second PS.");
- assertThat(cm2.getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm2.getAuthor()).isEqualTo(changeOwner.getAccount().id());
assertThat(cm2.getPatchSetId()).isEqualTo(ps2);
}
@@ -1417,14 +1443,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void changeMessageMultipleInOnePatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.setChangeMessage("First change message.\n");
update.commit();
PatchSet.Id ps1 = c.currentPatchSetId();
update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.setChangeMessage("Second change message.\n");
update.commit();
@@ -1433,10 +1459,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
List<ChangeMessage> cm = notes.getChangeMessages();
assertThat(cm).hasSize(2);
assertThat(cm.get(0).getMessage()).isEqualTo("First change message.\n");
- assertThat(cm.get(0).getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm.get(0).getAuthor()).isEqualTo(changeOwner.getAccount().id());
assertThat(cm.get(0).getPatchSetId()).isEqualTo(ps1);
assertThat(cm.get(1).getMessage()).isEqualTo("Second change message.\n");
- assertThat(cm.get(1).getAuthor()).isEqualTo(changeOwner.getAccount().getId());
+ assertThat(cm.get(1).getAuthor()).isEqualTo(changeOwner.getAccount().id());
assertThat(cm.get(1).getPatchSetId()).isEqualTo(ps1);
}
@@ -1445,7 +1471,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
PatchSet.Id psId = c.currentPatchSetId();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
Comment comment =
newComment(
@@ -1459,14 +1485,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"message",
(short) 1,
- revId.get(),
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(revId, comment));
+ assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment));
}
@Test
@@ -1474,7 +1500,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
PatchSet.Id psId = c.currentPatchSetId();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 0, 2, 0);
Comment comment =
@@ -1489,14 +1515,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"message",
(short) 1,
- revId.get(),
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(revId, comment));
+ assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment));
}
@Test
@@ -1504,7 +1530,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
PatchSet.Id psId = c.currentPatchSetId();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(0, 0, 0, 0);
Comment comment =
@@ -1519,14 +1545,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"message",
(short) 1,
- revId.get(),
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(revId, comment));
+ assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment));
}
@Test
@@ -1534,7 +1560,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
PatchSet.Id psId = c.currentPatchSetId();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 2, 3, 4);
Comment comment =
@@ -1549,18 +1575,18 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
TimeUtil.nowTs(),
"message",
(short) 1,
- revId.get(),
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(revId, comment));
+ assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment));
}
@Test
- public void patchLineCommentNotesFormatMultiplePatchSetsSameRevId() throws Exception {
+ public void patchLineCommentNotesFormatMultiplePatchSetsSameCommitId() throws Exception {
Change c = newChange();
PatchSet.Id psId1 = c.currentPatchSetId();
incrementPatchSet(c);
@@ -1574,7 +1600,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
CommentRange range1 = new CommentRange(1, 1, 2, 1);
CommentRange range2 = new CommentRange(2, 1, 3, 1);
Timestamp time = TimeUtil.nowTs();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
Comment comment1 =
newComment(
@@ -1588,7 +1614,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time,
message1,
(short) 0,
- revId.get(),
+ commitId,
false);
Comment comment2 =
newComment(
@@ -1602,7 +1628,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time,
message2,
(short) 0,
- revId.get(),
+ commitId,
false);
Comment comment3 =
newComment(
@@ -1616,23 +1642,23 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time,
message3,
(short) 0,
- revId.get(),
+ commitId,
false);
ChangeUpdate update = newUpdate(c, otherUser);
update.setPatchSetId(psId2);
- update.putComment(Status.PUBLISHED, comment3);
- update.putComment(Status.PUBLISHED, comment2);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment3);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getComments())
.isEqualTo(
ImmutableListMultimap.of(
- revId, comment1,
- revId, comment2,
- revId, comment3));
+ commitId, comment1,
+ commitId, comment2,
+ commitId, comment3));
}
@Test
@@ -1645,7 +1671,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
CommentRange range = new CommentRange(1, 1, 2, 1);
Timestamp time = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
- RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
Comment comment =
newComment(
@@ -1659,25 +1685,25 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time,
message,
(short) 1,
- revId.get(),
+ commitId,
false);
comment.setRealAuthor(changeOwner.getAccountId());
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(revId, comment));
+ assertThat(notes.getComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment));
}
@Test
public void patchLineCommentNotesFormatWeirdUser() throws Exception {
- Account account = new Account(new Account.Id(3), TimeUtil.nowTs());
+ Account.Builder account = Account.builder(Account.id(3), TimeUtil.nowTs());
account.setFullName("Weird\n\u0002<User>\n");
account.setPreferredEmail(" we\r\nird@ex>ample<.com");
- accountCache.put(account);
- IdentifiedUser user = userFactory.create(account.getId());
+ accountCache.put(account.build());
+ IdentifiedUser user = userFactory.create(Account.id(3));
Change c = newChange();
ChangeUpdate update = newUpdate(c, user);
@@ -1698,16 +1724,16 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
time,
"comment",
(short) 1,
- "abcd1234abcd1234abcd1234abcd1234abcd1234",
+ ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"),
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getComments())
- .isEqualTo(ImmutableListMultimap.of(new RevId(comment.revId), comment));
+ .isEqualTo(ImmutableListMultimap.of(comment.getCommitId(), comment));
}
@Test
@@ -1716,8 +1742,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
ChangeUpdate update = newUpdate(c, otherUser);
String uuid1 = "uuid1";
String uuid2 = "uuid2";
- String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- String rev2 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
String messageForBase = "comment for base";
String messageForPS = "comment for ps";
CommentRange range = new CommentRange(1, 1, 2, 1);
@@ -1736,10 +1762,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
messageForBase,
(short) 0,
- rev1,
+ commitId1,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, commentForBase);
+ update.putComment(Comment.Status.PUBLISHED, commentForBase);
update.commit();
update = newUpdate(c, otherUser);
@@ -1755,17 +1781,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
messageForPS,
(short) 1,
- rev2,
+ commitId2,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, commentForPS);
+ update.putComment(Comment.Status.PUBLISHED, commentForPS);
update.commit();
assertThat(newNotes(c).getComments())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev1), commentForBase,
- new RevId(rev2), commentForPS));
+ commitId1, commentForBase,
+ commitId2, commentForPS));
}
@Test
@@ -1773,7 +1799,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename = "filename";
@@ -1794,10 +1820,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
timeForComment1,
"comment 1",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1813,17 +1839,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
timeForComment2,
"comment 2",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
assertThat(newNotes(c).getComments())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev), comment1,
- new RevId(rev), comment2))
+ commitId, comment1,
+ commitId, comment2))
.inOrder();
}
@@ -1831,7 +1857,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void patchLineCommentMultipleOnePatchsetMultipleFiles() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename1 = "filename1";
@@ -1852,10 +1878,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment 1",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1871,17 +1897,17 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment 2",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
assertThat(newNotes(c).getComments())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev), comment1,
- new RevId(rev), comment2))
+ commitId, comment1,
+ commitId, comment2))
.inOrder();
}
@@ -1889,8 +1915,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void patchLineCommentMultiplePatchsets() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- String rev2 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -1910,10 +1936,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev1,
+ commitId1,
false);
update.setPatchSetId(ps1);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
incrementPatchSet(c);
@@ -1933,24 +1959,24 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps2",
side,
- rev2,
+ commitId2,
false);
update.setPatchSetId(ps2);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
assertThat(newNotes(c).getComments())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev1), comment1,
- new RevId(rev2), comment2));
+ commitId1, comment1,
+ commitId2, comment2));
}
@Test
public void patchLineCommentSingleDraftToPublished() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -1970,26 +1996,26 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(ps1);
- update.putComment(Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment1);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId))
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment1));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment1));
assertThat(notes.getComments()).isEmpty();
update = newUpdate(c, otherUser);
update.setPatchSetId(ps1);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId)).isEmpty();
assertThat(notes.getComments())
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment1));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment1));
}
@Test
@@ -1997,7 +2023,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
- String rev = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range1 = new CommentRange(1, 1, 2, 2);
CommentRange range2 = new CommentRange(2, 2, 3, 3);
String filename = "filename1";
@@ -2020,7 +2046,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev,
+ commitId,
false);
Comment comment2 =
newComment(
@@ -2034,32 +2060,32 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"other on ps1",
side,
- rev,
+ commitId,
false);
- update.putComment(Status.DRAFT, comment1);
- update.putComment(Status.DRAFT, comment2);
+ update.putComment(Comment.Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment2);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId))
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev), comment1,
- new RevId(rev), comment2))
+ commitId, comment1,
+ commitId, comment2))
.inOrder();
assertThat(notes.getComments()).isEmpty();
// Publish first draft.
update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId))
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment2));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment2));
assertThat(notes.getComments())
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment1));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment1));
}
@Test
@@ -2067,8 +2093,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
- String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- String rev2 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range1 = new CommentRange(1, 1, 2, 2);
CommentRange range2 = new CommentRange(2, 2, 3, 3);
String filename = "filename1";
@@ -2090,7 +2116,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on base",
(short) 0,
- rev1,
+ commitId1,
false);
Comment psComment =
newComment(
@@ -2104,27 +2130,27 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps",
(short) 1,
- rev2,
+ commitId2,
false);
- update.putComment(Status.DRAFT, baseComment);
- update.putComment(Status.DRAFT, psComment);
+ update.putComment(Comment.Status.DRAFT, baseComment);
+ update.putComment(Comment.Status.DRAFT, psComment);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId))
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev1), baseComment,
- new RevId(rev2), psComment));
+ commitId1, baseComment,
+ commitId2, psComment));
assertThat(notes.getComments()).isEmpty();
// Publish both comments.
update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, baseComment);
- update.putComment(Status.PUBLISHED, psComment);
+ update.putComment(Comment.Status.PUBLISHED, baseComment);
+ update.putComment(Comment.Status.PUBLISHED, psComment);
update.commit();
notes = newNotes(c);
@@ -2132,16 +2158,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(notes.getComments())
.containsExactlyEntriesIn(
ImmutableListMultimap.of(
- new RevId(rev1), baseComment,
- new RevId(rev2), psComment));
+ commitId1, baseComment,
+ commitId2, psComment));
}
@Test
public void patchLineCommentsDeleteAllDrafts() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- ObjectId objId = ObjectId.fromString(rev);
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id psId = c.currentPatchSetId();
String filename = "filename";
@@ -2161,15 +2186,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.DRAFT, comment);
+ update.putComment(Comment.Status.DRAFT, comment);
update.commit();
ChangeNotes notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId)).hasSize(1);
- assertThat(notes.getDraftCommentNotes().getNoteMap().contains(objId)).isTrue();
+ assertThat(notes.getDraftCommentNotes().getNoteMap().contains(commitId)).isTrue();
update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
@@ -2185,10 +2210,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void patchLineCommentsDeleteAllDraftsForOneRevision() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- String rev2 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
- ObjectId objId1 = ObjectId.fromString(rev1);
- ObjectId objId2 = ObjectId.fromString(rev2);
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -2208,10 +2231,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev1,
+ commitId1,
false);
update.setPatchSetId(ps1);
- update.putComment(Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment1);
update.commit();
incrementPatchSet(c);
@@ -2231,10 +2254,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps2",
side,
- rev2,
+ commitId2,
false);
update.setPatchSetId(ps2);
- update.putComment(Status.DRAFT, comment2);
+ update.putComment(Comment.Status.DRAFT, comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -2248,15 +2271,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
notes = newNotes(c);
assertThat(notes.getDraftComments(otherUserId)).hasSize(1);
NoteMap noteMap = notes.getDraftCommentNotes().getNoteMap();
- assertThat(noteMap.contains(objId1)).isTrue();
- assertThat(noteMap.contains(objId2)).isFalse();
+ assertThat(noteMap.contains(commitId1)).isTrue();
+ assertThat(noteMap.contains(commitId2)).isFalse();
}
@Test
public void addingPublishedCommentDoesNotCreateNoOpCommitOnEmptyDraftRef() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -2276,9 +2299,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev,
+ commitId,
false);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
assertThat(repo.exactRef(changeMetaRef(c.getId()))).isNotNull();
@@ -2289,7 +2312,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
@Test
public void addingPublishedCommentDoesNotCreateNoOpCommitOnNonEmptyDraftRef() throws Exception {
Change c = newChange();
- String rev = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -2309,9 +2332,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"draft comment on ps1",
side,
- rev,
+ commitId,
false);
- update.putComment(Status.DRAFT, draft);
+ update.putComment(Comment.Status.DRAFT, draft);
update.commit();
String draftRef = refsDraftComments(c.getId(), otherUser.getAccountId());
@@ -2331,9 +2354,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev,
+ commitId,
false);
- update.putComment(Status.PUBLISHED, pub);
+ update.putComment(Comment.Status.PUBLISHED, pub);
update.commit();
assertThat(exactRefAllUsers(draftRef)).isEqualTo(old);
@@ -2344,7 +2367,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
String uuid = "uuid";
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
String messageForBase = "comment for base";
Timestamp now = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
@@ -2361,14 +2384,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
messageForBase,
(short) 0,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
assertThat(newNotes(c).getComments())
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment));
}
@Test
@@ -2376,7 +2399,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, otherUser);
String uuid = "uuid";
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
String messageForBase = "comment for base";
Timestamp now = TimeUtil.nowTs();
PatchSet.Id psId = c.currentPatchSetId();
@@ -2393,22 +2416,22 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
messageForBase,
(short) 0,
- rev,
+ commitId,
false);
update.setPatchSetId(psId);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
assertThat(newNotes(c).getComments())
- .containsExactlyEntriesIn(ImmutableListMultimap.of(new RevId(rev), comment));
+ .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment));
}
@Test
public void putCommentsForMultipleRevisions() throws Exception {
Change c = newChange();
String uuid = "uuid";
- String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
- String rev2 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
@@ -2432,7 +2455,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev1,
+ commitId1,
false);
Comment comment2 =
newComment(
@@ -2446,10 +2469,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps2",
side,
- rev2,
+ commitId2,
false);
- update.putComment(Status.DRAFT, comment1);
- update.putComment(Status.DRAFT, comment2);
+ update.putComment(Comment.Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -2458,8 +2481,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update = newUpdate(c, otherUser);
update.setPatchSetId(ps2);
- update.putComment(Status.PUBLISHED, comment1);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
notes = newNotes(c);
@@ -2470,7 +2493,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
@Test
public void publishSubsetOfCommentsOnRevision() throws Exception {
Change c = newChange();
- RevId rev1 = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
short side = (short) 1;
@@ -2490,7 +2513,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment1",
side,
- rev1.get(),
+ commitId1,
false);
Comment comment2 =
newComment(
@@ -2504,24 +2527,25 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment2",
side,
- rev1.get(),
+ commitId1,
false);
- update.putComment(Status.DRAFT, comment1);
- update.putComment(Status.DRAFT, comment2);
+ update.putComment(Comment.Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment2);
update.commit();
ChangeNotes notes = newNotes(c);
- assertThat(notes.getDraftComments(otherUserId).get(rev1)).containsExactly(comment1, comment2);
+ assertThat(notes.getDraftComments(otherUserId).get(commitId1))
+ .containsExactly(comment1, comment2);
assertThat(notes.getComments()).isEmpty();
update = newUpdate(c, otherUser);
update.setPatchSetId(ps1);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
notes = newNotes(c);
- assertThat(notes.getDraftComments(otherUserId).get(rev1)).containsExactly(comment1);
- assertThat(notes.getComments().get(rev1)).containsExactly(comment2);
+ assertThat(notes.getDraftComments(otherUserId).get(commitId1)).containsExactly(comment1);
+ assertThat(notes.getComments().get(commitId1)).containsExactly(comment2);
}
@Test
@@ -2535,15 +2559,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(msg.getMessage()).isEqualTo("A message.");
assertThat(msg.getAuthor()).isNull();
- update = newUpdate(c, internalUser);
- exception.expect(IllegalStateException.class);
- update.putApproval("Code-Review", (short) 1);
+ ChangeUpdate failingUpdate = newUpdate(c, internalUser);
+ assertThrows(
+ IllegalStateException.class, () -> failingUpdate.putApproval("Code-Review", (short) 1));
}
@Test
public void filterOutAndFixUpZombieDraftComments() throws Exception {
Change c = newChange();
- RevId rev1 = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
short side = (short) 1;
@@ -2562,7 +2586,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"comment on ps1",
side,
- rev1.get(),
+ commitId1,
false);
Comment comment2 =
newComment(
@@ -2576,10 +2600,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
now,
"another comment",
side,
- rev1.get(),
+ commitId1,
false);
- update.putComment(Status.DRAFT, comment1);
- update.putComment(Status.DRAFT, comment2);
+ update.putComment(Comment.Status.DRAFT, comment1);
+ update.putComment(Comment.Status.DRAFT, comment2);
update.commit();
String refName = refsDraftComments(c.getId(), otherUserId);
@@ -2587,7 +2611,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
update = newUpdate(c, otherUser);
update.setPatchSetId(ps1);
- update.putComment(Status.PUBLISHED, comment2);
+ update.putComment(Comment.Status.PUBLISHED, comment2);
update.commit();
assertThat(exactRefAllUsers(refName)).isNotNull();
assertThat(exactRefAllUsers(refName)).isNotEqualTo(oldDraftId);
@@ -2604,16 +2628,16 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
// Looking at drafts directly shows the zombie comment.
DraftCommentNotes draftNotes = draftNotesFactory.create(c.getId(), otherUserId);
- assertThat(draftNotes.load().getComments().get(rev1)).containsExactly(comment1, comment2);
+ assertThat(draftNotes.load().getComments().get(commitId1)).containsExactly(comment1, comment2);
// Zombie comment is filtered out of drafts via ChangeNotes.
ChangeNotes notes = newNotes(c);
- assertThat(notes.getDraftComments(otherUserId).get(rev1)).containsExactly(comment1);
- assertThat(notes.getComments().get(rev1)).containsExactly(comment2);
+ assertThat(notes.getDraftComments(otherUserId).get(commitId1)).containsExactly(comment1);
+ assertThat(notes.getComments().get(commitId1)).containsExactly(comment2);
update = newUpdate(c, otherUser);
update.setPatchSetId(ps1);
- update.putComment(Status.PUBLISHED, comment1);
+ update.putComment(Comment.Status.PUBLISHED, comment1);
update.commit();
// Updating an unrelated comment causes the zombie comment to get fixed up.
@@ -2624,7 +2648,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void updateCommentsInSequentialUpdates() throws Exception {
Change c = newChange();
CommentRange range = new CommentRange(1, 1, 2, 1);
- String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
+ ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
ChangeUpdate update1 = newUpdate(c, otherUser);
Comment comment1 =
@@ -2639,9 +2663,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
new Timestamp(update1.getWhen().getTime()),
"comment 1",
(short) 1,
- rev,
+ commitId,
false);
- update1.putComment(Status.PUBLISHED, comment1);
+ update1.putComment(Comment.Status.PUBLISHED, comment1);
ChangeUpdate update2 = newUpdate(c, otherUser);
Comment comment2 =
@@ -2656,9 +2680,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
new Timestamp(update2.getWhen().getTime()),
"comment 2",
(short) 1,
- rev,
+ commitId,
false);
- update2.putComment(Status.PUBLISHED, comment2);
+ update2.putComment(Comment.Status.PUBLISHED, comment2);
try (NoteDbUpdateManager manager = updateManagerFactory.create(project)) {
manager.add(update1);
@@ -2667,7 +2691,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
}
ChangeNotes notes = newNotes(c);
- List<Comment> comments = notes.getComments().get(new RevId(rev));
+ List<Comment> comments = notes.getComments().get(commitId);
assertThat(comments).hasSize(2);
assertThat(comments.get(0).message).isEqualTo("comment 1");
assertThat(comments.get(1).message).isEqualTo("comment 2");
@@ -2697,7 +2721,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
int numComments = notes.getComments().size();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.setPatchSetId(new PatchSet.Id(c.getId(), c.currentPatchSetId().get() + 1));
+ update.setPatchSetId(PatchSet.id(c.getId(), c.currentPatchSetId().get() + 1));
update.setChangeMessage("Should be ignored");
update.putApproval("Code-Review", (short) 2);
CommentRange range = new CommentRange(1, 1, 2, 1);
@@ -2713,9 +2737,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
new Timestamp(update.getWhen().getTime()),
"comment",
(short) 1,
- "abcd1234abcd1234abcd1234abcd1234abcd1234",
+ ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"),
false);
- update.putComment(Status.PUBLISHED, comment);
+ update.putComment(Comment.Status.PUBLISHED, comment);
update.commit();
notes = newNotes(c);
@@ -2734,7 +2758,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
ChangeUpdate update = newUpdate(c, changeOwner);
- update.setPatchSetId(new PatchSet.Id(c.getId(), 1));
+ update.setPatchSetId(PatchSet.id(c.getId(), 1));
update.setCurrentPatchSet();
update.commit();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
@@ -2751,7 +2775,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
// Delete PS1, PS2 becomes current.
update = newUpdate(c, changeOwner);
- update.setPatchSetId(new PatchSet.Id(c.getId(), 1));
+ update.setPatchSetId(PatchSet.id(c.getId(), 1));
update.setPatchSetState(PatchSetState.DELETED);
update.commit();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
@@ -2930,8 +2954,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void pendingReviewers() throws Exception {
Address adr1 = new Address("Foo Bar1", "foo.bar1@gerritcodereview.com");
Address adr2 = new Address("Foo Bar2", "foo.bar2@gerritcodereview.com");
- Account.Id ownerId = changeOwner.getAccount().getId();
- Account.Id otherUserId = otherUser.getAccount().getId();
+ Account.Id ownerId = changeOwner.getAccount().id();
+ Account.Id otherUserId = otherUser.getAccount().id();
ChangeNotes notes = newNotes(newChange());
assertThat(notes.getPendingReviewers().asTable()).isEmpty();
@@ -3006,9 +3030,9 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
public void setRevertOfToCurrentChangeFails() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage("A change cannot revert itself");
- update.setRevertOf(c.getId().get());
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> update.setRevertOf(c.getId().get()));
+ assertThat(thrown).hasMessageThat().contains("A change cannot revert itself");
}
@Test
@@ -3016,9 +3040,58 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setRevertOf(newChange().getId().get());
- exception.expect(StorageException.class);
- exception.expectMessage("Given ChangeUpdate is only allowed on initial commit");
+ StorageException thrown = assertThrows(StorageException.class, () -> update.commit());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Given ChangeUpdate is only allowed on initial commit");
+ }
+
+ @Test
+ public void updateCount() throws Exception {
+ Change c = newChange();
+ assertThat(newNotes(c).getUpdateCount()).isEqualTo(1);
+
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.putApproval("Code-Review", (short) -1);
+ update.commit();
+ assertThat(newNotes(c).getUpdateCount()).isEqualTo(2);
+
+ update = newUpdate(c, changeOwner);
+ update.putApproval("Code-Review", (short) 1);
+ update.commit();
+ assertThat(newNotes(c).getUpdateCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void createPatchSetAfterPatchSetDeletion() throws Exception {
+ Change c = newChange();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
+
+ // Create PS2.
+ incrementCurrentPatchSetFieldOnly(c);
+ RevCommit commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setCommit(rw, commit);
+ update.setGroups(ImmutableList.of(commit.name()));
+ update.commit();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
+
+ // Delete PS2.
+ update = newUpdate(c, changeOwner);
+ update.setPatchSetState(PatchSetState.DELETED);
update.commit();
+ c = newNotes(c).getChange();
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+
+ // Create another PS2
+ incrementCurrentPatchSetFieldOnly(c);
+ commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create();
+ update = newUpdate(c, changeOwner);
+ update.setPatchSetState(PatchSetState.PUBLISHED);
+ update.setCommit(rw, commit);
+ update.setGroups(ImmutableList.of(commit.name()));
+ update.commit();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
}
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
@@ -3042,11 +3115,11 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
break;
}
}
- assertThat(cause)
- .named(
+ assertWithMessage(
expectedClass.getSimpleName()
+ " in causal chain of:\n"
+ Throwables.getStackTraceAsString(e))
+ .that(cause)
.isNotNull();
assertThat(cause.getMessage()).isEqualTo(expectedMsg);
}
diff --git a/javatests/com/google/gerrit/server/notedb/CommentTimestampAdapterTest.java b/javatests/com/google/gerrit/server/notedb/CommentTimestampAdapterTest.java
index 4dd2005170..c2620dc5cf 100644
--- a/javatests/com/google/gerrit/server/notedb/CommentTimestampAdapterTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommentTimestampAdapterTest.java
@@ -16,20 +16,20 @@ package com.google.gerrit.server.notedb;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.sql.Timestamp;
import java.time.ZonedDateTime;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class CommentTimestampAdapterTest extends GerritBaseTests {
+public class CommentTimestampAdapterTest {
/** Arbitrary time outside of a DST transition, as an ISO instant. */
private static final String NON_DST_STR = "2017-02-07T10:20:30.123Z";
@@ -153,14 +153,14 @@ public class CommentTimestampAdapterTest extends GerritBaseTests {
Comment c =
new Comment(
new Comment.Key("uuid", "filename", 1),
- new Account.Id(100),
+ Account.id(100),
NON_DST_TS,
(short) 0,
"message",
"serverId",
false);
c.lineNbr = 1;
- c.revId = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ c.setCommitId(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
String json = gson.toJson(c);
assertThat(json).contains("\"writtenOn\": \"" + NON_DST_STR_TRUNC + "\",");
diff --git a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 5552572a37..97781a4a34 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -19,9 +19,9 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.mail.Address;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.logging.RequestId;
import com.google.gerrit.server.util.time.TimeUtil;
@@ -44,8 +44,8 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
ChangeUpdate update = newUpdateForNewChange(c, changeOwner);
update.putApproval("Verified", (short) 1);
update.putApproval("Code-Review", (short) -1);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
- update.putReviewer(otherUser.getAccount().getId(), CC);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
+ update.putReviewer(otherUser.getAccount().id(), CC);
update.commit();
assertThat(update.getRefName()).isEqualTo("refs/changes/01/1/meta");
@@ -199,10 +199,13 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
@Test
public void anonymousUser() throws Exception {
- Account anon = new Account(new Account.Id(3), TimeUtil.nowTs());
+ Account anon =
+ Account.builder(Account.id(3), TimeUtil.nowTs())
+ .setMetaId("1234567812345678123456781234567812345678")
+ .build();
accountCache.put(anon);
Change c = newChange();
- ChangeUpdate update = newUpdate(c, userFactory.create(anon.getId()));
+ ChangeUpdate update = newUpdate(c, userFactory.create(anon.id()));
update.setChangeMessage("Comment on the change.");
update.commit();
@@ -241,7 +244,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
public void noChangeMessage() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
- update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+ update.putReviewer(changeOwner.getAccount().id(), REVIEWER);
update.commit();
assertBodyEquals(
@@ -311,7 +314,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
c.setCurrentPatchSet(c.currentPatchSetId(), " " + c.getSubject(), c.getOriginalSubject());
ChangeUpdate update = newUpdateForNewChange(c, changeOwner);
update.setChangeId(c.getKey().get());
- update.setBranch(c.getDest().get());
+ update.setBranch(c.getDest().branch());
update.commit();
assertBodyEquals(
@@ -332,7 +335,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
c.setCurrentPatchSet(c.currentPatchSetId(), "\t\t" + c.getSubject(), c.getOriginalSubject());
update = newUpdateForNewChange(c, changeOwner);
update.setChangeId(c.getKey().get());
- update.setBranch(c.getDest().get());
+ update.setBranch(c.getDest().branch());
update.commit();
assertBodyEquals(
diff --git a/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java b/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java
new file mode 100644
index 0000000000..bf498841c4
--- /dev/null
+++ b/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.server.util.time.TimeUtil;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class DraftCommentNotesTest extends AbstractChangeNotesTest {
+
+ @Test
+ public void createAndPublishCommentInOneAction_runsDraftOperationAsynchronously()
+ throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(c.currentPatchSetId());
+ update.putComment(Comment.Status.PUBLISHED, comment(c.currentPatchSetId()));
+ update.commit();
+
+ assertThat(newNotes(c).getDraftComments(otherUserId)).isEmpty();
+ assertableFanOutExecutor.assertInteractions(1);
+ }
+
+ @Test
+ public void createAndPublishComment_runsPublishDraftOperationAsynchronously() throws Exception {
+ Change c = newChange();
+ ChangeUpdate update = newUpdate(c, otherUser);
+
+ update.setPatchSetId(c.currentPatchSetId());
+ update.putComment(Comment.Status.DRAFT, comment(c.currentPatchSetId()));
+ update.commit();
+ assertThat(newNotes(c).getDraftComments(otherUserId)).hasSize(1);
+ assertableFanOutExecutor.assertInteractions(0);
+
+ update = newUpdate(c, otherUser);
+ update.putComment(Comment.Status.PUBLISHED, comment(c.currentPatchSetId()));
+ update.commit();
+
+ assertThat(newNotes(c).getDraftComments(otherUserId)).isEmpty();
+ assertableFanOutExecutor.assertInteractions(1);
+ }
+
+ @Test
+ public void createAndDeleteDraftComment_runsDraftOperationSynchronously() throws Exception {
+ Change c = newChange();
+
+ ChangeUpdate update = newUpdate(c, otherUser);
+ update.setPatchSetId(c.currentPatchSetId());
+ update.putComment(Comment.Status.DRAFT, comment(c.currentPatchSetId()));
+ update.commit();
+
+ ChangeNotes notes = newNotes(c);
+ assertThat(notes.getDraftComments(otherUserId)).hasSize(1);
+ assertableFanOutExecutor.assertInteractions(0);
+
+ update = newUpdate(c, otherUser);
+ update.setPatchSetId(c.currentPatchSetId());
+ update.deleteComment(comment(c.currentPatchSetId()));
+ update.commit();
+
+ notes = newNotes(c);
+ assertThat(notes.getDraftComments(otherUserId)).isEmpty();
+ assertableFanOutExecutor.assertInteractions(0);
+ }
+
+ private Comment comment(PatchSet.Id psId) {
+ return newComment(
+ psId,
+ "filename",
+ "uuid",
+ null,
+ 0,
+ otherUser,
+ null,
+ TimeUtil.nowTs(),
+ "comment",
+ (short) 0,
+ ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"),
+ false);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/IntBlobTest.java b/javatests/com/google/gerrit/server/notedb/IntBlobTest.java
index 1cbe61de96..333c229147 100644
--- a/javatests/com/google/gerrit/server/notedb/IntBlobTest.java
+++ b/javatests/com/google/gerrit/server/notedb/IntBlobTest.java
@@ -15,12 +15,12 @@
package com.google.gerrit.server.notedb;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -44,7 +44,7 @@ public class IntBlobTest {
@Before
public void setUp() throws Exception {
- projectName = new Project.NameKey("repo");
+ projectName = Project.nameKey("repo");
repo = new InMemoryRepository(new DfsRepositoryDescription(projectName.get()));
tr = new TestRepository<>(repo);
rw = tr.getRevWalk();
@@ -59,12 +59,7 @@ public class IntBlobTest {
public void parseNonBlob() throws Exception {
String refName = "refs/foo/master";
tr.branch(refName).commit().create();
- try {
- IntBlob.parse(repo, refName);
- assert_().fail("Expected IncorrectObjectTypeException");
- } catch (IncorrectObjectTypeException e) {
- // Expected.
- }
+ assertThrows(IncorrectObjectTypeException.class, () -> IntBlob.parse(repo, refName));
}
@Test
@@ -85,12 +80,9 @@ public class IntBlobTest {
public void parseInvalid() throws Exception {
String refName = "refs/foo";
ObjectId id = tr.update(refName, tr.blob("1 2 3"));
- try {
- IntBlob.parse(repo, refName);
- assert_().fail("Expected StorageException");
- } catch (StorageException e) {
- assertThat(e).hasMessageThat().isEqualTo("invalid value in refs/foo blob at " + id.name());
- }
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> IntBlob.parse(repo, refName));
+ assertThat(thrown).hasMessageThat().isEqualTo("invalid value in refs/foo blob at " + id.name());
}
@Test
@@ -180,19 +172,19 @@ public class IntBlobTest {
@Test
public void storeWrongOldId() throws Exception {
String refName = "refs/foo";
- try {
- IntBlob.store(
- repo,
- rw,
- projectName,
- refName,
- ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
- 123,
- GitReferenceUpdated.DISABLED);
- assert_().fail("expected LockFailureException");
- } catch (LockFailureException e) {
- assertThat(e.getFailedRefs()).containsExactly("refs/foo");
- }
+ LockFailureException thrown =
+ assertThrows(
+ LockFailureException.class,
+ () ->
+ IntBlob.store(
+ repo,
+ rw,
+ projectName,
+ refName,
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
+ 123,
+ GitReferenceUpdated.DISABLED));
+ assertThat(thrown.getFailedRefs()).containsExactly("refs/foo");
assertThat(IntBlob.parse(repo, refName)).isEmpty();
}
diff --git a/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java b/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
index 74cc507e27..a768eaf02d 100644
--- a/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
+++ b/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
@@ -15,22 +15,27 @@
package com.google.gerrit.server.notedb;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.junit.Assert.fail;
+import com.github.rholder.retry.BlockStrategy;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Expect;
import com.google.common.util.concurrent.Runnables;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -41,11 +46,14 @@ import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
-public class RepoSequenceTest extends GerritBaseTests {
+public class RepoSequenceTest {
+ @Rule public final Expect expect = Expect.create();
+
// Don't sleep in tests.
- private static final Retryer<RefUpdate> RETRYER =
+ private static final Retryer<ImmutableList<Integer>> RETRYER =
RepoSequence.retryerBuilder().withBlockStrategy(t -> {}).build();
private InMemoryRepositoryManager repoManager;
@@ -54,7 +62,7 @@ public class RepoSequenceTest extends GerritBaseTests {
@Before
public void setUp() throws Exception {
repoManager = new InMemoryRepositoryManager();
- project = new Project.NameKey("project");
+ project = Project.nameKey("project");
repoManager.createRepository(project);
}
@@ -66,13 +74,13 @@ public class RepoSequenceTest extends GerritBaseTests {
RepoSequence s = newSequence(name, 1, batchSize);
for (int i = 1; i <= max; i++) {
try {
- assertThat(s.next()).named("i=" + i + " for " + name).isEqualTo(i);
+ assertWithMessage("i=" + i + " for " + name).that(s.next()).isEqualTo(i);
} catch (StorageException e) {
throw new AssertionError("failed batchSize=" + batchSize + ", i=" + i, e);
}
}
- assertThat(s.acquireCount)
- .named("acquireCount for " + name)
+ assertWithMessage("acquireCount for " + name)
+ .that(s.acquireCount)
.isEqualTo(divCeil(max, batchSize));
}
}
@@ -160,7 +168,7 @@ public class RepoSequenceTest extends GerritBaseTests {
RepoSequence s = newSequence("id", 1, 10, bgUpdate, RETRYER);
assertThat(doneBgUpdate.get()).isFalse();
assertThat(s.next()).isEqualTo(1234);
- // Single acquire call that results in 2 ref reads.
+ // Two acquire calls, but only one successful.
assertThat(s.acquireCount).isEqualTo(1);
assertThat(doneBgUpdate.get()).isTrue();
}
@@ -168,9 +176,11 @@ public class RepoSequenceTest extends GerritBaseTests {
@Test
public void failOnInvalidValue() throws Exception {
ObjectId id = writeBlob("id", "not a number");
- exception.expect(StorageException.class);
- exception.expectMessage("invalid value in refs/sequences/id blob at " + id.name());
- newSequence("id", 1, 3).next();
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> newSequence("id", 1, 3).next());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("invalid value in refs/sequences/id blob at " + id.name());
}
@Test
@@ -178,13 +188,9 @@ public class RepoSequenceTest extends GerritBaseTests {
try (Repository repo = repoManager.openRepository(project);
TestRepository<Repository> tr = new TestRepository<>(repo)) {
tr.branch(RefNames.REFS_SEQUENCES + "id").commit().create();
- try {
- newSequence("id", 1, 3).next();
- fail();
- } catch (StorageException e) {
- assertThat(e.getCause()).isInstanceOf(ExecutionException.class);
- assertThat(e.getCause().getCause()).isInstanceOf(IncorrectObjectTypeException.class);
- }
+ StorageException e =
+ assertThrows(StorageException.class, () -> newSequence("id", 1, 3).next());
+ assertThat(e.getCause()).isInstanceOf(IncorrectObjectTypeException.class);
}
}
@@ -197,12 +203,84 @@ public class RepoSequenceTest extends GerritBaseTests {
1,
10,
() -> writeBlob("id", Integer.toString(bgCounter.getAndAdd(1000))),
- RetryerBuilder.<RefUpdate>newBuilder()
+ RetryerBuilder.<ImmutableList<Integer>>newBuilder()
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build());
- exception.expect(StorageException.class);
- exception.expectMessage("Failed to update refs/sequences/id: LOCK_FAILURE");
- s.next();
+ StorageException thrown = assertThrows(StorageException.class, () -> s.next());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Failed to update refs/sequences/id: LOCK_FAILURE");
+ }
+
+ @Test
+ public void idCanBeRetrievedFromOtherThreadWhileWaitingToRetry() throws Exception {
+ // Seed existing ref value.
+ writeBlob("id", "1");
+
+ // Let the first update of the sequence fail with LOCK_FAILURE, so that the update is retried.
+ CountDownLatch lockFailure = new CountDownLatch(1);
+ CountDownLatch parallelSuccessfulSequenceGeneration = new CountDownLatch(1);
+ AtomicBoolean doneBgUpdate = new AtomicBoolean(false);
+ Runnable bgUpdate =
+ () -> {
+ if (!doneBgUpdate.getAndSet(true)) {
+ writeBlob("id", "1234");
+ }
+ };
+
+ BlockStrategy blockStrategy =
+ t -> {
+ // Keep blocking until we verified that another thread can retrieve a sequence number
+ // while we are blocking here.
+ lockFailure.countDown();
+ parallelSuccessfulSequenceGeneration.await();
+ };
+
+ // Use batch size = 1 to make each call go to NoteDb.
+ RepoSequence s =
+ newSequence(
+ "id",
+ 1,
+ 1,
+ bgUpdate,
+ RepoSequence.retryerBuilder().withBlockStrategy(blockStrategy).build());
+
+ assertThat(doneBgUpdate.get()).isFalse();
+
+ // Start a thread to get a sequence number. This thread needs to update the sequence in NoteDb,
+ // but due to the background update (see bgUpdate) the first attempt to update NoteDb fails
+ // with LOCK_FAILURE. RepoSequence uses a retryer to retry the NoteDb update on LOCK_FAILURE,
+ // but our block strategy ensures that this retry only happens after isBlocking was set to
+ // false.
+ Future<?> future =
+ Executors.newFixedThreadPool(1)
+ .submit(
+ () -> {
+ // The background update sets the next available sequence number to 1234. Then the
+ // test thread retrieves one sequence number, so that the next available sequence
+ // number for this thread is 1235.
+ expect.that(s.next()).isEqualTo(1235);
+ });
+
+ // Wait until the LOCK_FAILURE has happened and the block strategy was entered.
+ lockFailure.await();
+
+ // Verify that the background update was done now.
+ assertThat(doneBgUpdate.get()).isTrue();
+
+ // Verify that we can retrieve a sequence number while the other thread is blocked. If the
+ // s.next() call hangs it means that the RepoSequence.counterLock was not released before the
+ // background thread started to block for retry. In this case the test would time out.
+ assertThat(s.next()).isEqualTo(1234);
+
+ // Stop blocking the retry of the background thread (and verify that it was still blocked).
+ parallelSuccessfulSequenceGeneration.countDown();
+
+ // Wait until the background thread is done.
+ future.get();
+
+ // Two successful acquire calls (because batch size == 1).
+ assertThat(s.acquireCount).isEqualTo(2);
}
@Test
@@ -260,7 +338,7 @@ public class RepoSequenceTest extends GerritBaseTests {
final int start,
int batchSize,
Runnable afterReadRef,
- Retryer<RefUpdate> retryer) {
+ Retryer<ImmutableList<Integer>> retryer) {
return new RepoSequence(
repoManager,
GitReferenceUpdated.DISABLED,
diff --git a/javatests/com/google/gerrit/server/patch/IntraLineLoaderTest.java b/javatests/com/google/gerrit/server/patch/IntraLineLoaderTest.java
index 6c63c5f6a0..52a81ade12 100644
--- a/javatests/com/google/gerrit/server/patch/IntraLineLoaderTest.java
+++ b/javatests/com/google/gerrit/server/patch/IntraLineLoaderTest.java
@@ -15,18 +15,18 @@
package com.google.gerrit.server.patch;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.jgit.diff.ReplaceEdit;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.List;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.junit.Test;
-public class IntraLineLoaderTest extends GerritBaseTests {
+public class IntraLineLoaderTest {
@Test
public void rewriteAtStartOfLineIsRecognized() throws Exception {
@@ -88,22 +88,30 @@ public class IntraLineLoaderTest extends GerritBaseTests {
// TODO: expected failure
// the current code does not work on the first line
// and the insert marker is in the wrong location
- @Test(expected = AssertionError.class)
+ @Test
public void preferInsertAtLineBreak2() throws Exception {
- String a = " abc\n def\n";
- String b = " abc\n def\n";
- assertThat(intraline(a, b))
- .isEqualTo(ref().insert(" ").common(" abc\n").insert(" ").common(" def\n").edits);
+ assertThrows(
+ AssertionError.class,
+ () -> {
+ String a = " abc\n def\n";
+ String b = " abc\n def\n";
+ assertThat(intraline(a, b))
+ .isEqualTo(ref().insert(" ").common(" abc\n").insert(" ").common(" def\n").edits);
+ });
}
// TODO: expected failure
// the current code does not work on the first line
- @Test(expected = AssertionError.class)
+ @Test
public void preferDeleteAtLineBreak() throws Exception {
- String a = " abc\n def\n";
- String b = " abc\n def\n";
- assertThat(intraline(a, b))
- .isEqualTo(ref().remove(" ").common(" abc\n").remove(" ").common(" def\n").edits);
+ assertThrows(
+ AssertionError.class,
+ () -> {
+ String a = " abc\n def\n";
+ String b = " abc\n def\n";
+ assertThat(intraline(a, b))
+ .isEqualTo(ref().remove(" ").common(" abc\n").remove(" ").common(" def\n").edits);
+ });
}
@Test
diff --git a/javatests/com/google/gerrit/server/patch/PatchListEntryTest.java b/javatests/com/google/gerrit/server/patch/PatchListEntryTest.java
index 4ed5f4b299..7ac1c3162b 100644
--- a/javatests/com/google/gerrit/server/patch/PatchListEntryTest.java
+++ b/javatests/com/google/gerrit/server/patch/PatchListEntryTest.java
@@ -19,11 +19,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Patch;
import org.junit.Test;
-public class PatchListEntryTest extends GerritBaseTests {
+public class PatchListEntryTest {
@Test
public void empty1() {
final String name = "empty-file";
diff --git a/javatests/com/google/gerrit/server/patch/PatchListTest.java b/javatests/com/google/gerrit/server/patch/PatchListTest.java
index ccdd040195..e224191a5a 100644
--- a/javatests/com/google/gerrit/server/patch/PatchListTest.java
+++ b/javatests/com/google/gerrit/server/patch/PatchListTest.java
@@ -16,8 +16,7 @@ package com.google.gerrit.server.patch;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Patch;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -26,7 +25,7 @@ import java.io.ObjectOutputStream;
import java.util.Arrays;
import org.junit.Test;
-public class PatchListTest extends GerritBaseTests {
+public class PatchListTest {
@Test
public void fileOrder() {
String[] names = {
diff --git a/javatests/com/google/gerrit/server/permissions/DefaultPermissionsMappingTest.java b/javatests/com/google/gerrit/server/permissions/DefaultPermissionsMappingTest.java
index ff9ac411f7..305e81bf1c 100644
--- a/javatests/com/google/gerrit/server/permissions/DefaultPermissionsMappingTest.java
+++ b/javatests/com/google/gerrit/server/permissions/DefaultPermissionsMappingTest.java
@@ -18,10 +18,9 @@ import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.server.permissions.DefaultPermissionMappings.refPermission;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class DefaultPermissionsMappingTest extends GerritBaseTests {
+public class DefaultPermissionsMappingTest {
@Test
public void stringToRefPermission() {
assertThat(refPermission("doesnotexist")).isEmpty();
diff --git a/javatests/com/google/gerrit/server/permissions/PluginPermissionsUtilTest.java b/javatests/com/google/gerrit/server/permissions/PluginPermissionsUtilTest.java
index f40c3bc1f3..7aa73a74e2 100644
--- a/javatests/com/google/gerrit/server/permissions/PluginPermissionsUtilTest.java
+++ b/javatests/com/google/gerrit/server/permissions/PluginPermissionsUtilTest.java
@@ -14,7 +14,7 @@
package com.google.gerrit.server.permissions;
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.server.permissions.PluginPermissionsUtil.isValidPluginPermission;
import com.google.common.collect.ImmutableList;
@@ -30,8 +30,8 @@ public final class PluginPermissionsUtilTest {
ImmutableList.of("plugin-foo-a", "plugin-foo-a-b");
for (String permission : validPluginPermissions) {
- assertThat(isValidPluginPermission(permission))
- .named("valid plugin permission: %s", permission)
+ assertWithMessage("valid plugin permission: %s", permission)
+ .that(isValidPluginPermission(permission))
.isTrue();
}
}
@@ -48,8 +48,8 @@ public final class PluginPermissionsUtilTest {
"plugin-foo-a1");
for (String permission : invalidPluginPermissions) {
- assertThat(isValidPluginPermission(permission))
- .named("invalid plugin permission: %s", permission)
+ assertWithMessage("invalid plugin permission: %s", permission)
+ .that(isValidPluginPermission(permission))
.isFalse();
}
}
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index 14fa01ceec..7f2f5a432a 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -15,284 +15,205 @@
package com.google.gerrit.server.permissions;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.blockLabel;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.common.data.Permission.EDIT_TOPIC_NAME;
import static com.google.gerrit.common.data.Permission.LABEL;
import static com.google.gerrit.common.data.Permission.OWNER;
import static com.google.gerrit.common.data.Permission.PUSH;
import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.common.data.Permission.SUBMIT;
+import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.ADMIN;
-import static com.google.gerrit.server.project.testing.Util.DEVS;
-import static com.google.gerrit.server.project.testing.Util.allow;
-import static com.google.gerrit.server.project.testing.Util.allowExclusive;
-import static com.google.gerrit.server.project.testing.Util.block;
-import static com.google.gerrit.server.project.testing.Util.deny;
-import static com.google.gerrit.server.project.testing.Util.doNotInherit;
-import static com.google.gerrit.testing.InMemoryRepositoryManager.newRepository;
-
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.collect.ImmutableSortedSet;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.PermissionRange;
-import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
-import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AllUsersNameProvider;
-import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefPattern;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryModule;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class RefControlTest extends GerritBaseTests {
- private void assertAdminsAreOwnersAndDevsAreNot() {
- ProjectControl uBlah = user(local, DEVS);
- ProjectControl uAdmin = user(local, DEVS, ADMIN);
+public class RefControlTest {
+ private static final AccountGroup.UUID ADMIN = AccountGroup.uuid("test.admin");
+ private static final AccountGroup.UUID DEVS = AccountGroup.uuid("test.devs");
+
+ private void assertAdminsAreOwnersAndDevsAreNot() throws Exception {
+ ProjectControl uBlah = user(localKey, DEVS);
+ ProjectControl uAdmin = user(localKey, DEVS, ADMIN);
- assertThat(uBlah.isOwner()).named("not owner").isFalse();
- assertThat(uAdmin.isOwner()).named("is owner").isTrue();
+ assertWithMessage("not owner").that(uBlah.isOwner()).isFalse();
+ assertWithMessage("is owner").that(uAdmin.isOwner()).isTrue();
}
private void assertOwner(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).isOwner()).named("OWN " + ref).isTrue();
+ assertWithMessage("OWN " + ref).that(u.controlForRef(ref).isOwner()).isTrue();
}
private void assertNotOwner(ProjectControl u) {
- assertThat(u.isOwner()).named("not owner").isFalse();
+ assertWithMessage("not owner").that(u.isOwner()).isFalse();
}
private void assertAllRefsAreVisible(ProjectControl u) {
- assertThat(u.allRefsAreVisible(Collections.emptySet())).named("all refs visible").isTrue();
+ assertWithMessage("all refs visible")
+ .that(u.allRefsAreVisible(Collections.emptySet()))
+ .isTrue();
}
private void assertAllRefsAreNotVisible(ProjectControl u) {
- assertThat(u.allRefsAreVisible(Collections.emptySet())).named("all refs NOT visible").isFalse();
+ assertWithMessage("all refs NOT visible")
+ .that(u.allRefsAreVisible(Collections.emptySet()))
+ .isFalse();
}
private void assertNotOwner(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
+ assertWithMessage("NOT OWN " + ref).that(u.controlForRef(ref).isOwner()).isFalse();
}
private void assertCanAccess(ProjectControl u) {
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
- assertThat(access).named("can access").isTrue();
+ assertWithMessage("can access").that(access).isTrue();
}
private void assertAccessDenied(ProjectControl u) {
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
- assertThat(access).named("cannot access").isFalse();
+ assertWithMessage("cannot access").that(access).isFalse();
}
private void assertCanRead(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).hasReadPermissionOnRef(true))
- // This should be false but the test relies on inheritance into refs/tags
- .named("can read " + ref)
+ assertWithMessage("can read " + ref)
+ .that(
+ u.controlForRef(ref)
+ .hasReadPermissionOnRef(
+ true)) // This should be false but the test relies on inheritance into refs/tags
.isTrue();
}
private void assertCannotRead(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).hasReadPermissionOnRef(true))
- // This should be false but the test relies on inheritance into refs/tags
- .named("cannot read " + ref)
+ assertWithMessage("cannot read " + ref)
+ .that(
+ u.controlForRef(ref)
+ .hasReadPermissionOnRef(
+ true)) // This should be false but the test relies on inheritance into refs/tags
.isFalse();
}
private void assertCanSubmit(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).canSubmit(false)).named("can submit " + ref).isTrue();
+ assertWithMessage("can submit " + ref).that(u.controlForRef(ref).canSubmit(false)).isTrue();
}
private void assertCannotSubmit(String ref, ProjectControl u) {
- assertThat(u.controlForRef(ref).canSubmit(false)).named("can submit " + ref).isFalse();
+ assertWithMessage("can submit " + ref).that(u.controlForRef(ref).canSubmit(false)).isFalse();
}
private void assertCanUpload(ProjectControl u) {
- assertThat(u.canPushToAtLeastOneRef()).named("can upload").isTrue();
+ assertWithMessage("can upload").that(u.canPushToAtLeastOneRef()).isTrue();
}
private void assertCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
- assertThat(create).named("can create change " + ref).isTrue();
+ assertWithMessage("can create change " + ref).that(create).isTrue();
}
private void assertCannotUpload(ProjectControl u) {
- assertThat(u.canPushToAtLeastOneRef()).named("cannot upload").isFalse();
+ assertWithMessage("cannot upload").that(u.canPushToAtLeastOneRef()).isFalse();
}
private void assertCannotCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
- assertThat(create).named("cannot create change " + ref).isFalse();
+ assertWithMessage("cannot create change " + ref).that(create).isFalse();
}
private void assertCanUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
- assertThat(update).named("can update " + ref).isTrue();
+ assertWithMessage("can update " + ref).that(update).isTrue();
}
private void assertCannotUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
- assertThat(update).named("cannot update " + ref).isFalse();
+ assertWithMessage("cannot update " + ref).that(update).isFalse();
}
private void assertCanForceUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
- assertThat(update).named("can force push " + ref).isTrue();
+ assertWithMessage("can force push " + ref).that(update).isTrue();
}
private void assertCannotForceUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
- assertThat(update).named("cannot force push " + ref).isFalse();
+ assertWithMessage("cannot force push " + ref).that(update).isFalse();
}
private void assertCanVote(int score, PermissionRange range) {
- assertThat(range.contains(score)).named("can vote " + score).isTrue();
+ assertWithMessage("can vote " + score).that(range.contains(score)).isTrue();
}
private void assertCannotVote(int score, PermissionRange range) {
- assertThat(range.contains(score)).named("cannot vote " + score).isFalse();
- }
-
- private final AllProjectsName allProjectsName =
- new AllProjectsName(AllProjectsNameProvider.DEFAULT);
- private final AllUsersName allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
- private final AccountGroup.UUID fixers = new AccountGroup.UUID("test.fixers");
- private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
- private Project.NameKey localKey = new Project.NameKey("local");
- private ProjectConfig local;
- private Project.NameKey parentKey = new Project.NameKey("parent");
- private ProjectConfig parent;
- private InMemoryRepositoryManager repoManager;
- private ProjectCache projectCache;
- private PermissionCollection.Factory sectionSorter;
- private ChangeControl.Factory changeControlFactory;
-
- @Inject private PermissionBackend permissionBackend;
- @Inject private CapabilityCollection.Factory capabilityCollectionFactory;
+ assertWithMessage("cannot vote " + score).that(range.contains(score)).isFalse();
+ }
+
+ private final AccountGroup.UUID fixers = AccountGroup.uuid("test.fixers");
+ private final Project.NameKey localKey = Project.nameKey("local");
+ private final Project.NameKey parentKey = Project.nameKey("parent");
+
+ @Inject private AllProjectsName allProjectsName;
+ @Inject private AllUsersName allUsersName;
+ @Inject private InMemoryRepositoryManager repoManager;
+ @Inject private MetaDataUpdate.Server metaDataUpdateFactory;
+ @Inject private ProjectCache projectCache;
+ @Inject private ProjectControl.Factory projectControlFactory;
+ @Inject private ProjectOperations projectOperations;
@Inject private SchemaCreator schemaCreator;
@Inject private SingleVersionListener singleVersionListener;
@Inject private ThreadLocalRequestContext requestContext;
- @Inject private DefaultRefFilter.Factory refFilterFactory;
- @Inject private TransferConfig transferConfig;
- @Inject private MetricMaker metricMaker;
- @Inject private ProjectConfig.Factory projectConfigFactory;
- @Inject private RefVisibilityControl refVisibilityControl;
@Before
public void setUp() throws Exception {
- repoManager = new InMemoryRepositoryManager();
- projectCache =
- new ProjectCache() {
- @Override
- public ProjectState getAllProjects() {
- return get(allProjectsName);
- }
-
- @Override
- public ProjectState getAllUsers() {
- return null;
- }
-
- @Override
- public ProjectState get(Project.NameKey projectName) {
- return all.get(projectName);
- }
-
- @Override
- public void evict(Project p) {}
-
- @Override
- public void remove(Project p) {}
-
- @Override
- public void remove(Project.NameKey name) {}
-
- @Override
- public ImmutableSortedSet<Project.NameKey> all() {
- return ImmutableSortedSet.of();
- }
-
- @Override
- public ImmutableSortedSet<Project.NameKey> byName(String prefix) {
- return ImmutableSortedSet.of();
- }
-
- @Override
- public void onCreateProject(Project.NameKey newProjectName) {}
-
- @Override
- public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
- return Collections.emptySet();
- }
-
- @Override
- public ProjectState checkedGet(Project.NameKey projectName) throws IOException {
- return all.get(projectName);
- }
-
- @Override
- public void evict(Project.NameKey p) {}
-
- @Override
- public ProjectState checkedGet(Project.NameKey projectName, boolean strict)
- throws Exception {
- return all.get(projectName);
- }
- };
-
Injector injector = Guice.createInjector(new InMemoryModule());
injector.injectMembers(this);
- try {
- Repository repo = repoManager.createRepository(allProjectsName);
- ProjectConfig allProjects =
- projectConfigFactory.create(new Project.NameKey(allProjectsName.get()));
- allProjects.load(repo);
- LabelType cr = Util.codeReview();
- allProjects.getLabelSections().put(cr.getName(), cr);
- add(allProjects);
- } catch (IOException | ConfigInvalidException e) {
- throw new RuntimeException(e);
- }
+ // Tests previously used ProjectConfig.Factory to create ProjectConfigs without going through
+ // the ProjectCache, which was wrong. Manually call getInstance so we don't store it in a
+ // field that is accessible to test methods.
+ ProjectConfig.Factory projectConfigFactory = injector.getInstance(ProjectConfig.Factory.class);
singleVersionListener.start();
try {
@@ -301,84 +222,111 @@ public class RefControlTest extends GerritBaseTests {
singleVersionListener.stop();
}
- Cache<SectionSortCache.EntryKey, SectionSortCache.EntryVal> c =
- CacheBuilder.newBuilder().build();
- sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c), metricMaker);
-
- parent = projectConfigFactory.create(parentKey);
- parent.load(newRepository(parentKey));
- add(parent);
+ // Clear out All-Projects and use the lowest-level API possible for project creation, so the
+ // only ACL entries are exactly what is initialized by this test, and we aren't subject to
+ // changing defaults in SchemaCreator or ProjectCreator.
+ try (Repository allProjectsRepo = repoManager.createRepository(allProjectsName);
+ TestRepository<Repository> tr = new TestRepository<>(allProjectsRepo)) {
+ tr.delete(REFS_CONFIG);
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(allProjectsName)) {
+ ProjectConfig allProjectsConfig = projectConfigFactory.create(allProjectsName);
+ allProjectsConfig.load(md);
+ LabelType cr = TestLabels.codeReview();
+ allProjectsConfig.getLabelSections().put(cr.getName(), cr);
+ allProjectsConfig.commit(md);
+ }
+ }
- local = projectConfigFactory.create(localKey);
- local.load(newRepository(localKey));
- add(local);
- local.getProject().setParentName(parentKey);
+ repoManager.createRepository(parentKey).close();
+ repoManager.createRepository(localKey).close();
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(localKey)) {
+ ProjectConfig newLocal = projectConfigFactory.create(localKey);
+ newLocal.load(md);
+ newLocal.getProject().setParentName(parentKey);
+ newLocal.commit(md);
+ }
requestContext.setContext(() -> null);
-
- changeControlFactory = injector.getInstance(ChangeControl.Factory.class);
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
requestContext.setContext(null);
}
@Test
public void ownerProject() throws Exception {
- allow(local, OWNER, ADMIN, "refs/*");
-
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(ADMIN))
+ .update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void denyOwnerProject() throws Exception {
- allow(local, OWNER, ADMIN, "refs/*");
- deny(local, OWNER, DEVS, "refs/*");
-
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(ADMIN))
+ .add(deny(OWNER).ref("refs/*").group(DEVS))
+ .update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void blockOwnerProject() throws Exception {
- allow(local, OWNER, ADMIN, "refs/*");
- block(local, OWNER, DEVS, "refs/*");
-
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(ADMIN))
+ .add(block(OWNER).ref("refs/*").group(DEVS))
+ .update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void allRefsAreVisibleForRegularProject() throws Exception {
- allow(local, READ, DEVS, "refs/*");
- allow(local, READ, DEVS, "refs/groups/*");
- allow(local, READ, DEVS, "refs/users/default");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(DEVS))
+ .add(allow(READ).ref("refs/groups/*").group(DEVS))
+ .add(allow(READ).ref("refs/users/default").group(DEVS))
+ .update();
- assertAllRefsAreVisible(user(local, DEVS));
+ assertAllRefsAreVisible(user(localKey, DEVS));
}
@Test
public void allRefsAreNotVisibleForAllUsers() throws Exception {
- ProjectConfig allUsers = projectConfigFactory.create(allUsersName);
- allUsers.load(newRepository(allUsersName));
-
- allow(allUsers, READ, DEVS, "refs/*");
- allow(allUsers, READ, DEVS, "refs/groups/*");
- allow(allUsers, READ, DEVS, "refs/users/default");
+ projectOperations
+ .project(allUsersName)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(DEVS))
+ .add(allow(READ).ref("refs/groups/*").group(DEVS))
+ .add(allow(READ).ref("refs/users/default").group(DEVS))
+ .update();
- assertAllRefsAreNotVisible(user(allUsers, DEVS));
+ assertAllRefsAreNotVisible(user(allUsersName, DEVS));
}
@Test
public void userRefIsVisibleForInternalUser() throws Exception {
- internalUser(local).controlForRef("refs/users/default").asForRef().check(RefPermission.READ);
+ internalUser(localKey).controlForRef("refs/users/default").asForRef().check(RefPermission.READ);
}
@Test
public void branchDelegation1() throws Exception {
- allow(local, OWNER, ADMIN, "refs/*");
- allow(local, OWNER, DEVS, "refs/heads/x/*");
-
- ProjectControl uDev = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(ADMIN))
+ .add(allow(OWNER).ref("refs/heads/x/*").group(DEVS))
+ .update();
+
+ ProjectControl uDev = user(localKey, DEVS);
assertNotOwner(uDev);
assertOwner("refs/heads/x/*", uDev);
@@ -391,12 +339,16 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void branchDelegation2() throws Exception {
- allow(local, OWNER, ADMIN, "refs/*");
- allow(local, OWNER, DEVS, "refs/heads/x/*");
- allow(local, OWNER, fixers, "refs/heads/x/y/*");
- doNotInherit(local, OWNER, "refs/heads/x/y/*");
-
- ProjectControl uDev = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(ADMIN))
+ .add(allow(OWNER).ref("refs/heads/x/*").group(DEVS))
+ .add(allow(OWNER).ref("refs/heads/x/y/*").group(fixers))
+ .setExclusiveGroup(permissionKey(OWNER).ref("refs/heads/x/y/*"), true)
+ .update();
+
+ ProjectControl uDev = user(localKey, DEVS);
assertNotOwner(uDev);
assertOwner("refs/heads/x/*", uDev);
@@ -405,7 +357,7 @@ public class RefControlTest extends GerritBaseTests {
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
- ProjectControl uFix = user(local, fixers);
+ ProjectControl uFix = user(localKey, fixers);
assertNotOwner(uFix);
assertOwner("refs/heads/x/y/*", uFix);
@@ -418,53 +370,41 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void inheritRead_SingleBranchDeniesUpload() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
- allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
- doNotInherit(local, READ, "refs/heads/foobar");
- doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(PUSH).ref("refs/for/refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/foobar").group(REGISTERED_USERS))
+ .setExclusiveGroup(permissionKey(READ).ref("refs/heads/foobar"), true)
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/for/refs/heads/foobar"), true)
+ .update();
+
+ ProjectControl u = user(localKey);
assertCanUpload(u);
assertCreateChange("refs/heads/master", u);
assertCannotCreateChange("refs/heads/foobar", u);
}
@Test
- public void blockPushDrafts() throws Exception {
- allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
- block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
- allow(local, PUSH, REGISTERED_USERS, "refs/drafts/*");
-
- ProjectControl u = user(local);
- assertCreateChange("refs/heads/master", u);
- assertThat(u.controlForRef("refs/drafts/master").canPerform(PUSH)).isFalse();
- }
-
- @Test
- public void blockPushDraftsUnblockAdmin() throws Exception {
- block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
- allow(parent, PUSH, ADMIN, "refs/drafts/*");
- allow(local, PUSH, REGISTERED_USERS, "refs/drafts/*");
-
- ProjectControl u = user(local);
- ProjectControl a = user(local, "a", ADMIN);
-
- assertThat(a.controlForRef("refs/drafts/master").canPerform(PUSH))
- .named("push is allowed")
- .isTrue();
- assertThat(u.controlForRef("refs/drafts/master").canPerform(PUSH))
- .named("push is not allowed")
- .isFalse();
- }
-
- @Test
public void inheritRead_SingleBranchDoesNotOverrideInherited() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
- allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(PUSH).ref("refs/for/refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/foobar").group(REGISTERED_USERS))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCanUpload(u);
assertCreateChange("refs/heads/master", u);
assertCreateChange("refs/heads/foobar", u);
@@ -472,31 +412,50 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void inheritDuplicateSections() throws Exception {
- allow(parent, READ, ADMIN, "refs/*");
- allow(local, READ, DEVS, "refs/heads/*");
- assertCanAccess(user(local, "a", ADMIN));
-
- local = projectConfigFactory.create(localKey);
- local.load(newRepository(localKey));
- local.getProject().setParentName(parentKey);
- allow(local, READ, DEVS, "refs/*");
- assertCanAccess(user(local, "d", DEVS));
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(ADMIN))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(DEVS))
+ .update();
+ assertCanAccess(user(localKey, "a", ADMIN));
+ assertCanAccess(user(localKey, "d", DEVS));
}
@Test
public void inheritRead_OverrideWithDeny() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- deny(local, READ, REGISTERED_USERS, "refs/*");
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(deny(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
- assertAccessDenied(user(local));
+ assertAccessDenied(user(localKey));
}
@Test
public void inheritRead_AppendWithDenyOfRef() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- deny(local, READ, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(deny(READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCanAccess(u);
assertCanRead("refs/master", u);
assertCanRead("refs/tags/foobar", u);
@@ -505,11 +464,19 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void inheritRead_OverridesAndDeniesOfRef() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- deny(local, READ, REGISTERED_USERS, "refs/*");
- allow(local, READ, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(deny(READ).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(READ).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCanAccess(u);
assertCannotRead("refs/foobar", u);
assertCannotRead("refs/tags/foobar", u);
@@ -518,11 +485,19 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void inheritSubmit_OverridesAndDeniesOfRef() throws Exception {
- allow(parent, SUBMIT, REGISTERED_USERS, "refs/*");
- deny(local, SUBMIT, REGISTERED_USERS, "refs/*");
- allow(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(deny(SUBMIT).ref("refs/*").group(REGISTERED_USERS))
+ .add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCannotSubmit("refs/foobar", u);
assertCannotSubmit("refs/tags/foobar", u);
assertCanSubmit("refs/heads/foobar", u);
@@ -530,46 +505,73 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void cannotUploadToAnyRef() throws Exception {
- allow(parent, READ, REGISTERED_USERS, "refs/*");
- allow(local, READ, DEVS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/for/refs/heads/*");
-
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/*").group(DEVS))
+ .add(allow(PUSH).ref("refs/for/refs/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCannotUpload(u);
assertCannotCreateChange("refs/heads/master", u);
}
@Test
public void usernamePatternCanUploadToAnyRef() throws Exception {
- allow(local, PUSH, REGISTERED_USERS, "refs/heads/users/${username}/*");
- ProjectControl u = user(local, "a-registered-user");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/users/${username}/*").group(REGISTERED_USERS))
+ .update();
+ ProjectControl u = user(localKey, "a-registered-user");
assertCanUpload(u);
}
@Test
public void usernamePatternRegExpCanUploadToAnyRef() throws Exception {
- allow(local, PUSH, REGISTERED_USERS, "^refs/heads/users/${username}/(public|private)/.+");
- ProjectControl u = user(local, "a-registered-user");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(
+ allow(PUSH)
+ .ref("^refs/heads/users/${username}/(public|private)/.+")
+ .group(REGISTERED_USERS))
+ .update();
+ ProjectControl u = user(localKey, "a-registered-user");
assertCanUpload(u);
assertCanUpdate("refs/heads/users/a-registered-user/private/a", u);
}
@Test
public void usernamePatternNonRegex() throws Exception {
- allow(local, READ, DEVS, "refs/sb/${username}/heads/*");
-
- ProjectControl u = user(local, "u", DEVS);
- ProjectControl d = user(local, "d", DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/sb/${username}/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, "u", DEVS);
+ ProjectControl d = user(localKey, "d", DEVS);
assertCannotRead("refs/sb/d/heads/foobar", u);
assertCanRead("refs/sb/d/heads/foobar", d);
}
@Test
public void usernamePatternWithRegex() throws Exception {
- allow(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
-
- ProjectControl u = user(local, "d.v", DEVS);
- ProjectControl d = user(local, "dev", DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("^refs/sb/${username}/heads/.*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, "d.v", DEVS);
+ ProjectControl d = user(localKey, "dev", DEVS);
assertCanAccess(u);
assertCanAccess(d);
assertCannotRead("refs/sb/dev/heads/foobar", u);
@@ -578,48 +580,80 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void usernameEmailPatternWithRegex() throws Exception {
- allow(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
-
- ProjectControl u = user(local, "d.v@ger-rit.org", DEVS);
- ProjectControl d = user(local, "dev@ger-rit.org", DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("^refs/sb/${username}/heads/.*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, "d.v@ger-rit.org", DEVS);
+ ProjectControl d = user(localKey, "dev@ger-rit.org", DEVS);
assertCannotRead("refs/sb/dev@ger-rit.org/heads/foobar", u);
assertCanRead("refs/sb/dev@ger-rit.org/heads/foobar", d);
}
@Test
public void sortWithRegex() throws Exception {
- allow(local, READ, DEVS, "^refs/heads/.*");
- allow(parent, READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
-
- ProjectControl u = user(local, DEVS);
- ProjectControl d = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("^refs/heads/.*").group(DEVS))
+ .update();
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(READ).ref("^refs/heads/.*-QA-.*").group(ANONYMOUS_USERS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
+ ProjectControl d = user(localKey, DEVS);
assertCanRead("refs/heads/foo-QA-bar", u);
assertCanRead("refs/heads/foo-QA-bar", d);
}
@Test
public void blockRule_ParentBlocksChild() throws Exception {
- allow(local, PUSH, DEVS, "refs/tags/*");
- block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/tags/*").group(DEVS))
+ .update();
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
+ .update();
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/tags/V10", u);
}
@Test
public void blockRule_ParentBlocksChildEvenIfAlreadyBlockedInChild() throws Exception {
- allow(local, PUSH, DEVS, "refs/tags/*");
- block(local, PUSH, ANONYMOUS_USERS, "refs/tags/*");
- block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/tags/*").group(DEVS))
+ .add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/tags/V10", u);
}
@Test
public void blockPartialRangeLocally() throws Exception {
- block(local, LABEL + "Code-Review", +1, +2, DEVS, "refs/heads/master");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/master").group(DEVS).range(+1, +2))
+ .update();
- ProjectControl u = user(local, DEVS);
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(2, range);
@@ -627,10 +661,18 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void blockLabelRange_ParentBlocksChild() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
- block(parent, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-1, range);
@@ -641,11 +683,19 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void blockLabelRange_ParentBlocksChildEvenIfAlreadyBlockedInChild() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
- block(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
- block(parent, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-1, range);
@@ -656,197 +706,317 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void inheritSubmit_AllowInChildDoesntAffectUnblockInParent() throws Exception {
- block(parent, SUBMIT, ANONYMOUS_USERS, "refs/heads/*");
- allow(parent, SUBMIT, REGISTERED_USERS, "refs/heads/*");
- allow(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local);
- assertThat(u.controlForRef("refs/heads/master").canPerform(SUBMIT))
- .named("submit is allowed")
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(SUBMIT).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
+ .update();
+
+ ProjectControl u = user(localKey);
+ assertWithMessage("submit is allowed")
+ .that(u.controlForRef("refs/heads/master").canPerform(SUBMIT))
.isTrue();
}
@Test
public void unblockNoForce() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockForce() throws Exception {
- PermissionRule r = block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- r.setForce(true);
- allow(local, PUSH, DEVS, "refs/heads/*").setForce(true);
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS).force(true))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCanForceUpdate("refs/heads/master", u);
}
@Test
public void unblockRead_NotPossible() throws Exception {
- block(parent, READ, ANONYMOUS_USERS, "refs/*");
- allow(parent, READ, ADMIN, "refs/*");
- allow(local, READ, ANONYMOUS_USERS, "refs/*");
- allow(local, READ, ADMIN, "refs/*");
- ProjectControl u = user(local);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(READ).ref("refs/*").group(ANONYMOUS_USERS))
+ .add(allow(READ).ref("refs/*").group(ADMIN))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(ANONYMOUS_USERS))
+ .add(allow(READ).ref("refs/*").group(ADMIN))
+ .update();
+
+ ProjectControl u = user(localKey);
assertCannotRead("refs/heads/master", u);
}
@Test
public void unblockForceWithAllowNoForce_NotPossible() throws Exception {
- PermissionRule r = block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- r.setForce(true);
- allow(local, PUSH, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotForceUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRef_Fails() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefInLocal_Fails() throws Exception {
- block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefWithExclusiveFlag() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/master", true);
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockVoteMoreSpecificRefWithExclusiveFlag() throws Exception {
- String perm = LABEL + "Code-Review";
-
- block(local, perm, -1, 1, ANONYMOUS_USERS, "refs/heads/*");
- allowExclusive(local, perm, -2, 2, DEVS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
- PermissionRange range = u.controlForRef("refs/heads/master").getRange(perm);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .add(allowLabel("Code-Review").ref("refs/heads/master").group(DEVS).range(-2, 2))
+ .setExclusiveGroup(labelPermissionKey("Code-Review").ref("refs/heads/master"), true)
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-2, range);
}
@Test
public void unblockFromParentDoesNotAffectChild() throws Exception {
- allow(parent, PUSH, DEVS, "refs/heads/master", true);
- block(local, PUSH, DEVS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/master").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockFromParentDoesNotAffectChildDifferentGroups() throws Exception {
- allow(parent, PUSH, DEVS, "refs/heads/master", true);
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/master").group(ANONYMOUS_USERS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefInLocalWithExclusiveFlag_Fails() throws Exception {
- block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/master", true);
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void blockMoreSpecificRefWithinProject() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/secret");
- allow(local, PUSH, DEVS, "refs/heads/*", true);
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/secret").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS))
+ .setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/*"), true)
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/secret", u);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockOtherPermissionWithMoreSpecificRefAndExclusiveFlag_Fails() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, DEVS, "refs/heads/master");
- allow(local, SUBMIT, DEVS, "refs/heads/master", true);
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/master").group(DEVS))
+ .add(allow(SUBMIT).ref("refs/heads/master").group(DEVS))
+ .setExclusiveGroup(permissionKey(SUBMIT).ref("refs/heads/master"), true)
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockLargerScope_Fails() throws Exception {
- block(local, PUSH, ANONYMOUS_USERS, "refs/heads/master");
- allow(local, PUSH, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/master").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockInLocal_Fails() throws Exception {
- block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, PUSH, fixers, "refs/heads/*");
-
- ProjectControl f = user(local, fixers);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(PUSH).ref("refs/heads/*").group(fixers))
+ .update();
+
+ ProjectControl f = user(localKey, fixers);
assertCannotUpdate("refs/heads/master", f);
}
@Test
public void unblockInParentBlockInLocal() throws Exception {
- block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
- allow(parent, PUSH, DEVS, "refs/heads/*");
- block(local, PUSH, DEVS, "refs/heads/*");
-
- ProjectControl d = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(PUSH).ref("refs/heads/*").group(DEVS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(PUSH).ref("refs/heads/*").group(DEVS))
+ .update();
+
+ ProjectControl d = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", d);
}
@Test
public void unblockForceEditTopicName() throws Exception {
- block(local, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
-
- ProjectControl u = user(local, DEVS);
- assertThat(u.controlForRef("refs/heads/master").canForceEditTopicName())
- .named("u can edit topic name")
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(block(EDIT_TOPIC_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(DEVS).force(true))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
+ assertWithMessage("u can edit topic name")
+ .that(u.controlForRef("refs/heads/master").canForceEditTopicName())
.isTrue();
}
@Test
public void unblockInLocalForceEditTopicName_Fails() throws Exception {
- block(parent, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
-
- ProjectControl u = user(local, REGISTERED_USERS);
- assertThat(u.controlForRef("refs/heads/master").canForceEditTopicName())
- .named("u can't edit topic name")
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(EDIT_TOPIC_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(DEVS).force(true))
+ .update();
+
+ ProjectControl u = user(localKey, REGISTERED_USERS);
+ assertWithMessage("u can't edit topic name")
+ .that(u.controlForRef("refs/heads/master").canForceEditTopicName())
.isFalse();
}
@Test
public void unblockRange() throws Exception {
- block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, +1))
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-2, range);
assertCanVote(2, range);
@@ -854,10 +1024,14 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unblockRangeOnMoreSpecificRef_Fails() throws Exception {
- block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/master");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, +1))
+ .add(allowLabel("Code-Review").ref("refs/heads/master").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-2, range);
assertCannotVote(2, range);
@@ -865,10 +1039,15 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unblockRangeOnLargerScope_Fails() throws Exception {
- block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/master");
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(
+ blockLabel("Code-Review").ref("refs/heads/master").group(ANONYMOUS_USERS).range(-1, +1))
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-2, range);
assertCannotVote(2, range);
@@ -876,9 +1055,13 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void nonconfiguredCannotVote() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
- ProjectControl u = user(local, REGISTERED_USERS);
+ ProjectControl u = user(localKey, REGISTERED_USERS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-1, range);
assertCannotVote(1, range);
@@ -886,10 +1069,18 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unblockInLocalRange_Fails() throws Exception {
- block(parent, LABEL + "Code-Review", -1, 1, ANONYMOUS_USERS, "refs/heads/*");
- allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-2, range);
assertCannotVote(2, range);
@@ -897,9 +1088,13 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unblockRangeForChangeOwner() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
+ .update();
- ProjectControl u = user(local, DEVS);
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review", true);
assertCanVote(-2, range);
@@ -908,9 +1103,13 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unblockRangeForNotChangeOwner() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
+ .update();
- ProjectControl u = user(local, DEVS);
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-2, range);
assertCannotVote(2, range);
@@ -918,9 +1117,13 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void blockChangeOwnerVote() throws Exception {
- block(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
+ .update();
- ProjectControl u = user(local, DEVS);
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCannotVote(-2, range);
assertCannotVote(2, range);
@@ -928,10 +1131,14 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unionOfPermissibleVotes() throws Exception {
- allow(local, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
- allow(local, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-1, +1))
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-2, range);
assertCanVote(2, range);
@@ -939,10 +1146,14 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unionOfPermissibleVotesPermissionOrder() throws Exception {
- allow(local, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
- allow(local, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-1, +1))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-2, range);
assertCanVote(2, range);
@@ -950,11 +1161,19 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void unionOfBlockedVotes() throws Exception {
- allow(parent, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
- block(parent, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
- block(local, LABEL + "Code-Review", -2, +1, REGISTERED_USERS, "refs/heads/*");
-
- ProjectControl u = user(local, DEVS);
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(DEVS).range(-1, +1))
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(blockLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +1))
+ .update();
+
+ ProjectControl u = user(localKey, DEVS);
PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
assertCanVote(-1, range);
assertCannotVote(1, range);
@@ -962,10 +1181,18 @@ public class RefControlTest extends GerritBaseTests {
@Test
public void blockOwner() throws Exception {
- block(parent, OWNER, ANONYMOUS_USERS, "refs/*");
- allow(local, OWNER, DEVS, "refs/*");
+ projectOperations
+ .project(parentKey)
+ .forUpdate()
+ .add(block(OWNER).ref("refs/*").group(ANONYMOUS_USERS))
+ .update();
+ projectOperations
+ .project(localKey)
+ .forUpdate()
+ .add(allow(OWNER).ref("refs/*").group(DEVS))
+ .update();
- assertThat(user(local, DEVS).isOwner()).isFalse();
+ assertThat(user(localKey, DEVS).isOwner()).isFalse();
}
@Test
@@ -977,14 +1204,16 @@ public class RefControlTest extends GerritBaseTests {
RefPattern.validate("^refs/heads/review/${username}/.+");
}
- @Test(expected = InvalidNameException.class)
+ @Test
public void testValidateBadRefPatternDoubleCaret() throws Exception {
- RefPattern.validate("^^refs/*");
+ assertThrows(InvalidNameException.class, () -> RefPattern.validate("^^refs/*"));
}
- @Test(expected = InvalidNameException.class)
+ @Test
public void testValidateBadRefPatternDanglingCharacter() throws Exception {
- RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*");
+ assertThrows(
+ InvalidNameException.class,
+ () -> RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*"));
}
@Test
@@ -992,71 +1221,23 @@ public class RefControlTest extends GerritBaseTests {
RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}");
}
- private InMemoryRepository add(ProjectConfig pc) {
- List<CommentLinkInfo> commentLinks = null;
+ private ProjectState getProjectState(Project.NameKey nameKey) throws Exception {
+ return projectCache.checkedGet(nameKey, true);
+ }
+
+ private ProjectControl internalUser(Project.NameKey localKey) throws Exception {
+ return projectControlFactory.create(new InternalUser(), getProjectState(localKey));
+ }
- InMemoryRepository repo;
- try {
- repo = repoManager.createRepository(pc.getName());
- if (pc.getProject() == null) {
- pc.load(repo);
- }
- } catch (IOException | ConfigInvalidException e) {
- throw new RuntimeException(e);
- }
- all.put(
- pc.getName(),
- new ProjectState(
- projectCache,
- allProjectsName,
- allUsersName,
- repoManager,
- commentLinks,
- capabilityCollectionFactory,
- transferConfig,
- metricMaker,
- pc));
- return repo;
- }
-
- private ProjectControl internalUser(ProjectConfig local) throws Exception {
- return new ProjectControl(
- Collections.emptySet(),
- Collections.emptySet(),
- sectionSorter,
- changeControlFactory,
- permissionBackend,
- refVisibilityControl,
- repoManager,
- refFilterFactory,
- allUsersName,
- new InternalUser(),
- newProjectState(local));
- }
-
- private ProjectControl user(ProjectConfig local, AccountGroup.UUID... memberOf) {
- return user(local, null, memberOf);
+ private ProjectControl user(Project.NameKey localKey, AccountGroup.UUID... memberOf)
+ throws Exception {
+ return user(localKey, null, memberOf);
}
private ProjectControl user(
- ProjectConfig local, @Nullable String name, AccountGroup.UUID... memberOf) {
- return new ProjectControl(
- Collections.emptySet(),
- Collections.emptySet(),
- sectionSorter,
- changeControlFactory,
- permissionBackend,
- refVisibilityControl,
- repoManager,
- refFilterFactory,
- allUsersName,
- new MockUser(name, memberOf),
- newProjectState(local));
- }
-
- private ProjectState newProjectState(ProjectConfig local) {
- add(local);
- return all.get(local.getProject().getNameKey());
+ Project.NameKey localKey, @Nullable String name, AccountGroup.UUID... memberOf)
+ throws Exception {
+ return projectControlFactory.create(new MockUser(name, memberOf), getProjectState(localKey));
}
private static class MockUser extends CurrentUser {
diff --git a/javatests/com/google/gerrit/server/plugins/AutoRegisterModulesTest.java b/javatests/com/google/gerrit/server/plugins/AutoRegisterModulesTest.java
index 0ffd4acbc1..55c9bc36e3 100644
--- a/javatests/com/google/gerrit/server/plugins/AutoRegisterModulesTest.java
+++ b/javatests/com/google/gerrit/server/plugins/AutoRegisterModulesTest.java
@@ -14,10 +14,11 @@
package com.google.gerrit.server.plugins;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.annotations.Export;
@@ -31,26 +32,17 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.jar.Manifest;
-import org.easymock.EasyMockSupport;
import org.junit.Test;
public class AutoRegisterModulesTest {
@Test
public void shouldRegisterSshCommand() throws InvalidPluginException {
- EasyMockSupport ems = new EasyMockSupport();
- ModuleGenerator sshModule = ems.createNiceMock(ModuleGenerator.class);
+ ModuleGenerator sshModule = mock(ModuleGenerator.class);
+ PluginGuiceEnvironment env = mock(PluginGuiceEnvironment.class);
- PluginGuiceEnvironment env = ems.createNiceMock(PluginGuiceEnvironment.class);
- expect(env.hasSshModule()).andReturn(true);
- expect(env.newSshModuleGenerator()).andReturn(sshModule);
-
- sshModule.setPluginName("test_plugin_name");
- expectLastCall();
- sshModule.export(anyObject(Export.class), eq(TestSshCommand.class));
- expectLastCall();
-
- ems.replayAll();
+ when(env.hasSshModule()).thenReturn(true);
+ when(env.newSshModuleGenerator()).thenReturn(sshModule);
PluginContentScanner scanner = new TestPluginContextScanner();
ClassLoader classLoader = this.getClass().getClassLoader();
@@ -59,7 +51,8 @@ public class AutoRegisterModulesTest {
new AutoRegisterModules("test_plugin_name", env, scanner, classLoader);
objectUnderTest.discover();
- ems.verifyAll();
+ verify(sshModule).setPluginName("test_plugin_name");
+ verify(sshModule).export(any(Export.class), eq(TestSshCommand.class));
}
@Export(value = "test")
diff --git a/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java b/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
index cf6d50fab6..61a2d2fb3c 100644
--- a/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
+++ b/javatests/com/google/gerrit/server/project/CommitsCollectionTest.java
@@ -14,27 +14,31 @@
package com.google.gerrit.server.project;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.common.data.Permission.READ;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.restapi.project.CommitsCollection;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.InMemoryTestEnvironment;
import com.google.inject.Inject;
@@ -44,12 +48,13 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
/** Unit tests for {@link CommitsCollection}. */
-public class CommitsCollectionTest extends GerritBaseTests {
+public class CommitsCollectionTest {
@Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment();
@Inject private AccountManager accountManager;
@@ -58,10 +63,10 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
@Inject protected AllProjectsName allProjects;
@Inject private CommitsCollection commits;
- @Inject private ProjectConfig.Factory projectConfigFactory;
+ @Inject private ProjectOperations projectOperations;
private TestRepository<InMemoryRepository> repo;
- private ProjectConfig project;
+ private Project.NameKey project;
@Before
public void setUp() throws Exception {
@@ -69,17 +74,22 @@ public class CommitsCollectionTest extends GerritBaseTests {
Account.Id user = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
testEnvironment.setApiUser(user);
+ project = projectOperations.newProject().create();
+ repo = new TestRepository<>(repoManager.openRepository(project));
+ }
- Project.NameKey name = new Project.NameKey("project");
- InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
- project = projectConfigFactory.create(name);
- project.load(inMemoryRepo);
- repo = new TestRepository<>(inMemoryRepo);
+ @After
+ public void tearDown() {
+ repo.getRepository().close();
}
@Test
public void canReadCommitWhenAllRefsVisible() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/*");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
ObjectId id = repo.branch("master").commit().create();
ProjectState state = readProjectState();
RevWalk rw = repo.getRevWalk();
@@ -90,8 +100,12 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Test
public void canReadCommitIfTwoRefsVisible() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
- allow(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/branch1").group(REGISTERED_USERS))
+ .add(allow(READ).ref("refs/heads/branch2").group(REGISTERED_USERS))
+ .update();
ObjectId id1 = repo.branch("branch1").commit().create();
ObjectId id2 = repo.branch("branch2").commit().create();
@@ -106,8 +120,12 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Test
public void canReadCommitIfRefVisible() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
- deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/branch1").group(REGISTERED_USERS))
+ .add(deny(READ).ref("refs/heads/branch2").group(REGISTERED_USERS))
+ .update();
ObjectId id1 = repo.branch("branch1").commit().create();
ObjectId id2 = repo.branch("branch2").commit().create();
@@ -122,8 +140,12 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Test
public void canReadCommitIfReachableFromVisibleRef() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
- deny(project, READ, REGISTERED_USERS, "refs/heads/branch2");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/branch1").group(REGISTERED_USERS))
+ .add(deny(READ).ref("refs/heads/branch2").group(REGISTERED_USERS))
+ .update();
RevCommit parent1 = repo.commit().create();
repo.branch("branch1").commit().parent(parent1).create();
@@ -140,7 +162,11 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Test
public void cannotReadAfterRollbackWithRestrictedRead() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/heads/branch1");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/heads/branch1").group(REGISTERED_USERS))
+ .update();
RevCommit parent1 = repo.commit().create();
ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
@@ -159,7 +185,11 @@ public class CommitsCollectionTest extends GerritBaseTests {
@Test
public void canReadAfterRollbackWithAllRefsVisible() throws Exception {
- allow(project, READ, REGISTERED_USERS, "refs/*");
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
RevCommit parent1 = repo.commit().create();
ObjectId id1 = repo.branch("branch1").commit().parent(parent1).create();
@@ -177,41 +207,19 @@ public class CommitsCollectionTest extends GerritBaseTests {
}
private ProjectState readProjectState() throws Exception {
- return projectCache.get(project.getName());
- }
-
- protected void allow(ProjectConfig project, String permission, AccountGroup.UUID id, String ref)
- throws Exception {
- Util.allow(project, permission, id, ref);
- saveProjectConfig(project);
- }
-
- protected void deny(ProjectConfig project, String permission, AccountGroup.UUID id, String ref)
- throws Exception {
- Util.deny(project, permission, id, ref);
- saveProjectConfig(project);
- }
-
- protected void saveProjectConfig(ProjectConfig cfg) throws Exception {
- try (MetaDataUpdate md = metaDataUpdateFactory.create(cfg.getName())) {
- cfg.commit(md);
- }
- projectCache.evict(cfg.getProject());
+ return projectCache.get(project);
}
private void setUpPermissions() throws Exception {
- ImmutableList<AccountGroup.UUID> admins = getAdmins();
-
// Remove read permissions for all users besides admin, because by default
// Anonymous user group has ALLOW READ permission in refs/*.
// This method is idempotent, so is safe to call on every test setup.
- ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
- for (AccessSection sec : pc.getAccessSections()) {
- sec.removePermission(Permission.READ);
- }
- for (AccountGroup.UUID admin : admins) {
- allow(pc, Permission.READ, admin, "refs/*");
- }
+ TestProjectUpdate.Builder u = projectOperations.allProjectsForUpdate();
+ projectCache.checkedGet(allProjects).getConfig().getAccessSectionNames().stream()
+ .filter(sec -> sec.startsWith(R_REFS))
+ .forEach(sec -> u.remove(permissionKey(Permission.READ).ref(sec)));
+ getAdmins().forEach(admin -> u.add(allow(Permission.READ).ref("refs/*").group(admin)));
+ u.update();
}
private ImmutableList<AccountGroup.UUID> getAdmins() {
diff --git a/javatests/com/google/gerrit/server/project/GroupListTest.java b/javatests/com/google/gerrit/server/project/GroupListTest.java
index 08aca9f987..518f85dc48 100644
--- a/javatests/com/google/gerrit/server/project/GroupListTest.java
+++ b/javatests/com/google/gerrit/server/project/GroupListTest.java
@@ -14,22 +14,19 @@
package com.google.gerrit.server.project;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.testing.GerritBaseTests;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
@@ -37,8 +34,8 @@ import java.util.Set;
import org.junit.Before;
import org.junit.Test;
-public class GroupListTest extends GerritBaseTests {
- private static final Project.NameKey PROJECT = new Project.NameKey("project");
+public class GroupListTest {
+ private static final Project.NameKey PROJECT = Project.nameKey("project");
private static final String TEXT =
"# UUID \tGroup Name\n"
+ "#\n"
@@ -49,14 +46,13 @@ public class GroupListTest extends GerritBaseTests {
@Before
public void setup() throws IOException {
- ValidationError.Sink sink = createNiceMock(ValidationError.Sink.class);
- replay(sink);
+ ValidationError.Sink sink = mock(ValidationError.Sink.class);
groupList = GroupList.parse(PROJECT, TEXT, sink);
}
@Test
public void byUUID() throws Exception {
- AccountGroup.UUID uuid = new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
+ AccountGroup.UUID uuid = AccountGroup.uuid("d96b998f8a66ff433af50befb975d0e2bb6e0999");
GroupReference groupReference = groupList.byUUID(uuid);
@@ -66,7 +62,7 @@ public class GroupListTest extends GerritBaseTests {
@Test
public void put() {
- AccountGroup.UUID uuid = new AccountGroup.UUID("abc");
+ AccountGroup.UUID uuid = AccountGroup.uuid("abc");
GroupReference groupReference = new GroupReference(uuid, "Hutzliputz");
groupList.put(uuid, groupReference);
@@ -81,7 +77,7 @@ public class GroupListTest extends GerritBaseTests {
Collection<GroupReference> result = groupList.references();
assertEquals(2, result.size());
- AccountGroup.UUID uuid = new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
+ AccountGroup.UUID uuid = AccountGroup.uuid("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
GroupReference expected = new GroupReference(uuid, "Administrators");
assertTrue(result.contains(expected));
@@ -92,27 +88,24 @@ public class GroupListTest extends GerritBaseTests {
Set<AccountGroup.UUID> result = groupList.uuids();
assertEquals(2, result.size());
- AccountGroup.UUID expected = new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
+ AccountGroup.UUID expected = AccountGroup.uuid("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
assertTrue(result.contains(expected));
}
@Test
public void validationError() throws Exception {
- ValidationError.Sink sink = createMock(ValidationError.Sink.class);
- sink.error(anyObject(ValidationError.class));
- expectLastCall().times(2);
- replay(sink);
+ ValidationError.Sink sink = mock(ValidationError.Sink.class);
groupList = GroupList.parse(PROJECT, TEXT.replace("\t", " "), sink);
- verify(sink);
+ verify(sink, times(2)).error(any(ValidationError.class));
}
@Test
public void retainAll() throws Exception {
- AccountGroup.UUID uuid = new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
+ AccountGroup.UUID uuid = AccountGroup.uuid("d96b998f8a66ff433af50befb975d0e2bb6e0999");
groupList.retainUUIDs(Collections.singleton(uuid));
assertNotNull(groupList.byUUID(uuid));
- assertNull(groupList.byUUID(new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310")));
+ assertNull(groupList.byUUID(AccountGroup.uuid("ebe31c01aec2c9ac3b3c03e87a47450829ff4310")));
}
@Test
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 3436153b41..0dd643627b 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -16,7 +16,7 @@ package com.google.gerrit.server.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.reviewdb.client.BooleanProjectConfig.REQUIRE_CHANGE_ID;
+import static com.google.gerrit.entities.BooleanProjectConfig.REQUIRE_CHANGE_ID;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -26,18 +26,17 @@ import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.testing.Util;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.server.project.testing.TestLabels;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -62,9 +61,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-public class ProjectConfigTest extends GerritBaseTests {
+public class ProjectConfigTest {
private static final String LABEL_SCORES_CONFIG =
- " copyMinScore = "
+ " copyAnyScore = "
+ + !LabelType.DEF_COPY_ANY_SCORE
+ + "\n"
+ + " copyMinScore = "
+ !LabelType.DEF_COPY_MIN_SCORE
+ "\n"
+ " copyMaxScore = "
@@ -88,8 +90,8 @@ public class ProjectConfigTest extends GerritBaseTests {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final GroupReference developers =
- new GroupReference(new AccountGroup.UUID("X"), "Developers");
- private final GroupReference staff = new GroupReference(new AccountGroup.UUID("Y"), "Staff");
+ new GroupReference(AccountGroup.uuid("X"), "Developers");
+ private final GroupReference staff = new GroupReference(AccountGroup.uuid("Y"), "Staff");
private SitePaths sitePaths;
private ProjectConfig.Factory factory;
@@ -260,6 +262,7 @@ public class ProjectConfigTest extends GerritBaseTests {
ProjectConfig cfg = read(rev);
Map<String, LabelType> labels = cfg.getLabelSections();
LabelType type = labels.entrySet().iterator().next().getValue();
+ assertThat(type.isCopyAnyScore()).isNotEqualTo(LabelType.DEF_COPY_ANY_SCORE);
assertThat(type.isCopyMinScore()).isNotEqualTo(LabelType.DEF_COPY_MIN_SCORE);
assertThat(type.isCopyMaxScore()).isNotEqualTo(LabelType.DEF_COPY_MAX_SCORE);
assertThat(type.isCopyAllScoresOnMergeFirstParentUpdate())
@@ -319,17 +322,17 @@ public class ProjectConfigTest extends GerritBaseTests {
+ "\tsubmit = group Staff\n"
+ " upload = group Developers\n"
+ " read = group Developers\n"
- + "[accounts]\n"
- + " sameGroupVisibility = group Staff\n"
- + "[contributor-agreement \"Individual\"]\n"
- + " description = A new description\n"
- + " accepted = group Staff\n"
- + " agreementUrl = http://www.example.com/agree\n"
- + "\texcludeProjects = ^/theirproject\n"
+ "[label \"CustomLabel\"]\n"
+ LABEL_SCORES_CONFIG
+ "\tfunction = MaxWithBlock\n" // label gets this function when it is created
- + "\tdefaultValue = 0\n"); // label gets this value when it is created
+ + "\tdefaultValue = 0\n" // label gets this value when it is created
+ + "[accounts]\n"
+ + "\tsameGroupVisibility = group Staff\n"
+ + "[contributor-agreement \"Individual\"]\n"
+ + "\tdescription = A new description\n"
+ + "\tagreementUrl = http://www.example.com/agree\n"
+ + "\taccepted = group Staff\n"
+ + "\texcludeProjects = ^/theirproject\n");
}
@Test
@@ -341,11 +344,11 @@ public class ProjectConfigTest extends GerritBaseTests {
cfg.getLabelSections()
.put(
"My-Label",
- Util.category(
+ TestLabels.label(
"My-Label",
- Util.value(-1, "Negative"),
- Util.value(0, "No score"),
- Util.value(1, "Positive")));
+ TestLabels.value(-1, "Negative"),
+ TestLabels.value(0, "No score"),
+ TestLabels.value(1, "Positive")));
rev = commit(cfg);
assertThat(text(rev, "project.config"))
.isEqualTo(
@@ -422,7 +425,7 @@ public class ProjectConfigTest extends GerritBaseTests {
@Test
public void readUnexistingPluginConfig() throws Exception {
- ProjectConfig cfg = factory.create(new Project.NameKey("test"));
+ ProjectConfig cfg = factory.create(Project.nameKey("test"));
cfg.load(db);
PluginConfig pluginCfg = cfg.getPluginConfig("somePlugin");
assertThat(pluginCfg.getNames()).isEmpty();
@@ -625,7 +628,7 @@ public class ProjectConfigTest extends GerritBaseTests {
@Test
public void readOtherProjectIgnoresAllProjectsBaseConfig() throws Exception {
- ProjectConfig cfg = factory.create(new Project.NameKey("test"));
+ ProjectConfig cfg = factory.create(Project.nameKey("test"));
cfg.load(db);
assertThat(cfg.getProject().getBooleanConfig(REQUIRE_CHANGE_ID))
.isEqualTo(InheritableBoolean.INHERIT);
@@ -641,6 +644,118 @@ public class ProjectConfigTest extends GerritBaseTests {
.isEqualTo(InheritableBoolean.INHERIT);
}
+ @Test
+ public void accountsSectionIsUnsetIfNoSameGroupVisibilityIsSet() throws Exception {
+ RevCommit rev =
+ tr.commit()
+ .add(
+ "project.config",
+ "[commentlink \"bugzilla\"]\n"
+ + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+ + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+ + "[accounts]\n"
+ + " sameGroupVisibility = group Staff\n")
+ .create();
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ cfg.getAccountsSection().setSameGroupVisibility(ImmutableList.of());
+ rev = commit(cfg);
+ assertThat(text(rev, "project.config"))
+ .isEqualTo(
+ "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+ }
+
+ @Test
+ public void contributorSectionIsUnsetIfNoContributorAgreementIsSet() throws Exception {
+ RevCommit rev =
+ tr.commit()
+ .add(
+ "project.config",
+ "[commentlink \"bugzilla\"]\n"
+ + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+ + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+ + "[contributor-agreement \"Individual\"]\n"
+ + " accepted = group Developers\n"
+ + " accepted = group Staff\n")
+ .create();
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ ContributorAgreement section = cfg.getContributorAgreement("Individual");
+ section.setAccepted(ImmutableList.of());
+ rev = commit(cfg);
+ assertThat(text(rev, "project.config"))
+ .isEqualTo(
+ "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+ }
+
+ @Test
+ public void notifySectionIsUnsetIfNoNotificationsAreSet() throws Exception {
+ RevCommit rev =
+ tr.commit()
+ .add(
+ "project.config",
+ "[commentlink \"bugzilla\"]\n"
+ + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+ + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+ + "[notify \"name\"]\n"
+ + " email = example@example.com\n")
+ .create();
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ cfg.getNotifyConfigs().clear();
+ rev = commit(cfg);
+ assertThat(text(rev, "project.config"))
+ .isEqualTo(
+ "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+ }
+
+ @Test
+ public void commentLinkSectionIsUnsetIfNoCommentLinksAreSet() throws Exception {
+ RevCommit rev =
+ tr.commit()
+ .add(
+ "project.config",
+ "[commentlink \"bugzilla\"]\n"
+ + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+ + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+ + "[notify \"name\"]\n"
+ + " email = example@example.com\n")
+ .create();
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ cfg.getCommentLinkSections().clear();
+ rev = commit(cfg);
+ assertThat(text(rev, "project.config"))
+ .isEqualTo("[notify \"name\"]\n\temail = example@example.com\n");
+ }
+
+ @Test
+ public void pluginSectionIsUnsetIfAllPluginConfigsAreEmpty() throws Exception {
+ RevCommit rev =
+ tr.commit()
+ .add(
+ "project.config",
+ "[commentlink \"bugzilla\"]\n"
+ + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+ + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+ + "[plugin \"somePlugin\"]\n"
+ + " key = value\n")
+ .create();
+ update(rev);
+
+ ProjectConfig cfg = read(rev);
+ PluginConfig pluginCfg = cfg.getPluginConfig("somePlugin");
+ pluginCfg.unset("key");
+ rev = commit(cfg);
+ assertThat(text(rev, "project.config"))
+ .isEqualTo(
+ "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+ }
+
private Path writeDefaultAllProjectsConfig(String... lines) throws IOException {
Path dir = sitePaths.etc_dir.resolve(ALL_PROJECTS.get());
Files.createDirectories(dir);
@@ -648,7 +763,7 @@ public class ProjectConfigTest extends GerritBaseTests {
}
private ProjectConfig read(RevCommit rev) throws IOException, ConfigInvalidException {
- ProjectConfig cfg = factory.create(new Project.NameKey("test"));
+ ProjectConfig cfg = factory.create(Project.nameKey("test"));
cfg.load(db, rev);
return cfg;
}
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index a2b2866a72..e7f0812c24 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -15,13 +15,17 @@
package com.google.gerrit.server.query.account;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
@@ -47,8 +51,6 @@ import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -79,6 +81,7 @@ import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testing.GerritServerTests;
+import com.google.gerrit.testing.GerritTestName;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
@@ -93,10 +96,13 @@ import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
@Ignore
public abstract class AbstractQueryAccountsTest extends GerritServerTests {
+ @Rule public final GerritTestName testName = new GerritTestName();
+
@Inject protected Accounts accounts;
@Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdate;
@@ -253,7 +259,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
addEmails(user1, secondaryEmail);
AccountInfo user2 = newAccount("user");
- requestContext.setContext(newRequestContext(new Account.Id(user2._accountId)));
+ requestContext.setContext(newRequestContext(Account.id(user2._accountId)));
if (getSchemaVersion() < 5) {
assertMissingField(AccountField.PREFERRED_EMAIL);
@@ -340,7 +346,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccount("user");
- requestContext.setContext(newRequestContext(new Account.Id(user3._accountId)));
+ requestContext.setContext(newRequestContext(Account.id(user3._accountId)));
assertQuery("notexisting");
assertQuery("Not Existing");
@@ -575,13 +581,13 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
String[] secondaryEmails = new String[] {"dfg@example.com", "hij@example.com"};
addEmails(otherUser, secondaryEmails);
- requestContext.setContext(newRequestContext(new Account.Id(user._accountId)));
+ requestContext.setContext(newRequestContext(Account.id(user._accountId)));
List<AccountInfo> result = newQuery(otherUser.username).withSuggest(true).get();
assertThat(result.get(0).secondaryEmails).isNull();
-
- exception.expect(AuthException.class);
- newQuery(otherUser.username).withOption(ListAccountsOption.ALL_EMAILS).get();
+ assertThrows(
+ AuthException.class,
+ () -> newQuery(otherUser.username).withOption(ListAccountsOption.ALL_EMAILS).get());
}
@Test
@@ -600,7 +606,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
AccountInfo user1 = newAccountWithFullName("tester", "Test Usre");
// update account without reindex so that account index is stale
- Account.Id accountId = new Account.Id(user1._accountId);
+ Account.Id accountId = Account.id(user1._accountId);
String newName = "Test User";
try (Repository repo = repoManager.openRepository(allUsers)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, repo);
@@ -625,19 +631,22 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
public void rawDocument() throws Exception {
AccountInfo userInfo = gApi.accounts().id(admin.getAccountId().get()).get();
+ Schema<AccountState> schema = indexes.getSearchIndex().getSchema();
Optional<FieldBundle> rawFields =
indexes
.getSearchIndex()
.getRaw(
- new Account.Id(userInfo._accountId),
+ Account.id(userInfo._accountId),
QueryOptions.create(
- IndexConfig.createDefault(),
- 0,
- 1,
- indexes.getSearchIndex().getSchema().getStoredFields().keySet()));
+ IndexConfig.createDefault(), 0, 1, schema.getStoredFields().keySet()));
assertThat(rawFields).isPresent();
- assertThat(rawFields.get().getValue(AccountField.ID)).isEqualTo(userInfo._accountId);
+ if (schema.useLegacyNumericFields()) {
+ assertThat(rawFields.get().getValue(AccountField.ID)).isEqualTo(userInfo._accountId);
+ } else {
+ assertThat(Integer.valueOf(rawFields.get().getValue(AccountField.ID_STR)))
+ .isEqualTo(userInfo._accountId);
+ }
// The field EXTERNAL_ID_STATE is only supported from schema version 6.
if (getSchemaVersion() < 6) {
@@ -695,7 +704,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
in.name = name;
in.createEmptyCommit = true;
gApi.projects().create(in);
- return new Project.NameKey(name);
+ return Project.nameKey(name);
}
protected void blockRead(Project.NameKey project, GroupInfo group) throws RestApiException {
@@ -750,7 +759,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
return null;
}
- String suffix = getSanitizedMethodName();
+ String suffix = testName.getSanitizedMethodName();
if (name.contains("@")) {
return name + "." + suffix;
}
@@ -777,7 +786,7 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
}
private void addEmails(AccountInfo account, String... emails) throws Exception {
- Account.Id id = new Account.Id(account._accountId);
+ Account.Id id = Account.id(account._accountId);
for (String email : emails) {
accountManager.link(id, AuthRequest.forEmail(email));
}
@@ -802,15 +811,15 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
throws Exception {
List<AccountInfo> result = query.get();
Iterable<Integer> ids = ids(result);
- assertThat(ids)
- .named(format(query, result, accounts))
+ assertWithMessage(format(query, result, accounts))
+ .that(ids)
.containsExactlyElementsIn(ids(accounts))
.inOrder();
return result;
}
protected void assertAccounts(List<AccountState> accounts, AccountInfo... expectedAccounts) {
- assertThat(accounts.stream().map(a -> a.getAccount().getId().get()).collect(toList()))
+ assertThat(accounts.stream().map(a -> a.account().id().get()).collect(toList()))
.containsExactlyElementsIn(
Arrays.asList(expectedAccounts).stream().map(a -> a._accountId).collect(toList()));
}
@@ -860,8 +869,8 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
}
protected void assertMissingField(FieldDef<AccountState, ?> field) {
- assertThat(getSchema().hasField(field))
- .named("schema %s has field %s", getSchemaVersion(), field.getName())
+ assertWithMessage("schema %s has field %s", getSchemaVersion(), field.getName())
+ .that(getSchema().hasField(field))
.isFalse();
}
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index ba0f779e02..5c910a04f3 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -9,19 +9,20 @@ java_library(
srcs = ABSTRACT_QUERY_TEST,
visibility = ["//visibility:public"],
runtime_deps = [
+ "//java/com/google/gerrit/lucene",
"//prolog:gerrit-prolog-common",
],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
@@ -39,7 +40,7 @@ junit_tests(
":abstract_query_tests",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index ff45c086ae..558658b924 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -16,14 +16,15 @@ package com.google.gerrit.server.query.change;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.project.testing.Util.allow;
-import static com.google.gerrit.server.project.testing.Util.category;
-import static com.google.gerrit.server.project.testing.Util.value;
-import static com.google.gerrit.server.project.testing.Util.verified;
+import static com.google.gerrit.server.project.testing.TestLabels.label;
+import static com.google.gerrit.server.project.testing.TestLabels.value;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -40,11 +41,21 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.common.truth.ThrowableSubject;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AssigneeInput;
@@ -74,14 +85,6 @@ import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -93,6 +96,7 @@ import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeTriplet;
@@ -107,7 +111,6 @@ import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.testing.Util;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.util.ManualRequestContext;
@@ -159,7 +162,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Inject protected AllUsersName allUsersName;
@Inject protected BatchUpdate.Factory updateFactory;
@Inject protected ChangeInserter.Factory changeFactory;
- @Inject protected ChangeQueryBuilder queryBuilder;
+ @Inject protected Provider<ChangeQueryBuilder> queryBuilderProvider;
@Inject protected GerritApi gApi;
@Inject protected IdentifiedUser.GenericFactory userFactory;
@Inject protected ChangeIndexCollection indexes;
@@ -180,7 +183,9 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Inject protected ProjectCache projectCache;
@Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
@Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
- @Inject protected ProjectConfig.Factory projectConfigFactory;
+
+ @Inject private ProjectConfig.Factory projectConfigFactory;
+ @Inject private ProjectOperations projectOperations;
protected Injector injector;
protected LifecycleManager lifecycle;
@@ -416,13 +421,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byPrivate() throws Exception {
- if (getSchemaVersion() < 40) {
- assertMissingField(ChangeField.PRIVATE);
- assertFailingQuery(
- "is:private", "'is:private' operator is not supported by change index version");
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChange(repo), userId);
Account.Id user2 =
@@ -447,12 +445,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byWip() throws Exception {
- if (getSchemaVersion() < 42) {
- assertMissingField(ChangeField.WIP);
- assertFailingQuery("is:wip", "'is:wip' operator is not supported by change index version");
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChange(repo), userId);
@@ -469,24 +461,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
}
@Test
- public void excludeWipChangeFromReviewersDashboardsBeforeSchema42() throws Exception {
- assume().that(getSchemaVersion()).isLessThan(42);
-
- assertMissingField(ChangeField.WIP);
- assertFailingQuery("is:wip", "'is:wip' operator is not supported by change index version");
-
- Account.Id user1 = createAccount("user1");
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
- assertQuery("reviewer:" + user1, change1);
- gApi.changes().id(change1.getChangeId()).setWorkInProgress();
- assertQuery("reviewer:" + user1, change1);
- }
-
- @Test
public void excludeWipChangeFromReviewersDashboards() throws Exception {
- assume().that(getSchemaVersion()).isAtLeast(42);
-
Account.Id user1 = createAccount("user1");
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
@@ -504,17 +479,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
}
@Test
- public void byStartedBeforeSchema44() throws Exception {
- assume().that(getSchemaVersion()).isLessThan(44);
- assertMissingField(ChangeField.STARTED);
- assertFailingQuery(
- "is:started", "'is:started' operator is not supported by change index version");
- }
-
- @Test
public void byStarted() throws Exception {
- assume().that(getSchemaVersion()).isAtLeast(44);
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWorkInProgress(repo));
@@ -550,9 +515,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void restorePendingReviewers() throws Exception {
- assume().that(getSchemaVersion()).isAtLeast(44);
-
- Project.NameKey project = new Project.NameKey("repo");
+ Project.NameKey project = Project.nameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
@@ -569,7 +532,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
.reviewer(user2.toString(), ReviewerState.CC, false)
.reviewer(email1)
.reviewer(email2, ReviewerState.CC, false);
- gApi.changes().id(change1.getId().get()).revision("current").review(in);
+ gApi.changes().id(change1.getId().get()).current().review(in);
List<ChangeInfo> changeInfos =
assertQuery(newQuery("is:wip").withOption(DETAILED_LABELS), change1);
@@ -579,8 +542,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
changeInfos.get(0).pendingReviewers;
assertThat(pendingReviewers).isNotNull();
- assertReviewers(
- pendingReviewers.get(ReviewerState.REVIEWER), userId.toString(), user1.toString(), email1);
+ assertReviewers(pendingReviewers.get(ReviewerState.REVIEWER), user1.toString(), email1);
assertReviewers(pendingReviewers.get(ReviewerState.CC), user2.toString(), email2);
assertReviewers(pendingReviewers.get(ReviewerState.REMOVED));
@@ -710,10 +672,10 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertQuery(searchOperator + "\"John Smith\"");
// By invalid query.
- exception.expect(BadRequestException.class);
- exception.expectMessage("invalid value");
// SchemaUtil.getNameParts will return an empty set for query only containing these characters.
- assertQuery(searchOperator + "@.- /_");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> assertQuery(searchOperator + "@.- /_"));
+ assertThat(thrown).hasMessageThat().contains("invalid value");
}
private Change createChange(TestRepository<Repo> repo, PersonIdent person) throws Exception {
@@ -1054,20 +1016,24 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
public void byLabelMulti() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Project.NameKey project =
- new Project.NameKey(repo.getRepository().getDescription().getRepositoryName());
- ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
LabelType verified =
- category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
- cfg.getLabelSections().put(verified.getName(), verified);
-
- String heads = RefNames.REFS_HEADS + "*";
- allow(cfg, Permission.forLabel(verified().getName()), -1, 1, REGISTERED_USERS, heads);
-
+ label("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
+ ProjectConfig cfg = projectConfigFactory.create(project);
+ cfg.load(md);
+ cfg.getLabelSections().put(verified.getName(), verified);
cfg.commit(md);
}
- projectCache.evict(cfg.getProject());
+ projectCache.evict(project);
+
+ String heads = RefNames.REFS_HEADS + "*";
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel(verified.getName()).ref(heads).group(REGISTERED_USERS).range(-1, 1))
+ .update();
ReviewInput reviewVerified = new ReviewInput().label("Verified", 1);
ChangeInserter ins = newChange(repo);
@@ -1204,9 +1170,9 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
}
String q = "status:new limit:" + i;
List<ChangeInfo> results = newQuery(q).get();
- assertThat(results).named(q).hasSize(expectedSize);
- assertThat(results.get(results.size() - 1)._moreChanges)
- .named(q)
+ assertWithMessage(q).that(results).hasSize(expectedSize);
+ assertWithMessage(q)
+ .that(results.get(results.size() - 1)._moreChanges)
.isEqualTo(expectedMoreChanges);
assertThat(results.get(0)._number).isEqualTo(last.getId().get());
}
@@ -1378,15 +1344,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byExtension() throws Exception {
- if (getSchemaVersion() < 52) {
- assertMissingField(ChangeField.EXTENSION);
- String unsupportedOperatorMsg =
- "'extension' operator is not supported by change index version";
- assertFailingQuery("extension:txt", unsupportedOperatorMsg);
- assertFailingQuery("ext:txt", unsupportedOperatorMsg);
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc"));
Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC"));
@@ -1410,15 +1367,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byOnlyExtensions() throws Exception {
- if (getSchemaVersion() < 53) {
- assertMissingField(ChangeField.ONLY_EXTENSIONS);
- String unsupportedOperatorMessage =
- "'onlyextensions' operator is not supported by change index version";
- assertFailingQuery("onlyextensions:txt,jpg", unsupportedOperatorMessage);
- assertFailingQuery("onlyexts:txt,jpg", unsupportedOperatorMessage);
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc", "bar.cc"));
Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC", "foo.H"));
@@ -1466,14 +1414,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byFooter() throws Exception {
- if (getSchemaVersion() < 54) {
- assertMissingField(ChangeField.FOOTER);
- assertFailingQuery(
- "footer:Change-Id=I3d2b978ed455f835d1dad2daa920be0b0ec2ae36",
- "'footer' operator is not supported by change index version");
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
@@ -1523,15 +1463,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byDirectory() throws Exception {
- if (getSchemaVersion() < 55) {
- assertMissingField(ChangeField.DIRECTORY);
- String unsupportedOperatorMessage =
- "'directory' operator is not supported by change index version";
- assertFailingQuery("directory:src/java", unsupportedOperatorMessage);
- assertFailingQuery("dir:src/java", unsupportedOperatorMessage);
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
Change change2 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
@@ -1598,8 +1529,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byDirectoryRegex() throws Exception {
- assume().that(getSchemaVersion()).isAtLeast(55);
-
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
Change change2 =
@@ -1874,11 +1803,11 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
// change is visible to group ONLY when access is granted
grant(
- new Project.NameKey("repo"),
+ Project.nameKey("repo"),
"refs/*",
Permission.READ,
false,
- new AccountGroup.UUID(gApi.groups().id(g1).get().id));
+ AccountGroup.uuid(gApi.groups().id(g1).get().id));
assertQuery(q + " visibleto:" + g1, change1);
// Both changes are visible to InternalUser
@@ -1929,7 +1858,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
ProjectConfig config = projectConfigFactory.read(md);
AccessSection s = config.getAccessSection(ref, true);
Permission p = s.getPermission(permission, true);
- PermissionRule rule = Util.newRule(config, groupUUID);
+ PermissionRule rule = new PermissionRule(new GroupReference(groupUUID, groupUUID.get()));
rule.setForce(force);
p.add(rule);
config.commit(md);
@@ -2010,7 +1939,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void byDraftByExcludesZombieDrafts() throws Exception {
- Project.NameKey project = new Project.NameKey("repo");
+ Project.NameKey project = Project.nameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
Change change = insert(repo, newChange(repo));
Change.Id id = change.getId();
@@ -2179,8 +2108,15 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertQuery("conflicts:" + change2.getId().get(), change1);
assertQuery("is:mergeable", change2, change1);
- gApi.changes().id(change1.getChangeId()).revision("current").review(ReviewInput.approve());
- gApi.changes().id(change1.getChangeId()).revision("current").submit();
+ gApi.changes().id(change1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(change1.getChangeId()).current().submit();
+
+ // If a change gets submitted, the remaining open changes get reindexed asynchronously to update
+ // their mergeability information. If the further assertions in this test are done before the
+ // asynchronous reindex completed they fail because the mergeability information in the index
+ // was not updated yet. To avoid this flakiness reindexAfterRefUpdate is switched off for the
+ // tests and we index change2 synchronously here.
+ gApi.changes().id(change2.getChangeId()).index();
assertQuery("status:open conflicts:" + change2.getId().get());
assertQuery("status:open is:mergeable");
@@ -2249,7 +2185,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertQuery("is:reviewer");
assertQuery("reviewer:self");
- gApi.changes().id(change3.getChangeId()).revision("current").review(ReviewInput.recommend());
+ gApi.changes().id(change3.getChangeId()).current().review(ReviewInput.recommend());
assertQuery("is:reviewer", change3);
assertQuery("reviewer:self", change3);
@@ -2330,7 +2266,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void reviewerAndCcByEmail() throws Exception {
- Project.NameKey project = new Project.NameKey("repo");
+ Project.NameKey project = Project.nameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
@@ -2353,30 +2289,17 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
rin.state = ReviewerState.CC;
gApi.changes().id(change2.getId().get()).addReviewer(rin);
- if (getSchemaVersion() >= 41) {
- assertQuery("reviewer:\"" + userByEmailWithName + "\"", change1);
- assertQuery("cc:\"" + userByEmailWithName + "\"", change2);
-
- // Omitting the name:
- assertQuery("reviewer:\"" + userByEmail + "\"", change1);
- assertQuery("cc:\"" + userByEmail + "\"", change2);
- } else {
- assertMissingField(ChangeField.REVIEWER_BY_EMAIL);
+ assertQuery("reviewer:\"" + userByEmailWithName + "\"", change1);
+ assertQuery("cc:\"" + userByEmailWithName + "\"", change2);
- assertFailingQuery(
- "reviewer:\"" + userByEmailWithName + "\"", "User " + userByEmailWithName + " not found");
- assertFailingQuery(
- "cc:\"" + userByEmailWithName + "\"", "User " + userByEmailWithName + " not found");
-
- // Omitting the name:
- assertFailingQuery("reviewer:\"" + userByEmail + "\"", "User " + userByEmail + " not found");
- assertFailingQuery("cc:\"" + userByEmail + "\"", "User " + userByEmail + " not found");
- }
+ // Omitting the name:
+ assertQuery("reviewer:\"" + userByEmail + "\"", change1);
+ assertQuery("cc:\"" + userByEmail + "\"", change2);
}
@Test
public void reviewerAndCcByEmailWithQueryForDifferentUser() throws Exception {
- Project.NameKey project = new Project.NameKey("repo");
+ Project.NameKey project = Project.nameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
@@ -2398,17 +2321,8 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
rin.state = ReviewerState.CC;
gApi.changes().id(change2.getId().get()).addReviewer(rin);
- if (getSchemaVersion() >= 41) {
- assertQuery("reviewer:\"someone@example.com\"");
- assertQuery("cc:\"someone@example.com\"");
- } else {
- assertMissingField(ChangeField.REVIEWER_BY_EMAIL);
-
- String someoneEmail = "someone@example.com";
- assertFailingQuery(
- "reviewer:\"" + someoneEmail + "\"", "User " + someoneEmail + " not found");
- assertFailingQuery("cc:\"" + someoneEmail + "\"", "User " + someoneEmail + " not found");
- }
+ assertQuery("reviewer:\"someone@example.com\"");
+ assertQuery("cc:\"someone@example.com\"");
}
@Test
@@ -2511,7 +2425,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
public void byCommitsOnBranchNotMergedSkipsMissingChanges() throws Exception {
TestRepository<Repo> repo = createProject("repo");
ObjectId missing =
- repo.branch(new PatchSet.Id(new Change.Id(987654), 1).toRefName())
+ repo.branch(PatchSet.id(Change.id(987654), 1).toRefName())
.commit()
.message("No change for this commit")
.insertChangeId()
@@ -2526,7 +2440,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
List<String> shas = new ArrayList<>(n + extra.size());
extra.forEach(i -> shas.add(i.name()));
List<Integer> expectedIds = new ArrayList<>(n);
- Branch.NameKey dest = null;
+ BranchNameKey dest = null;
for (int i = 0; i < n; i++) {
ChangeInserter ins = newChange(repo);
insert(repo, ins);
@@ -2542,15 +2456,15 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), dest, shas, i);
Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
String name = "limit " + i;
- assertThat(ids).named(name).hasSize(n);
- assertThat(ids).named(name).containsExactlyElementsIn(expectedIds);
+ assertWithMessage(name).that(ids).hasSize(n);
+ assertWithMessage(name).that(ids).containsExactlyElementsIn(expectedIds);
}
}
@Test
public void reindexIfStale() throws Exception {
Account.Id user = createAccount("user");
- Project.NameKey project = new Project.NameKey("repo");
+ Project.NameKey project = Project.nameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
Change change = insert(repo, newChange(repo));
String changeId = change.getKey().get();
@@ -2563,8 +2477,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertThat(indexer.reindexIfStale(project, change.getId()).get()).isFalse();
// Delete edit ref behind index's back.
- RefUpdate ru =
- repo.getRepository().updateRef(RefNames.refsEdit(user, change.getId(), ps.getId()));
+ RefUpdate ru = repo.getRepository().updateRef(RefNames.refsEdit(user, change.getId(), ps.id()));
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
@@ -2648,13 +2561,6 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
@Test
public void revertOf() throws Exception {
- if (getSchemaVersion() < 45) {
- assertMissingField(ChangeField.REVERT_OF);
- assertFailingQuery(
- "revertof:1", "'revertof' operator is not supported by change index version");
- return;
- }
-
TestRepository<Repo> repo = createProject("repo");
// Create two commits and revert second commit (initial commit can't be reverted)
Change initial = insert(repo, newChange(repo));
@@ -2667,8 +2573,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
gApi.changes().id(changeToRevert.id).current().submit();
ChangeInfo changeThatReverts = gApi.changes().id(changeToRevert.id).revert().get();
- assertQueryByIds(
- "revertof:" + changeToRevert._number, new Change.Id(changeThatReverts._number));
+ assertQueryByIds("revertof:" + changeToRevert._number, Change.id(changeThatReverts._number));
}
/** Change builder for helping in tests for dashboard sections. */
@@ -3123,27 +3028,27 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
Change change1 = insert(repo, newChange(repo));
Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
- String queries =
+ String queryListText =
"query1\tproject:repo\n"
+ "query2\tproject:repo status:open\n"
+ "query3\tproject:repo branch:stable\n"
+ "query4\tproject:repo branch:other";
try (TestRepository<Repo> allUsers =
- new TestRepository<>(repoManager.openRepository(allUsersName))) {
- String refsUsers = RefNames.refsUsers(userId);
- allUsers.branch(refsUsers).commit().add("queries", queries).create();
-
- Ref userRef = allUsers.getRepository().exactRef(refsUsers);
- assertThat(userRef).isNotNull();
+ new TestRepository<>(repoManager.openRepository(allUsersName));
+ MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName)) {
+ VersionedAccountQueries queries = VersionedAccountQueries.forUser(userId);
+ queries.load(md);
+ queries.setQueryList(queryListText);
+ queries.commit(md);
}
assertThatQueryException("query:foo").hasMessageThat().isEqualTo("Unknown named query: foo");
assertQuery("query:query1", change2, change1);
assertQuery("query:query2", change2, change1);
- gApi.changes().id(change1.getChangeId()).revision("current").review(ReviewInput.approve());
- gApi.changes().id(change1.getChangeId()).revision("current").submit();
+ gApi.changes().id(change1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(change1.getChangeId()).current().submit();
assertQuery("query:query2", change2);
assertQuery("query:query3", change2);
assertQuery("query:query4");
@@ -3221,6 +3126,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
assertQuery(ChangeIndexPredicate.none());
+ ChangeQueryBuilder queryBuilder = queryBuilderProvider.get();
for (Predicate<ChangeData> matchingOneChange :
ImmutableList.of(
// One index query, one post-filtering query.
@@ -3293,7 +3199,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
branch = "refs/heads/" + branch;
}
- Change.Id id = new Change.Id(seq.nextChangeId());
+ Change.Id id = Change.id(seq.nextChangeId());
ChangeInserter ins =
changeFactory
.create(id, commit, branch)
@@ -3321,7 +3227,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
Timestamp createdOn)
throws Exception {
Project.NameKey project =
- new Project.NameKey(repo.getRepository().getDescription().getRepositoryName());
+ Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
Account.Id ownerId = owner != null ? owner : userId;
IdentifiedUser user = userFactory.create(ownerId);
try (BatchUpdate bu = updateFactory.create(project, user, createdOn)) {
@@ -3340,7 +3246,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
PatchSetInserter inserter =
patchSetFactory
- .create(changeNotesFactory.createChecked(c), new PatchSet.Id(c.getId(), n), commit)
+ .create(changeNotesFactory.createChecked(c), PatchSet.id(c.getId(), n), commit)
.setFireRevisionCreated(false)
.setValidate(false);
try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.nowTs());
@@ -3380,7 +3286,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
protected TestRepository<Repo> createProject(String name) throws Exception {
gApi.projects().create(name).get();
- return new TestRepository<>(repoManager.openRepository(new Project.NameKey(name)));
+ return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
}
protected TestRepository<Repo> createProject(String name, String parent) throws Exception {
@@ -3388,7 +3294,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
input.name = name;
input.parent = parent;
gApi.projects().create(input).get();
- return new TestRepository<>(repoManager.openRepository(new Project.NameKey(name)));
+ return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
}
protected QueryRequest newQuery(Object query) {
@@ -3412,8 +3318,8 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
throws Exception {
List<ChangeInfo> result = query.get();
Iterable<Change.Id> ids = ids(result);
- assertThat(ids)
- .named(format(query.getQuery(), ids, changes))
+ assertWithMessage(format(query.getQuery(), ids, changes))
+ .that(ids)
.containsExactlyElementsIn(Arrays.asList(changes))
.inOrder();
return result;
@@ -3425,8 +3331,8 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
.map(ChangeData::getId)
.collect(toImmutableList());
Change.Id[] expectedIds = Arrays.stream(changes).map(Change::getId).toArray(Change.Id[]::new);
- assertThat(actualIds)
- .named(format(predicate.toString(), actualIds, expectedIds))
+ assertWithMessage(format(predicate.toString(), actualIds, expectedIds))
+ .that(actualIds)
.containsExactlyElementsIn(expectedIds)
.inOrder();
}
@@ -3457,7 +3363,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
.append(c.changeId)
.append("), ")
.append("dest=")
- .append(new Branch.NameKey(new Project.NameKey(c.project), c.branch))
+ .append(BranchNameKey.create(Project.nameKey(c.project), c.branch))
.append(", ")
.append("status=")
.append(c.status)
@@ -3478,7 +3384,7 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
}
protected static Iterable<Change.Id> ids(Iterable<ChangeInfo> changes) {
- return Streams.stream(changes).map(c -> new Change.Id(c._number)).collect(toList());
+ return Streams.stream(changes).map(c -> Change.id(c._number)).collect(toList());
}
protected static long lastUpdatedMs(Change c) {
@@ -3515,8 +3421,8 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
}
protected void assertMissingField(FieldDef<ChangeData, ?> field) {
- assertThat(getSchema().hasField(field))
- .named("schema %s has field %s", getSchemaVersion(), field.getName())
+ assertWithMessage("schema %s has field %s", getSchemaVersion(), field.getName())
+ .that(getSchema().hasField(field))
.isFalse();
}
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index 8347484e2e..d0162d3810 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -1,7 +1,10 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
-ABSTRACT_QUERY_TEST = ["AbstractQueryChangesTest.java"]
+ABSTRACT_QUERY_TEST = [
+ "AbstractQueryChangesTest.java",
+ "LuceneQueryChangesTest.java",
+]
java_library(
name = "abstract_query_tests",
@@ -9,48 +12,53 @@ java_library(
srcs = ABSTRACT_QUERY_TEST,
visibility = ["//visibility:public"],
runtime_deps = [
+ "//java/com/google/gerrit/lucene",
"//prolog:gerrit-prolog-common",
],
deps = [
+ "//java/com/google/gerrit/acceptance/testsuite/project",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
],
)
-LUCENE_QUERY_TEST = ["LuceneQueryChangesTest.java"]
+LUCENE_QUERY_TEST = [
+ "LuceneQueryChangesLatestIndexVersionTest.java",
+ "LuceneQueryChangesPreviousIndexVersionTest.java",
+]
-junit_tests(
- name = "lucene_query_test",
+[junit_tests(
+ name = f[:f.index(".")],
size = "large",
- srcs = LUCENE_QUERY_TEST,
+ srcs = [f],
visibility = ["//visibility:public"],
deps = [
":abstract_query_tests",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
],
-)
+) for f in LUCENE_QUERY_TEST]
junit_tests(
name = "small_tests",
@@ -61,15 +69,15 @@ junit_tests(
),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/proto/testing",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/cache/testing",
+ "//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit",
"//lib/truth",
"//lib/truth:truth-proto-extension",
"//proto:cache_java_proto",
diff --git a/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java b/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
index aba0018384..e42230ffbc 100644
--- a/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
+++ b/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
@@ -17,26 +17,36 @@ package com.google.gerrit.server.query.change;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.TestChanges;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class ChangeDataTest extends GerritBaseTests {
+public class ChangeDataTest {
@Test
public void setPatchSetsClearsCurrentPatchSet() throws Exception {
- Project.NameKey project = new Project.NameKey("project");
- ChangeData cd = ChangeData.createForTest(project, new Change.Id(1), 1);
- cd.setChange(TestChanges.newChange(project, new Account.Id(1000)));
+ Project.NameKey project = Project.nameKey("project");
+ ChangeData cd = ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId());
+ cd.setChange(TestChanges.newChange(project, Account.id(1000)));
PatchSet curr1 = cd.currentPatchSet();
- int currId = curr1.getId().get();
- PatchSet ps1 = new PatchSet(new PatchSet.Id(cd.getId(), currId + 1));
- PatchSet ps2 = new PatchSet(new PatchSet.Id(cd.getId(), currId + 2));
+ int currId = curr1.id().get();
+ PatchSet ps1 = newPatchSet(cd.getId(), currId + 1);
+ PatchSet ps2 = newPatchSet(cd.getId(), currId + 2);
cd.setPatchSets(ImmutableList.of(ps1, ps2));
PatchSet curr2 = cd.currentPatchSet();
assertThat(curr2).isNotSameInstanceAs(curr1);
}
+
+ private static PatchSet newPatchSet(Change.Id changeId, int num) {
+ return PatchSet.builder()
+ .id(PatchSet.id(changeId, num))
+ .commitId(ObjectId.zeroId())
+ .uploader(Account.id(1234))
+ .createdOn(TimeUtil.nowTs())
+ .build();
+ }
}
diff --git a/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java b/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
index e550f8e865..00c1a8083c 100644
--- a/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
+++ b/javatests/com/google/gerrit/server/query/change/ConflictKeyTest.java
@@ -25,11 +25,10 @@ import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.server.cache.proto.Cache.ConflictKeyProto;
-import com.google.gerrit.testing.GerritBaseTests;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class ConflictKeyTest extends GerritBaseTests {
+public class ConflictKeyTest {
@Test
public void ffOnlyPreservesInputOrder() {
ObjectId id1 = ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee");
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesLatestIndexVersionTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesLatestIndexVersionTest.java
new file mode 100644
index 0000000000..52a9170fe1
--- /dev/null
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesLatestIndexVersionTest.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.IndexConfig;
+import org.eclipse.jgit.lib.Config;
+
+public class LuceneQueryChangesLatestIndexVersionTest extends LuceneQueryChangesTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return IndexConfig.createForLucene();
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesPreviousIndexVersionTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesPreviousIndexVersionTest.java
new file mode 100644
index 0000000000..62483fa0b7
--- /dev/null
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesPreviousIndexVersionTest.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.IndexConfig;
+import com.google.gerrit.testing.IndexVersions;
+import org.eclipse.jgit.lib.Config;
+
+public class LuceneQueryChangesPreviousIndexVersionTest extends LuceneQueryChangesTest {
+ @ConfigSuite.Default
+ public static Config againstPreviousIndexVersion() {
+ // the current schema version is already tested by the inherited default config suite
+ return Iterables.getOnlyElement(
+ IndexVersions.asConfigMap(
+ ChangeSchemaDefinitions.INSTANCE,
+ IndexVersions.getWithoutLatest(ChangeSchemaDefinitions.INSTANCE),
+ "againstIndexVersion",
+ IndexConfig.createForLucene())
+ .values());
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 2ea198f6d8..6a83fb9d9c 100644
--- a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -14,37 +14,21 @@
package com.google.gerrit.server.query.change;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
-import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.InMemoryModule;
import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.gerrit.testing.IndexVersions;
import com.google.inject.Guice;
import com.google.inject.Injector;
-import java.util.List;
-import java.util.Map;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
-public class LuceneQueryChangesTest extends AbstractQueryChangesTest {
- @ConfigSuite.Default
- public static Config defaultConfig() {
- return IndexConfig.createForLucene();
- }
-
- @ConfigSuite.Configs
- public static Map<String, Config> againstPreviousIndexVersion() {
- // the current schema version is already tested by the inherited default config suite
- List<Integer> schemaVersions = IndexVersions.getWithoutLatest(ChangeSchemaDefinitions.INSTANCE);
- return IndexVersions.asConfigMap(
- ChangeSchemaDefinitions.INSTANCE, schemaVersions, "againstIndexVersion", defaultConfig());
- }
-
+public abstract class LuceneQueryChangesTest extends AbstractQueryChangesTest {
@Override
protected Injector createInjector() {
Config luceneConfig = new Config(config);
@@ -76,8 +60,10 @@ public class LuceneQueryChangesTest extends AbstractQueryChangesTest {
Change change1 = insert(repo, newChange(repo), userId);
String nameEmail = user.asIdentifiedUser().getNameEmail();
- exception.expect(BadRequestException.class);
- exception.expectMessage("Cannot create full-text query with value: \\");
- assertQuery("owner: \"" + nameEmail + "\"\\", change1);
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> assertQuery("owner: \"" + nameEmail + "\"\\", change1));
+ assertThat(thrown).hasMessageThat().contains("Cannot create full-text query with value: \\");
}
}
diff --git a/javatests/com/google/gerrit/server/query/change/RegexPathPredicateTest.java b/javatests/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
index 135e9c268d..72fdd149b3 100644
--- a/javatests/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
+++ b/javatests/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
@@ -17,13 +17,13 @@ package com.google.gerrit.server.query.change;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import java.util.Arrays;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
-public class RegexPathPredicateTest extends GerritBaseTests {
+public class RegexPathPredicateTest {
@Test
public void prefixOnlyOptimization() {
RegexPathPredicate p = predicate("^a/b/.*");
@@ -83,7 +83,8 @@ public class RegexPathPredicateTest extends GerritBaseTests {
private static ChangeData change(String... files) {
Arrays.sort(files);
- ChangeData cd = ChangeData.createForTest(new Project.NameKey("project"), new Change.Id(1), 1);
+ ChangeData cd =
+ ChangeData.createForTest(Project.nameKey("project"), Change.id(1), 1, ObjectId.zeroId());
cd.setCurrentFilePaths(Arrays.asList(files));
return cd;
}
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 4a3c75577d..d80eac0c47 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -15,11 +15,15 @@
package com.google.gerrit.server.query.group;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -32,8 +36,6 @@ import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -57,6 +59,7 @@ import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testing.GerritServerTests;
+import com.google.gerrit.testing.GerritTestName;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
@@ -68,10 +71,13 @@ import java.util.Optional;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
@Ignore
public abstract class AbstractQueryGroupsTest extends GerritServerTests {
+ @Rule public final GerritTestName testName = new GerritTestName();
+
@Inject protected Accounts accounts;
@Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdate;
@@ -184,7 +190,7 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
@Test
public void byInname() throws Exception {
- String namePart = getSanitizedMethodName();
+ String namePart = testName.getSanitizedMethodName();
namePart = CharMatcher.is('_').removeFrom(namePart);
GroupInfo group1 = createGroup("group-" + namePart);
@@ -204,9 +210,9 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
assertQuery("description:non-existing");
- exception.expect(BadRequestException.class);
- exception.expectMessage("description operator requires a value");
- assertQuery("description:\"\"");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> assertQuery("description:\"\""));
+ assertThat(thrown).hasMessageThat().contains("description operator requires a value");
}
@Test
@@ -340,7 +346,7 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
// update group in the database so that group index is stale
String newDescription = "barY";
- AccountGroup.UUID groupUuid = new AccountGroup.UUID(group1.id);
+ AccountGroup.UUID groupUuid = AccountGroup.uuid(group1.id);
InternalGroupUpdate groupUpdate =
InternalGroupUpdate.builder().setDescription(newDescription).build();
groupsUpdateProvider.get().updateGroupInNoteDb(groupUuid, groupUpdate);
@@ -356,7 +362,7 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
@Test
public void rawDocument() throws Exception {
GroupInfo group1 = createGroup(name("group1"));
- AccountGroup.UUID uuid = new AccountGroup.UUID(group1.id);
+ AccountGroup.UUID uuid = AccountGroup.uuid(group1.id);
Optional<FieldBundle> rawFields =
indexes
@@ -376,7 +382,7 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
@Test
public void byDeletedGroup() throws Exception {
GroupInfo group = createGroup(name("group"));
- AccountGroup.UUID uuid = new AccountGroup.UUID(group.id);
+ AccountGroup.UUID uuid = AccountGroup.uuid(group.id);
String query = "uuid:" + uuid;
assertQuery(query, group);
@@ -459,8 +465,8 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
throws Exception {
List<GroupInfo> result = query.get();
Iterable<String> uuids = uuids(result);
- assertThat(uuids)
- .named(format(query, result, groups))
+ assertWithMessage(format(query, result, groups))
+ .that(uuids)
.containsExactlyElementsIn(uuids(groups))
.inOrder();
return result;
@@ -535,7 +541,7 @@ public abstract class AbstractQueryGroupsTest extends GerritServerTests {
return null;
}
- return name + "_" + getSanitizedMethodName();
+ return name + "_" + testName.getSanitizedMethodName();
}
protected int getSchemaVersion() {
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index 1271f4e1ed..e14350f093 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -8,17 +8,18 @@ java_library(
testonly = True,
srcs = ABSTRACT_QUERY_TEST,
visibility = ["//visibility:public"],
+ runtime_deps = ["//java/com/google/gerrit/lucene"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
@@ -36,7 +37,7 @@ junit_tests(
":abstract_query_tests",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index 08ef2b0e05..dfd7928bf0 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -15,12 +15,16 @@
package com.google.gerrit.server.query.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
@@ -37,8 +41,6 @@ import com.google.gerrit.index.Schema;
import com.google.gerrit.index.project.ProjectData;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -57,6 +59,7 @@ import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testing.GerritServerTests;
+import com.google.gerrit.testing.GerritTestName;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
@@ -68,10 +71,13 @@ import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
@Ignore
public abstract class AbstractQueryProjectsTest extends GerritServerTests {
+ @Rule public final GerritTestName testName = new GerritTestName();
+
@Inject protected Accounts accounts;
@Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdate;
@@ -185,7 +191,7 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
@Test
public void byInname() throws Exception {
- String namePart = getSanitizedMethodName();
+ String namePart = testName.getSanitizedMethodName();
namePart = CharMatcher.is('_').removeFrom(namePart);
ProjectInfo project1 = createProject(name("project1-" + namePart));
@@ -207,9 +213,9 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
assertQuery("description:non-existing");
- exception.expect(BadRequestException.class);
- exception.expectMessage("description operator requires a value");
- assertQuery("description:\"\"");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> assertQuery("description:\"\""));
+ assertThat(thrown).hasMessageThat().contains("description operator requires a value");
}
@Test
@@ -224,16 +230,18 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
@Test
public void byState_emptyQuery() throws Exception {
- exception.expect(BadRequestException.class);
- exception.expectMessage("state operator requires a value");
- assertQuery("state:\"\"");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> assertQuery("state:\"\""));
+ assertThat(thrown).hasMessageThat().contains("state operator requires a value");
}
@Test
public void byState_badQuery() throws Exception {
- exception.expect(BadRequestException.class);
- exception.expectMessage("state operator must be either 'active' or 'read-only'");
- assertQuery("state:bla");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> assertQuery("state:bla"));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("state operator must be either 'active' or 'read-only'");
}
@Test
@@ -375,8 +383,8 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
throws Exception {
List<ProjectInfo> result = query.get();
Iterable<String> names = names(result);
- assertThat(names)
- .named(format(query, result, projects))
+ assertWithMessage(format(query, result, projects))
+ .that(names)
.containsExactlyElementsIn(names(projects))
.inOrder();
return result;
@@ -443,6 +451,6 @@ public abstract class AbstractQueryProjectsTest extends GerritServerTests {
return null;
}
- return name + "_" + getSanitizedMethodName();
+ return name + "_" + testName.getSanitizedMethodName();
}
}
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index 5afc7da590..984d82470f 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -8,18 +8,19 @@ java_library(
testonly = True,
srcs = ABSTRACT_QUERY_TEST,
visibility = ["//visibility:public"],
+ runtime_deps = ["//java/com/google/gerrit/lucene"],
deps = [
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index/project",
"//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
"//lib/truth",
],
)
@@ -36,7 +37,7 @@ junit_tests(
":abstract_query_tests",
"//java/com/google/gerrit/index/project",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/javatests/com/google/gerrit/server/rules/BUILD b/javatests/com/google/gerrit/server/rules/BUILD
index 1e335db063..250b0cefba 100644
--- a/javatests/com/google/gerrit/server/rules/BUILD
+++ b/javatests/com/google/gerrit/server/rules/BUILD
@@ -8,14 +8,15 @@ junit_tests(
runtime_deps = ["//prolog:gerrit-prolog-common"],
deps = [
"//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/mockito",
"//lib/prolog:runtime",
"//lib/truth",
],
diff --git a/javatests/com/google/gerrit/server/rules/GerritCommonTest.java b/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
index 180c16b26d..9d7afbc8cb 100644
--- a/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
+++ b/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
@@ -14,10 +14,13 @@
package com.google.gerrit.server.rules;
-import static org.easymock.EasyMock.expect;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.server.project.testing.Util;
+import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.AbstractModule;
import com.googlecode.prolog_cafe.exceptions.CompileException;
@@ -29,7 +32,6 @@ import com.googlecode.prolog_cafe.lang.SymbolTerm;
import java.io.PushbackReader;
import java.io.StringReader;
import java.util.Arrays;
-import org.easymock.EasyMock;
import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;
@@ -56,10 +58,10 @@ public class GerritCommonTest extends PrologTestCase {
@Override
protected void setUpEnvironment(PrologEnvironment env) throws Exception {
- LabelTypes labelTypes = new LabelTypes(Arrays.asList(Util.codeReview(), Util.verified()));
- ChangeData cd = EasyMock.createMock(ChangeData.class);
- expect(cd.getLabelTypes()).andStubReturn(labelTypes);
- EasyMock.replay(cd);
+ LabelTypes labelTypes =
+ new LabelTypes(Arrays.asList(TestLabels.codeReview(), TestLabels.verified()));
+ ChangeData cd = mock(ChangeData.class);
+ when(cd.getLabelTypes()).thenReturn(labelTypes);
env.set(StoredValues.CHANGE_DATA, cd);
}
@@ -82,11 +84,14 @@ public class GerritCommonTest extends PrologTestCase {
throw new CompileException("Cannot consult " + nameTerm);
}
- exception.expect(ReductionLimitException.class);
- exception.expectMessage("exceeded reduction limit of 1300");
- env.once(
- Prolog.BUILTIN,
- "call",
- new StructureTerm(":", SymbolTerm.create("user"), SymbolTerm.create("loopy")));
+ ReductionLimitException thrown =
+ assertThrows(
+ ReductionLimitException.class,
+ () ->
+ env.once(
+ Prolog.BUILTIN,
+ "call",
+ new StructureTerm(":", SymbolTerm.create("user"), SymbolTerm.create("loopy"))));
+ assertThat(thrown).hasMessageThat().contains("exceeded reduction limit of 1300");
}
}
diff --git a/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
index 14124fa90c..d8af0e50b5 100644
--- a/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
+++ b/javatests/com/google/gerrit/server/rules/IgnoreSelfApprovalRuleTest.java
@@ -19,12 +19,11 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.PatchSetApproval;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,9 +31,9 @@ import java.util.Date;
import java.util.List;
import org.junit.Test;
-public class IgnoreSelfApprovalRuleTest extends GerritBaseTests {
- private static final Change.Id CHANGE_ID = new Change.Id(100);
- private static final PatchSet.Id PS_ID = new PatchSet.Id(CHANGE_ID, 1);
+public class IgnoreSelfApprovalRuleTest {
+ private static final Change.Id CHANGE_ID = Change.id(100);
+ private static final PatchSet.Id PS_ID = PatchSet.id(CHANGE_ID, 1);
private static final LabelType VERIFIED = makeLabel("Verified");
private static final Account.Id USER1 = makeAccount(100001);
@@ -82,16 +81,14 @@ public class IgnoreSelfApprovalRuleTest extends GerritBaseTests {
}
private static PatchSetApproval makeApproval(LabelId labelId, Account.Id accountId, int value) {
- PatchSetApproval.Key key = makeKey(PS_ID, accountId, labelId);
- return new PatchSetApproval(key, (short) value, Date.from(Instant.now()));
- }
-
- private static PatchSetApproval.Key makeKey(
- PatchSet.Id psId, Account.Id accountId, LabelId labelId) {
- return new PatchSetApproval.Key(psId, accountId, labelId);
+ return PatchSetApproval.builder()
+ .key(PatchSetApproval.key(PS_ID, accountId, labelId))
+ .value(value)
+ .granted(Date.from(Instant.now()))
+ .build();
}
private static Account.Id makeAccount(int account) {
- return new Account.Id(account);
+ return Account.id(account);
}
}
diff --git a/javatests/com/google/gerrit/server/rules/PrologRuleEvaluatorTest.java b/javatests/com/google/gerrit/server/rules/PrologRuleEvaluatorTest.java
index 6eb0747680..8622b32d35 100644
--- a/javatests/com/google/gerrit/server/rules/PrologRuleEvaluatorTest.java
+++ b/javatests/com/google/gerrit/server/rules/PrologRuleEvaluatorTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.rules;
import static com.google.common.truth.Truth.assertThat;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class PrologRuleEvaluatorTest extends GerritBaseTests {
+public class PrologRuleEvaluatorTest {
@Test
public void validLabelNamesAreKept() {
diff --git a/javatests/com/google/gerrit/server/rules/PrologTestCase.java b/javatests/com/google/gerrit/server/rules/PrologTestCase.java
index f4d8eacb3d..c2b6dbb4c1 100644
--- a/javatests/com/google/gerrit/server/rules/PrologTestCase.java
+++ b/javatests/com/google/gerrit/server/rules/PrologTestCase.java
@@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.inject.Guice;
import com.google.inject.Module;
import com.googlecode.prolog_cafe.exceptions.CompileException;
@@ -45,7 +44,7 @@ import org.junit.Ignore;
/** Base class for any tests written in Prolog. */
@Ignore
-public abstract class PrologTestCase extends GerritBaseTests {
+public abstract class PrologTestCase {
private static final SymbolTerm test_1 = SymbolTerm.intern("test", 1);
private String pkg;
diff --git a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
index e5890c936b..d5ddeff88f 100644
--- a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
+++ b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
@@ -21,20 +21,20 @@ import static com.google.gerrit.server.schema.testing.AllProjectsCreatorTestUtil
import static com.google.gerrit.server.schema.testing.AllProjectsCreatorTestUtil.getAllProjectsWithoutDefaultAcls;
import static com.google.gerrit.server.schema.testing.AllProjectsCreatorTestUtil.getDefaultAllProjectsWithAllDefaultSections;
import static com.google.gerrit.server.schema.testing.AllProjectsCreatorTestUtil.readAllProjectsConfig;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.Sequences;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -43,7 +43,7 @@ import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
-public class AllProjectsCreatorTest extends GerritBaseTests {
+public class AllProjectsCreatorTest {
private static final LabelType TEST_LABEL =
new LabelType(
"Test-Label",
@@ -127,7 +127,7 @@ public class AllProjectsCreatorTest extends GerritBaseTests {
allProjectsCreator.create(allProjectsInput);
Config config = readAllProjectsConfig(repoManager, allProjectsName);
- assertThat(config.getString("project", null, "description")).isEqualTo(testDescription);
+ assertThat(config).stringValue("project", null, "description").isEqualTo(testDescription);
}
@Test
@@ -143,7 +143,7 @@ public class AllProjectsCreatorTest extends GerritBaseTests {
allProjectsCreator.create(allProjectsInput);
Config config = readAllProjectsConfig(repoManager, allProjectsName);
- assertThat(config.getBoolean("submit", null, "rejectEmptyCommit", false)).isTrue();
+ assertThat(config).booleanValue("submit", null, "rejectEmptyCommit", false).isTrue();
}
@Test
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
index 5c1b20152f..059b7f391f 100644
--- a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
@@ -15,15 +15,15 @@
package com.google.gerrit.server.schema;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.server.schema.NoteDbSchemaUpdater.requiredUpgrades;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -31,7 +31,6 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.IntBlob;
import com.google.gerrit.server.notedb.RepoSequence;
import com.google.gerrit.server.notedb.Sequences;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.TestUpdateUI;
import java.io.IOException;
@@ -44,7 +43,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
-public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
+public class NoteDbSchemaUpdaterTest {
@Test
public void requiredUpgradesFromNoVersion() throws Exception {
assertThat(requiredUpgrades(0, versions(10))).containsExactly(10).inOrder();
@@ -62,26 +61,20 @@ public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
@Test
public void downgradeNotSupported() throws Exception {
- try {
- requiredUpgrades(14, versions(10, 11, 12, 13));
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e)
- .hasMessageThat()
- .contains("Cannot downgrade NoteDb schema from version 14 to 13");
- }
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> requiredUpgrades(14, versions(10, 11, 12, 13)));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("Cannot downgrade NoteDb schema from version 14 to 13");
}
@Test
public void skipToFirstVersionNotSupported() throws Exception {
ImmutableSortedSet<Integer> versions = versions(10, 11, 12);
assertThat(requiredUpgrades(9, versions)).containsExactly(10, 11, 12).inOrder();
- try {
- requiredUpgrades(8, versions);
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e).hasMessageThat().contains("Cannot skip NoteDb schema from version 8 to 10");
- }
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> requiredUpgrades(8, versions));
+ assertThat(thrown).hasMessageThat().contains("Cannot skip NoteDb schema from version 8 to 10");
}
private static class TestUpdate {
@@ -231,12 +224,8 @@ public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
seedGroupSequenceRef();
}
};
- try {
- u.update();
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e).hasMessageThat().contains("NoteDb change migration was not completed");
- }
+ StorageException thrown = assertThrows(StorageException.class, () -> u.update());
+ assertThat(thrown).hasMessageThat().contains("NoteDb change migration was not completed");
assertThat(u.getMessages()).isEmpty();
assertThat(u.readVersion()).isEmpty();
}
@@ -250,12 +239,8 @@ public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
setNotesMigrationConfig();
}
};
- try {
- u.update();
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e).hasMessageThat().contains("upgrade to 2.16.x first");
- }
+ StorageException thrown = assertThrows(StorageException.class, () -> u.update());
+ assertThat(thrown).hasMessageThat().contains("upgrade to 2.16.x first");
assertThat(u.getMessages()).isEmpty();
assertThat(u.readVersion()).isEmpty();
}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionCheckTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionCheckTest.java
new file mode 100644
index 0000000000..a5fd4a2932
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionCheckTest.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.inject.ProvisionException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NoteDbSchemaVersionCheckTest {
+ private NoteDbSchemaVersionManager versionManager;
+ private SitePaths sitePaths;
+
+ @Before
+ public void setup() throws Exception {
+ AllProjectsName allProjectsName = new AllProjectsName("All-Projects");
+ GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+ repoManager.createRepository(allProjectsName);
+ versionManager = new NoteDbSchemaVersionManager(allProjectsName, repoManager);
+ versionManager.init();
+
+ sitePaths = new SitePaths(Paths.get("/tmp/foo"));
+ }
+
+ @Test
+ public void shouldNotFailIfCurrentVersionIsExpected() {
+ new NoteDbSchemaVersionCheck(versionManager, sitePaths, new Config()).start();
+ // No exceptions should be thrown
+ }
+
+ @Test
+ public void shouldFailIfCurrentVersionIsOneMoreThanExpected() throws IOException {
+ versionManager.increment(NoteDbSchemaVersions.LATEST);
+
+ ProvisionException e =
+ assertThrows(
+ ProvisionException.class,
+ () -> new NoteDbSchemaVersionCheck(versionManager, sitePaths, new Config()).start());
+
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Unsupported schema version " + (NoteDbSchemaVersions.LATEST + 1));
+ }
+
+ @Test
+ public void
+ shouldNotFailWithExperimentalRollingUpgradeEnabledAndCurrentVersionIsOneMoreThanExpected()
+ throws IOException {
+ Config gerritConfig = new Config();
+ gerritConfig.setBoolean("gerrit", null, "experimentalRollingUpgrade", true);
+ versionManager.increment(NoteDbSchemaVersions.LATEST);
+
+ NoteDbSchemaVersionCheck versionCheck =
+ new NoteDbSchemaVersionCheck(versionManager, sitePaths, gerritConfig);
+ versionCheck.start();
+ // No exceptions should be thrown
+ }
+}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionManagerTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionManagerTest.java
index 9c62d7f870..38e19f76bb 100644
--- a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionManagerTest.java
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionManagerTest.java
@@ -15,20 +15,19 @@
package com.google.gerrit.server.schema;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+import static com.google.gerrit.entities.RefNames.REFS_VERSION;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Before;
import org.junit.Test;
-public class NoteDbSchemaVersionManagerTest extends GerritBaseTests {
+public class NoteDbSchemaVersionManagerTest {
private NoteDbSchemaVersionManager manager;
private TestRepository<?> tr;
@@ -55,14 +54,10 @@ public class NoteDbSchemaVersionManagerTest extends GerritBaseTests {
public void readInvalid() throws Exception {
ObjectId blobId = tr.blob(" 1 2 3 ");
tr.update(REFS_VERSION, blobId);
- try {
- manager.read();
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo("invalid value in refs/meta/version blob at " + blobId.name());
- }
+ StorageException thrown = assertThrows(StorageException.class, () -> manager.read());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("invalid value in refs/meta/version blob at " + blobId.name());
}
@Test
@@ -81,13 +76,9 @@ public class NoteDbSchemaVersionManagerTest extends GerritBaseTests {
@Test
public void incrementWrongOldVersion() throws Exception {
tr.update(REFS_VERSION, tr.blob("123"));
- try {
- manager.increment(456);
- assert_().fail("expected StorageException");
- } catch (StorageException e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo("Expected old version 456 for refs/meta/version, found 123");
- }
+ StorageException thrown = assertThrows(StorageException.class, () -> manager.increment(456));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Expected old version 456 for refs/meta/version, found 123");
}
}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
index 7bc3848f6d..31697fd855 100644
--- a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
@@ -24,11 +24,10 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.stream.IntStream;
import org.junit.Test;
-public class NoteDbSchemaVersionsTest extends GerritBaseTests {
+public class NoteDbSchemaVersionsTest {
@Test
public void testGuessVersion() {
assertThat(guessVersion(getClass())).isEmpty();
diff --git a/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
index c5c956c997..da22f76247 100644
--- a/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
+++ b/javatests/com/google/gerrit/server/schema/ProjectConfigSchemaUpdateTest.java
@@ -17,7 +17,7 @@ package com.google.gerrit.server.schema;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -103,7 +103,7 @@ public class ProjectConfigSchemaUpdateTest {
return factory
.read(
new MetaDataUpdate(
- GitReferenceUpdated.DISABLED, new Project.NameKey(ALL_PROJECTS), repo, null))
+ GitReferenceUpdated.DISABLED, Project.nameKey(ALL_PROJECTS), repo, null))
.getConfig();
}
}
diff --git a/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
index 35f580c771..c92a8e0436 100644
--- a/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
+++ b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
@@ -25,7 +25,6 @@ import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Inject;
import java.util.ArrayList;
@@ -36,7 +35,7 @@ import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
-public class SchemaCreatorImplTest extends GerritBaseTests {
+public class SchemaCreatorImplTest {
@Inject private AllProjectsName allProjects;
@Inject private GitRepositoryManager repoManager;
@@ -82,7 +81,7 @@ public class SchemaCreatorImplTest extends GerritBaseTests {
private void assertValueRange(LabelType label, Integer... range) {
List<Integer> rangeList = Arrays.asList(range);
assertThat(rangeList).isNotEmpty();
- assertThat(rangeList).isStrictlyOrdered();
+ assertThat(rangeList).isInStrictOrder();
assertThat(label.getValues().stream().map(v -> (int) v.getValue()))
.containsExactlyElementsIn(rangeList)
diff --git a/javatests/com/google/gerrit/server/schema/TestGroup.java b/javatests/com/google/gerrit/server/schema/TestGroup.java
index 4627e8be0e..cca6d6c918 100644
--- a/javatests/com/google/gerrit/server/schema/TestGroup.java
+++ b/javatests/com/google/gerrit/server/schema/TestGroup.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.schema;
import com.google.auto.value.AutoValue;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.server.util.time.TimeUtil;
import java.sql.Timestamp;
import java.util.Optional;
@@ -47,7 +47,7 @@ public abstract class TestGroup {
public abstract Builder setNameKey(AccountGroup.NameKey nameKey);
public Builder setName(String name) {
- return setNameKey(new AccountGroup.NameKey(name));
+ return setNameKey(AccountGroup.nameKey(name));
}
public abstract Builder setGroupUuid(AccountGroup.UUID uuid);
@@ -66,10 +66,9 @@ public abstract class TestGroup {
public AccountGroup build() {
TestGroup testGroup = autoBuild();
- AccountGroup.NameKey name = testGroup.getNameKey().orElse(new AccountGroup.NameKey("users"));
- AccountGroup.Id id = testGroup.getId().orElse(new AccountGroup.Id(Math.abs(name.hashCode())));
- AccountGroup.UUID uuid =
- testGroup.getGroupUuid().orElse(new AccountGroup.UUID(name + "-UUID"));
+ AccountGroup.NameKey name = testGroup.getNameKey().orElse(AccountGroup.nameKey("users"));
+ AccountGroup.Id id = testGroup.getId().orElse(AccountGroup.id(Math.abs(name.hashCode())));
+ AccountGroup.UUID uuid = testGroup.getGroupUuid().orElse(AccountGroup.uuid(name + "-UUID"));
Timestamp createdOn = testGroup.getCreatedOn().orElseGet(TimeUtil::nowTs);
AccountGroup accountGroup = new AccountGroup(name, id, uuid, createdOn);
testGroup.getOwnerGroupUuid().ifPresent(accountGroup::setOwnerGroupUUID);
diff --git a/javatests/com/google/gerrit/server/update/BUILD b/javatests/com/google/gerrit/server/update/BUILD
index c98b35fc3a..d1030b5ca0 100644
--- a/javatests/com/google/gerrit/server/update/BUILD
+++ b/javatests/com/google/gerrit/server/update/BUILD
@@ -4,15 +4,23 @@ junit_tests(
name = "small_tests",
size = "small",
srcs = glob(["*.java"]),
+ runtime_deps = [
+ "//java/com/google/gerrit/lucene",
+ "//prolog:gerrit-prolog-common",
+ ],
deps = [
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/util/time",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//lib:guava",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
],
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index cca844ebdd..a2f800a012 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -14,36 +14,67 @@
package com.google.gerrit.server.update;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.ChangeInserter;
+import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.notedb.TooManyUpdatesException;
import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.InMemoryTestEnvironment;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-public class BatchUpdateTest extends GerritBaseTests {
- @Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment();
+public class BatchUpdateTest {
+ private static final int MAX_UPDATES = 4;
+
+ @Rule
+ public InMemoryTestEnvironment testEnvironment =
+ new InMemoryTestEnvironment(
+ () -> {
+ Config cfg = new Config();
+ cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
+ return cfg;
+ });
- @Inject private GitRepositoryManager repoManager;
@Inject private BatchUpdate.Factory batchUpdateFactory;
+ @Inject private ChangeInserter.Factory changeInserterFactory;
+ @Inject private ChangeNotes.Factory changeNotesFactory;
+ @Inject private GitRepositoryManager repoManager;
+ @Inject private PatchSetInserter.Factory patchSetInserterFactory;
@Inject private Provider<CurrentUser> user;
+ @Inject private Sequences sequences;
private Project.NameKey project;
private TestRepository<Repository> repo;
@Before
public void setUp() throws Exception {
- project = new Project.NameKey("test");
+ project = Project.nameKey("test");
Repository inMemoryRepo = repoManager.createRepository(project);
repo = new TestRepository<>(inMemoryRepo);
@@ -68,4 +99,220 @@ public class BatchUpdateTest extends GerritBaseTests {
assertThat(repo.getRepository().exactRef("refs/heads/master").getObjectId())
.isEqualTo(branchCommit.getId());
}
+
+ @Test
+ public void cannotExceedMaxUpdates() throws Exception {
+ Change.Id id = createChangeWithUpdates(MAX_UPDATES);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(id, new AddMessageOp("Excessive update"));
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> bu.execute());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(TooManyUpdatesException.message(id, MAX_UPDATES));
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES);
+ assertThat(getMetaId(id)).isEqualTo(oldMetaId);
+ }
+
+ @Test
+ public void cannotExceedMaxUpdatesCountingMultipleChangeUpdatesInSingleBatch() throws Exception {
+ Change.Id id = createChangeWithTwoPatchSets(MAX_UPDATES - 1);
+
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(id, new AddMessageOp("Update on PS1", PatchSet.id(id, 1)));
+ bu.addOp(id, new AddMessageOp("Update on PS2", PatchSet.id(id, 2)));
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> bu.execute());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(TooManyUpdatesException.message(id, MAX_UPDATES));
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES - 1);
+ assertThat(getMetaId(id)).isEqualTo(oldMetaId);
+ }
+
+ @Test
+ public void exceedingMaxUpdatesAllowedWithCompleteNoOp() throws Exception {
+ Change.Id id = createChangeWithUpdates(MAX_UPDATES);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(
+ id,
+ new BatchUpdateOp() {
+ @Override
+ public boolean updateChange(ChangeContext ctx) {
+ return false;
+ }
+ });
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES);
+ assertThat(getMetaId(id)).isEqualTo(oldMetaId);
+ }
+
+ @Test
+ public void exceedingMaxUpdatesAllowedWithNoOpAfterPopulatingUpdate() throws Exception {
+ Change.Id id = createChangeWithUpdates(MAX_UPDATES);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(
+ id,
+ new BatchUpdateOp() {
+ @Override
+ public boolean updateChange(ChangeContext ctx) {
+ ctx.getUpdate(ctx.getChange().currentPatchSetId()).setChangeMessage("No-op");
+ return false;
+ }
+ });
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES);
+ assertThat(getMetaId(id)).isEqualTo(oldMetaId);
+ }
+
+ @Test
+ public void exceedingMaxUpdatesAllowedWithSubmit() throws Exception {
+ Change.Id id = createChangeWithUpdates(MAX_UPDATES);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(id, new SubmitOp());
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES + 1);
+ assertThat(getMetaId(id)).isNotEqualTo(oldMetaId);
+ }
+
+ @Test
+ public void exceedingMaxUpdatesAllowedWithSubmitAfterOtherOp() throws Exception {
+ Change.Id id = createChangeWithTwoPatchSets(MAX_UPDATES - 1);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(id, new AddMessageOp("Message on PS1", PatchSet.id(id, 1)));
+ bu.addOp(id, new SubmitOp());
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES + 1);
+ assertThat(getMetaId(id)).isNotEqualTo(oldMetaId);
+ }
+ // Not possible to write a variant of this test that submits first and adds a message second in
+ // the same batch, since submit always comes last.
+
+ @Test
+ public void exceedingMaxUpdatesAllowedWithAbandon() throws Exception {
+ Change.Id id = createChangeWithUpdates(MAX_UPDATES);
+ ObjectId oldMetaId = getMetaId(id);
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(
+ id,
+ new BatchUpdateOp() {
+ @Override
+ public boolean updateChange(ChangeContext ctx) {
+ ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+ update.setChangeMessage("Abandon");
+ update.setStatus(Change.Status.ABANDONED);
+ return true;
+ }
+ });
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(MAX_UPDATES + 1);
+ assertThat(getMetaId(id)).isNotEqualTo(oldMetaId);
+ }
+
+ private Change.Id createChangeWithUpdates(int totalUpdates) throws Exception {
+ checkArgument(totalUpdates > 0);
+ checkArgument(totalUpdates <= MAX_UPDATES);
+ Change.Id id = Change.id(sequences.nextChangeId());
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.insertChange(
+ changeInserterFactory.create(
+ id, repo.commit().message("Change").insertChangeId().create(), "refs/heads/master"));
+ bu.execute();
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(1);
+ for (int i = 2; i <= totalUpdates; i++) {
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ bu.addOp(id, new AddMessageOp("Update " + i));
+ bu.execute();
+ }
+ }
+ assertThat(getUpdateCount(id)).isEqualTo(totalUpdates);
+ return id;
+ }
+
+ private Change.Id createChangeWithTwoPatchSets(int totalUpdates) throws Exception {
+ Change.Id id = createChangeWithUpdates(totalUpdates - 1);
+ ChangeNotes notes = changeNotesFactory.create(project, id);
+
+ try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+ ObjectId commitId = repo.amend(notes.getCurrentPatchSet().commitId()).message("PS2").create();
+ bu.addOp(
+ id,
+ patchSetInserterFactory
+ .create(notes, PatchSet.id(id, 2), commitId)
+ .setMessage("Add PS2"));
+ bu.execute();
+ }
+
+ assertThat(getUpdateCount(id)).isEqualTo(totalUpdates);
+ return id;
+ }
+
+ private static class AddMessageOp implements BatchUpdateOp {
+ private final String message;
+ @Nullable private final PatchSet.Id psId;
+
+ AddMessageOp(String message) {
+ this(message, null);
+ }
+
+ AddMessageOp(String message, PatchSet.Id psId) {
+ this.message = message;
+ this.psId = psId;
+ }
+
+ @Override
+ public boolean updateChange(ChangeContext ctx) throws Exception {
+ PatchSet.Id psIdToUpdate = psId;
+ if (psIdToUpdate == null) {
+ psIdToUpdate = ctx.getChange().currentPatchSetId();
+ } else {
+ checkState(
+ ctx.getNotes().getPatchSets().containsKey(psIdToUpdate),
+ "%s not in %s",
+ psIdToUpdate,
+ ctx.getNotes().getPatchSets().keySet());
+ }
+ ctx.getUpdate(psIdToUpdate).setChangeMessage(message);
+ return true;
+ }
+ }
+
+ private int getUpdateCount(Change.Id changeId) throws Exception {
+ return changeNotesFactory.create(project, changeId).getUpdateCount();
+ }
+
+ private ObjectId getMetaId(Change.Id changeId) throws Exception {
+ return repo.getRepository().exactRef(RefNames.changeMetaRef(changeId)).getObjectId();
+ }
+
+ private static class SubmitOp implements BatchUpdateOp {
+ @Override
+ public boolean updateChange(ChangeContext ctx) throws Exception {
+ SubmitRecord sr = new SubmitRecord();
+ sr.status = SubmitRecord.Status.OK;
+ SubmitRecord.Label cr = new SubmitRecord.Label();
+ cr.status = SubmitRecord.Label.Status.OK;
+ cr.appliedBy = ctx.getAccountId();
+ cr.label = "Code-Review";
+ sr.labels = ImmutableList.of(cr);
+ ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+ update.merge(new RequestId(), ImmutableList.of(sr));
+ update.setChangeMessage("Submitted");
+ return true;
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/server/update/RepoViewTest.java b/javatests/com/google/gerrit/server/update/RepoViewTest.java
index b41c66c280..b37e302dbf 100644
--- a/javatests/com/google/gerrit/server/update/RepoViewTest.java
+++ b/javatests/com/google/gerrit/server/update/RepoViewTest.java
@@ -18,8 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
@@ -31,7 +30,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-public class RepoViewTest extends GerritBaseTests {
+public class RepoViewTest {
private static final String MASTER = "refs/heads/master";
private static final String BRANCH = "refs/heads/branch";
@@ -42,7 +41,7 @@ public class RepoViewTest extends GerritBaseTests {
@Before
public void setUp() throws Exception {
InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
- Project.NameKey project = new Project.NameKey("project");
+ Project.NameKey project = Project.nameKey("project");
repo = repoManager.createRepository(project);
tr = new TestRepository<>(repo);
tr.branch(MASTER).commit().create();
diff --git a/javatests/com/google/gerrit/server/util/IdGeneratorTest.java b/javatests/com/google/gerrit/server/util/IdGeneratorTest.java
index e702656bd1..808eca8691 100644
--- a/javatests/com/google/gerrit/server/util/IdGeneratorTest.java
+++ b/javatests/com/google/gerrit/server/util/IdGeneratorTest.java
@@ -17,11 +17,10 @@ package com.google.gerrit.server.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.HashSet;
import org.junit.Test;
-public class IdGeneratorTest extends GerritBaseTests {
+public class IdGeneratorTest {
@Test
public void test1234() {
final HashSet<Integer> seen = new HashSet<>();
diff --git a/javatests/com/google/gerrit/server/util/LabelVoteTest.java b/javatests/com/google/gerrit/server/util/LabelVoteTest.java
index 3048b75deb..bda99a8bf9 100644
--- a/javatests/com/google/gerrit/server/util/LabelVoteTest.java
+++ b/javatests/com/google/gerrit/server/util/LabelVoteTest.java
@@ -15,14 +15,13 @@
package com.google.gerrit.server.util;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.server.util.LabelVote.parse;
import static com.google.gerrit.server.util.LabelVote.parseWithEquals;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class LabelVoteTest extends GerritBaseTests {
+public class LabelVoteTest {
@Test
public void labelVoteParse() {
assertLabelVoteEquals(parse("Code-Review-2"), "Code-Review", -2);
@@ -83,11 +82,6 @@ public class LabelVoteTest extends GerritBaseTests {
}
private void assertParseWithEqualsFails(String value) {
- try {
- parseWithEquals(value);
- assert_().fail("expected IllegalArgumentException when parsing \"%s\"", value);
- } catch (IllegalArgumentException e) {
- // Expected.
- }
+ assertThrows(IllegalArgumentException.class, () -> parseWithEquals(value));
}
}
diff --git a/javatests/com/google/gerrit/server/util/MostSpecificComparatorTest.java b/javatests/com/google/gerrit/server/util/MostSpecificComparatorTest.java
index 9ea17f394f..025bf847eb 100644
--- a/javatests/com/google/gerrit/server/util/MostSpecificComparatorTest.java
+++ b/javatests/com/google/gerrit/server/util/MostSpecificComparatorTest.java
@@ -16,10 +16,9 @@ package com.google.gerrit.server.util;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.testing.GerritBaseTests;
import org.junit.Test;
-public class MostSpecificComparatorTest extends GerritBaseTests {
+public class MostSpecificComparatorTest {
private MostSpecificComparator cmp;
diff --git a/javatests/com/google/gerrit/server/util/SocketUtilTest.java b/javatests/com/google/gerrit/server/util/SocketUtilTest.java
index 018b8dbee6..25114f9360 100644
--- a/javatests/com/google/gerrit/server/util/SocketUtilTest.java
+++ b/javatests/com/google/gerrit/server/util/SocketUtilTest.java
@@ -14,17 +14,18 @@
package com.google.gerrit.server.util;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.util.SocketUtil.hostname;
import static com.google.gerrit.server.util.SocketUtil.isIPv6;
import static com.google.gerrit.server.util.SocketUtil.parse;
import static com.google.gerrit.server.util.SocketUtil.resolve;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.net.InetAddress.getByName;
import static java.net.InetSocketAddress.createUnresolved;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.google.gerrit.testing.GerritBaseTests;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -32,7 +33,7 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import org.junit.Test;
-public class SocketUtilTest extends GerritBaseTests {
+public class SocketUtilTest {
@Test
public void testIsIPv6() throws UnknownHostException {
final InetAddress ipv6 = getByName("1:2:3:4:5:6:7:8");
@@ -105,16 +106,16 @@ public class SocketUtilTest extends GerritBaseTests {
@Test
public void testParseInvalidIPv6() {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage("invalid IPv6: [:3");
- parse("[:3", 80);
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> parse("[:3", 80));
+ assertThat(thrown).hasMessageThat().contains("invalid IPv6: [:3");
}
@Test
public void testParseInvalidPort() {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage("invalid port: localhost:A");
- parse("localhost:A", 80);
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> parse("localhost:A", 80));
+ assertThat(thrown).hasMessageThat().contains("invalid port: localhost:A");
}
@Test
diff --git a/javatests/com/google/gerrit/server/util/git/BUILD b/javatests/com/google/gerrit/server/util/git/BUILD
index 0cb7b8aee9..883898f873 100644
--- a/javatests/com/google/gerrit/server/util/git/BUILD
+++ b/javatests/com/google/gerrit/server/util/git/BUILD
@@ -8,20 +8,18 @@ junit_tests(
),
visibility = ["//visibility:public"],
deps = [
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server/util/git",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//java/com/google/gerrit/truth",
"//lib:gson",
"//lib:guava",
"//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jgit-junit",
"//lib:protobuf",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/commons:codec",
"//lib/guice",
- "//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/jgit/org.eclipse.jgit.junit:junit",
"//lib/truth",
"//lib/truth:truth-java8-extension",
"//lib/truth:truth-proto-extension",
diff --git a/javatests/com/google/gerrit/server/util/git/SubmoduleSectionParserTest.java b/javatests/com/google/gerrit/server/util/git/SubmoduleSectionParserTest.java
index c5e683fefe..531785fb20 100644
--- a/javatests/com/google/gerrit/server/util/git/SubmoduleSectionParserTest.java
+++ b/javatests/com/google/gerrit/server/util/git/SubmoduleSectionParserTest.java
@@ -17,20 +17,19 @@ package com.google.gerrit.server.util.git;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmoduleSubscription;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
-public class SubmoduleSectionParserTest extends GerritBaseTests {
+public class SubmoduleSectionParserTest {
private static final String THIS_SERVER = "http://localhost/";
@Test
public void followMasterBranch() throws Exception {
- Project.NameKey p = new Project.NameKey("proj");
+ Project.NameKey p = Project.nameKey("proj");
Config cfg = new Config();
cfg.fromText(
""
@@ -40,7 +39,7 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ p.get()
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
@@ -48,14 +47,14 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
new SubmoduleSubscription(
- targetBranch, new Branch.NameKey(p, "master"), "localpath-to-a"));
+ targetBranch, BranchNameKey.create(p, "master"), "localpath-to-a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void followMatchingBranch() throws Exception {
- Project.NameKey p = new Project.NameKey("a");
+ Project.NameKey p = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -66,32 +65,32 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = .\n");
- Branch.NameKey targetBranch1 = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch1 = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res1 =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch1).parseAllSections();
Set<SubmoduleSubscription> expected1 =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch1, new Branch.NameKey(p, "master"), "a"));
+ new SubmoduleSubscription(targetBranch1, BranchNameKey.create(p, "master"), "a"));
assertThat(res1).containsExactlyElementsIn(expected1);
- Branch.NameKey targetBranch2 = new Branch.NameKey(new Project.NameKey("project"), "somebranch");
+ BranchNameKey targetBranch2 = BranchNameKey.create(Project.nameKey("project"), "somebranch");
Set<SubmoduleSubscription> res2 =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch2).parseAllSections();
Set<SubmoduleSubscription> expected2 =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch2, new Branch.NameKey(p, "somebranch"), "a"));
+ new SubmoduleSubscription(targetBranch2, BranchNameKey.create(p, "somebranch"), "a"));
assertThat(res2).containsExactlyElementsIn(expected2);
}
@Test
public void followAnotherBranch() throws Exception {
- Project.NameKey p = new Project.NameKey("a");
+ Project.NameKey p = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -102,21 +101,21 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = anotherbranch\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p, "anotherbranch"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p, "anotherbranch"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withAnotherURI() throws Exception {
- Project.NameKey p = new Project.NameKey("a");
+ Project.NameKey p = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -127,21 +126,21 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p, "master"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p, "master"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withSlashesInProjectName() throws Exception {
- Project.NameKey p = new Project.NameKey("project/with/slashes/a");
+ Project.NameKey p = Project.nameKey("project/with/slashes/a");
Config cfg = new Config();
cfg.fromText(
""
@@ -152,21 +151,21 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p, "master"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p, "master"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withSlashesInPath() throws Exception {
- Project.NameKey p = new Project.NameKey("a");
+ Project.NameKey p = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -177,22 +176,23 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p, "master"), "a/b/c/d/e"));
+ new SubmoduleSubscription(
+ targetBranch, BranchNameKey.create(p, "master"), "a/b/c/d/e"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withMoreSections() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a");
- Project.NameKey p2 = new Project.NameKey("b");
+ Project.NameKey p1 = Project.nameKey("a");
+ Project.NameKey p2 = Project.nameKey("b");
Config cfg = new Config();
cfg.fromText(
""
@@ -209,23 +209,23 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ " branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a"),
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p2, "master"), "b"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a"),
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p2, "master"), "b"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withSubProjectFound() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a/b");
- Project.NameKey p2 = new Project.NameKey("b");
+ Project.NameKey p1 = Project.nameKey("a/b");
+ Project.NameKey p2 = Project.nameKey("b");
Config cfg = new Config();
cfg.fromText(
"\n"
@@ -242,25 +242,25 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = .\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p2, "master"), "b"),
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a/b"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p2, "master"), "b"),
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a/b"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withAnInvalidSection() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a");
- Project.NameKey p2 = new Project.NameKey("b");
- Project.NameKey p3 = new Project.NameKey("d");
- Project.NameKey p4 = new Project.NameKey("e");
+ Project.NameKey p1 = Project.nameKey("a");
+ Project.NameKey p2 = Project.nameKey("b");
+ Project.NameKey p3 = Project.nameKey("d");
+ Project.NameKey p4 = Project.nameKey("e");
Config cfg = new Config();
cfg.fromText(
"\n"
@@ -293,15 +293,15 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ " branch = refs/heads/master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a"),
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p4, "master"), "e"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a"),
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p4, "master"), "e"));
assertThat(res).containsExactlyElementsIn(expected);
}
@@ -317,7 +317,7 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
// Project "a" doesn't exist
+ "branch = .\\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
@@ -327,7 +327,7 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
@Test
public void withSectionToOtherServer() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a");
+ Project.NameKey p1 = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -338,7 +338,7 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = .");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
@@ -348,7 +348,7 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
@Test
public void withRelativeURI() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a");
+ Project.NameKey p1 = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -359,21 +359,21 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch = new Branch.NameKey(new Project.NameKey("project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withDeepRelativeURI() throws Exception {
- Project.NameKey p1 = new Project.NameKey("a");
+ Project.NameKey p1 = Project.nameKey("a");
Config cfg = new Config();
cfg.fromText(
""
@@ -384,22 +384,21 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch =
- new Branch.NameKey(new Project.NameKey("nested/project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("nested/project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
@Test
public void withOverlyDeepRelativeURI() throws Exception {
- Project.NameKey p1 = new Project.NameKey("nested/a");
+ Project.NameKey p1 = Project.nameKey("nested/a");
Config cfg = new Config();
cfg.fromText(
""
@@ -410,15 +409,14 @@ public class SubmoduleSectionParserTest extends GerritBaseTests {
+ "\n"
+ "branch = master\n");
- Branch.NameKey targetBranch =
- new Branch.NameKey(new Project.NameKey("nested/project"), "master");
+ BranchNameKey targetBranch = BranchNameKey.create(Project.nameKey("nested/project"), "master");
Set<SubmoduleSubscription> res =
new SubmoduleSectionParser(cfg, THIS_SERVER, targetBranch).parseAllSections();
Set<SubmoduleSubscription> expected =
Sets.newHashSet(
- new SubmoduleSubscription(targetBranch, new Branch.NameKey(p1, "master"), "a"));
+ new SubmoduleSubscription(targetBranch, BranchNameKey.create(p1, "master"), "a"));
assertThat(res).containsExactlyElementsIn(expected);
}
diff --git a/javatests/com/google/gerrit/sshd/BUILD b/javatests/com/google/gerrit/sshd/BUILD
index c010d7c4d3..3e11ff22e2 100644
--- a/javatests/com/google/gerrit/sshd/BUILD
+++ b/javatests/com/google/gerrit/sshd/BUILD
@@ -6,7 +6,6 @@ junit_tests(
deps = [
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/sshd",
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//lib/mina:sshd",
"//lib/truth",
],
diff --git a/javatests/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java b/javatests/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
index b663849419..777cb4fb1e 100644
--- a/javatests/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
+++ b/javatests/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
@@ -17,13 +17,12 @@ package com.google.gerrit.sshd.commands;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.extensions.api.projects.ConfigValue;
-import com.google.gerrit.testing.GerritBaseTests;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
-public class ProjectConfigParamParserTest extends GerritBaseTests {
+public class ProjectConfigParamParserTest {
private CreateProjectCommand cmd;
diff --git a/javatests/com/google/gerrit/testing/GerritJUnitTest.java b/javatests/com/google/gerrit/testing/GerritJUnitTest.java
index 430f48f832..56dda083f1 100644
--- a/javatests/com/google/gerrit/testing/GerritJUnitTest.java
+++ b/javatests/com/google/gerrit/testing/GerritJUnitTest.java
@@ -15,7 +15,7 @@
package com.google.gerrit.testing;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import org.junit.Test;
@@ -68,7 +68,7 @@ public class GerritJUnitTest {
() -> {
throw new MyException("foo");
});
- assert_().fail("expected AssertionError");
+ assertWithMessage("expected AssertionError").fail();
} catch (AssertionError e) {
assertThat(e).hasMessageThat().contains(IllegalStateException.class.getSimpleName());
assertThat(e).hasMessageThat().contains(MyException.class.getSimpleName());
@@ -81,7 +81,7 @@ public class GerritJUnitTest {
public void assertThrowsThrowsAssertionErrorWhenNothingThrown() {
try {
assertThrows(MyException.class, () -> {});
- assert_().fail("expected AssertionError");
+ assertWithMessage("expected AssertionError").fail();
} catch (AssertionError e) {
assertThat(e).hasMessageThat().contains(MyException.class.getSimpleName());
assertThat(e).hasCauseThat().isNull();
diff --git a/javatests/com/google/gerrit/testing/IndexVersionsTest.java b/javatests/com/google/gerrit/testing/IndexVersionsTest.java
index 36247f8d6a..0362ddc271 100644
--- a/javatests/com/google/gerrit/testing/IndexVersionsTest.java
+++ b/javatests/com/google/gerrit/testing/IndexVersionsTest.java
@@ -16,6 +16,7 @@ package com.google.gerrit.testing;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.testing.IndexVersions.ALL;
import static com.google.gerrit.testing.IndexVersions.CURRENT;
import static com.google.gerrit.testing.IndexVersions.PREVIOUS;
@@ -25,7 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
-public class IndexVersionsTest extends GerritBaseTests {
+public class IndexVersionsTest {
private static final ChangeSchemaDefinitions SCHEMA_DEF = ChangeSchemaDefinitions.INSTANCE;
@Test
@@ -133,8 +134,8 @@ public class IndexVersionsTest extends GerritBaseTests {
}
private void assertIllegalArgument(String value, String expectedMessage) {
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage(expectedMessage);
- get(value);
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> get(value));
+ assertThat(thrown).hasMessageThat().contains(expectedMessage);
}
}
diff --git a/javatests/com/google/gerrit/util/http/BUILD b/javatests/com/google/gerrit/util/http/BUILD
index 8f6afbb070..4711faa589 100644
--- a/javatests/com/google/gerrit/util/http/BUILD
+++ b/javatests/com/google/gerrit/util/http/BUILD
@@ -4,12 +4,10 @@ junit_tests(
name = "http_tests",
srcs = glob(["**/*.java"]),
deps = [
- "//java/com/google/gerrit/testing:gerrit-test-util",
"//java/com/google/gerrit/util/http",
"//javatests/com/google/gerrit/util/http/testutil",
"//lib:junit",
"//lib:servlet-api-without-neverlink",
- "//lib/easymock",
"//lib/truth",
],
)
diff --git a/javatests/com/google/gerrit/util/http/RequestUtilTest.java b/javatests/com/google/gerrit/util/http/RequestUtilTest.java
index adda5e7092..bef9d4b173 100644
--- a/javatests/com/google/gerrit/util/http/RequestUtilTest.java
+++ b/javatests/com/google/gerrit/util/http/RequestUtilTest.java
@@ -18,11 +18,10 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.util.http.RequestUtil.getEncodedPathInfo;
import static com.google.gerrit.util.http.RequestUtil.getRestPathWithoutIds;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import org.junit.Test;
-public class RequestUtilTest extends GerritBaseTests {
+public class RequestUtilTest {
@Test
public void getEncodedPathInfo_emptyContextPath() {
assertThat(getEncodedPathInfo(fakeRequest("", "/s", "/foo/bar"))).isEqualTo("/foo/bar");
diff --git a/javatests/com/google/gerrit/util/http/testutil/BUILD b/javatests/com/google/gerrit/util/http/testutil/BUILD
index d7ac5bdf4e..3a67d45daf 100644
--- a/javatests/com/google/gerrit/util/http/testutil/BUILD
+++ b/javatests/com/google/gerrit/util/http/testutil/BUILD
@@ -7,8 +7,8 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//lib:guava",
+ "//lib:jgit",
"//lib:servlet-api",
"//lib/httpcomponents:httpclient",
- "//lib/jgit/org.eclipse.jgit:jgit",
],
)
diff --git a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
index a4175e3eb7..0bb4de493b 100644
--- a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
+++ b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletRequest.java
@@ -17,6 +17,7 @@ package com.google.gerrit.util.http.testutil;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
@@ -66,6 +67,7 @@ public class FakeHttpServletRequest implements HttpServletRequest {
private String contextPath;
private String servletPath;
private String path;
+ private String method;
public FakeHttpServletRequest() {
this("gerrit.example.com", 80, "", SERVLET_PATH);
@@ -80,6 +82,7 @@ public class FakeHttpServletRequest implements HttpServletRequest {
attributes = Maps.newConcurrentMap();
parameters = LinkedListMultimap.create();
headers = LinkedListMultimap.create();
+ method = "GET";
}
@Override
@@ -104,6 +107,11 @@ public class FakeHttpServletRequest implements HttpServletRequest {
@Override
public String getContentType() {
+ List<String> contentType = headers.get("Content-Type");
+ if (contentType != null && !contentType.isEmpty()) {
+ return contentType.get(0);
+ }
+
return null;
}
@@ -257,7 +265,15 @@ public class FakeHttpServletRequest implements HttpServletRequest {
@Override
public Cookie[] getCookies() {
- return new Cookie[0];
+ return Splitter.on(";").splitToList(Strings.nullToEmpty(getHeader("Cookie"))).stream()
+ .filter(s -> !s.isEmpty())
+ .map(
+ (String cookieValue) -> {
+ String[] kv = cookieValue.split("=");
+ return new Cookie(kv[0], kv[1]);
+ })
+ .collect(toList())
+ .toArray(new Cookie[0]);
}
@Override
@@ -288,7 +304,11 @@ public class FakeHttpServletRequest implements HttpServletRequest {
@Override
public String getMethod() {
- return "GET";
+ return method;
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
}
@Override
diff --git a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
index 9a98ecdcaa..f39b875500 100644
--- a/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
+++ b/javatests/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
@@ -161,7 +161,7 @@ public class FakeHttpServletResponse implements HttpServletResponse {
@Override
public void addCookie(Cookie cookie) {
- throw new UnsupportedOperationException();
+ addHeader("Set-Cookie", cookie.getName() + "=" + cookie.getValue());
}
@Override
diff --git a/lib/BUILD b/lib/BUILD
index 2ea8f54214..2e5668e224 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -36,6 +36,49 @@ java_library(
)
java_library(
+ name = "jgit",
+ data = ["//lib:LICENSE-jgit"],
+ visibility = ["//visibility:public"],
+ exports = ["@jgit//org.eclipse.jgit:jgit"],
+ runtime_deps = [
+ ":javaewah",
+ "//lib/log:api",
+ ],
+)
+
+java_library(
+ name = "jgit-archive",
+ data = ["//lib:LICENSE-jgit"],
+ visibility = ["//visibility:public"],
+ exports = ["@jgit//org.eclipse.jgit.archive:jgit-archive"],
+ runtime_deps = [":jgit"],
+)
+
+java_library(
+ name = "jgit-junit",
+ testonly = True,
+ data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+ visibility = ["//visibility:public"],
+ exports = ["@jgit//org.eclipse.jgit.junit:junit"],
+ runtime_deps = [":jgit"],
+)
+
+java_library(
+ name = "jgit-servlet",
+ data = ["//lib:LICENSE-jgit"],
+ visibility = ["//visibility:public"],
+ exports = ["@jgit//org.eclipse.jgit.http.server:jgit-servlet"],
+ runtime_deps = [":jgit"],
+)
+
+java_library(
+ name = "javaewah",
+ data = ["//lib:LICENSE-Apache2.0"],
+ visibility = ["//visibility:public"],
+ exports = ["@javaewah//jar"],
+)
+
+java_library(
name = "protobuf",
data = ["//lib:LICENSE-protobuf"],
visibility = ["//visibility:public"],
@@ -71,6 +114,7 @@ java_library(
name = "caffeine",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/server/cache/mem:__pkg__",
],
exports = ["@caffeine//jar"],
@@ -85,6 +129,7 @@ java_library(
name = "caffeine-guava",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/server/cache/mem:__pkg__",
],
exports = [":caffeine-guava-renamed"],
@@ -442,13 +487,6 @@ java_library(
)
java_library(
- name = "javassist",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = ["@javassist//jar"],
-)
-
-java_library(
name = "soy",
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
@@ -485,6 +523,18 @@ java_library(
exports = ["@icu4j//jar"],
)
+java_library(
+ name = "javax-annotation",
+ data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+ visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
+ "//java/com/google/gerrit/extensions:__pkg__",
+ "//java/com/google/gerrit/server:__pkg__",
+ "//plugins:__pkg__",
+ ],
+ exports = ["@javax-annotation//jar"],
+)
+
sh_test(
name = "nongoogle_test",
srcs = ["nongoogle_test.sh"],
diff --git a/lib/LICENSE-shadycss b/lib/LICENSE-shadycss
new file mode 100644
index 0000000000..0fe5c522de
--- /dev/null
+++ b/lib/LICENSE-shadycss
@@ -0,0 +1,20 @@
+# License
+
+Everything in this repo is BSD style license unless otherwise specified.
+
+Copyright (c) 2015 The Polymer Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+* Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/lib/easymock/BUILD b/lib/easymock/BUILD
deleted file mode 100644
index 90c9673d31..0000000000
--- a/lib/easymock/BUILD
+++ /dev/null
@@ -1,26 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-
-java_library(
- name = "easymock",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = ["@easymock//jar"],
- runtime_deps = [
- ":cglib-3_2",
- ":objenesis",
- ],
-)
-
-java_library(
- name = "cglib-3_2",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = ["@cglib-3_2//jar"],
-)
-
-java_library(
- name = "objenesis",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = ["@objenesis//jar"],
-)
diff --git a/lib/errorprone/BUILD b/lib/errorprone/BUILD
new file mode 100644
index 0000000000..456860aaee
--- /dev/null
+++ b/lib/errorprone/BUILD
@@ -0,0 +1,8 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+java_library(
+ name = "annotations",
+ data = ["//lib:LICENSE-Apache2.0"],
+ visibility = ["//visibility:public"],
+ exports = ["@error-prone-annotations//jar"],
+)
diff --git a/lib/greenmail/BUILD b/lib/greenmail/BUILD
index e8845e2628..68da16a514 100644
--- a/lib/greenmail/BUILD
+++ b/lib/greenmail/BUILD
@@ -17,7 +17,7 @@ java_library(
data = ["//lib:LICENSE-Apache2.0"],
exports = ["@greenmail//jar"],
runtime_deps = select({
- "//:java9": POST_JDK8_DEPS,
+ "//:java11": POST_JDK8_DEPS,
"//:java_next": POST_JDK8_DEPS,
"//conditions:default": [],
}),
diff --git a/lib/guava.bzl b/lib/guava.bzl
index c36bf14c2f..18a8355c13 100644
--- a/lib/guava.bzl
+++ b/lib/guava.bzl
@@ -1,5 +1,5 @@
-GUAVA_VERSION = "27.1-jre"
+GUAVA_VERSION = "28.1-jre"
-GUAVA_BIN_SHA1 = "e47b59c893079b87743cdcfb6f17ca95c08c592c"
+GUAVA_BIN_SHA1 = "b0e91dcb6a44ffb6221b5027e12a5cb34b841145"
GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
diff --git a/lib/highlightjs/building.md b/lib/highlightjs/building.md
index fcd28641d3..18c574699a 100644
--- a/lib/highlightjs/building.md
+++ b/lib/highlightjs/building.md
@@ -26,27 +26,10 @@ languages included. Build it with the following:
$> ln -s ../../highlightjs-closure-templates/soy.js src/languages/soy.js
$> mkdir test/detect/soy && ln -s ../../../highlightjs-closure-templates/test/detect/soy/default.txt test/detect/soy/default.txt
$> npm install
- $> node tools/build.js -n
+ $> node tools/build.js
-The resulting JS file will appear in the "build" directory of the Highlight.js
-repo under the name "highlight.pack.js".
-
-## Minification
-
-Minify the file using closure-compiler using the command below.
-
- $> wget https://dl.google.com/closure-compiler/compiler-latest.zip
-
- $> unzip compiler-latest.zip
-
- $> mv closure-compiler-*.jar closure-compiler.jar
-
- $> java -jar ./closure-compiler.jar \
- --js build/highlight.js \
- --js_output_file build/highlight.min.js
-
-Copy the header comment that appears on the first line of
-build/highlight.pack.js and add it to the start of build/highlight.min.js.
+The resulting minified JS file will appear in the "build" directory of the Highlight.js
+repo under the name "highlight.min.js".
## Finish
diff --git a/lib/highlightjs/highlight.min.js b/lib/highlightjs/highlight.min.js
index 959e442073..458cb0c563 100644
--- a/lib/highlightjs/highlight.min.js
+++ b/lib/highlightjs/highlight.min.js
@@ -1,633 +1,3679 @@
-var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.ISOLATE_POLYFILLS=!1;
-$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
-$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.SymbolClass=function(a,b){this.$jscomp$symbol$id_=a;$jscomp.defineProperty(this,"description",{configurable:!0,writable:!0,value:b})};$jscomp.SymbolClass.prototype.toString=function(){return this.$jscomp$symbol$id_};
-$jscomp.Symbol=function(){function a(c){if(this instanceof a)throw new TypeError("Symbol is not a constructor");return new $jscomp.SymbolClass($jscomp.SYMBOL_PREFIX+(c||"")+"_"+b++,c)}var b=0;return a}();
-$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("Symbol.iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.iteratorPrototype($jscomp.arrayIteratorImpl(this))}});$jscomp.initSymbolIterator=function(){}};
-$jscomp.initSymbolAsyncIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.asyncIterator;a||(a=$jscomp.global.Symbol.asyncIterator=$jscomp.global.Symbol("Symbol.asyncIterator"));$jscomp.initSymbolAsyncIterator=function(){}};$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};
-$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$jscomp.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):$jscomp.arrayIterator(a)};$jscomp.arrayFromIterator=function(a){for(var b,c=[];!(b=a.next()).done;)c.push(b.value);return c};$jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};
-$jscomp.objectCreate=$jscomp.ASSUME_ES5||"function"==typeof Object.create?Object.create:function(a){var b=function(){};b.prototype=a;return new b};$jscomp.underscoreProtoCanBeSet=function(){var a={a:!0},b={};try{return b.__proto__=a,b.a}catch(c){}return!1};$jscomp.setPrototypeOf="function"==typeof Object.setPrototypeOf?Object.setPrototypeOf:$jscomp.underscoreProtoCanBeSet()?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null;
-$jscomp.inherits=function(a,b){a.prototype=$jscomp.objectCreate(b.prototype);a.prototype.constructor=a;if($jscomp.setPrototypeOf){var c=$jscomp.setPrototypeOf;c(a,b)}else for(c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.superClass_=b.prototype};$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};
-$jscomp.assign="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)$jscomp.owns(d,e)&&(a[e]=d[e])}return a};$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");
-var $jscomp$lookupPolyfilledValue=function(a,b){var c=$jscomp.propertyToPolyfillSymbol[b];if(null==c)return a[b];c=a[c];return void 0!==c?c:a[b]};$jscomp.polyfill=function(a,b,c,d){b&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(a,b,c,d):$jscomp.polyfillUnisolated(a,b,c,d))};
-$jscomp.polyfillUnisolated=function(a,b,c,d){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})};
-$jscomp.polyfillIsolated=function(a,b,c,d){var e=a.split(".");a=1===e.length;d=e[0];d=!a&&d in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var f=0;f<e.length-1;f++){var h=e[f];h in d||(d[h]={});d=d[h]}e=e[e.length-1];c=$jscomp.IS_SYMBOL_NATIVE&&"es6"===c?d[e]:null;b=b(c);null!=b&&(a?$jscomp.defineProperty($jscomp.polyfills,e,{configurable:!0,writable:!0,value:b}):b!==c&&($jscomp.propertyToPolyfillSymbol[e]=$jscomp.IS_SYMBOL_NATIVE?$jscomp.global.Symbol(e):$jscomp.POLYFILL_PREFIX+e,e=$jscomp.propertyToPolyfillSymbol[e],
-$jscomp.defineProperty(d,e,{configurable:!0,writable:!0,value:b})))};$jscomp.polyfill("Object.assign",function(a){return a||$jscomp.assign},"es6","es3");$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,e=0;e<d;e++){var f=a[e];if(b.call(c,f,e,a))return{i:e,v:f}}return{i:-1,v:void 0}};$jscomp.polyfill("Array.prototype.findIndex",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).i}},"es6","es3");
-$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3");$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var b=this;b instanceof String&&(b=String(b));var e=b.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c<e;c++){var f=b[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
-$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3");
-$jscomp.iteratorFromArray=function(a,b){$jscomp.initSymbolIterator();a instanceof String&&(a+="");var c=0,d={next:function(){if(c<a.length){var e=c++;return{value:b(e,a[e]),done:!1}}d.next=function(){return{done:!0,value:void 0}};return d.next()}};d[Symbol.iterator]=function(){return d};return d};$jscomp.polyfill("Array.prototype.keys",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a){return a})}},"es6","es3");
-$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).v}},"es6","es3");
-var hljs=function(){function a(b){Object.freeze(b);var c="function"===typeof b;Object.getOwnPropertyNames(b).forEach(function(d){!b.hasOwnProperty(d)||null===b[d]||"object"!==typeof b[d]&&"function"!==typeof b[d]||c&&("caller"===d||"callee"===d||"arguments"===d)||Object.isFrozen(b[d])||a(b[d])});return b}function b(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function c(a){var b={},c=Array.prototype.slice.call(arguments,1),d;for(d in a)b[d]=a[d];c.forEach(function(a){for(var c in a)b[c]=
-a[c]});return b}function d(a){return a&&a.source||a}function e(a){for(var b=[],c=0;c<arguments.length;++c)b[c-0]=arguments[c];return b.map(function(a){return d(a)}).join("")}function f(a,b){for(var c=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,e=0,f="",t=0;t<a.length;t++){var h=e+=1,y=d(a[t]);0<t&&(f+=b);for(f+="(";0<y.length;){var g=c.exec(y);if(null==g){f+=y;break}f+=y.substring(0,g.index);y=y.substring(g.index+g[0].length);"\\"===g[0][0]&&g[1]?f+="\\"+String(Number(g[1])+h):(f+=g[0],"("===
-g[0]&&e++)}f+=")"}return f}function h(a){function b(b,c){return new RegExp(d(b),"m"+(a.case_insensitive?"i":"")+(c?"g":""))}function c(a){var b=new g;a.contains.forEach(function(a){return b.addRule(a.begin,{rule:a,type:"begin"})});a.terminator_end&&b.addRule(a.terminator_end,{type:"end"});a.illegal&&b.addRule(a.illegal,{type:"illegal"});return b}function e(a,b){var c=a.input[a.index+a[0].length];"."!==a.input[a.index-1]&&"."!==c||b.ignoreMatch()}function t(f,h){if(!f.compiled){f.compiled=!0;f.__beforeBegin=
-null;f.keywords=f.keywords||f.beginKeywords;var g=null;"object"===typeof f.keywords&&(g=f.keywords.$pattern,delete f.keywords.$pattern);f.keywords&&(f.keywords=l(f.keywords,a.case_insensitive));if(f.lexemes&&g)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");f.keywordPatternRe=b(f.lexemes||g||/\w+/,!0);h&&(f.beginKeywords&&(f.begin="\\b("+f.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",f.__beforeBegin=e),f.begin||(f.begin=/\B|\b/),f.beginRe=
-b(f.begin),f.endSameAsBegin&&(f.end=f.begin),f.end||f.endsWithParent||(f.end=/\B|\b/),f.end&&(f.endRe=b(f.end)),f.terminator_end=d(f.end)||"",f.endsWithParent&&h.terminator_end&&(f.terminator_end+=(f.end?"|":"")+h.terminator_end));f.illegal&&(f.illegalRe=b(f.illegal));null==f.relevance&&(f.relevance=1);f.contains||(f.contains=[]);f.contains=[].concat.apply([],$jscomp.arrayFromIterable(f.contains.map(function(a){return k("self"===a?f:a)})));f.contains.forEach(function(a){t(a,f)});f.starts&&t(f.starts,
-h);f.matcher=c(f)}}var h=function(){this.matchIndexes={};this.regexes=[];this.matchAt=1;this.position=0};h.prototype.addRule=function(a,b){b.position=this.position++;this.matchIndexes[this.matchAt]=b;this.regexes.push([b,a]);this.matchAt+=(new RegExp(a.toString()+"|")).exec("").length-1+1};h.prototype.compile=function(){0===this.regexes.length&&(this.exec=function(){return null});var a=this.regexes.map(function(a){return a[1]});this.matcherRe=b(f(a,"|"),!0);this.lastIndex=0};h.prototype.exec=function(a){this.matcherRe.lastIndex=
-this.lastIndex;a=this.matcherRe.exec(a);if(!a)return null;var b=a.findIndex(function(a,b){return 0<b&&void 0!==a}),c=this.matchIndexes[b];a.splice(0,b);return Object.assign(a,c)};var g=function(){this.rules=[];this.multiRegexes=[];this.regexIndex=this.lastIndex=this.count=0};g.prototype.getMatcher=function(a){if(this.multiRegexes[a])return this.multiRegexes[a];var b=new h;this.rules.slice(a).forEach(function(a){var c=$jscomp.makeIterator(a);a=c.next().value;c=c.next().value;return b.addRule(a,c)});
-b.compile();return this.multiRegexes[a]=b};g.prototype.considerAll=function(){this.regexIndex=0};g.prototype.addRule=function(a,b){this.rules.push([a,b]);"begin"===b.type&&this.count++};g.prototype.exec=function(a){var b=this.getMatcher(this.regexIndex);b.lastIndex=this.lastIndex;if(a=b.exec(a))this.regexIndex+=a.position+1,this.regexIndex===this.count&&(this.regexIndex=0);return a};if(a.contains&&a.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");
-t(a)}function g(a){return a?a.endsWithParent||g(a.starts):!1}function k(a){a.variants&&!a.cached_variants&&(a.cached_variants=a.variants.map(function(b){return c(a,{variants:null},b)}));return a.cached_variants?a.cached_variants:g(a)?c(a,{starts:a.starts?c(a.starts):null}):Object.isFrozen(a)?c(a):a}function l(a,b){function c(a,c){b&&(c=c.toLowerCase());c.split(" ").forEach(function(b){var c=b.split("|");b=c[0];var e=c[1];c=e?Number(e):I.includes(c[0].toLowerCase())?0:1;d[b]=[a,c]})}var d={};"string"===
-typeof a?c("keyword",a):Object.keys(a).forEach(function(b){c(b,a[b])});return d}var m=function(a){void 0===a.data&&(a.data={});this.data=a.data};m.prototype.ignoreMatch=function(){this.ignore=!0};var p=Object.freeze({__proto__:null,escapeHTML:b,inherit:c,nodeStream:function(a){var b=[];(function R(a,c){for(a=a.firstChild;a;a=a.nextSibling)3===a.nodeType?c+=a.nodeValue.length:1===a.nodeType&&(b.push({event:"start",offset:c,node:a}),c=R(a,c),a.nodeName.toLowerCase().match(/br|hr|img|input/)||b.push({event:"stop",
-offset:c,node:a}));return c})(a,0);return b},mergeStreams:function(a,c,d){function e(){return a.length&&c.length?a[0].offset!==c[0].offset?a[0].offset<c[0].offset?a:c:"start"===c[0].event?a:c:a.length?a:c}function f(a){l+="<"+a.nodeName.toLowerCase()+[].map.call(a.attributes,function(a){return" "+a.nodeName+'="'+b(a.value).replace(/"/g,"&quot;")+'"'}).join("")+">"}function h(a){l+="</"+a.nodeName.toLowerCase()+">"}function g(a){("start"===a.event?f:h)(a.node)}for(var t=0,l="",k=[];a.length||c.length;){var m=
-e();l+=b(d.substring(t,m[0].offset));t=m[0].offset;if(m===a){k.reverse().forEach(h);do g(m.splice(0,1)[0]),m=e();while(m===a&&m.length&&m[0].offset===t);k.reverse().forEach(f)}else"start"===m[0].event?k.push(m[0].node):k.pop(),g(m.splice(0,1)[0])}return l+b(d.substr(t))}}),q=function(a,b){this.buffer="";this.classPrefix=b.classPrefix;a.walk(this)};q.prototype.addText=function(a){this.buffer+=b(a)};q.prototype.openNode=function(a){if(a.kind){var b=a.kind;a.sublanguage||(b=""+this.classPrefix+b);this.span(b)}};
-q.prototype.closeNode=function(a){a.kind&&(this.buffer+="</span>")};q.prototype.span=function(a){this.buffer+='<span class="'+a+'">'};q.prototype.value=function(){return this.buffer};var r=function(){this.rootNode={children:[]};this.stack=[this.rootNode]};r.prototype.add=function(a){this.top.children.push(a)};r.prototype.openNode=function(a){a={kind:a,children:[]};this.add(a);this.stack.push(a)};r.prototype.closeNode=function(){if(1<this.stack.length)return this.stack.pop()};r.prototype.closeAllNodes=
-function(){for(;this.closeNode(););};r.prototype.toJSON=function(){return JSON.stringify(this.rootNode,null,4)};r.prototype.walk=function(a){return this.constructor._walk(a,this.rootNode)};r._walk=function(a,b){var c=this;"string"===typeof b?a.addText(b):b.children&&(a.openNode(b),b.children.forEach(function(b){return c._walk(a,b)}),a.closeNode(b));return a};r._collapse=function(a){a.children&&(a.children.every(function(a){return"string"===typeof a})?(a.text=a.children.join(""),delete a.children):
-a.children.forEach(function(a){"string"!==typeof a&&r._collapse(a)}))};$jscomp.global.Object.defineProperties(r.prototype,{top:{configurable:!0,enumerable:!0,get:function(){return this.stack[this.stack.length-1]}},root:{configurable:!0,enumerable:!0,get:function(){return this.rootNode}}});var z=function(a){var b=r.call(this)||this;b.options=a;return b};$jscomp.inherits(z,r);z.prototype.addKeyword=function(a,b){""!==a&&(this.openNode(b),this.addText(a),this.closeNode())};z.prototype.addText=function(a){""!==
-a&&this.add(a)};z.prototype.addSublanguage=function(a,b){a=a.root;a.kind=b;a.sublanguage=!0;this.add(a)};z.prototype.toHTML=function(){return(new q(this,this.options)).value()};z.prototype.finalize=function(){return!0};var x={begin:"\\\\[\\s\\S]",relevance:0},S={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[x]},T={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[x]},C={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},
-A=function(a,b,d){a=c({className:"comment",begin:a,end:b,contains:[]},d||{});a.contains.push(C);a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0});return a},U=A("//","$"),V=A("/\\*","\\*/"),W=A("#","$"),G=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:"(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
-SHEBANG:function(a){a=void 0===a?{}:a;var b=/^#![ ]*\//;a.binary&&(a.begin=e(b,/.*\b/,a.binary,/\b.*/));return c({className:"meta",begin:b,end:/$/,relevance:0,"on:begin":function(a,b){0!==a.index&&b.ignoreMatch()}},a)},BACKSLASH_ESCAPE:x,APOS_STRING_MODE:S,QUOTE_STRING_MODE:T,PHRASAL_WORDS_MODE:C,COMMENT:A,C_LINE_COMMENT_MODE:U,C_BLOCK_COMMENT_MODE:V,HASH_COMMENT_MODE:W,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:"(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",
-relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[x,{begin:/\[/,end:/\]/,relevance:0,contains:[x]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",
-begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(a){return Object.assign(a,{"on:begin":function(a,b){b.data._beginMatch=a[1]},"on:end":function(a,b){b.data._beginMatch!==a[1]&&b.ignoreMatch()}})}}),I="of and for in not or if then".split(" "),K=p.nodeStream,X=p.mergeStreams;$jscomp.initSymbol();var L=Symbol("nomatch");return function(d){function e(a){var b=a.className+" ";b+=a.parentNode?a.parentNode.className:"";var c=w.languageDetectRe.exec(b);
-return c?(b=t(c[1]),b||(console.warn("Could not find the language '{}', did you forget to load/include a language module?".replace("{}",c[1])),console.warn("Falling back to no-highlight mode for this block.",a)),b?c[1]:"no-highlight"):b.split(/\s+/).find(function(a){return w.noHighlightRe.test(a)||t(a)})}function f(a,b,c,d){a={code:b,language:a};B("before:highlight",a);c=a.result?a.result:g(a.language,a.code,c,d);c.code=a.code;B("after:highlight",c);return c}function g(a,c,d,e){function f(){if(null!=
-n.subLanguage){if(""!==u){var a="string"===typeof n.subLanguage;if(a&&!E[n.subLanguage])v.addText(u);else{var b=a?g(n.subLanguage,u,!0,r[n.subLanguage]):k(u,n.subLanguage.length?n.subLanguage:null);0<n.relevance&&(x+=b.relevance);a&&(r[n.subLanguage]=b.top);v.addSublanguage(b.emitter,b.language)}}}else if(n.keywords){var c=0;n.keywordPatternRe.lastIndex=0;a=n.keywordPatternRe.exec(u);for(b="";a;){b+=u.substring(c,a.index);c=n;var d=a;d=q.case_insensitive?d[0].toLowerCase():d[0];(c=Object.prototype.hasOwnProperty.call(c.keywords,
-d)&&c.keywords[d])?(d=$jscomp.makeIterator(c),c=d.next().value,d=d.next().value,v.addText(b),b="",x+=d,v.addKeyword(a[0],c)):b+=a[0];c=n.keywordPatternRe.lastIndex;a=n.keywordPatternRe.exec(u)}b+=u.substr(c);v.addText(b)}else v.addText(u);u=""}function l(a){a.className&&v.openNode(a.className);return n=Object.create(a,{parent:{value:n}})}function M(a,b,c){var d;if(d=(d=(d=a.endRe)&&d.exec(c))&&0===d.index){if(a["on:end"]){var e=new m(a);a["on:end"](b,e);e.ignore&&(d=!1)}if(d){for(;a.endsParent&&a.parent;)a=
-a.parent;return a}}if(a.endsWithParent)return M(a.parent,b,c)}function N(b,c){var e=c&&c[0];u+=b;if(null==e)return f(),0;if("begin"===H.type&&"end"===c.type&&H.index===c.index&&""===e){u+=p.slice(c.index,c.index+1);if(!F)throw c=Error("0 width match regex"),c.languageName=a,c.badRule=H.rule,c;return 1}H=c;if("begin"===c.type){a:{e=c[0];b=c.rule;for(var g=new m(b),h=$jscomp.makeIterator([b.__beforeBegin,b["on:begin"]]),k=h.next();!k.done;k=h.next())if(k=k.value)if(k(c,g),g.ignore){0===n.matcher.regexIndex?
-(u+=e[0],c=1):(A=!0,c=0);break a}b&&b.endSameAsBegin&&(b.endRe=new RegExp(e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m"));b.skip?u+=e:(b.excludeBegin&&(u+=e),f(),b.returnBegin||b.excludeBegin||(u=e));l(b);c=b.returnBegin?0:e.length}return c}if("illegal"===c.type&&!d)throw c=Error('Illegal lexeme "'+e+'" for mode "'+(n.className||"<unnamed>")+'"'),c.mode=n,c;if("end"===c.type){b=c[0];g=p.substr(c.index);if(g=M(n,c,g)){h=n;h.skip?u+=b:(h.returnEnd||h.excludeEnd||(u+=b),f(),h.excludeEnd&&(u=b));do n.className&&
-v.closeNode(),n.skip||n.subLanguage||(x+=n.relevance),n=n.parent;while(n!==g.parent);g.starts&&(g.endSameAsBegin&&(g.starts.endRe=g.endRe),l(g.starts));b=h.returnEnd?0:b.length}else b=L;if(b!==L)return b}if("illegal"===c.type&&""===e)return 1;if(1E5<z&&z>3*c.index)throw Error("potential infinite loop, way more iterations than matches");u+=e;return e.length}var p=c,H={},q=t(a);if(!q)throw console.error("Could not find the language '{}', did you forget to load/include a language module?".replace("{}",
-a)),Error('Unknown language: "'+a+'"');h(q);c="";var n=e||q,r={},v=new w.__emitter(w);(function(){for(var a=[],b=n;b!==q;b=b.parent)b.className&&a.unshift(b.className);a.forEach(function(a){return v.openNode(a)})})();var u="",x=0,z=e=0,A=!1;try{for(n.matcher.considerAll();;){z++;A?A=!1:(n.matcher.lastIndex=e,n.matcher.considerAll());var y=n.matcher.exec(p);if(!y)break;var B=p.substring(e,y.index),C=N(B,y);e=y.index+C}N(p.substr(e));v.closeAllNodes();v.finalize();c=v.toHTML();return{relevance:x,value:c,
-language:a,illegal:!1,emitter:v,top:n}}catch(D){if(D.message&&D.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:D.message,context:p.slice(e-100,e+100),mode:D.mode},sofar:c,relevance:0,value:b(p),emitter:v};if(F)return{relevance:0,value:b(p),emitter:v,language:a,top:n,errorRaised:D};throw D;}}function l(a){var c={relevance:0,emitter:new w.__emitter(w),value:b(a),illegal:!1,top:O};c.emitter.addText(a);return c}function k(a,b){b=b||w.languages||Object.keys(E);var c=l(a),d=c;b.filter(t).filter(A).forEach(function(b){var e=
-g(b,a,!1);e.language=b;e.relevance>d.relevance&&(d=e);e.relevance>c.relevance&&(d=c,c=e)});d.language&&(c.second_best=d);return c}function p(a){return w.tabReplace||w.useBR?a.replace(I,function(a){return"\n"===a?w.useBR?"<br>":a:w.tabReplace?a.replace(/\t/g,w.tabReplace):a}):a}function q(a){var b=e(a);if(!w.noHighlightRe.test(b)){B("before:highlightBlock",{block:a,language:b});if(w.useBR){var c=document.createElement("div");c.innerHTML=a.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n")}else c=
-a;var d=c.textContent,g=b?f(b,d,!0):k(d);c=K(c);if(c.length){var h=document.createElement("div");h.innerHTML=g.value;g.value=X(c,K(h),d)}g.value=p(g.value);B("after:highlightBlock",{block:a,result:g});a.innerHTML=g.value;d=a.className;b=b?J[b]:g.language;c=[d.trim()];d.match(/\bhljs\b/)||c.push("hljs");d.includes(b)||c.push(b);b=c.join(" ").trim();a.className=b;a.result={language:g.language,re:g.relevance};g.second_best&&(a.second_best={language:g.second_best.language,re:g.second_best.relevance})}}
-function r(){if(!r.called){r.called=!0;var a=document.querySelectorAll("pre code");C.forEach.call(a,q)}}function t(a){a=(a||"").toLowerCase();return E[a]||E[J[a]]}function x(a,b){var c=b.languageName;"string"===typeof a&&(a=[a]);a.forEach(function(a){return J[a]=c})}function A(a){return(a=t(a))&&!a.disableAutodetect}function B(a,b){P.forEach(function(c){if(c[a])c[a](b)})}var C=[],E={},J={},P=[],F=!0,I=/(^(<[^>]+>|\t|)+|\n)/gm,w={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,
-classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:z},O={disableAutodetect:!0,name:"Plain text"};Object.assign(d,{highlight:f,highlightAuto:k,fixMarkup:p,highlightBlock:q,configure:function(a){w=c(w,a)},initHighlighting:r,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",r,!1)},registerLanguage:function(a,b){var c=null;try{c=b(d)}catch(v){console.error("Language definition for '{}' could not be registered.".replace("{}",a));if(F)console.error(v);else throw v;
-c=O}c.name||(c.name=a);E[a]=c;c.rawDefinition=b.bind(null,d);c.aliases&&x(c.aliases,{languageName:a})},listLanguages:function(){return Object.keys(E)},getLanguage:t,registerAliases:x,requireLanguage:function(a){var b=t(a);if(b)return b;throw Error("The '{}' language is required, but not loaded.".replace("{}",a));},autoDetection:A,inherit:c,addPlugin:function(a){P.push(a)}});d.debugMode=function(){F=!1};d.safeMode=function(){F=!0};d.versionString="10.0.0";for(var Q in G)"object"===typeof G[Q]&&a(G[Q]);
-Object.assign(d,G);return d}({})}();"object"===typeof exports&&"undefined"!==typeof module&&(module.exports=hljs);
-hljs.registerLanguage("1c",function(){return function(a){var b=a.inherit(a.NUMBER_MODE),c={className:"string",begin:'"|\\|',end:'"|$',contains:[{begin:'""'}]},d={begin:"'",end:"'",excludeBegin:!0,excludeEnd:!0,contains:[{className:"number",begin:"\\d{4}([\\.\\\\/:-]?\\d{2}){0,5}"}]},e=a.inherit(a.C_LINE_COMMENT_MODE),f={className:"meta",begin:"#|&",end:"$",keywords:{$pattern:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+","meta-keyword":"\u0434\u0430\u043b\u0435\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u043b\u044f \u0435\u0441\u043b\u0438 \u0438 \u0438\u0437 \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0438\u043d\u0430\u0447\u0435\u0435\u0441\u043b\u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043d\u0435\u0446\u0435\u0441\u043b\u0438 \u043a\u043e\u043d\u0435\u0446\u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043a\u043e\u043d\u0435\u0446\u0446\u0438\u043a\u043b\u0430 \u043d\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u0435\u0440\u0435\u043c \u043f\u043e \u043f\u043e\u043a\u0430 \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0442\u043e\u0433\u0434\u0430 \u0446\u0438\u043a\u043b \u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0438\u0437\u0444\u0430\u0439\u043b\u0430 \u0432\u0435\u0431\u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u0435\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442 \u043a\u043e\u043d\u0435\u0446\u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435\u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435\u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435\u0431\u0435\u0437\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435\u0431\u0435\u0437\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434 \u043f\u043e\u0441\u043b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u043b\u0441\u0442\u044b\u0439\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0431\u044b\u0447\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u043b\u0441\u0442\u044b\u0439\u043a\u043b\u0438\u0435\u043d\u0442\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u043d\u043a\u0438\u0439\u043a\u043b\u0438\u0435\u043d\u0442 "},
-contains:[e]};a={className:"function",variants:[{begin:"\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430|\u0444\u0443\u043d\u043a\u0446\u0438\u044f",end:"\\)",keywords:"\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f"},{begin:"\u043a\u043e\u043d\u0435\u0446\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b|\u043a\u043e\u043d\u0435\u0446\u0444\u0443\u043d\u043a\u0446\u0438\u0438",keywords:"\u043a\u043e\u043d\u0435\u0446\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b \u043a\u043e\u043d\u0435\u0446\u0444\u0443\u043d\u043a\u0446\u0438\u0438"}],
-contains:[{begin:"\\(",end:"\\)",endsParent:!0,contains:[{className:"params",begin:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+",end:",",excludeEnd:!0,endsWithParent:!0,keywords:{$pattern:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+",keyword:"\u0437\u043d\u0430\u0447",literal:"null \u0438\u0441\u0442\u0438\u043d\u0430 \u043b\u043e\u0436\u044c \u043d\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e"},
-contains:[b,c,d]},e]},a.inherit(a.TITLE_MODE,{begin:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+"})]};return{name:"1C:Enterprise",case_insensitive:!0,keywords:{$pattern:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+",keyword:"\u0434\u0430\u043b\u0435\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u043b\u044f \u0435\u0441\u043b\u0438 \u0438 \u0438\u0437 \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0438\u043d\u0430\u0447\u0435\u0435\u0441\u043b\u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043d\u0435\u0446\u0435\u0441\u043b\u0438 \u043a\u043e\u043d\u0435\u0446\u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043a\u043e\u043d\u0435\u0446\u0446\u0438\u043a\u043b\u0430 \u043d\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u0435\u0440\u0435\u043c \u043f\u043e \u043f\u043e\u043a\u0430 \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0442\u043e\u0433\u0434\u0430 \u0446\u0438\u043a\u043b \u044d\u043a\u0441\u043f\u043e\u0440\u0442 ",
+/*
+ Highlight.js 10.6.0 (d24895f4)
+ License: BSD-3-Clause
+ Copyright (c) 2006-2020, Ivan Sagalaev
+*/
+var hljs=function(){"use strict";function e(t){
+return t instanceof Map?t.clear=t.delete=t.set=()=>{
+throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{
+throw Error("set is read-only")
+}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{var i=t[n]
+;"object"!=typeof i||Object.isFrozen(i)||e(i)})),t}var t=e,n=e;t.default=n
+;class i{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}
+ignoreMatch(){this.ignore=!0}}function r(e){
+return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")
+}function s(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
+;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const a=e=>!!e.kind
+;class l{constructor(e,t){
+this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){
+this.buffer+=r(e)}openNode(e){if(!a(e))return;let t=e.kind
+;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){
+a(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){
+this.buffer+=`<span class="${e}">`}}class o{constructor(){this.rootNode={
+children:[]},this.stack=[this.rootNode]}get top(){
+return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){
+this.top.children.push(e)}openNode(e){const t={kind:e,children:[]}
+;this.add(t),this.stack.push(t)}closeNode(){
+if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){
+for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}
+walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){
+return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),
+t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){
+"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{
+o._collapse(e)})))}}class c extends o{constructor(e){super(),this.options=e}
+addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}
+addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root
+;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){
+return new l(this,this.options).value()}finalize(){return!0}}function g(e){
+return e?"string"==typeof e?e:e.source:null}
+const u=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,d="[a-zA-Z]\\w*",h="[a-zA-Z_]\\w*",f="\\b\\d+(\\.\\d+)?",p="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",m="\\b(0b[01]+)",b={
+begin:"\\\\[\\s\\S]",relevance:0},E={className:"string",begin:"'",end:"'",
+illegal:"\\n",contains:[b]},x={className:"string",begin:'"',end:'"',
+illegal:"\\n",contains:[b]},v={
+begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
+},w=(e,t,n={})=>{const i=s({className:"comment",begin:e,end:t,contains:[]},n)
+;return i.contains.push(v),i.contains.push({className:"doctag",
+begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),i
+},y=w("//","$"),N=w("/\\*","\\*/"),R=w("#","$");var _=Object.freeze({
+__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:d,UNDERSCORE_IDENT_RE:h,
+NUMBER_RE:f,C_NUMBER_RE:p,BINARY_NUMBER_RE:m,
+RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
+SHEBANG:(e={})=>{const t=/^#![ ]*\//
+;return e.binary&&(e.begin=((...e)=>e.map((e=>g(e))).join(""))(t,/.*\b/,e.binary,/\b.*/)),
+s({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{
+0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:b,APOS_STRING_MODE:E,
+QUOTE_STRING_MODE:x,PHRASAL_WORDS_MODE:v,COMMENT:w,C_LINE_COMMENT_MODE:y,
+C_BLOCK_COMMENT_MODE:N,HASH_COMMENT_MODE:R,NUMBER_MODE:{className:"number",
+begin:f,relevance:0},C_NUMBER_MODE:{className:"number",begin:p,relevance:0},
+BINARY_NUMBER_MODE:{className:"number",begin:m,relevance:0},CSS_NUMBER_MODE:{
+className:"number",
+begin:f+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",
+relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",
+begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[b,{begin:/\[/,end:/\]/,
+relevance:0,contains:[b]}]}]},TITLE_MODE:{className:"title",begin:d,relevance:0
+},UNDERSCORE_TITLE_MODE:{className:"title",begin:h,relevance:0},METHOD_GUARD:{
+begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{
+"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{
+t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function k(e,t){
+"."===e.input[e.index-1]&&t.ignoreMatch()}function O(e,t){
+t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",
+e.__beforeBegin=k,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,
+void 0===e.relevance&&(e.relevance=0))}function M(e,t){
+Array.isArray(e.illegal)&&(e.illegal=((...e)=>"("+e.map((e=>g(e))).join("|")+")")(...e.illegal))
+}function A(e,t){if(e.match){
+if(e.begin||e.end)throw Error("begin & end are not supported with match")
+;e.begin=e.match,delete e.match}}function L(e,t){
+void 0===e.relevance&&(e.relevance=1)}
+const j=["of","and","for","in","not","or","if","then","parent","list","value"]
+;function B(e,t,n="keyword"){const i={}
+;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{
+Object.assign(i,B(e[n],t,n))})),i;function r(e,n){
+t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|")
+;i[n[0]]=[e,I(n[0],n[1])]}))}}function I(e,t){
+return t?Number(t):(e=>j.includes(e.toLowerCase()))(e)?0:1}
+function T(e,{plugins:t}){function n(t,n){
+return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class i{
+constructor(){
+this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}
+addRule(e,t){
+t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),
+this.matchAt+=(e=>RegExp(e.toString()+"|").exec("").length-1)(e)+1}compile(){
+0===this.regexes.length&&(this.exec=()=>null)
+;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(((e,t="|")=>{let n=0
+;return e.map((e=>{n+=1;const t=n;let i=g(e),r="";for(;i.length>0;){
+const e=u.exec(i);if(!e){r+=i;break}
+r+=i.substring(0,e.index),i=i.substring(e.index+e[0].length),
+"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0],"("===e[0]&&n++)}return r
+})).map((e=>`(${e})`)).join(t)})(e),!0),this.lastIndex=0}exec(e){
+this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e)
+;if(!t)return null
+;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n]
+;return t.splice(0,n),Object.assign(t,i)}}class r{constructor(){
+this.rules=[],this.multiRegexes=[],
+this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){
+if(this.multiRegexes[e])return this.multiRegexes[e];const t=new i
+;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),
+t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){
+return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){
+this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){
+const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex
+;let n=t.exec(e)
+;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{
+const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}
+return n&&(this.regexIndex+=n.position+1,
+this.regexIndex===this.count&&this.considerAll()),n}}
+if(e.compilerExtensions||(e.compilerExtensions=[]),
+e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.")
+;return e.classNameAliases=s(e.classNameAliases||{}),function t(i,a){const l=i
+;if(i.compiled)return l
+;[A].forEach((e=>e(i,a))),e.compilerExtensions.forEach((e=>e(i,a))),
+i.__beforeBegin=null,[O,M,L].forEach((e=>e(i,a))),i.compiled=!0;let o=null
+;if("object"==typeof i.keywords&&(o=i.keywords.$pattern,
+delete i.keywords.$pattern),
+i.keywords&&(i.keywords=B(i.keywords,e.case_insensitive)),
+i.lexemes&&o)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ")
+;return o=o||i.lexemes||/\w+/,
+l.keywordPatternRe=n(o,!0),a&&(i.begin||(i.begin=/\B|\b/),
+l.beginRe=n(i.begin),i.endSameAsBegin&&(i.end=i.begin),
+i.end||i.endsWithParent||(i.end=/\B|\b/),
+i.end&&(l.endRe=n(i.end)),l.terminatorEnd=g(i.end)||"",
+i.endsWithParent&&a.terminatorEnd&&(l.terminatorEnd+=(i.end?"|":"")+a.terminatorEnd)),
+i.illegal&&(l.illegalRe=n(i.illegal)),
+i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>s(e,{
+variants:null},t)))),e.cachedVariants?e.cachedVariants:S(e)?s(e,{
+starts:e.starts?s(e.starts):null
+}):Object.isFrozen(e)?s(e):e))("self"===e?i:e)))),i.contains.forEach((e=>{t(e,l)
+})),i.starts&&t(i.starts,a),l.matcher=(e=>{const t=new r
+;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"
+}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"
+}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(l),l}(e)}function S(e){
+return!!e&&(e.endsWithParent||S(e.starts))}function P(e){const t={
+props:["language","code","autodetect"],data:()=>({detectedLanguage:"",
+unknownLanguage:!1}),computed:{className(){
+return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){
+if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),
+this.unknownLanguage=!0,r(this.code);let t={}
+;return this.autoDetect?(t=e.highlightAuto(this.code),
+this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),
+this.detectedLanguage=this.language),t.value},autoDetect(){
+return!(this.language&&(e=this.autodetect,!e&&""!==e));var e},
+ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{
+class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{
+Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const D={
+"after:highlightElement":({el:e,result:t,text:n})=>{const i=H(e)
+;if(!i.length)return;const s=document.createElement("div")
+;s.innerHTML=t.value,t.value=((e,t,n)=>{let i=0,s="";const a=[];function l(){
+return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset<t[0].offset?e:t:"start"===t[0].event?e:t:e.length?e:t
+}function o(e){s+="<"+C(e)+[].map.call(e.attributes,(function(e){
+return" "+e.nodeName+'="'+r(e.value)+'"'})).join("")+">"}function c(e){
+s+="</"+C(e)+">"}function g(e){("start"===e.event?o:c)(e.node)}
+for(;e.length||t.length;){let t=l()
+;if(s+=r(n.substring(i,t[0].offset)),i=t[0].offset,t===e){a.reverse().forEach(c)
+;do{g(t.splice(0,1)[0]),t=l()}while(t===e&&t.length&&t[0].offset===i)
+;a.reverse().forEach(o)
+}else"start"===t[0].event?a.push(t[0].node):a.pop(),g(t.splice(0,1)[0])}
+return s+r(n.substr(i))})(i,H(s),n)}};function C(e){
+return e.nodeName.toLowerCase()}function H(e){const t=[];return function e(n,i){
+for(let r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?i+=r.nodeValue.length:1===r.nodeType&&(t.push({
+event:"start",offset:i,node:r}),i=e(r,i),C(r).match(/br|hr|img|input/)||t.push({
+event:"stop",offset:i,node:r}));return i}(e,0),t}const $=e=>{console.error(e)
+},U=(e,...t)=>{console.log("WARN: "+e,...t)},z=(e,t)=>{
+console.log(`Deprecated as of ${e}. ${t}`)},K=r,G=s,V=Symbol("nomatch")
+;return(e=>{const n=Object.create(null),r=Object.create(null),s=[];let a=!0
+;const l=/(^(<[^>]+>|\t|)+|\n)/gm,o="Could not find the language '{}', did you forget to load/include a language module?",g={
+disableAutodetect:!0,name:"Plain text",contains:[]};let u={
+noHighlightRe:/^(no-?highlight)$/i,
+languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",
+tabReplace:null,useBR:!1,languages:null,__emitter:c};function d(e){
+return u.noHighlightRe.test(e)}function h(e,t,n,i){const r={code:t,language:e}
+;M("before:highlight",r);const s=r.result?r.result:f(r.language,r.code,n,i)
+;return s.code=r.code,M("after:highlight",s),s}function f(e,t,r,l){const c=t
+;function g(e,t){const n=w.case_insensitive?t[0].toLowerCase():t[0]
+;return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}
+function d(){null!=_.subLanguage?(()=>{if(""===M)return;let e=null
+;if("string"==typeof _.subLanguage){
+if(!n[_.subLanguage])return void O.addText(M)
+;e=f(_.subLanguage,M,!0,k[_.subLanguage]),k[_.subLanguage]=e.top
+}else e=p(M,_.subLanguage.length?_.subLanguage:null)
+;_.relevance>0&&(A+=e.relevance),O.addSublanguage(e.emitter,e.language)
+})():(()=>{if(!_.keywords)return void O.addText(M);let e=0
+;_.keywordPatternRe.lastIndex=0;let t=_.keywordPatternRe.exec(M),n="";for(;t;){
+n+=M.substring(e,t.index);const i=g(_,t);if(i){const[e,r]=i
+;O.addText(n),n="",A+=r;const s=w.classNameAliases[e]||e;O.addKeyword(t[0],s)
+}else n+=t[0];e=_.keywordPatternRe.lastIndex,t=_.keywordPatternRe.exec(M)}
+n+=M.substr(e),O.addText(n)})(),M=""}function h(e){
+return e.className&&O.openNode(w.classNameAliases[e.className]||e.className),
+_=Object.create(e,{parent:{value:_}}),_}function m(e,t,n){let r=((e,t)=>{
+const n=e&&e.exec(t);return n&&0===n.index})(e.endRe,n);if(r){if(e["on:end"]){
+const n=new i(e);e["on:end"](t,n),n.ignore&&(r=!1)}if(r){
+for(;e.endsParent&&e.parent;)e=e.parent;return e}}
+if(e.endsWithParent)return m(e.parent,t,n)}function b(e){
+return 0===_.matcher.regexIndex?(M+=e[0],1):(B=!0,0)}function E(e){
+const t=e[0],n=c.substr(e.index),i=m(_,e,n);if(!i)return V;const r=_
+;r.skip?M+=t:(r.returnEnd||r.excludeEnd||(M+=t),d(),r.excludeEnd&&(M=t));do{
+_.className&&O.closeNode(),_.skip||_.subLanguage||(A+=_.relevance),_=_.parent
+}while(_!==i.parent)
+;return i.starts&&(i.endSameAsBegin&&(i.starts.endRe=i.endRe),
+h(i.starts)),r.returnEnd?0:t.length}let x={};function v(t,n){const s=n&&n[0]
+;if(M+=t,null==s)return d(),0
+;if("begin"===x.type&&"end"===n.type&&x.index===n.index&&""===s){
+if(M+=c.slice(n.index,n.index+1),!a){const t=Error("0 width match regex")
+;throw t.languageName=e,t.badRule=x.rule,t}return 1}
+if(x=n,"begin"===n.type)return function(e){
+const t=e[0],n=e.rule,r=new i(n),s=[n.__beforeBegin,n["on:begin"]]
+;for(const n of s)if(n&&(n(e,r),r.ignore))return b(t)
+;return n&&n.endSameAsBegin&&(n.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),
+n.skip?M+=t:(n.excludeBegin&&(M+=t),
+d(),n.returnBegin||n.excludeBegin||(M=t)),h(n),n.returnBegin?0:t.length}(n)
+;if("illegal"===n.type&&!r){
+const e=Error('Illegal lexeme "'+s+'" for mode "'+(_.className||"<unnamed>")+'"')
+;throw e.mode=_,e}if("end"===n.type){const e=E(n);if(e!==V)return e}
+if("illegal"===n.type&&""===s)return 1
+;if(j>1e5&&j>3*n.index)throw Error("potential infinite loop, way more iterations than matches")
+;return M+=s,s.length}const w=R(e)
+;if(!w)throw $(o.replace("{}",e)),Error('Unknown language: "'+e+'"')
+;const y=T(w,{plugins:s});let N="",_=l||y;const k={},O=new u.__emitter(u);(()=>{
+const e=[];for(let t=_;t!==w;t=t.parent)t.className&&e.unshift(t.className)
+;e.forEach((e=>O.openNode(e)))})();let M="",A=0,L=0,j=0,B=!1;try{
+for(_.matcher.considerAll();;){
+j++,B?B=!1:_.matcher.considerAll(),_.matcher.lastIndex=L
+;const e=_.matcher.exec(c);if(!e)break;const t=v(c.substring(L,e.index),e)
+;L=e.index+t}return v(c.substr(L)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{
+relevance:Math.floor(A),value:N,language:e,illegal:!1,emitter:O,top:_}}catch(t){
+if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{
+msg:t.message,context:c.slice(L-100,L+100),mode:t.mode},sofar:N,relevance:0,
+value:K(c),emitter:O};if(a)return{illegal:!1,relevance:0,value:K(c),emitter:O,
+language:e,top:_,errorRaised:t};throw t}}function p(e,t){
+t=t||u.languages||Object.keys(n);const i=(e=>{const t={relevance:0,
+emitter:new u.__emitter(u),value:K(e),illegal:!1,top:g}
+;return t.emitter.addText(e),t})(e),r=t.filter(R).filter(O).map((t=>f(t,e,!1)))
+;r.unshift(i);const s=r.sort(((e,t)=>{
+if(e.relevance!==t.relevance)return t.relevance-e.relevance
+;if(e.language&&t.language){if(R(e.language).supersetOf===t.language)return 1
+;if(R(t.language).supersetOf===e.language)return-1}return 0})),[a,l]=s,o=a
+;return o.second_best=l,o}const m={"before:highlightElement":({el:e})=>{
+u.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"))
+},"after:highlightElement":({result:e})=>{
+u.useBR&&(e.value=e.value.replace(/\n/g,"<br>"))}},b=/^(<[^>]+>|\t)+/gm,E={
+"after:highlightElement":({result:e})=>{
+u.tabReplace&&(e.value=e.value.replace(b,(e=>e.replace(/\t/g,u.tabReplace))))}}
+;function x(e){let t=null;const n=(e=>{let t=e.className+" "
+;t+=e.parentNode?e.parentNode.className:"";const n=u.languageDetectRe.exec(t)
+;if(n){const t=R(n[1])
+;return t||(U(o.replace("{}",n[1])),U("Falling back to no-highlight mode for this block.",e)),
+t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>d(e)||R(e)))})(e)
+;if(d(n))return;M("before:highlightElement",{el:e,language:n}),t=e
+;const i=t.textContent,s=n?h(n,i,!0):p(i);M("after:highlightElement",{el:e,
+result:s,text:i}),e.innerHTML=s.value,((e,t,n)=>{const i=t?r[t]:n
+;e.classList.add("hljs"),i&&e.classList.add(i)})(e,n,s.language),e.result={
+language:s.language,re:s.relevance,relavance:s.relevance
+},s.second_best&&(e.second_best={language:s.second_best.language,
+re:s.second_best.relevance,relavance:s.second_best.relevance})}const v=()=>{
+v.called||(v.called=!0,
+z("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead."),
+document.querySelectorAll("pre code").forEach(x))};let w=!1,y=!1;function N(){
+y?document.querySelectorAll("pre code").forEach(x):w=!0}function R(e){
+return e=(e||"").toLowerCase(),n[e]||n[r[e]]}function k(e,{languageName:t}){
+"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e.toLowerCase()]=t}))}
+function O(e){const t=R(e);return t&&!t.disableAutodetect}function M(e,t){
+const n=e;s.forEach((e=>{e[n]&&e[n](t)}))}
+"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{
+y=!0,w&&N()}),!1),Object.assign(e,{highlight:h,highlightAuto:p,highlightAll:N,
+fixMarkup:e=>{
+return z("10.2.0","fixMarkup will be removed entirely in v11.0"),z("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),
+t=e,
+u.tabReplace||u.useBR?t.replace(l,(e=>"\n"===e?u.useBR?"<br>":e:u.tabReplace?e.replace(/\t/g,u.tabReplace):e)):t
+;var t},highlightElement:x,
+highlightBlock:e=>(z("10.7.0","highlightBlock will be removed entirely in v12.0"),
+z("10.7.0","Please use highlightElement now."),x(e)),configure:e=>{
+e.useBR&&(z("10.3.0","'useBR' will be removed entirely in v11.0"),
+z("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),
+u=G(u,e)},initHighlighting:v,initHighlightingOnLoad:()=>{
+z("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),
+w=!0},registerLanguage:(t,i)=>{let r=null;try{r=i(e)}catch(e){
+if($("Language definition for '{}' could not be registered.".replace("{}",t)),
+!a)throw e;$(e),r=g}
+r.name||(r.name=t),n[t]=r,r.rawDefinition=i.bind(null,e),r.aliases&&k(r.aliases,{
+languageName:t})},unregisterLanguage:e=>{delete n[e]
+;for(const t of Object.keys(r))r[t]===e&&delete r[t]},
+listLanguages:()=>Object.keys(n),getLanguage:R,registerAliases:k,
+requireLanguage:e=>{
+z("10.4.0","requireLanguage will be removed entirely in v11."),
+z("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844")
+;const t=R(e);if(t)return t
+;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},
+autoDetection:O,inherit:G,addPlugin:e=>{(e=>{
+e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{
+e["before:highlightBlock"](Object.assign({block:t.el},t))
+}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{
+e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)},
+vuePlugin:P(e).VuePlugin}),e.debugMode=()=>{a=!1},e.safeMode=()=>{a=!0
+},e.versionString="10.6.0";for(const e in _)"object"==typeof _[e]&&t(_[e])
+;return Object.assign(e,_),e.addPlugin(m),e.addPlugin(D),e.addPlugin(E),e})({})
+}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);
+hljs.registerLanguage("1c",(()=>{"use strict";return s=>{
+var x="[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]+",n="\u0434\u0430\u043b\u0435\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u043b\u044f \u0435\u0441\u043b\u0438 \u0438 \u0438\u0437 \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0438\u043d\u0430\u0447\u0435\u0435\u0441\u043b\u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043d\u0435\u0446\u0435\u0441\u043b\u0438 \u043a\u043e\u043d\u0435\u0446\u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043a\u043e\u043d\u0435\u0446\u0446\u0438\u043a\u043b\u0430 \u043d\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u0435\u0440\u0435\u043c \u043f\u043e \u043f\u043e\u043a\u0430 \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0442\u043e\u0433\u0434\u0430 \u0446\u0438\u043a\u043b \u044d\u043a\u0441\u043f\u043e\u0440\u0442 ",e="null \u0438\u0441\u0442\u0438\u043d\u0430 \u043b\u043e\u0436\u044c \u043d\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e",o=s.inherit(s.NUMBER_MODE),t={
+className:"string",begin:'"|\\|',end:'"|$',contains:[{begin:'""'}]},a={
+begin:"'",end:"'",excludeBegin:!0,excludeEnd:!0,contains:[{className:"number",
+begin:"\\d{4}([\\.\\\\/:-]?\\d{2}){0,5}"}]},m=s.inherit(s.C_LINE_COMMENT_MODE)
+;return{name:"1C:Enterprise",case_insensitive:!0,keywords:{$pattern:x,keyword:n,
built_in:"\u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u0441\u0442\u0440\u043e\u043a \u0441\u0438\u043c\u0432\u043e\u043b\u0442\u0430\u0431\u0443\u043b\u044f\u0446\u0438\u0438 ansitooem oemtoansi \u0432\u0432\u0435\u0441\u0442\u0438\u0432\u0438\u0434\u0441\u0443\u0431\u043a\u043e\u043d\u0442\u043e \u0432\u0432\u0435\u0441\u0442\u0438\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0432\u0432\u0435\u0441\u0442\u0438\u043f\u0435\u0440\u0438\u043e\u0434 \u0432\u0432\u0435\u0441\u0442\u0438\u043f\u043b\u0430\u043d\u0441\u0447\u0435\u0442\u043e\u0432 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439\u043f\u043b\u0430\u043d\u0441\u0447\u0435\u0442\u043e\u0432 \u0434\u0430\u0442\u0430\u0433\u043e\u0434 \u0434\u0430\u0442\u0430\u043c\u0435\u0441\u044f\u0446 \u0434\u0430\u0442\u0430\u0447\u0438\u0441\u043b\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0432\u0441\u0442\u0440\u043e\u043a\u0443 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0438\u0437\u0441\u0442\u0440\u043e\u043a\u0438 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0438\u0431 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a\u043e\u0434\u0441\u0438\u043c\u0432 \u043a\u043e\u043d\u0433\u043e\u0434\u0430 \u043a\u043e\u043d\u0435\u0446\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u0431\u0438 \u043a\u043e\u043d\u0435\u0446\u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0433\u043e\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u0431\u0438 \u043a\u043e\u043d\u0435\u0446\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043a\u043e\u043d\u043a\u0432\u0430\u0440\u0442\u0430\u043b\u0430 \u043a\u043e\u043d\u043c\u0435\u0441\u044f\u0446\u0430 \u043a\u043e\u043d\u043d\u0435\u0434\u0435\u043b\u0438 \u043b\u043e\u0433 \u043b\u043e\u043310 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u0441\u0443\u0431\u043a\u043e\u043d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043d\u0430\u0431\u043e\u0440\u0430\u043f\u0440\u0430\u0432 \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c\u0432\u0438\u0434 \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c\u0441\u0447\u0435\u0442 \u043d\u0430\u0439\u0442\u0438\u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430\u0447\u0430\u043b\u043e\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u0431\u0438 \u043d\u0430\u0447\u0430\u043b\u043e\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043d\u0430\u0447\u0433\u043e\u0434\u0430 \u043d\u0430\u0447\u043a\u0432\u0430\u0440\u0442\u0430\u043b\u0430 \u043d\u0430\u0447\u043c\u0435\u0441\u044f\u0446\u0430 \u043d\u0430\u0447\u043d\u0435\u0434\u0435\u043b\u0438 \u043d\u043e\u043c\u0435\u0440\u0434\u043d\u044f\u0433\u043e\u0434\u0430 \u043d\u043e\u043c\u0435\u0440\u0434\u043d\u044f\u043d\u0435\u0434\u0435\u043b\u0438 \u043d\u043e\u043c\u0435\u0440\u043d\u0435\u0434\u0435\u043b\u0438\u0433\u043e\u0434\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439\u0436\u0443\u0440\u043d\u0430\u043b\u0440\u0430\u0441\u0447\u0435\u0442\u043e\u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439\u043f\u043b\u0430\u043d\u0441\u0447\u0435\u0442\u043e\u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439\u044f\u0437\u044b\u043a \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c\u043e\u043a\u043d\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u0435\u0440\u0438\u043e\u0434\u0441\u0442\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u0430\u0442\u0443\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043e\u0442\u0431\u043e\u0440\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u043e\u0437\u0438\u0446\u0438\u044e\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u0443\u0441\u0442\u043e\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0442\u0430 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430\u0432\u0442\u043e\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u043f\u0438\u0441\u044c \u043f\u0443\u0441\u0442\u043e\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u043f\u043e\u0437\u0438\u0446\u0438\u044e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u043d\u0430 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u043f\u043e \u0441\u0438\u043c\u0432 \u0441\u043e\u0437\u0434\u0430\u0442\u044c\u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0441\u0442\u0440\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u0441\u0442\u0440\u043e\u043a \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u043f\u043e\u0437\u0438\u0446\u0438\u044e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0441\u0447\u0435\u0442\u043f\u043e\u043a\u043e\u0434\u0443 \u0442\u0435\u043a\u0443\u0449\u0435\u0435\u0432\u0440\u0435\u043c\u044f \u0442\u0438\u043f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0441\u0442\u0440 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0442\u0430\u043d\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0442\u0430\u043f\u043e \u0444\u0438\u043a\u0441\u0448\u0430\u0431\u043b\u043e\u043d \u0448\u0430\u0431\u043b\u043e\u043d acos asin atan base64\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 base64\u0441\u0442\u0440\u043e\u043a\u0430 cos exp log log10 pow sin sqrt tan xml\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 xml\u0441\u0442\u0440\u043e\u043a\u0430 xml\u0442\u0438\u043f xml\u0442\u0438\u043f\u0437\u043d\u0447 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435\u043e\u043a\u043d\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c\u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u043b\u0435\u0432\u043e \u0432\u0432\u0435\u0441\u0442\u0438\u0434\u0430\u0442\u0443 \u0432\u0432\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432\u0432\u0435\u0441\u0442\u0438\u0441\u0442\u0440\u043e\u043a\u0443 \u0432\u0432\u0435\u0441\u0442\u0438\u0447\u0438\u0441\u043b\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u0447\u0442\u0435\u043d\u0438\u044fxml \u0432\u043e\u043f\u0440\u043e\u0441 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u0433 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0436\u0443\u0440\u043d\u0430\u043b\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443\u043f\u0440\u0430\u0432\u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0433\u043e\u0434 \u0434\u0430\u043d\u043d\u044b\u0435\u0444\u043e\u0440\u043c\u044b\u0432\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u0442\u0430 \u0434\u0435\u043d\u044c \u0434\u0435\u043d\u044c\u0433\u043e\u0434\u0430 \u0434\u0435\u043d\u044c\u043d\u0435\u0434\u0435\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c\u043c\u0435\u0441\u044f\u0446 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0434\u0430\u043d\u043d\u044b\u0435\u0434\u043b\u044f\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0440\u0430\u0431\u043e\u0442\u0443\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\u0440\u0430\u0431\u043e\u0442\u0443\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0432\u043d\u0435\u0448\u043d\u044e\u044e\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u0437\u0430\u043a\u0440\u044b\u0442\u044c\u0441\u043f\u0440\u0430\u0432\u043a\u0443 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044cjson \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044cxml \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c\u0434\u0430\u0442\u0443json \u0437\u0430\u043f\u0438\u0441\u044c\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0432\u0434\u0430\u043d\u043d\u044b\u0435\u0444\u043e\u0440\u043c\u044b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0432\u0441\u0442\u0440\u043e\u043a\u0443\u0432\u043d\u0443\u0442\u0440 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0432\u0444\u0430\u0439\u043b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0438\u0437\u0441\u0442\u0440\u043e\u043a\u0438\u0432\u043d\u0443\u0442\u0440 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0438\u0437\u0444\u0430\u0439\u043b\u0430 \u0438\u0437xml\u0442\u0438\u043f\u0430 \u0438\u043c\u043f\u043e\u0440\u0442\u043c\u043e\u0434\u0435\u043b\u0438xdto \u0438\u043c\u044f\u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430 \u0438\u043c\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u043e\u0431\u043e\u0448\u0438\u0431\u043a\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438\u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445\u0444\u0430\u0439\u043b\u043e\u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u0442\u0440\u043e\u043a\u0443 \u043a\u043e\u0434\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043a\u043e\u0434\u0441\u0438\u043c\u0432\u043e\u043b\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043a\u043e\u043d\u0435\u0446\u0433\u043e\u0434\u0430 \u043a\u043e\u043d\u0435\u0446\u0434\u043d\u044f \u043a\u043e\u043d\u0435\u0446\u043a\u0432\u0430\u0440\u0442\u0430\u043b\u0430 \u043a\u043e\u043d\u0435\u0446\u043c\u0435\u0441\u044f\u0446\u0430 \u043a\u043e\u043d\u0435\u0446\u043c\u0438\u043d\u0443\u0442\u044b \u043a\u043e\u043d\u0435\u0446\u043d\u0435\u0434\u0435\u043b\u0438 \u043a\u043e\u043d\u0435\u0446\u0447\u0430\u0441\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0431\u0430\u0437\u044b\u0434\u0430\u043d\u043d\u044b\u0445\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0430\u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0430 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0434\u0430\u043d\u043d\u044b\u0435\u0444\u043e\u0440\u043c\u044b \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0444\u0430\u0439\u043b \u043a\u0440\u0430\u0442\u043a\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043e\u0448\u0438\u0431\u043a\u0438 \u043b\u0435\u0432 \u043c\u0430\u043a\u0441 \u043c\u0435\u0441\u0442\u043d\u043e\u0435\u0432\u0440\u0435\u043c\u044f \u043c\u0435\u0441\u044f\u0446 \u043c\u0438\u043d \u043c\u0438\u043d\u0443\u0442\u0430 \u043c\u043e\u043d\u043e\u043f\u043e\u043b\u044c\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u043d\u0430\u0439\u0442\u0438 \u043d\u0430\u0439\u0442\u0438\u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435\u0441\u0438\u043c\u0432\u043e\u043b\u044bxml \u043d\u0430\u0439\u0442\u0438\u043e\u043a\u043d\u043e\u043f\u043e\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0441\u0441\u044b\u043b\u043a\u0435 \u043d\u0430\u0439\u0442\u0438\u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435\u043d\u0430\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0439\u0442\u0438\u043f\u043e\u0441\u0441\u044b\u043b\u043a\u0430\u043c \u043d\u0430\u0439\u0442\u0438\u0444\u0430\u0439\u043b\u044b \u043d\u0430\u0447\u0430\u043b\u043e\u0433\u043e\u0434\u0430 \u043d\u0430\u0447\u0430\u043b\u043e\u0434\u043d\u044f \u043d\u0430\u0447\u0430\u043b\u043e\u043a\u0432\u0430\u0440\u0442\u0430\u043b\u0430 \u043d\u0430\u0447\u0430\u043b\u043e\u043c\u0435\u0441\u044f\u0446\u0430 \u043d\u0430\u0447\u0430\u043b\u043e\u043c\u0438\u043d\u0443\u0442\u044b \u043d\u0430\u0447\u0430\u043b\u043e\u043d\u0435\u0434\u0435\u043b\u0438 \u043d\u0430\u0447\u0430\u043b\u043e\u0447\u0430\u0441\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u0437\u0430\u043f\u0440\u043e\u0441\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430\u0447\u0430\u0442\u044c\u0437\u0430\u043f\u0443\u0441\u043a\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0447\u0430\u0442\u044c\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u0432\u043d\u0435\u0448\u043d\u0435\u0439\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0435\u0439 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u0438\u0441\u043a\u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445\u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u043f\u043e\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u0434\u0432\u043e\u0438\u0447\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445\u0438\u0437\u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043d\u0430\u0447\u0430\u0442\u044c\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043d\u0430\u0447\u0430\u0442\u044c\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430\u0447\u0430\u0442\u044c\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443\u0432\u043d\u0435\u0448\u043d\u0435\u0439\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043d\u0430\u0447\u0430\u0442\u044c\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0435\u0439 \u043d\u0430\u0447\u0430\u0442\u044c\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u043d\u0435\u0434\u0435\u043b\u044f\u0433\u043e\u0434\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043d\u043e\u043c\u0435\u0440\u0441\u0435\u0430\u043d\u0441\u0430\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043d\u043e\u043c\u0435\u0440\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043d\u0440\u0435\u0433 \u043d\u0441\u0442\u0440 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044e\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u043f\u0440\u0435\u0440\u044b\u0432\u0430\u043d\u0438\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c\u0444\u0430\u0439\u043b\u044b \u043e\u043a\u0440 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043e\u0448\u0438\u0431\u043a\u0438 \u043e\u043f\u043e\u0432\u0435\u0441\u0442\u0438\u0442\u044c \u043e\u043f\u043e\u0432\u0435\u0441\u0442\u0438\u0442\u044c\u043e\u0431\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0438\u043d\u0434\u0435\u043a\u0441\u0441\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u0441\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0441\u043f\u0440\u0430\u0432\u043a\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0444\u043e\u0440\u043c\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u044c\u0444\u043e\u0440\u043c\u0443\u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c\u0436\u0443\u0440\u043d\u0430\u043b\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u0435\u0440\u0435\u0439\u0442\u0438\u043f\u043e\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0441\u0441\u044b\u043b\u043a\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0444\u0430\u0439\u043b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0432\u043d\u0435\u0448\u043d\u044e\u044e\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0435\u0439 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0432\u0432\u043e\u0434\u0434\u0430\u0442\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0432\u0432\u043e\u0434\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0432\u0432\u043e\u0434\u0441\u0442\u0440\u043e\u043a\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0432\u0432\u043e\u0434\u0447\u0438\u0441\u043b\u0430 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e\u043e\u0431\u043e\u0448\u0438\u0431\u043a\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u043d\u0430\u043a\u0430\u0440\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u043d\u043e\u0435\u0438\u043c\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044ccom\u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044cxml\u0442\u0438\u043f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0430\u0434\u0440\u0435\u0441\u043f\u043e\u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0443\u0441\u0435\u0430\u043d\u0441\u043e\u0432 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f\u0441\u043f\u044f\u0449\u0435\u0433\u043e\u0441\u0435\u0430\u043d\u0441\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u0437\u0430\u0441\u044b\u043f\u0430\u043d\u0438\u044f\u043f\u0430\u0441\u0441\u0438\u0432\u043d\u043e\u0433\u043e\u0441\u0435\u0430\u043d\u0441\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u0430\u043d\u043d\u044b\u0435\u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435\u043a\u043e\u0434\u044b\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435\u0447\u0430\u0441\u043e\u0432\u044b\u0435\u043f\u043e\u044f\u0441\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043e\u0442\u0431\u043e\u0440\u0430\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u0437\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u043c\u044f\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e\u0444\u0430\u0439\u043b\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u043c\u044f\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e\u044d\u043a\u0440\u0430\u043d\u043e\u0432\u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043a\u0440\u0430\u0442\u043a\u0438\u0439\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0430\u043a\u0435\u0442\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0430\u0441\u043a\u0443\u0432\u0441\u0435\u0444\u0430\u0439\u043b\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0430\u0441\u043a\u0443\u0432\u0441\u0435\u0444\u0430\u0439\u043b\u044b\u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0430\u0441\u043a\u0443\u0432\u0441\u0435\u0444\u0430\u0439\u043b\u044b\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e\u0434\u043b\u0438\u043d\u0443\u043f\u0430\u0440\u043e\u043b\u0435\u0439\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e\u0441\u0441\u044b\u043b\u043a\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e\u0441\u0441\u044b\u043b\u043a\u0443\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u0431\u0430\u0437\u044b\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u0431\u0449\u0438\u0439\u043c\u0430\u043a\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u0431\u0449\u0443\u044e\u0444\u043e\u0440\u043c\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u043a\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e\u043e\u0442\u043c\u0435\u0442\u043a\u0443\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e\u0440\u0435\u0436\u0438\u043c\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445\u043e\u043f\u0446\u0438\u0439\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u043e\u043b\u043d\u043e\u0435\u0438\u043c\u044f\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445\u0441\u0441\u044b\u043b\u043e\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443\u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438\u043f\u0430\u0440\u043e\u043b\u0435\u0439\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u043f\u0443\u0442\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u043f\u0443\u0442\u0438\u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u043f\u0443\u0442\u0438\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u0435\u0430\u043d\u0441\u044b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435\u043e\u0431\u044a\u0435\u043a\u0442\u0430\u0438\u0444\u043e\u0440\u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u043e\u0441\u0442\u0430\u0432\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430odata \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f\u0431\u0430\u0437\u044b\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0442\u0435\u043a\u0443\u0449\u0438\u0439\u0441\u0435\u0430\u043d\u0441\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0444\u0430\u0439\u043b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0444\u0430\u0439\u043b\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0444\u043e\u0440\u043c\u0443 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0443\u044e\u043e\u043f\u0446\u0438\u044e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0443\u044e\u043e\u043f\u0446\u0438\u044e\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0447\u0430\u0441\u043e\u0432\u043e\u0439\u043f\u043e\u044f\u0441\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438\u043e\u0441 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0432\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0444\u0430\u0439\u043b \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0444\u0430\u0439\u043b\u044b \u043f\u0440\u0430\u0432 \u043f\u0440\u0430\u0432\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043a\u043e\u0434\u0430\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0435\u0440\u0438\u043e\u0434\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0430\u0432\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0447\u0430\u0441\u043e\u0432\u043e\u0433\u043e\u043f\u043e\u044f\u0441\u0430 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u0442\u044c\u0440\u0430\u0431\u043e\u0442\u0443\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c\u0432\u044b\u0437\u043e\u0432 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044cjson \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044cxml \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\u0434\u0430\u0442\u0443json \u043f\u0443\u0441\u0442\u0430\u044f\u0441\u0442\u0440\u043e\u043a\u0430 \u0440\u0430\u0431\u043e\u0447\u0438\u0439\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0440\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0434\u0430\u043d\u043d\u044b\u0435\u0434\u043b\u044f\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c\u0444\u0430\u0439\u043b \u0440\u0430\u0437\u043e\u0440\u0432\u0430\u0442\u044c\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u0441\u0432\u043d\u0435\u0448\u043d\u0438\u043c\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u0442\u0440\u043e\u043a\u0443 \u0440\u043e\u043b\u044c\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0441\u0435\u043a\u0443\u043d\u0434\u0430 \u0441\u0438\u0433\u043d\u0430\u043b \u0441\u0438\u043c\u0432\u043e\u043b \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0436\u0443\u0440\u043d\u0430\u043b\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u043b\u0435\u0442\u043d\u0435\u0433\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c\u0431\u0443\u0444\u0435\u0440\u044b\u0434\u0432\u043e\u0438\u0447\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0442\u044c\u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0444\u0430\u0431\u0440\u0438\u043a\u0443xdto \u0441\u043e\u043a\u0440\u043b \u0441\u043e\u043a\u0440\u043b\u043f \u0441\u043e\u043a\u0440\u043f \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u0440\u0435\u0434 \u0441\u0442\u0440\u0434\u043b\u0438\u043d\u0430 \u0441\u0442\u0440\u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f\u043d\u0430 \u0441\u0442\u0440\u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0442\u0440\u043d\u0430\u0439\u0442\u0438 \u0441\u0442\u0440\u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f\u0441 \u0441\u0442\u0440\u043e\u043a\u0430 \u0441\u0442\u0440\u043e\u043a\u0430\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u0441\u0442\u0440\u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u0442\u0440\u043e\u043a\u0443 \u0441\u0442\u0440\u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0441\u0442\u0440\u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0441\u0442\u0440\u0447\u0438\u0441\u043b\u043e\u0432\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0439 \u0441\u0442\u0440\u0447\u0438\u0441\u043b\u043e\u0441\u0442\u0440\u043e\u043a \u0441\u0442\u0440\u0448\u0430\u0431\u043b\u043e\u043d \u0442\u0435\u043a\u0443\u0449\u0430\u044f\u0434\u0430\u0442\u0430 \u0442\u0435\u043a\u0443\u0449\u0430\u044f\u0434\u0430\u0442\u0430\u0441\u0435\u0430\u043d\u0441\u0430 \u0442\u0435\u043a\u0443\u0449\u0430\u044f\u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0430\u044f\u0434\u0430\u0442\u0430 \u0442\u0435\u043a\u0443\u0449\u0430\u044f\u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0430\u044f\u0434\u0430\u0442\u0430\u0432\u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e\u0448\u0440\u0438\u0444\u0442\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u043a\u043e\u0434\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u0440\u0435\u0436\u0438\u043c\u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u044f\u0437\u044b\u043a \u0442\u0435\u043a\u0443\u0449\u0438\u0439\u044f\u0437\u044b\u043a\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0442\u0438\u043f \u0442\u0438\u043f\u0437\u043d\u0447 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u0442\u0440\u0435\u0433 \u0443\u0434\u0430\u043b\u0438\u0442\u044c\u0434\u0430\u043d\u043d\u044b\u0435\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u0443\u0434\u0430\u043b\u0438\u0442\u044c\u0438\u0437\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0443\u0434\u0430\u043b\u0438\u0442\u044c\u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0443\u0434\u0430\u043b\u0438\u0442\u044c\u0444\u0430\u0439\u043b\u044b \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0435\u0432\u0440\u0435\u043c\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c\u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0443\u0441\u0435\u0430\u043d\u0441\u043e\u0432 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0432\u043d\u0435\u0448\u043d\u044e\u044e\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f\u0441\u043f\u044f\u0449\u0435\u0433\u043e\u0441\u0435\u0430\u043d\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u0437\u0430\u0441\u044b\u043f\u0430\u043d\u0438\u044f\u043f\u0430\u0441\u0441\u0438\u0432\u043d\u043e\u0433\u043e\u0441\u0435\u0430\u043d\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0432\u0440\u0435\u043c\u044f\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043a\u0440\u0430\u0442\u043a\u0438\u0439\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e\u0434\u043b\u0438\u043d\u0443\u043f\u0430\u0440\u043e\u043b\u0435\u0439\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043c\u043e\u043d\u043e\u043f\u043e\u043b\u044c\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e\u0440\u0435\u0436\u0438\u043c\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445\u043e\u043f\u0446\u0438\u0439\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443\u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438\u043f\u0430\u0440\u043e\u043b\u0435\u0439\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0435\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0442\u044b\u0441\u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u0441\u0432\u043d\u0435\u0448\u043d\u0438\u043c\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c\u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435\u043e\u0431\u044a\u0435\u043a\u0442\u0430\u0438\u0444\u043e\u0440\u043c\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u043e\u0441\u0442\u0430\u0432\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430odata \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0447\u0430\u0441\u043e\u0432\u043e\u0439\u043f\u043e\u044f\u0441\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0447\u0430\u0441\u043e\u0432\u043e\u0439\u043f\u043e\u044f\u0441\u0441\u0435\u0430\u043d\u0441\u0430 \u0444\u043e\u0440\u043c\u0430\u0442 \u0446\u0435\u043b \u0447\u0430\u0441 \u0447\u0430\u0441\u043e\u0432\u043e\u0439\u043f\u043e\u044f\u0441 \u0447\u0430\u0441\u043e\u0432\u043e\u0439\u043f\u043e\u044f\u0441\u0441\u0435\u0430\u043d\u0441\u0430 \u0447\u0438\u0441\u043b\u043e \u0447\u0438\u0441\u043b\u043e\u043f\u0440\u043e\u043f\u0438\u0441\u044c\u044e \u044d\u0442\u043e\u0430\u0434\u0440\u0435\u0441\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 ws\u0441\u0441\u044b\u043b\u043a\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043c\u0430\u043a\u0435\u0442\u043e\u0432\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u0441\u0442\u0438\u043b\u0435\u0439 \u0431\u0438\u0437\u043d\u0435\u0441\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0432\u043d\u0435\u0448\u043d\u0438\u0435\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u043d\u0435\u0448\u043d\u0438\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0435\u043e\u0442\u0447\u0435\u0442\u044b \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435\u043f\u043e\u043a\u0443\u043f\u043a\u0438 \u0433\u043b\u0430\u0432\u043d\u044b\u0439\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0433\u043b\u0430\u0432\u043d\u044b\u0439\u0441\u0442\u0438\u043b\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0435\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0436\u0443\u0440\u043d\u0430\u043b\u044b\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u043e\u0431\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u0439\u0434\u0430\u0442\u044b \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0440\u0430\u0431\u043e\u0442\u044b\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0438\u043e\u0442\u0431\u043e\u0440\u0430 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0440\u0435\u043a\u043b\u0430\u043c\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0445\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043e\u0442\u0447\u0435\u0442\u044b \u043f\u0430\u043d\u0435\u043b\u044c\u0437\u0430\u0434\u0430\u0447\u043e\u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0441\u0435\u0430\u043d\u0441\u0430 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043f\u043b\u0430\u043d\u044b\u0432\u0438\u0434\u043e\u0432\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u043f\u043b\u0430\u043d\u044b\u0432\u0438\u0434\u043e\u0432\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a \u043f\u043b\u0430\u043d\u044b\u043e\u0431\u043c\u0435\u043d\u0430 \u043f\u043b\u0430\u043d\u044b\u0441\u0447\u0435\u0442\u043e\u0432 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u043f\u043e\u0438\u0441\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0431\u0430\u0437\u044b \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445\u043f\u043e\u043a\u0443\u043f\u043e\u043a \u0440\u0430\u0431\u043e\u0447\u0430\u044f\u0434\u0430\u0442\u0430 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u0440\u0435\u0433\u043b\u0430\u043c\u0435\u043d\u0442\u043d\u044b\u0435\u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440xdto \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u0433\u0435\u043e\u043f\u043e\u0437\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043c\u0443\u043b\u044c\u0442\u0438\u043c\u0435\u0434\u0438\u0430 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0440\u0435\u043a\u043b\u0430\u043c\u044b \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043f\u043e\u0447\u0442\u044b \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u0438 \u0444\u0430\u0431\u0440\u0438\u043a\u0430xdto \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0435\u043f\u043e\u0442\u043e\u043a\u0438 \u0444\u043e\u043d\u043e\u0432\u044b\u0435\u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432\u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u0434\u0430\u043d\u043d\u044b\u0445\u0444\u043e\u0440\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043e\u0431\u0449\u0438\u0445\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445\u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a ",
class:"web\u0446\u0432\u0435\u0442\u0430 windows\u0446\u0432\u0435\u0442\u0430 windows\u0448\u0440\u0438\u0444\u0442\u044b \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0440\u0430\u043c\u043a\u0438\u0441\u0442\u0438\u043b\u044f \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u0446\u0432\u0435\u0442\u0430\u0441\u0442\u0438\u043b\u044f \u0448\u0440\u0438\u0444\u0442\u044b\u0441\u0442\u0438\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445\u0444\u043e\u0440\u043c\u044b\u0432\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0430\u0432\u0442\u043e\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044f\u0432\u0444\u043e\u0440\u043c\u0435 \u0430\u0432\u0442\u043e\u0440\u0430\u0437\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435\u0441\u0435\u0440\u0438\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u044f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0438\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0432\u044b\u0441\u043e\u0442\u043e\u0439\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0430\u044f\u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430\u0444\u043e\u0440\u043c\u044b \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0432\u0438\u0434\u0433\u0440\u0443\u043f\u043f\u044b\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u0434\u0435\u043a\u043e\u0440\u0430\u0446\u0438\u0438\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0438\u0434\u043a\u043d\u043e\u043f\u043a\u0438\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044f \u0432\u0438\u0434\u043f\u043e\u0434\u043f\u0438\u0441\u0435\u0439\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u0432\u0438\u0434\u043f\u043e\u043b\u044f\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u0444\u043b\u0430\u0436\u043a\u0430 \u0432\u043b\u0438\u044f\u043d\u0438\u0435\u0440\u0430\u0437\u043c\u0435\u0440\u0430\u043d\u0430\u043f\u0443\u0437\u044b\u0440\u0435\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0430\u043a\u043e\u043b\u043e\u043d\u043e\u043a \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0430\u043f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u043d\u044b\u0445\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0444\u043e\u0440\u043c\u044b \u0433\u0440\u0443\u043f\u043f\u044b\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439\u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u043c\u0435\u0436\u0434\u0443\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c\u0438\u0444\u043e\u0440\u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0432\u044b\u0432\u043e\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043f\u043e\u043b\u043e\u0441\u044b\u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0442\u043e\u0447\u043a\u0438\u0431\u0438\u0440\u0436\u0435\u0432\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0432\u044b\u0431\u043e\u0440\u0430\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u043e\u0441\u0438\u0442\u043e\u0447\u0435\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0440\u0430\u0437\u043c\u0435\u0440\u0430\u043f\u0443\u0437\u044b\u0440\u044c\u043a\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\u0433\u0440\u0443\u043f\u043f\u044b\u043a\u043e\u043c\u0430\u043d\u0434 \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c\u0441\u0435\u0440\u0438\u0439 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0434\u0435\u0440\u0435\u0432\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0441\u043f\u0438\u0441\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0434\u0435\u043d\u0434\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u043c\u0435\u0442\u043e\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u043c\u0435\u0442\u043e\u043a\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0444\u043e\u0440\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0432\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0432\u043b\u0435\u0433\u0435\u043d\u0434\u0435\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0433\u0440\u0443\u043f\u043f\u044b\u043a\u043d\u043e\u043f\u043e\u043a \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0448\u043a\u0430\u043b\u044b\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u0433\u0430\u043d\u0442\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043a\u043d\u043e\u043f\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043a\u043d\u043e\u043f\u043a\u0438\u0432\u044b\u0431\u043e\u0440\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0439\u0444\u043e\u0440\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043e\u0431\u044b\u0447\u043d\u043e\u0439\u0433\u0440\u0443\u043f\u043f\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u043f\u0443\u0437\u044b\u0440\u044c\u043a\u043e\u0432\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043f\u0430\u043d\u0435\u043b\u0438\u043f\u043e\u0438\u0441\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f\u043f\u0440\u0438\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438\u043f\u043e\u043b\u043e\u0441\u044b\u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0444\u043e\u0440\u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u0433\u0430\u043d\u0442\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043e\u0431\u044b\u0447\u043d\u043e\u0439\u0433\u0440\u0443\u043f\u043f\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0444\u0438\u0433\u0443\u0440\u044b\u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0430\u043b\u0438\u0442\u0440\u0430\u0446\u0432\u0435\u0442\u043e\u0432\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435\u043e\u0431\u044b\u0447\u043d\u043e\u0439\u0433\u0440\u0443\u043f\u043f\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430\u0434\u0435\u043d\u0434\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u0433\u0430\u043d\u0442\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u0438\u0441\u043a\u0432\u0442\u0430\u0431\u043b\u0438\u0446\u0435\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0444\u043e\u0440\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438\u043a\u043d\u043e\u043f\u043a\u0438\u0444\u043e\u0440\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439\u043f\u0430\u043d\u0435\u043b\u0438\u0444\u043e\u0440\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439\u043f\u0430\u043d\u0435\u043b\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0444\u043e\u0440\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043e\u043f\u043e\u0440\u043d\u043e\u0439\u0442\u043e\u0447\u043a\u0438\u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u0434\u043f\u0438\u0441\u0435\u0439\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u0434\u043f\u0438\u0441\u0435\u0439\u0448\u043a\u0430\u043b\u044b\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0441\u0442\u0440\u043e\u043a\u0438\u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439\u043b\u0438\u043d\u0438\u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043f\u043e\u0438\u0441\u043a\u043e\u043c \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0448\u043a\u0430\u043b\u044b\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u043e\u0440\u044f\u0434\u043e\u043a\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0442\u043e\u0447\u0435\u043a\u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439\u0433\u0438\u0441\u0442\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u0440\u044f\u0434\u043e\u043a\u0441\u0435\u0440\u0438\u0439\u0432\u043b\u0435\u0433\u0435\u043d\u0434\u0435\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0440\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0448\u043a\u0430\u043b\u044b\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u043d\u0438\u0435\u043f\u043e\u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u0433\u0430\u043d\u0442\u0430 \u0440\u0435\u0436\u0438\u043c\u0430\u0432\u0442\u043e\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0432\u0432\u043e\u0434\u0430\u0441\u0442\u0440\u043e\u043a\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0440\u0435\u0436\u0438\u043c\u0432\u044b\u0431\u043e\u0440\u0430\u043d\u0435\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0434\u0430\u0442\u044b \u0440\u0435\u0436\u0438\u043c\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0441\u0442\u0440\u043e\u043a\u0438\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0440\u0435\u0436\u0438\u043c\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0440\u0435\u0436\u0438\u043c\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0440\u0435\u0436\u0438\u043c\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0434\u0438\u0430\u043b\u043e\u0433\u0430\u043f\u0435\u0447\u0430\u0442\u0438 \u0440\u0435\u0436\u0438\u043c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0440\u0435\u0436\u0438\u043c\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0440\u0435\u0436\u0438\u043c\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e\u043e\u043a\u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f\u043e\u043a\u043d\u0430\u0444\u043e\u0440\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u0441\u0435\u0440\u0438\u0438 \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438\u0441\u0435\u0442\u043a\u0438\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u043f\u043e\u043b\u0443\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u043f\u0440\u043e\u0431\u0435\u043b\u043e\u0432\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u043d\u0430\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0440\u0435\u0436\u0438\u043c\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u0440\u0435\u0436\u0438\u043c\u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0440\u0435\u0436\u0438\u043c\u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u044f\u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0440\u0435\u0436\u0438\u043c\u0441\u043f\u0438\u0441\u043a\u0430\u0437\u0430\u0434\u0430\u0447 \u0441\u043a\u0432\u043e\u0437\u043d\u043e\u0435\u0432\u044b\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445\u0444\u043e\u0440\u043c\u044b\u0432\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u0442\u0435\u043a\u0441\u0442\u0430\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0448\u043a\u0430\u043b\u044b\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u044e\u0449\u0435\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f\u0433\u0440\u0443\u043f\u043f\u0430\u043a\u043e\u043c\u0430\u043d\u0434 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u044f\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u0442\u0438\u043b\u044c\u0441\u0442\u0440\u0435\u043b\u043a\u0438 \u0442\u0438\u043f\u0430\u043f\u043f\u0440\u043e\u043a\u0441\u0438\u043c\u0430\u0446\u0438\u0438\u043b\u0438\u043d\u0438\u0438\u0442\u0440\u0435\u043d\u0434\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u0435\u0434\u0438\u043d\u0438\u0446\u044b\u0448\u043a\u0430\u043b\u044b\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0438\u043f\u0438\u043c\u043f\u043e\u0440\u0442\u0430\u0441\u0435\u0440\u0438\u0439\u0441\u043b\u043e\u044f\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043b\u0438\u043d\u0438\u0438\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043b\u0438\u043d\u0438\u0438\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u043c\u0430\u0440\u043a\u0435\u0440\u0430\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043c\u0430\u0440\u043a\u0435\u0440\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u043e\u0431\u043b\u0430\u0441\u0442\u0438\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0441\u0435\u0440\u0438\u0438\u0441\u043b\u043e\u044f\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0442\u043e\u0447\u0435\u0447\u043d\u043e\u0433\u043e\u043e\u0431\u044a\u0435\u043a\u0442\u0430\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0448\u043a\u0430\u043b\u044b\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u0435\u0433\u0435\u043d\u0434\u044b\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043f\u043e\u0438\u0441\u043a\u0430\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u043f\u0440\u043e\u0435\u043a\u0446\u0438\u0438\u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439 \u0442\u0438\u043f\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u043e\u0432\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439 \u0442\u0438\u043f\u0440\u0430\u043c\u043a\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u0441\u0432\u044f\u0437\u0438\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u0433\u0430\u043d\u0442\u0430 \u0442\u0438\u043f\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u043f\u043e\u0441\u0435\u0440\u0438\u044f\u043c\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0442\u043e\u0447\u0435\u043a\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0442\u0438\u043f\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439\u043b\u0438\u043d\u0438\u0438 \u0442\u0438\u043f\u0441\u0442\u043e\u0440\u043e\u043d\u044b\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0442\u0438\u043f\u0444\u043e\u0440\u043c\u044b\u043e\u0442\u0447\u0435\u0442\u0430 \u0442\u0438\u043f\u0448\u043a\u0430\u043b\u044b\u0440\u0430\u0434\u0430\u0440\u043d\u043e\u0439\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0444\u0430\u043a\u0442\u043e\u0440\u043b\u0438\u043d\u0438\u0438\u0442\u0440\u0435\u043d\u0434\u0430\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b \u0444\u0438\u0433\u0443\u0440\u0430\u043a\u043d\u043e\u043f\u043a\u0438 \u0444\u0438\u0433\u0443\u0440\u044b\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439\u0441\u0445\u0435\u043c\u044b \u0444\u0438\u043a\u0441\u0430\u0446\u0438\u044f\u0432\u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0434\u043d\u044f\u0448\u043a\u0430\u043b\u044b\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0448\u0438\u0440\u0438\u043d\u0430\u043f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u043d\u044b\u0445\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0444\u043e\u0440\u043c\u044b \u0432\u0438\u0434\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u0438 \u0432\u0438\u0434\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0432\u0438\u0434\u0441\u0447\u0435\u0442\u0430 \u0432\u0438\u0434\u0442\u043e\u0447\u043a\u0438\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u0431\u0438\u0437\u043d\u0435\u0441\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0430\u0433\u0440\u0435\u0433\u0430\u0442\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0433\u0440\u0443\u043f\u043f\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0435\u0436\u0438\u043c\u0430\u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0441\u0440\u0435\u0437\u0430 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u0430\u0433\u0440\u0435\u0433\u0430\u0442\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0430\u0432\u0442\u043e\u0432\u0440\u0435\u043c\u044f \u0440\u0435\u0436\u0438\u043c\u0437\u0430\u043f\u0438\u0441\u0438\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0440\u0435\u0436\u0438\u043c\u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0430\u0432\u0442\u043e\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439\u043d\u043e\u043c\u0435\u0440\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043e\u0440\u0438\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0438\u0442\u043e\u0433\u043e\u0432\u043a\u043e\u043b\u043e\u043d\u043e\u043a\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0438\u0442\u043e\u0433\u043e\u0432\u0441\u0442\u0440\u043e\u043a\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430\u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u0447\u0442\u0435\u043d\u0438\u044f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u0434\u0432\u0443\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0439\u043f\u0435\u0447\u0430\u0442\u0438 \u0442\u0438\u043f\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u043e\u0431\u043b\u0430\u0441\u0442\u0438\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043a\u0443\u0440\u0441\u043e\u0440\u043e\u0432\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043b\u0438\u043d\u0438\u0438\u0440\u0438\u0441\u0443\u043d\u043a\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043b\u0438\u043d\u0438\u0438\u044f\u0447\u0435\u0439\u043a\u0438\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043b\u0438\u043d\u0438\u0439\u0441\u0432\u043e\u0434\u043d\u043e\u0439\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0442\u0438\u043f\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0442\u0435\u043a\u0441\u0442\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u0440\u0438\u0441\u0443\u043d\u043a\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u0441\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u0443\u0437\u043e\u0440\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u0444\u0430\u0439\u043b\u0430\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c\u043f\u0435\u0447\u0430\u0442\u0438 \u0447\u0435\u0440\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u0438\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430 \u0442\u0438\u043f\u0444\u0430\u0439\u043b\u0430\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043e\u0431\u0445\u043e\u0434\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u0437\u0430\u043f\u0438\u0441\u0438\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0432\u0438\u0434\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044f\u043e\u0442\u0447\u0435\u0442\u0430 \u0442\u0438\u043f\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0442\u0438\u043f\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044f\u043e\u0442\u0447\u0435\u0442\u0430 \u0442\u0438\u043f\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0438\u0442\u043e\u0433\u043e\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u043a\u0444\u0430\u0439\u043b\u0443 \u0440\u0435\u0436\u0438\u043c\u0434\u0438\u0430\u043b\u043e\u0433\u0430\u0432\u044b\u0431\u043e\u0440\u0430\u0444\u0430\u0439\u043b\u0430 \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f\u0444\u0430\u0439\u043b\u0430 \u0442\u0438\u043f\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044f\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0432\u0438\u0434\u0434\u0430\u043d\u043d\u044b\u0445\u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u043c\u0435\u0442\u043e\u0434\u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u0435\u0434\u0438\u043d\u0438\u0446\u044b\u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430\u0432\u0440\u0435\u043c\u0435\u043d\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u0442\u0430\u0431\u043b\u0438\u0446\u044b\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0447\u0438\u0441\u043b\u043e\u0432\u044b\u0445\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u0438\u0441\u043a\u0430\u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0446\u0438\u0439 \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u0434\u0435\u0440\u0435\u0432\u043e\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043e\u0431\u0449\u0430\u044f\u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u0438\u0441\u043a\u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0446\u0438\u0439 \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u0438\u0441\u043a\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0435\u0439 \u0442\u0438\u043f\u043a\u043e\u043b\u043e\u043d\u043a\u0438\u043c\u043e\u0434\u0435\u043b\u0438\u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u0442\u0438\u043f\u043c\u0435\u0440\u044b\u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0442\u0441\u0435\u0447\u0435\u043d\u0438\u044f\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043f\u043e\u043b\u044f\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0430\u0446\u0438\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0443\u043f\u043e\u0440\u044f\u0434\u043e\u0447\u0438\u0432\u0430\u043d\u0438\u044f\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0446\u0438\u0438\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0443\u043f\u043e\u0440\u044f\u0434\u043e\u0447\u0438\u0432\u0430\u043d\u0438\u044f\u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0435\u0439\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f\u0434\u0435\u0440\u0435\u0432\u0430\u0440\u0435\u0448\u0435\u043d\u0438\u0439 ws\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442xpathxs \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0437\u0430\u043f\u0438\u0441\u0438\u0434\u0430\u0442\u044bjson \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e\u0442\u0438\u043f\u0430xs \u0432\u0438\u0434\u0433\u0440\u0443\u043f\u043f\u044b\u043c\u043e\u0434\u0435\u043b\u0438xs \u0432\u0438\u0434\u0444\u0430\u0441\u0435\u0442\u0430xdto \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044fdom \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e\u0442\u0438\u043f\u0430xs \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0433\u043e\u0442\u0438\u043f\u0430xs \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u0441\u0445\u0435\u043c\u044bxs \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u044b\u0435\u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438xs \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\u0433\u0440\u0443\u043f\u043f\u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438xs \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430xs \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0438xs \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0438\u043c\u0435\u043dxs \u043c\u0435\u0442\u043e\u0434\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044fxs \u043c\u043e\u0434\u0435\u043b\u044c\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043exs \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0442\u0438\u043f\u0430xml \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435\u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438xs \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u043f\u0440\u043e\u0431\u0435\u043b\u044c\u043d\u044b\u0445\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432xs \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043exs \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fxs \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u043e\u0442\u0431\u043e\u0440\u0430\u0443\u0437\u043b\u043e\u0432dom \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0441\u0442\u0440\u043e\u043ajson \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u0432\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435dom \u043f\u0440\u043e\u0431\u0435\u043b\u044c\u043d\u044b\u0435\u0441\u0438\u043c\u0432\u043e\u043b\u044bxml \u0442\u0438\u043f\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430xml \u0442\u0438\u043f\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fjson \u0442\u0438\u043f\u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043exml \u0442\u0438\u043f\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044bxs \u0442\u0438\u043f\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438xml \u0442\u0438\u043f\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430domxpath \u0442\u0438\u043f\u0443\u0437\u043b\u0430dom \u0442\u0438\u043f\u0443\u0437\u043b\u0430xml \u0444\u043e\u0440\u043c\u0430xml \u0444\u043e\u0440\u043c\u0430\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044fxs \u0444\u043e\u0440\u043c\u0430\u0442\u0434\u0430\u0442\u044bjson \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432json \u0432\u0438\u0434\u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0438\u0442\u043e\u0433\u043e\u0432\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u043b\u0435\u0439\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043f\u043e\u043b\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u043e\u0432\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u043e\u0433\u043e\u043e\u0441\u0442\u0430\u0442\u043a\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0432\u044b\u0432\u043e\u0434\u0430\u0442\u0435\u043a\u0441\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0433\u0440\u0443\u043f\u043f\u044b\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u043e\u0442\u0431\u043e\u0440\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043f\u043e\u043b\u0435\u0439\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043c\u0430\u043a\u0435\u0442\u0430\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043c\u0430\u043a\u0435\u0442\u0430\u043e\u0431\u043b\u0430\u0441\u0442\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0441\u0442\u0430\u0442\u043a\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f\u0442\u0435\u043a\u0441\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u0441\u0432\u044f\u0437\u0438\u043d\u0430\u0431\u043e\u0440\u043e\u0432\u0434\u0430\u043d\u043d\u044b\u0445\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043b\u0435\u0433\u0435\u043d\u0434\u044b\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043e\u0442\u0431\u043e\u0440\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0435\u0436\u0438\u043c\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0430\u0432\u0442\u043e\u043f\u043e\u0437\u0438\u0446\u0438\u044f\u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432\u0432\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0444\u0438\u043a\u0441\u0430\u0446\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0443\u0441\u043b\u043e\u0432\u043d\u043e\u0433\u043e\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0430\u0436\u043d\u043e\u0441\u0442\u044c\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u0442\u0435\u043a\u0441\u0442\u0430\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043d\u0435ascii\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0442\u0435\u043a\u0441\u0442\u0430\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u044b \u0441\u0442\u0430\u0442\u0443\u0441\u0440\u0430\u0437\u0431\u043e\u0440\u0430\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438\u0437\u0430\u043f\u0438\u0441\u0438\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438\u0437\u0430\u043f\u0438\u0441\u0438\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0432\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0440\u0435\u0436\u0438\u043c\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0432\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0440\u0435\u0436\u0438\u043c\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0442\u0438\u043f\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0432\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430\u0438\u043c\u0435\u043d\u0444\u0430\u0439\u043b\u043e\u0432\u0432zip\u0444\u0430\u0439\u043b\u0435 \u043c\u0435\u0442\u043e\u0434\u0441\u0436\u0430\u0442\u0438\u044fzip \u043c\u0435\u0442\u043e\u0434\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044fzip \u0440\u0435\u0436\u0438\u043c\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043f\u0443\u0442\u0435\u0439\u0444\u0430\u0439\u043b\u043e\u0432zip \u0440\u0435\u0436\u0438\u043c\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\u043f\u043e\u0434\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u0432zip \u0440\u0435\u0436\u0438\u043c\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f\u043f\u0443\u0442\u0435\u0439zip \u0443\u0440\u043e\u0432\u0435\u043d\u044c\u0441\u0436\u0430\u0442\u0438\u044fzip \u0437\u0432\u0443\u043a\u043e\u0432\u043e\u0435\u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\u043a\u0441\u0442\u0440\u043e\u043a\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u0432\u043f\u043e\u0442\u043e\u043a\u0435 \u043f\u043e\u0440\u044f\u0434\u043e\u043a\u0431\u0430\u0439\u0442\u043e\u0432 \u0440\u0435\u0436\u0438\u043c\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0435\u0436\u0438\u043c\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u043e\u0439\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445\u043f\u043e\u043a\u0443\u043f\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u0444\u043e\u043d\u043e\u0432\u043e\u0433\u043e\u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u0430\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0445\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u043e\u0433\u043e\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044fftp \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u043e\u0440\u044f\u0434\u043a\u0430\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f\u043f\u0435\u0440\u0438\u043e\u0434\u0430\u043c\u0438\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u043e\u0439\u0442\u043e\u0447\u043a\u0438\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439\u0442\u0430\u0431\u043b\u0438\u0446\u044b\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0442\u0438\u043f\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u0441\u0445\u0435\u043c\u044b\u0437\u0430\u043f\u0440\u043e\u0441\u0430 http\u043c\u0435\u0442\u043e\u0434 \u0430\u0432\u0442\u043e\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0430\u0432\u0442\u043e\u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043d\u043e\u043c\u0435\u0440\u0430\u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e\u044f\u0437\u044b\u043a\u0430 \u0432\u0438\u0434\u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0438 \u0432\u0438\u0434\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0442\u0430\u0431\u043b\u0438\u0446\u044b\u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u044c\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0439\u043f\u0440\u0438\u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0435\u0439 \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0431\u0430\u0437\u044b\u043f\u043b\u0430\u043d\u0430\u0432\u0438\u0434\u043e\u0432\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e\u0432\u044b\u0431\u043e\u0440\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e\u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u043c\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0435\u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0432\u0438\u0434\u0430\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0432\u0438\u0434\u0430\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0437\u0430\u0434\u0430\u0447\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u043b\u0430\u043d\u0430\u043e\u0431\u043c\u0435\u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0441\u0447\u0435\u0442\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435\u0433\u0440\u0430\u043d\u0438\u0446\u044b\u043f\u0440\u0438\u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u043d\u043e\u043c\u0435\u0440\u0430\u0431\u0438\u0437\u043d\u0435\u0441\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u043d\u043e\u043c\u0435\u0440\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0445\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u043f\u043e\u0438\u0441\u043a\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435\u043f\u043e\u0441\u0442\u0440\u043e\u043a\u0435 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c\u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0439\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u043e\u0431\u0449\u0435\u0433\u043e\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 \u0440\u0435\u0436\u0438\u043c\u0430\u0432\u0442\u043e\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u0438\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0440\u0435\u0436\u0438\u043c\u0437\u0430\u043f\u0438\u0441\u0438\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430 \u0440\u0435\u0436\u0438\u043c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0436\u0438\u043c\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445\u0432\u044b\u0437\u043e\u0432\u043e\u0432\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0439\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b\u0438\u0432\u043d\u0435\u0448\u043d\u0438\u0445\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0440\u0435\u0436\u0438\u043c\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u0435\u0430\u043d\u0441\u043e\u0432 \u0440\u0435\u0436\u0438\u043c\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f\u0434\u0430\u043d\u043d\u044b\u0445\u0432\u044b\u0431\u043e\u0440\u0430\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435\u043f\u043e\u0441\u0442\u0440\u043e\u043a\u0435 \u0440\u0435\u0436\u0438\u043c\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0435\u0436\u0438\u043c\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0440\u0435\u0436\u0438\u043c\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u043e\u0439\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0441\u0435\u0440\u0438\u0438\u043a\u043e\u0434\u043e\u0432\u043f\u043b\u0430\u043d\u0430\u0432\u0438\u0434\u043e\u0432\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a \u0441\u0435\u0440\u0438\u0438\u043a\u043e\u0434\u043e\u0432\u043f\u043b\u0430\u043d\u0430\u0441\u0447\u0435\u0442\u043e\u0432 \u0441\u0435\u0440\u0438\u0438\u043a\u043e\u0434\u043e\u0432\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0432\u044b\u0431\u043e\u0440\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u043f\u043e\u0438\u0441\u043a\u0430\u0441\u0442\u0440\u043e\u043a\u0438\u043f\u0440\u0438\u0432\u0432\u043e\u0434\u0435\u043f\u043e\u0441\u0442\u0440\u043e\u043a\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u0434\u0430\u043d\u043d\u044b\u0445\u0442\u0430\u0431\u043b\u0438\u0446\u044b\u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043a\u043e\u0434\u0430\u043f\u043b\u0430\u043d\u0430\u0432\u0438\u0434\u043e\u0432\u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0442\u0438\u043f\u043a\u043e\u0434\u0430\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u0442\u0438\u043f\u043c\u0430\u043a\u0435\u0442\u0430 \u0442\u0438\u043f\u043d\u043e\u043c\u0435\u0440\u0430\u0431\u0438\u0437\u043d\u0435\u0441\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0442\u0438\u043f\u043d\u043e\u043c\u0435\u0440\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0442\u0438\u043f\u043d\u043e\u043c\u0435\u0440\u0430\u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0438\u043f\u0444\u043e\u0440\u043c\u044b \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0439 \u0432\u0430\u0436\u043d\u043e\u0441\u0442\u044c\u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b\u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430\u0444\u043e\u0440\u043c\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e\u0448\u0440\u0438\u0444\u0442\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u043f\u0435\u0440\u0438\u043e\u0434\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439\u0434\u0430\u0442\u044b\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u0438\u0434\u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0438\u0434\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432\u0438\u0434\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e\u043f\u043e\u0438\u0441\u043a\u0430 \u0432\u0438\u0434\u0440\u0430\u043c\u043a\u0438 \u0432\u0438\u0434\u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0446\u0432\u0435\u0442\u0430 \u0432\u0438\u0434\u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0448\u0440\u0438\u0444\u0442\u0430 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0430\u044f\u0434\u043b\u0438\u043d\u0430 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439\u0437\u043d\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435byteordermark \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e\u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0439\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043a\u043b\u0430\u0432\u0438\u0448\u0430 \u043a\u043e\u0434\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430\u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430xbase \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430\u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u0438\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043f\u0430\u043d\u0435\u043b\u0438\u0440\u0430\u0437\u0434\u0435\u043b\u043e\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u0434\u0438\u0430\u043b\u043e\u0433\u0430\u0432\u043e\u043f\u0440\u043e\u0441 \u0440\u0435\u0436\u0438\u043c\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f\u0444\u043e\u0440\u043c\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0436\u0438\u043c\u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e\u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c\u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u0431\u0430\u0437\u044b\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u0432\u044b\u0431\u043e\u0440\u0430\u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430windows \u0441\u043f\u043e\u0441\u043e\u0431\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u0442\u0440\u043e\u043a\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0432\u043d\u0435\u0448\u043d\u0435\u0439\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0442\u0438\u043f\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0442\u0438\u043f\u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f\u043a\u043b\u0430\u0432\u0438\u0448\u0438enter \u0442\u0438\u043f\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438\u043e\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u0431\u0430\u0437\u044b\u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0438\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439 \u0445\u0435\u0448\u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0447\u0430\u0441\u0442\u0438\u0434\u0430\u0442\u044b",
type:"com\u043e\u0431\u044a\u0435\u043a\u0442 ftp\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 http\u0437\u0430\u043f\u0440\u043e\u0441 http\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0442\u0432\u0435\u0442 http\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 ws\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f ws\u043f\u0440\u043e\u043a\u0441\u0438 xbase \u0430\u043d\u0430\u043b\u0438\u0437\u0434\u0430\u043d\u043d\u044b\u0445 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044fxs \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0444\u0435\u0440\u0434\u0432\u043e\u0438\u0447\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435xs \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445\u0447\u0438\u0441\u0435\u043b \u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f\u0441\u0445\u0435\u043c\u0430 \u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0435\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f\u0441\u0445\u0435\u043c\u0430 \u0433\u0440\u0443\u043f\u043f\u0430\u043c\u043e\u0434\u0435\u043b\u0438xs \u0434\u0430\u043d\u043d\u044b\u0435\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u0432\u043e\u0438\u0447\u043d\u044b\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u0435\u043d\u0434\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\u0433\u0430\u043d\u0442\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0432\u044b\u0431\u043e\u0440\u0430\u0444\u0430\u0439\u043b\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0432\u044b\u0431\u043e\u0440\u0430\u0446\u0432\u0435\u0442\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0432\u044b\u0431\u043e\u0440\u0430\u0448\u0440\u0438\u0444\u0442\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f\u0440\u0435\u0433\u043b\u0430\u043c\u0435\u043d\u0442\u043d\u043e\u0433\u043e\u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0434\u0438\u0430\u043b\u043e\u0433\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e\u043f\u0435\u0440\u0438\u043e\u0434\u0430 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442dom \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442html \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044fxs \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0435\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044cdom \u0437\u0430\u043f\u0438\u0441\u044cfastinfoset \u0437\u0430\u043f\u0438\u0441\u044chtml \u0437\u0430\u043f\u0438\u0441\u044cjson \u0437\u0430\u043f\u0438\u0441\u044cxml \u0437\u0430\u043f\u0438\u0441\u044czip\u0444\u0430\u0439\u043b\u0430 \u0437\u0430\u043f\u0438\u0441\u044c\u0434\u0430\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u044c\u0442\u0435\u043a\u0441\u0442\u0430 \u0437\u0430\u043f\u0438\u0441\u044c\u0443\u0437\u043b\u043e\u0432dom \u0437\u0430\u043f\u0440\u043e\u0441 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u043e\u0435\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435openssl \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043f\u043e\u043b\u0435\u0439\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430 \u0438\u043c\u043f\u043e\u0440\u0442xs \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u0430 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0435\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043f\u0440\u043e\u043a\u0441\u0438 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0434\u043b\u044f\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044fxs \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430xs \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0442\u0435\u0440\u0430\u0442\u043e\u0440\u0443\u0437\u043b\u043e\u0432dom \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b\u0434\u0430\u0442\u044b \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b\u0434\u0432\u043e\u0438\u0447\u043d\u044b\u0445\u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b\u0441\u0442\u0440\u043e\u043a\u0438 \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b\u0447\u0438\u0441\u043b\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u0449\u0438\u043a\u043c\u0430\u043a\u0435\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u0449\u0438\u043a\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u043c\u0430\u043a\u0435\u0442\u0430\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0444\u043e\u0440\u043c\u0430\u0442\u043d\u043e\u0439\u0441\u0442\u0440\u043e\u043a\u0438 \u043b\u0438\u043d\u0438\u044f \u043c\u0430\u043a\u0435\u0442\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u0430\u043a\u0435\u0442\u043e\u0431\u043b\u0430\u0441\u0442\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u0430\u043a\u0435\u0442\u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044f\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u0430\u0441\u043a\u0430xs \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043d\u0430\u0431\u043e\u0440\u0441\u0445\u0435\u043cxml \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438json \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431\u0445\u043e\u0434\u0434\u0435\u0440\u0435\u0432\u0430dom \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430xs \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u043d\u043e\u0442\u0430\u0446\u0438\u0438xs \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430xs \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0434\u043e\u0441\u0442\u0443\u043f\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043e\u0442\u043a\u0430\u0437\u0432\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u0436\u0443\u0440\u043d\u0430\u043b\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0433\u043e\u0444\u0430\u0439\u043b\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0442\u0438\u043f\u043e\u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0433\u0440\u0443\u043f\u043f\u044b\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432xs \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0433\u0440\u0443\u043f\u043f\u044b\u043c\u043e\u0434\u0435\u043b\u0438xs \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0438xs \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e\u0442\u0438\u043f\u0430xs \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0433\u043e\u0442\u0438\u043f\u0430xs \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u0442\u0438\u043f\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430dom \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044fxpathxs \u043e\u0442\u0431\u043e\u0440\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0445\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0437\u0430\u043f\u0438\u0441\u0438json \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0437\u0430\u043f\u0438\u0441\u0438xml \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u0447\u0442\u0435\u043d\u0438\u044fxml \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435xs \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u043f\u043e\u043b\u0435\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044cdom \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044c\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044c\u043e\u0442\u0447\u0435\u0442\u0430 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044c\u043e\u0442\u0447\u0435\u0442\u0430\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044c\u0441\u0445\u0435\u043cxml \u043f\u043e\u0442\u043e\u043a \u043f\u043e\u0442\u043e\u043a\u0432\u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u043e\u0447\u0442\u0430 \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0435\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435xsl \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043a\u043a\u0430\u043d\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u043c\u0443xml \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0432\u044b\u0432\u043e\u0434\u0430\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445\u0432\u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0432\u044b\u0432\u043e\u0434\u0430\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445\u0432\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0439\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0440\u0430\u0437\u044b\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0438\u043c\u0435\u043ddom \u0440\u0430\u043c\u043a\u0430 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0440\u0435\u0433\u043b\u0430\u043c\u0435\u043d\u0442\u043d\u043e\u0433\u043e\u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0435\u0438\u043c\u044fxml \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0447\u0442\u0435\u043d\u0438\u044f\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0432\u043e\u0434\u043d\u0430\u044f\u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430 \u0441\u0432\u044f\u0437\u044c\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u0432\u044b\u0431\u043e\u0440\u0430 \u0441\u0432\u044f\u0437\u044c\u043f\u043e\u0442\u0438\u043f\u0443 \u0441\u0432\u044f\u0437\u044c\u043f\u043e\u0442\u0438\u043f\u0443\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440xdto \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043a\u043b\u0438\u0435\u043d\u0442\u0430windows \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u0444\u0430\u0439\u043b \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044b\u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u044e\u0449\u0438\u0445\u0446\u0435\u043d\u0442\u0440\u043e\u0432windows \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u044b\u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u044e\u0449\u0438\u0445\u0446\u0435\u043d\u0442\u0440\u043e\u0432\u0444\u0430\u0439\u043b \u0441\u0436\u0430\u0442\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u0430\u044f\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u0435\u043a\u043b\u0430\u0432\u0438\u0448 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f\u0434\u0430\u0442\u0430\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439\u043f\u0435\u0440\u0438\u043e\u0434 \u0441\u0445\u0435\u043c\u0430xml \u0441\u0445\u0435\u043c\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0439\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0434\u0430\u043d\u043d\u044b\u0445xml \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0444\u0430\u0431\u0440\u0438\u043a\u0430xdto \u0444\u0430\u0439\u043b \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0439\u043f\u043e\u0442\u043e\u043a \u0444\u0430\u0441\u0435\u0442\u0434\u043b\u0438\u043d\u044bxs \u0444\u0430\u0441\u0435\u0442\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430\u0440\u0430\u0437\u0440\u044f\u0434\u043e\u0432\u0434\u0440\u043e\u0431\u043d\u043e\u0439\u0447\u0430\u0441\u0442\u0438xs \u0444\u0430\u0441\u0435\u0442\u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fxs \u0444\u0430\u0441\u0435\u0442\u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0438\u0441\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fxs \u0444\u0430\u0441\u0435\u0442\u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439\u0434\u043b\u0438\u043d\u044bxs \u0444\u0430\u0441\u0435\u0442\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fxs \u0444\u0430\u0441\u0435\u0442\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0438\u0441\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0433\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044fxs \u0444\u0430\u0441\u0435\u0442\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439\u0434\u043b\u0438\u043d\u044bxs \u0444\u0430\u0441\u0435\u0442\u043e\u0431\u0440\u0430\u0437\u0446\u0430xs \u0444\u0430\u0441\u0435\u0442\u043e\u0431\u0449\u0435\u0433\u043e\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430\u0440\u0430\u0437\u0440\u044f\u0434\u043e\u0432xs \u0444\u0430\u0441\u0435\u0442\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044fxs \u0444\u0430\u0441\u0435\u0442\u043f\u0440\u043e\u0431\u0435\u043b\u044c\u043d\u044b\u0445\u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432xs \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0437\u043b\u043e\u0432dom \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f\u0441\u0442\u0440\u043e\u043a\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442xs \u0445\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0446\u0432\u0435\u0442 \u0447\u0442\u0435\u043d\u0438\u0435fastinfoset \u0447\u0442\u0435\u043d\u0438\u0435html \u0447\u0442\u0435\u043d\u0438\u0435json \u0447\u0442\u0435\u043d\u0438\u0435xml \u0447\u0442\u0435\u043d\u0438\u0435zip\u0444\u0430\u0439\u043b\u0430 \u0447\u0442\u0435\u043d\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u0447\u0442\u0435\u043d\u0438\u0435\u0442\u0435\u043a\u0441\u0442\u0430 \u0447\u0442\u0435\u043d\u0438\u0435\u0443\u0437\u043b\u043e\u0432dom \u0448\u0440\u0438\u0444\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043a\u043e\u043c\u043f\u043e\u043d\u043e\u0432\u043a\u0438\u0434\u0430\u043d\u043d\u044b\u0445 comsafearray \u0434\u0435\u0440\u0435\u0432\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043c\u0430\u0441\u0441\u0438\u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u043f\u0438\u0441\u043e\u043a\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u043c\u0430\u0441\u0441\u0438\u0432 ",
-literal:"null \u0438\u0441\u0442\u0438\u043d\u0430 \u043b\u043e\u0436\u044c \u043d\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e"},contains:[f,a,e,{className:"symbol",begin:"~",end:";|:",excludeEnd:!0},b,c,d]}}}());
-hljs.registerLanguage("abnf",function(){return function(a){return{name:"Augmented Backus-Naur Form",illegal:"[!@#$^&',?+~`|:]",keywords:"ALPHA BIT CHAR CR CRLF CTL DIGIT DQUOTE HEXDIG HTAB LF LWSP OCTET SP VCHAR WSP",contains:[{className:"attribute",begin:"^[a-zA-Z][a-zA-Z0-9-]*(?=\\s*=)"},a.COMMENT(";","$"),{className:"symbol",begin:/%b[0-1]+(-[0-1]+|(\.[0-1]+)+){0,1}/},{className:"symbol",begin:/%d[0-9]+(-[0-9]+|(\.[0-9]+)+){0,1}/},{className:"symbol",begin:/%x[0-9A-F]+(-[0-9A-F]+|(\.[0-9A-F]+)+){0,1}/},
-{className:"symbol",begin:/%[si]/},a.QUOTE_STRING_MODE,a.NUMBER_MODE]}}}());
-hljs.registerLanguage("accesslog",function(){return function(a){a="GET POST HEAD PUT DELETE CONNECT OPTIONS PATCH TRACE".split(" ");return{name:"Apache Access Log",contains:[{className:"number",begin:"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b",relevance:5},{className:"number",begin:"\\b\\d+\\b",relevance:0},{className:"string",begin:'"('+a.join("|")+")",end:'"',keywords:a.join(" "),illegal:"\\n",relevance:5,contains:[{begin:"HTTP/[12]\\.\\d",relevance:5}]},{className:"string",begin:/\[\d[^\]\n]{8,}\]/,
-illegal:"\\n",relevance:1},{className:"string",begin:/\[/,end:/\]/,illegal:"\\n",relevance:0},{className:"string",begin:'"Mozilla/\\d\\.\\d \\(',end:'"',illegal:"\\n",relevance:3},{className:"string",begin:'"',end:'"',illegal:"\\n",relevance:0}]}}}());
-hljs.registerLanguage("actionscript",function(){return function(a){return{name:"ActionScript",aliases:["as"],keywords:{keyword:"as break case catch class const continue default delete do dynamic each else extends final finally for function get if implements import in include instanceof interface internal is namespace native new override package private protected public return set static super switch this throw try typeof use var void while with",literal:"true false null undefined"},contains:[a.APOS_STRING_MODE,
-a.QUOTE_STRING_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.C_NUMBER_MODE,{className:"class",beginKeywords:"package",end:"{",contains:[a.TITLE_MODE]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,contains:[{beginKeywords:"extends implements"},a.TITLE_MODE]},{className:"meta",beginKeywords:"import include",end:";",keywords:{"meta-keyword":"import include"}},{className:"function",beginKeywords:"function",end:"[{;]",excludeEnd:!0,illegal:"\\S",contains:[a.TITLE_MODE,
-{className:"params",begin:"\\(",end:"\\)",contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"rest_arg",begin:"[.]{3}",end:"[a-zA-Z_$][a-zA-Z0-9_$]*",relevance:10}]},{begin:":\\s*([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)"}]},a.METHOD_GUARD],illegal:/#/}}}());
-hljs.registerLanguage("ada",function(){return function(a){a=a.COMMENT("--","$");var b={begin:"\\s+:\\s+",end:"\\s*(:=|;|\\)|=>|$)",illegal:"[]{}%#'\"",contains:[{beginKeywords:"loop for declare others",endsParent:!0},{className:"keyword",beginKeywords:"not null constant access function procedure in out aliased exception"},{className:"type",begin:"[A-Za-z](_?[A-Za-z0-9.])*",endsParent:!0,relevance:0}]};return{name:"Ada",case_insensitive:!0,keywords:{keyword:"abort else new return abs elsif not reverse abstract end accept entry select access exception of separate aliased exit or some all others subtype and for out synchronized array function overriding at tagged generic package task begin goto pragma terminate body private then if procedure type case in protected constant interface is raise use declare range delay limited record when delta loop rem while digits renames with do mod requeue xor",
-literal:"True False"},contains:[a,{className:"string",begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{className:"string",begin:/'.'/},{className:"number",begin:"\\b(\\d(_|\\d)*#\\w+(\\.\\w+)?#([eE][-+]?\\d(_|\\d)*)?|\\d(_|\\d)*(\\.\\d(_|\\d)*)?([eE][-+]?\\d(_|\\d)*)?)",relevance:0},{className:"symbol",begin:"'[A-Za-z](_?[A-Za-z0-9.])*"},{className:"title",begin:"(\\bwith\\s+)?(\\bprivate\\s+)?\\bpackage\\s+(\\bbody\\s+)?",end:"(is|$)",keywords:"package body",excludeBegin:!0,excludeEnd:!0,
-illegal:"[]{}%#'\""},{begin:"(\\b(with|overriding)\\s+)?\\b(function|procedure)\\s+",end:"(\\bis|\\bwith|\\brenames|\\)\\s*;)",keywords:"overriding function procedure with is renames return",returnBegin:!0,contains:[a,{className:"title",begin:"(\\bwith\\s+)?\\b(function|procedure)\\s+",end:"(\\(|\\s+|$)",excludeBegin:!0,excludeEnd:!0,illegal:"[]{}%#'\""},b,{className:"type",begin:"\\breturn\\s+",end:"(\\s+|;|$)",keywords:"return",excludeBegin:!0,excludeEnd:!0,endsParent:!0,illegal:"[]{}%#'\""}]},
-{className:"type",begin:"\\b(sub)?type\\s+",end:"\\s+",keywords:"type",excludeBegin:!0,illegal:"[]{}%#'\""},b]}}}());
-hljs.registerLanguage("angelscript",function(){return function(a){var b={className:"built_in",begin:"\\b(void|bool|int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|string|ref|array|double|float|auto|dictionary)"},c={className:"symbol",begin:"[a-zA-Z0-9_]+@"},d={className:"keyword",begin:"<",end:">",contains:[b,c]};b.contains=[d];c.contains=[d];return{name:"AngelScript",aliases:["asc"],keywords:"for in|0 break continue while do|0 return if else case switch namespace is cast or and xor not get|0 in inout|10 out override set|0 private public const default|0 final shared external mixin|10 enum typedef funcdef this super import from interface abstract|0 try catch protected explicit property",
-illegal:"(^using\\s+[A-Za-z0-9_\\.]+;$|\\bfunctions*[^\\(])",contains:[{className:"string",begin:"'",end:"'",illegal:"\\n",contains:[a.BACKSLASH_ESCAPE],relevance:0},{className:"string",begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE],relevance:0},{className:"string",begin:'"""',end:'"""'},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{beginKeywords:"interface namespace",end:"{",illegal:"[;.\\-]",contains:[{className:"symbol",begin:"[a-zA-Z0-9_]+"}]},{beginKeywords:"class",end:"{",illegal:"[;.\\-]",
-contains:[{className:"symbol",begin:"[a-zA-Z0-9_]+",contains:[{begin:"[:,]\\s*",contains:[{className:"symbol",begin:"[a-zA-Z0-9_]+"}]}]}]},b,c,{className:"literal",begin:"\\b(null|true|false)"},{className:"number",begin:"(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?f?|\\.\\d+f?)([eE][-+]?\\d+f?)?)"}]}}}());
-hljs.registerLanguage("apache",function(){return function(a){var b={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[a.HASH_COMMENT_MODE,{className:"section",begin:"</?",end:">",contains:[b,{className:"number",begin:":\\d{1,5}"},a.inherit(a.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},
-starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},b,{className:"number",begin:"\\d+"},a.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());
-hljs.registerLanguage("applescript",function(){return function(a){var b=a.inherit(a.QUOTE_STRING_MODE,{illegal:""}),c={className:"params",begin:"\\(",end:"\\)",contains:["self",a.C_NUMBER_MODE,b]},d=a.COMMENT("--","$"),e=a.COMMENT("\\(\\*","\\*\\)",{contains:["self",d]});return{name:"AppleScript",aliases:["osascript"],keywords:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",
-literal:"AppleScript false linefeed return pi quote result space tab true",built_in:"alias application boolean class constant date file integer list number real record string text activate beep count delay launch log offset read round run say summarize write character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},contains:[b,a.C_NUMBER_MODE,{className:"built_in",begin:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},
-{className:"literal",begin:"\\b(text item delimiters|current application|missing value)\\b"},{className:"keyword",begin:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference)|POSIX file|POSIX path|(date|time) string|quoted form)\\b"},{beginKeywords:"on",illegal:"[${=;\\n]",contains:[a.UNDERSCORE_TITLE_MODE,c]}].concat([d,
-e,a.HASH_COMMENT_MODE]),illegal:"//|->|=>|\\[\\["}}}());
-hljs.registerLanguage("arcade",function(){return function(a){var b={keyword:"if for while var new function do return void else break",literal:"BackSlash DoubleQuote false ForwardSlash Infinity NaN NewLine null PI SingleQuote Tab TextFormatting true undefined",built_in:"Abs Acos Angle Attachments Area AreaGeodetic Asin Atan Atan2 Average Bearing Boolean Buffer BufferGeodetic Ceil Centroid Clip Console Constrain Contains Cos Count Crosses Cut Date DateAdd DateDiff Day Decode DefaultValue Dictionary Difference Disjoint Distance DistanceGeodetic Distinct DomainCode DomainName Equals Exp Extent Feature FeatureSet FeatureSetByAssociation FeatureSetById FeatureSetByPortalItem FeatureSetByRelationshipName FeatureSetByTitle FeatureSetByUrl Filter First Floor Geometry GroupBy Guid HasKey Hour IIf IndexOf Intersection Intersects IsEmpty IsNan IsSelfIntersecting Length LengthGeodetic Log Max Mean Millisecond Min Minute Month MultiPartToSinglePart Multipoint NextSequenceValue Now Number OrderBy Overlaps Point Polygon Polyline Portal Pow Random Relate Reverse RingIsClockWise Round Second SetGeometry Sin Sort Sqrt Stdev Sum SymmetricDifference Tan Text Timestamp Today ToLocal Top Touches ToUTC TrackCurrentTime TrackGeometryWindow TrackIndex TrackStartTime TrackWindow TypeOf Union UrlEncode Variance Weekday When Within Year "},c=
-{className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:a.C_NUMBER_RE}],relevance:0},d={className:"subst",begin:"\\$\\{",end:"\\}",keywords:b,contains:[]},e={className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE,d]};d.contains=[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,e,c,a.REGEXP_MODE];d=d.contains.concat([a.C_BLOCK_COMMENT_MODE,a.C_LINE_COMMENT_MODE]);return{name:"ArcGIS Arcade",aliases:["arcade"],keywords:b,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,
-e,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"symbol",begin:"\\$[datastore|feature|layer|map|measure|sourcefeature|sourcelayer|targetfeature|targetlayer|value|view]+"},c,{begin:/[{,]\s*/,relevance:0,contains:[{begin:"[A-Za-z_][0-9A-Za-z_]*\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:"[A-Za-z_][0-9A-Za-z_]*",relevance:0}]}]},{begin:"("+a.RE_STARTERS_RE+"|\\b(return)\\b)\\s*",keywords:"return",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.REGEXP_MODE,{className:"function",
-begin:"(\\(.*?\\)|[A-Za-z_][0-9A-Za-z_]*)\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:"[A-Za-z_][0-9A-Za-z_]*"},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:d}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_][0-9A-Za-z_]*"}),{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:d}],illegal:/\[|%/},{begin:/\$[(.]/}],
-illegal:/#(?!!)/}}}());
-hljs.registerLanguage("c-like",function(){return function(a){var b={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},c={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},a.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},d={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],
-relevance:0},e={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},a.inherit(c,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},f={className:"title",begin:"(?:[a-zA-Z_]\\w*::)?"+a.IDENT_RE,relevance:0},h="(?:[a-zA-Z_]\\w*::)?"+a.IDENT_RE+"\\s*\\(",g={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",
+literal:e},contains:[{className:"meta",begin:"#|&",end:"$",keywords:{$pattern:x,
+"meta-keyword":n+"\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0438\u0437\u0444\u0430\u0439\u043b\u0430 \u0432\u0435\u0431\u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u0435\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442 \u043a\u043e\u043d\u0435\u0446\u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435\u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043d\u0430\u043a\u043b\u0438\u0435\u043d\u0442\u0435\u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435\u0431\u0435\u0437\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043d\u0430\u0441\u0435\u0440\u0432\u0435\u0440\u0435\u0431\u0435\u0437\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434 \u043f\u043e\u0441\u043b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u043b\u0441\u0442\u044b\u0439\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0431\u044b\u0447\u043d\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u043b\u0441\u0442\u044b\u0439\u043a\u043b\u0438\u0435\u043d\u0442\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0435\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u043d\u043a\u0438\u0439\u043a\u043b\u0438\u0435\u043d\u0442 "
+},contains:[m]},{className:"function",variants:[{
+begin:"\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430|\u0444\u0443\u043d\u043a\u0446\u0438\u044f",
+end:"\\)",
+keywords:"\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f"
+},{
+begin:"\u043a\u043e\u043d\u0435\u0446\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b|\u043a\u043e\u043d\u0435\u0446\u0444\u0443\u043d\u043a\u0446\u0438\u0438",
+keywords:"\u043a\u043e\u043d\u0435\u0446\u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b \u043a\u043e\u043d\u0435\u0446\u0444\u0443\u043d\u043a\u0446\u0438\u0438"
+}],contains:[{begin:"\\(",end:"\\)",endsParent:!0,contains:[{className:"params",
+begin:x,end:",",excludeEnd:!0,endsWithParent:!0,keywords:{$pattern:x,
+keyword:"\u0437\u043d\u0430\u0447",literal:e},contains:[o,t,a]},m]
+},s.inherit(s.TITLE_MODE,{begin:x})]},m,{className:"symbol",begin:"~",end:";|:",
+excludeEnd:!0},o,t,a]}}})());
+hljs.registerLanguage("abnf",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(a=e)?"string"==typeof a?a:a.source:null;var a
+})).join("")}return a=>{const s={ruleDeclaration:/^[a-zA-Z][a-zA-Z0-9-]*/,
+unexpectedChars:/[!@#$^&',?+~`|:]/},n=a.COMMENT(/;/,/$/),r={
+className:"attribute",begin:e(s.ruleDeclaration,/(?=\s*=)/)};return{
+name:"Augmented Backus-Naur Form",illegal:s.unexpectedChars,
+keywords:["ALPHA","BIT","CHAR","CR","CRLF","CTL","DIGIT","DQUOTE","HEXDIG","HTAB","LF","LWSP","OCTET","SP","VCHAR","WSP"],
+contains:[r,n,{className:"symbol",begin:/%b[0-1]+(-[0-1]+|(\.[0-1]+)+){0,1}/},{
+className:"symbol",begin:/%d[0-9]+(-[0-9]+|(\.[0-9]+)+){0,1}/},{
+className:"symbol",begin:/%x[0-9A-F]+(-[0-9A-F]+|(\.[0-9A-F]+)+){0,1}/},{
+className:"symbol",begin:/%[si]/},a.QUOTE_STRING_MODE,a.NUMBER_MODE]}}})());
+hljs.registerLanguage("accesslog",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}function a(...n){
+return"("+n.map((n=>e(n))).join("|")+")"}return e=>{
+const l=["GET","POST","HEAD","PUT","DELETE","CONNECT","OPTIONS","PATCH","TRACE"]
+;return{name:"Apache Access Log",contains:[{className:"number",
+begin:/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{1,5})?\b/,relevance:5},{
+className:"number",begin:/\b\d+\b/,relevance:0},{className:"string",
+begin:n(/"/,a(...l)),end:/"/,keywords:l,illegal:/\n/,relevance:5,contains:[{
+begin:/HTTP\/[12]\.\d'/,relevance:5}]},{className:"string",
+begin:/\[\d[^\]\n]{8,}\]/,illegal:/\n/,relevance:1},{className:"string",
+begin:/\[/,end:/\]/,illegal:/\n/,relevance:0},{className:"string",
+begin:/"Mozilla\/\d\.\d \(/,end:/"/,illegal:/\n/,relevance:3},{
+className:"string",begin:/"/,end:/"/,illegal:/\n/,relevance:0}]}}})());
+hljs.registerLanguage("actionscript",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>({name:"ActionScript",aliases:["as"],keywords:{
+keyword:"as break case catch class const continue default delete do dynamic each else extends final finally for function get if implements import in include instanceof interface internal is namespace native new override package private protected public return set static super switch this throw try typeof use var void while with",
+literal:"true false null undefined"},
+contains:[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.C_NUMBER_MODE,{
+className:"class",beginKeywords:"package",end:/\{/,contains:[n.TITLE_MODE]},{
+className:"class",beginKeywords:"class interface",end:/\{/,excludeEnd:!0,
+contains:[{beginKeywords:"extends implements"},n.TITLE_MODE]},{className:"meta",
+beginKeywords:"import include",end:/;/,keywords:{"meta-keyword":"import include"
+}},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,
+illegal:/\S/,contains:[n.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,
+contains:[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,{
+className:"rest_arg",begin:/[.]{3}/,end:/[a-zA-Z_$][a-zA-Z0-9_$]*/,relevance:10
+}]},{begin:e(/:\s*/,/([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)/)}]},n.METHOD_GUARD],
+illegal:/#/})})());
+hljs.registerLanguage("ada",(()=>{"use strict";return e=>{
+const n="[A-Za-z](_?[A-Za-z0-9.])*",s="[]\\{\\}%#'\"",a=e.COMMENT("--","$"),r={
+begin:"\\s+:\\s+",end:"\\s*(:=|;|\\)|=>|$)",illegal:s,contains:[{
+beginKeywords:"loop for declare others",endsParent:!0},{className:"keyword",
+beginKeywords:"not null constant access function procedure in out aliased exception"
+},{className:"type",begin:n,endsParent:!0,relevance:0}]};return{name:"Ada",
+case_insensitive:!0,keywords:{
+keyword:"abort else new return abs elsif not reverse abstract end accept entry select access exception of separate aliased exit or some all others subtype and for out synchronized array function overriding at tagged generic package task begin goto pragma terminate body private then if procedure type case in protected constant interface is raise use declare range delay limited record when delta loop rem while digits renames with do mod requeue xor",
+literal:"True False"},contains:[a,{className:"string",begin:/"/,end:/"/,
+contains:[{begin:/""/,relevance:0}]},{className:"string",begin:/'.'/},{
+className:"number",
+begin:"\\b(\\d(_|\\d)*#\\w+(\\.\\w+)?#([eE][-+]?\\d(_|\\d)*)?|\\d(_|\\d)*(\\.\\d(_|\\d)*)?([eE][-+]?\\d(_|\\d)*)?)",
+relevance:0},{className:"symbol",begin:"'"+n},{className:"title",
+begin:"(\\bwith\\s+)?(\\bprivate\\s+)?\\bpackage\\s+(\\bbody\\s+)?",
+end:"(is|$)",keywords:"package body",excludeBegin:!0,excludeEnd:!0,illegal:s},{
+begin:"(\\b(with|overriding)\\s+)?\\b(function|procedure)\\s+",
+end:"(\\bis|\\bwith|\\brenames|\\)\\s*;)",
+keywords:"overriding function procedure with is renames return",returnBegin:!0,
+contains:[a,{className:"title",
+begin:"(\\bwith\\s+)?\\b(function|procedure)\\s+",end:"(\\(|\\s+|$)",
+excludeBegin:!0,excludeEnd:!0,illegal:s},r,{className:"type",
+begin:"\\breturn\\s+",end:"(\\s+|;|$)",keywords:"return",excludeBegin:!0,
+excludeEnd:!0,endsParent:!0,illegal:s}]},{className:"type",
+begin:"\\b(sub)?type\\s+",end:"\\s+",keywords:"type",excludeBegin:!0,illegal:s
+},r]}}})());
+hljs.registerLanguage("angelscript",(()=>{"use strict";return e=>{var n={
+className:"built_in",
+begin:"\\b(void|bool|int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|string|ref|array|double|float|auto|dictionary)"
+},a={className:"symbol",begin:"[a-zA-Z0-9_]+@"},i={className:"keyword",
+begin:"<",end:">",contains:[n,a]};return n.contains=[i],a.contains=[i],{
+name:"AngelScript",aliases:["asc"],
+keywords:"for in|0 break continue while do|0 return if else case switch namespace is cast or and xor not get|0 in inout|10 out override set|0 private public const default|0 final shared external mixin|10 enum typedef funcdef this super import from interface abstract|0 try catch protected explicit property",
+illegal:"(^using\\s+[A-Za-z0-9_\\.]+;$|\\bfunction\\s*[^\\(])",contains:[{
+className:"string",begin:"'",end:"'",illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE],relevance:0},{className:"string",begin:'"""',
+end:'"""'},{className:"string",begin:'"',end:'"',illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE],relevance:0
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",
+begin:"^\\s*\\[",end:"\\]"},{beginKeywords:"interface namespace",end:/\{/,
+illegal:"[;.\\-]",contains:[{className:"symbol",begin:"[a-zA-Z0-9_]+"}]},{
+beginKeywords:"class",end:/\{/,illegal:"[;.\\-]",contains:[{className:"symbol",
+begin:"[a-zA-Z0-9_]+",contains:[{begin:"[:,]\\s*",contains:[{className:"symbol",
+begin:"[a-zA-Z0-9_]+"}]}]}]},n,a,{className:"literal",
+begin:"\\b(null|true|false)"},{className:"number",relevance:0,
+begin:"(-?)(\\b0[xXbBoOdD][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?f?|\\.\\d+f?)([eE][-+]?\\d+f?)?)"
+}]}}})());
+hljs.registerLanguage("apache",(()=>{"use strict";return e=>{const n={
+className:"number",begin:/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{1,5})?/}
+;return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,
+contains:[e.HASH_COMMENT_MODE,{className:"section",begin:/<\/?/,end:/>/,
+contains:[n,{className:"number",begin:/:\d{1,5}/
+},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",
+begin:/\w+/,relevance:0,keywords:{
+nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"
+},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},
+contains:[{className:"meta",begin:/\s\[/,end:/\]$/},{className:"variable",
+begin:/[\$%]\{/,end:/\}/,contains:["self",{className:"number",begin:/[$%]\d+/}]
+},n,{className:"number",begin:/\d+/},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}
+})());
+hljs.registerLanguage("applescript",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function t(...t){
+return t.map((t=>e(t))).join("")}function n(...t){
+return"("+t.map((t=>e(t))).join("|")+")"}return e=>{
+const r=e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),i={className:"params",
+begin:/\(/,end:/\)/,contains:["self",e.C_NUMBER_MODE,r]
+},o=e.COMMENT(/--/,/$/),a=[o,e.COMMENT(/\(\*/,/\*\)/,{contains:["self",o]
+}),e.HASH_COMMENT_MODE];return{name:"AppleScript",aliases:["osascript"],
+keywords:{
+keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",
+literal:"AppleScript false linefeed return pi quote result space tab true",
+built_in:"alias application boolean class constant date file integer list number real record string text activate beep count delay launch log offset read round run say summarize write character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"
+},contains:[r,e.C_NUMBER_MODE,{className:"built_in",
+begin:t(/\b/,n(/clipboard info/,/the clipboard/,/info for/,/list (disks|folder)/,/mount volume/,/path to/,/(close|open for) access/,/(get|set) eof/,/current date/,/do shell script/,/get volume settings/,/random number/,/set volume/,/system attribute/,/system info/,/time to GMT/,/(load|run|store) script/,/scripting components/,/ASCII (character|number)/,/localized string/,/choose (application|color|file|file name|folder|from list|remote application|URL)/,/display (alert|dialog)/),/\b/)
+},{className:"built_in",begin:/^\s*return\b/},{className:"literal",
+begin:/\b(text item delimiters|current application|missing value)\b/},{
+className:"keyword",
+begin:t(/\b/,n(/apart from/,/aside from/,/instead of/,/out of/,/greater than/,/isn't|(doesn't|does not) (equal|come before|come after|contain)/,/(greater|less) than( or equal)?/,/(starts?|ends|begins?) with/,/contained by/,/comes (before|after)/,/a (ref|reference)/,/POSIX (file|path)/,/(date|time) string/,/quoted form/),/\b/)
+},{beginKeywords:"on",illegal:/[${=;\n]/,contains:[e.UNDERSCORE_TITLE_MODE,i]
+},...a],illegal:/\/\/|->|=>|\[\[/}}})());
+hljs.registerLanguage("arcade",(()=>{"use strict";return e=>{
+const n="[A-Za-z_][0-9A-Za-z_]*",a={
+keyword:"if for while var new function do return void else break",
+literal:"BackSlash DoubleQuote false ForwardSlash Infinity NaN NewLine null PI SingleQuote Tab TextFormatting true undefined",
+built_in:"Abs Acos Angle Attachments Area AreaGeodetic Asin Atan Atan2 Average Bearing Boolean Buffer BufferGeodetic Ceil Centroid Clip Console Constrain Contains Cos Count Crosses Cut Date DateAdd DateDiff Day Decode DefaultValue Dictionary Difference Disjoint Distance DistanceGeodetic Distinct DomainCode DomainName Equals Exp Extent Feature FeatureSet FeatureSetByAssociation FeatureSetById FeatureSetByPortalItem FeatureSetByRelationshipName FeatureSetByTitle FeatureSetByUrl Filter First Floor Geometry GroupBy Guid HasKey Hour IIf IndexOf Intersection Intersects IsEmpty IsNan IsSelfIntersecting Length LengthGeodetic Log Max Mean Millisecond Min Minute Month MultiPartToSinglePart Multipoint NextSequenceValue Now Number OrderBy Overlaps Point Polygon Polyline Portal Pow Random Relate Reverse RingIsClockWise Round Second SetGeometry Sin Sort Sqrt Stdev Sum SymmetricDifference Tan Text Timestamp Today ToLocal Top Touches ToUTC TrackCurrentTime TrackGeometryWindow TrackIndex TrackStartTime TrackWindow TypeOf Union UrlEncode Variance Weekday When Within Year "
+},t={className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{
+begin:"\\b(0[oO][0-7]+)"},{begin:e.C_NUMBER_RE}],relevance:0},i={
+className:"subst",begin:"\\$\\{",end:"\\}",keywords:a,contains:[]},r={
+className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]}
+;i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,t,e.REGEXP_MODE]
+;const s=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE])
+;return{name:"ArcGIS Arcade",aliases:["arcade"],keywords:a,
+contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+className:"symbol",
+begin:"\\$[datastore|feature|layer|map|measure|sourcefeature|sourcelayer|targetfeature|targetlayer|value|view]+"
+},t,{begin:/[{,]\s*/,relevance:0,contains:[{begin:n+"\\s*:",returnBegin:!0,
+relevance:0,contains:[{className:"attr",begin:n,relevance:0}]}]},{
+begin:"("+e.RE_STARTERS_RE+"|\\b(return)\\b)\\s*",keywords:"return",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{
+className:"function",begin:"(\\(.*?\\)|"+n+")\\s*=>",returnBegin:!0,
+end:"\\s*=>",contains:[{className:"params",variants:[{begin:n},{begin:/\(\s*\)/
+},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:a,contains:s}]}]
+}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,
+excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:n}),{className:"params",
+begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:s}],illegal:/\[|%/},{
+begin:/\$[(.]/}],illegal:/#(?!!)/}}})());
+hljs.registerLanguage("arduino",(()=>{"use strict";function e(e){
+return((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(",e,")?")
+}return t=>{const r=(t=>{const r=t.COMMENT("//","$",{contains:[{begin:/\\\n/}]
+}),n="[a-zA-Z_]\\w*::",i="(decltype\\(auto\\)|"+e(n)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",a={
+className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},s={className:"string",
+variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",
+contains:[t.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},t.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},l={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},t.inherit(s,{className:"meta-string"}),{
+className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"
+},r,t.C_BLOCK_COMMENT_MODE]},c={className:"title",begin:e(n)+t.IDENT_RE,
+relevance:0},d=e(n)+t.IDENT_RE+"\\s*\\(",u={
+keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",
built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",
-literal:"true false nullptr NULL"},k=[b,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,d,c],l={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:g,contains:k.concat([{begin:/\(/,end:/\)/,keywords:g,contains:k.concat(["self"]),relevance:0}]),relevance:0};return{aliases:"c cc h c++ h++ hpp hh hxx cxx".split(" "),keywords:g,disableAutodetect:!0,illegal:"</",contains:[].concat(l,{className:"function",begin:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+
-h,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:g,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:g,relevance:0},{begin:h,returnBegin:!0,contains:[f],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:g,relevance:0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,d,b,{begin:/\(/,end:/\)/,keywords:g,relevance:0,contains:["self",a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,d,b]}]},b,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,e]},k,[e,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",
-end:">",keywords:g,contains:["self",b]},{begin:a.IDENT_RE+"::",keywords:g},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},a.TITLE_MODE]}]),exports:{preprocessor:e,strings:c,keywords:g}}}}());hljs.registerLanguage("cpp",function(){return function(a){a=a.getLanguage("c-like").rawDefinition();a.disableAutodetect=!1;a.name="C++";a.aliases="cc c++ h++ hpp hh hxx cxx".split(" ");return a}}());
-hljs.registerLanguage("arduino",function(){return function(a){a=a.requireLanguage("cpp").rawDefinition();var b=a.keywords;b.keyword+=" boolean byte word String";b.literal+=" DIGITAL_MESSAGE FIRMATA_STRING ANALOG_MESSAGE REPORT_DIGITAL REPORT_ANALOG INPUT_PULLUP SET_PIN_MODE INTERNAL2V56 SYSTEM_RESET LED_BUILTIN INTERNAL1V1 SYSEX_START INTERNAL EXTERNAL DEFAULT OUTPUT INPUT HIGH LOW";b.built_in+=" setup loop KeyboardController MouseController SoftwareSerial EthernetServer EthernetClient LiquidCrystal RobotControl GSMVoiceCall EthernetUDP EsploraTFT HttpClient RobotMotor WiFiClient GSMScanner FileSystem Scheduler GSMServer YunClient YunServer IPAddress GSMClient GSMModem Keyboard Ethernet Console GSMBand Esplora Stepper Process WiFiUDP GSM_SMS Mailbox USBHost Firmata PImage Client Server GSMPIN FileIO Bridge Serial EEPROM Stream Mouse Audio Servo File Task GPRS WiFi Wire TFT GSM SPI SD runShellCommandAsynchronously analogWriteResolution retrieveCallingNumber printFirmwareVersion analogReadResolution sendDigitalPortPair noListenOnLocalhost readJoystickButton setFirmwareVersion readJoystickSwitch scrollDisplayRight getVoiceCallStatus scrollDisplayLeft writeMicroseconds delayMicroseconds beginTransmission getSignalStrength runAsynchronously getAsynchronously listenOnLocalhost getCurrentCarrier readAccelerometer messageAvailable sendDigitalPorts lineFollowConfig countryNameWrite runShellCommand readStringUntil rewindDirectory readTemperature setClockDivider readLightSensor endTransmission analogReference detachInterrupt countryNameRead attachInterrupt encryptionType readBytesUntil robotNameWrite readMicrophone robotNameRead cityNameWrite userNameWrite readJoystickY readJoystickX mouseReleased openNextFile scanNetworks noInterrupts digitalWrite beginSpeaker mousePressed isActionDone mouseDragged displayLogos noAutoscroll addParameter remoteNumber getModifiers keyboardRead userNameRead waitContinue processInput parseCommand printVersion readNetworks writeMessage blinkVersion cityNameRead readMessage setDataMode parsePacket isListening setBitOrder beginPacket isDirectory motorsWrite drawCompass digitalRead clearScreen serialEvent rightToLeft setTextSize leftToRight requestFrom keyReleased compassRead analogWrite interrupts WiFiServer disconnect playMelody parseFloat autoscroll getPINUsed setPINUsed setTimeout sendAnalog readSlider analogRead beginWrite createChar motorsStop keyPressed tempoWrite readButton subnetMask debugPrint macAddress writeGreen randomSeed attachGPRS readString sendString remotePort releaseAll mouseMoved background getXChange getYChange answerCall getResult voiceCall endPacket constrain getSocket writeJSON getButton available connected findUntil readBytes exitValue readGreen writeBlue startLoop IPAddress isPressed sendSysex pauseMode gatewayIP setCursor getOemKey tuneWrite noDisplay loadImage switchPIN onRequest onReceive changePIN playFile noBuffer parseInt overflow checkPIN knobRead beginTFT bitClear updateIR bitWrite position writeRGB highByte writeRed setSpeed readBlue noStroke remoteIP transfer shutdown hangCall beginSMS endWrite attached maintain noCursor checkReg checkPUK shiftOut isValid shiftIn pulseIn connect println localIP pinMode getIMEI display noBlink process getBand running beginSD drawBMP lowByte setBand release bitRead prepare pointTo readRed setMode noFill remove listen stroke detach attach noTone exists buffer height bitSet circle config cursor random IRread setDNS endSMS getKey micros millis begin print write ready flush width isPIN blink clear press mkdir rmdir close point yield image BSSID click delay read text move peek beep rect line open seek fill size turn stop home find step tone sqrt RSSI SSID end bit tan cos sin pow map abs max min get run put";
-a.name="Arduino";return a}}());
-hljs.registerLanguage("armasm",function(){return function(a){var b={variants:[a.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),a.COMMENT("[;@]","$",{relevance:0}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+a.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},
-contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},
-b,a.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());
-hljs.registerLanguage("xml",function(){return function(a){var b={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},c={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},d=a.inherit(c,{begin:"\\(",end:"\\)"}),e=a.inherit(a.APOS_STRING_MODE,{className:"meta-string"}),f=a.inherit(a.QUOTE_STRING_MODE,{className:"meta-string"}),h={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,
-relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[b]},{begin:/'/,end:/'/,contains:[b]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:"html xhtml rss atom xjb xsd xsl plist wsf svg".split(" "),case_insensitive:!0,contains:[{className:"meta",begin:"<![a-z]",end:">",relevance:10,contains:[c,f,e,d,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"<![a-z]",end:">",contains:[c,d,f,e]}]}]},a.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",
-end:"\\]\\]>",relevance:10},b,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:"<style(?=\\s|>)",end:">",keywords:{name:"style"},contains:[h],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>)",end:">",keywords:{name:"script"},contains:[h],starts:{end:"\x3c/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},
-h]}]}}}());
-hljs.registerLanguage("asciidoc",function(){return function(a){return{name:"AsciiDoc",aliases:["adoc"],contains:[a.COMMENT("^/{4,}\\n","\\n/{4,}$",{relevance:10}),a.COMMENT("^//","$",{relevance:0}),{className:"title",begin:"^\\.\\w.*$"},{begin:"^[=\\*]{4,}\\n",end:"\\n^[=\\*]{4,}$",relevance:10},{className:"section",relevance:10,variants:[{begin:"^(={1,5}) .+?( \\1)?$"},{begin:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{className:"meta",begin:"^:.+?:",end:"\\s",excludeEnd:!0,relevance:10},{className:"meta",
-begin:"^\\[.+?\\]$",relevance:0},{className:"quote",begin:"^_{4,}\\n",end:"\\n_{4,}$",relevance:10},{className:"code",begin:"^[\\-\\.]{4,}\\n",end:"\\n[\\-\\.]{4,}$",relevance:10},{begin:"^\\+{4,}\\n",end:"\\n\\+{4,}$",contains:[{begin:"<",end:">",subLanguage:"xml",relevance:0}],relevance:10},{className:"bullet",begin:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{className:"symbol",begin:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",relevance:10},{className:"strong",begin:"\\B\\*(?![\\*\\s])",end:"(\\n{2}|\\*)",
-contains:[{begin:"\\\\*\\w",relevance:0}]},{className:"emphasis",begin:"\\B'(?!['\\s])",end:"(\\n{2}|')",contains:[{begin:"\\\\'\\w",relevance:0}],relevance:0},{className:"emphasis",begin:"_(?![_\\s])",end:"(\\n{2}|_)",relevance:0},{className:"string",variants:[{begin:"``.+?''"},{begin:"`.+?'"}]},{className:"code",begin:"(`.+?`|\\+.+?\\+)",relevance:0},{className:"code",begin:"^[ \\t]",end:"$",relevance:0},{begin:"^'{3,}[ \\t]*$",relevance:10},{begin:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",
-returnBegin:!0,contains:[{begin:"(link|image:?):",relevance:0},{className:"link",begin:"\\w",end:"[^\\[]+",relevance:0},{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0,relevance:0}],relevance:10}]}}}());
-hljs.registerLanguage("aspectj",function(){return function(a){return{name:"AspectJ",keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",illegal:/<\/|#/,
-contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"class",beginKeywords:"aspect",end:/[{;=]/,excludeEnd:!0,illegal:/[:;"\[\]]/,contains:[{beginKeywords:"extends implements pertypewithin perthis pertarget percflowbelow percflow issingleton"},a.UNDERSCORE_TITLE_MODE,{begin:/\([^\)]*/,end:/[)]+/,keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance get set args call",
-excludeEnd:!1}]},{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,relevance:0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"pointcut after before around throwing returning",end:/[)]/,excludeEnd:!1,illegal:/["\[\]]/,contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[a.UNDERSCORE_TITLE_MODE]}]},{begin:/[:]/,returnBegin:!0,end:/[{;]/,relevance:0,excludeEnd:!1,keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",
-illegal:/["\[\]]/,contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance get set args call",
-relevance:0},a.QUOTE_STRING_MODE]},{beginKeywords:"new throw",relevance:0},{className:"function",begin:/\w+ +\w+(\.)?\w+\s*\([^\)]*\)\s*((throws)[\w\s,]+)?[\{;]/,returnBegin:!0,end:/[{;=]/,keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",
-excludeEnd:!0,contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,relevance:0,keywords:"false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",
-contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());
-hljs.registerLanguage("autohotkey",function(){return function(a){var b={begin:"`[\\s\\S]"};return{name:"AutoHotkey",case_insensitive:!0,aliases:["ahk"],keywords:{keyword:"Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group",literal:"true false NOT AND OR",built_in:"ComSpec Clipboard ClipboardAll ErrorLevel"},contains:[b,a.inherit(a.QUOTE_STRING_MODE,{contains:[b]}),a.COMMENT(";","$",{relevance:0}),
-a.C_BLOCK_COMMENT_MODE,{className:"number",begin:a.NUMBER_RE,relevance:0},{className:"variable",begin:"%[a-zA-Z0-9#_$@]+%"},{className:"built_in",begin:"^\\s*\\w+\\s*(,|%)"},{className:"title",variants:[{begin:'^[^\\n";]+::(?!=)'},{begin:'^[^\\n";]+:(?!=)',relevance:0}]},{className:"meta",begin:"^\\s*#\\w+",end:"$",relevance:0},{className:"built_in",begin:"A_[a-zA-Z0-9]+"},{begin:",\\s*,"}]}}}());
-hljs.registerLanguage("autoit",function(){return function(a){var b={variants:[a.COMMENT(";","$",{relevance:0}),a.COMMENT("#cs","#ce"),a.COMMENT("#comments-start","#comments-end")]},c={begin:"\\$[A-z0-9_]+"},d={className:"string",variants:[{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]},e={variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]};return{name:"AutoIt",case_insensitive:!0,illegal:/\/\*/,keywords:{keyword:"ByRef Case Const ContinueCase ContinueLoop Default Dim Do Else ElseIf EndFunc EndIf EndSelect EndSwitch EndWith Enum Exit ExitLoop For Func Global If In Local Next ReDim Return Select Static Step Switch Then To Until Volatile WEnd While With",
-built_in:"Abs ACos AdlibRegister AdlibUnRegister Asc AscW ASin Assign ATan AutoItSetOption AutoItWinGetTitle AutoItWinSetTitle Beep Binary BinaryLen BinaryMid BinaryToString BitAND BitNOT BitOR BitRotate BitShift BitXOR BlockInput Break Call CDTray Ceiling Chr ChrW ClipGet ClipPut ConsoleRead ConsoleWrite ConsoleWriteError ControlClick ControlCommand ControlDisable ControlEnable ControlFocus ControlGetFocus ControlGetHandle ControlGetPos ControlGetText ControlHide ControlListView ControlMove ControlSend ControlSetText ControlShow ControlTreeView Cos Dec DirCopy DirCreate DirGetSize DirMove DirRemove DllCall DllCallAddress DllCallbackFree DllCallbackGetPtr DllCallbackRegister DllClose DllOpen DllStructCreate DllStructGetData DllStructGetPtr DllStructGetSize DllStructSetData DriveGetDrive DriveGetFileSystem DriveGetLabel DriveGetSerial DriveGetType DriveMapAdd DriveMapDel DriveMapGet DriveSetLabel DriveSpaceFree DriveSpaceTotal DriveStatus EnvGet EnvSet EnvUpdate Eval Execute Exp FileChangeDir FileClose FileCopy FileCreateNTFSLink FileCreateShortcut FileDelete FileExists FileFindFirstFile FileFindNextFile FileFlush FileGetAttrib FileGetEncoding FileGetLongName FileGetPos FileGetShortcut FileGetShortName FileGetSize FileGetTime FileGetVersion FileInstall FileMove FileOpen FileOpenDialog FileRead FileReadLine FileReadToArray FileRecycle FileRecycleEmpty FileSaveDialog FileSelectFolder FileSetAttrib FileSetEnd FileSetPos FileSetTime FileWrite FileWriteLine Floor FtpSetProxy FuncName GUICreate GUICtrlCreateAvi GUICtrlCreateButton GUICtrlCreateCheckbox GUICtrlCreateCombo GUICtrlCreateContextMenu GUICtrlCreateDate GUICtrlCreateDummy GUICtrlCreateEdit GUICtrlCreateGraphic GUICtrlCreateGroup GUICtrlCreateIcon GUICtrlCreateInput GUICtrlCreateLabel GUICtrlCreateList GUICtrlCreateListView GUICtrlCreateListViewItem GUICtrlCreateMenu GUICtrlCreateMenuItem GUICtrlCreateMonthCal GUICtrlCreateObj GUICtrlCreatePic GUICtrlCreateProgress GUICtrlCreateRadio GUICtrlCreateSlider GUICtrlCreateTab GUICtrlCreateTabItem GUICtrlCreateTreeView GUICtrlCreateTreeViewItem GUICtrlCreateUpdown GUICtrlDelete GUICtrlGetHandle GUICtrlGetState GUICtrlRead GUICtrlRecvMsg GUICtrlRegisterListViewSort GUICtrlSendMsg GUICtrlSendToDummy GUICtrlSetBkColor GUICtrlSetColor GUICtrlSetCursor GUICtrlSetData GUICtrlSetDefBkColor GUICtrlSetDefColor GUICtrlSetFont GUICtrlSetGraphic GUICtrlSetImage GUICtrlSetLimit GUICtrlSetOnEvent GUICtrlSetPos GUICtrlSetResizing GUICtrlSetState GUICtrlSetStyle GUICtrlSetTip GUIDelete GUIGetCursorInfo GUIGetMsg GUIGetStyle GUIRegisterMsg GUISetAccelerators GUISetBkColor GUISetCoord GUISetCursor GUISetFont GUISetHelp GUISetIcon GUISetOnEvent GUISetState GUISetStyle GUIStartGroup GUISwitch Hex HotKeySet HttpSetProxy HttpSetUserAgent HWnd InetClose InetGet InetGetInfo InetGetSize InetRead IniDelete IniRead IniReadSection IniReadSectionNames IniRenameSection IniWrite IniWriteSection InputBox Int IsAdmin IsArray IsBinary IsBool IsDeclared IsDllStruct IsFloat IsFunc IsHWnd IsInt IsKeyword IsNumber IsObj IsPtr IsString Log MemGetStats Mod MouseClick MouseClickDrag MouseDown MouseGetCursor MouseGetPos MouseMove MouseUp MouseWheel MsgBox Number ObjCreate ObjCreateInterface ObjEvent ObjGet ObjName OnAutoItExitRegister OnAutoItExitUnRegister Ping PixelChecksum PixelGetColor PixelSearch ProcessClose ProcessExists ProcessGetStats ProcessList ProcessSetPriority ProcessWait ProcessWaitClose ProgressOff ProgressOn ProgressSet Ptr Random RegDelete RegEnumKey RegEnumVal RegRead RegWrite Round Run RunAs RunAsWait RunWait Send SendKeepActive SetError SetExtended ShellExecute ShellExecuteWait Shutdown Sin Sleep SoundPlay SoundSetWaveVolume SplashImageOn SplashOff SplashTextOn Sqrt SRandom StatusbarGetText StderrRead StdinWrite StdioClose StdoutRead String StringAddCR StringCompare StringFormat StringFromASCIIArray StringInStr StringIsAlNum StringIsAlpha StringIsASCII StringIsDigit StringIsFloat StringIsInt StringIsLower StringIsSpace StringIsUpper StringIsXDigit StringLeft StringLen StringLower StringMid StringRegExp StringRegExpReplace StringReplace StringReverse StringRight StringSplit StringStripCR StringStripWS StringToASCIIArray StringToBinary StringTrimLeft StringTrimRight StringUpper Tan TCPAccept TCPCloseSocket TCPConnect TCPListen TCPNameToIP TCPRecv TCPSend TCPShutdown, UDPShutdown TCPStartup, UDPStartup TimerDiff TimerInit ToolTip TrayCreateItem TrayCreateMenu TrayGetMsg TrayItemDelete TrayItemGetHandle TrayItemGetState TrayItemGetText TrayItemSetOnEvent TrayItemSetState TrayItemSetText TraySetClick TraySetIcon TraySetOnEvent TraySetPauseIcon TraySetState TraySetToolTip TrayTip UBound UDPBind UDPCloseSocket UDPOpen UDPRecv UDPSend VarGetType WinActivate WinActive WinClose WinExists WinFlash WinGetCaretPos WinGetClassList WinGetClientSize WinGetHandle WinGetPos WinGetProcess WinGetState WinGetText WinGetTitle WinKill WinList WinMenuSelectItem WinMinimizeAll WinMinimizeAllUndo WinMove WinSetOnTop WinSetState WinSetTitle WinSetTrans WinWait",
-literal:"True False And Null Not Or"},contains:[b,c,d,e,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"comments include include-once NoTrayIcon OnAutoItStartRegister pragma compile RequireAdmin"},contains:[{begin:/\\\n/,relevance:0},{beginKeywords:"include",keywords:{"meta-keyword":"include"},end:"$",contains:[d,{className:"meta-string",variants:[{begin:"<",end:">"},{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]}]},d,
-b]},{className:"symbol",begin:"@[A-z0-9_]+"},{className:"function",beginKeywords:"Func",end:"$",illegal:"\\$|\\[|%",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",contains:[c,d,e]}]}]}}}());
-hljs.registerLanguage("avrasm",function(){return function(a){return{name:"AVR Assembly",case_insensitive:!0,keywords:{$pattern:"\\.?"+a.IDENT_RE,keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",
-meta:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"},contains:[a.C_BLOCK_COMMENT_MODE,a.COMMENT(";","$",{relevance:0}),a.C_NUMBER_MODE,a.BINARY_NUMBER_MODE,{className:"number",begin:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},a.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",illegal:"[^\\\\][^']"},{className:"symbol",begin:"^[A-Za-z0-9_.$]+:"},{className:"meta",begin:"#",end:"$"},{className:"subst",begin:"@[0-9]+"}]}}}());
-hljs.registerLanguage("awk",function(){return function(a){return{name:"Awk",keywords:{keyword:"BEGIN END if else while do for in break continue delete next nextfile function func exit|10"},contains:[{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},{className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,relevance:10},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,
-relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},a.REGEXP_MODE,a.HASH_COMMENT_MODE,a.NUMBER_MODE]}}}());
-hljs.registerLanguage("axapta",function(){return function(a){return{name:"Dynamics 365",keywords:"false int abstract private char boolean static null if for true while long throw finally protected final return void enum else break new catch byte super case short default double public try this switch continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count order group by asc desc index hint like dispaly edit client server ttsbegin ttscommit str real date container anytype common div mod",contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,{className:"meta",begin:"#",end:"$"},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:":",contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]}]}}}());
-hljs.registerLanguage("bash",function(){return function(a){var b={};Object.assign(b,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[b]}]}]});var c={className:"subst",begin:/\$\(/,end:/\)/,contains:[a.BACKSLASH_ESCAPE]},d={className:"string",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,b,c]};c.contains.push(d);c={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},a.NUMBER_MODE,b]};var e=a.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",
-relevance:10}),f={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",
-_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[e,a.SHEBANG(),f,c,a.HASH_COMMENT_MODE,d,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},b]}}}());
-hljs.registerLanguage("basic",function(){return function(a){return{name:"BASIC",case_insensitive:!0,illegal:"^.",keywords:{$pattern:"[a-zA-Z][a-zA-Z0-9_$%!#]*",keyword:"ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO HEX$ IF THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE WEND WIDTH WINDOW WRITE XOR"},contains:[a.QUOTE_STRING_MODE,
-a.COMMENT("REM","$",{relevance:10}),a.COMMENT("'","$",{relevance:0}),{className:"symbol",begin:"^[0-9]+ ",relevance:10},{className:"number",begin:"\\b([0-9]+[0-9edED.]*[#!]?)",relevance:0},{className:"number",begin:"(&[hH][0-9a-fA-F]{1,4})"},{className:"number",begin:"(&[oO][0-7]{1,6})"}]}}}());
-hljs.registerLanguage("bnf",function(){return function(a){return{name:"Backus\u2013Naur Form",contains:[{className:"attribute",begin:/</,end:/>/},{begin:/::=/,end:/$/,contains:[{begin:/</,end:/>/},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]}]}}}());
-hljs.registerLanguage("brainfuck",function(){return function(a){var b={className:"literal",begin:"[\\+\\-]",relevance:0};return{name:"Brainfuck",aliases:["bf"],contains:[a.COMMENT("[^\\[\\]\\.,\\+\\-<> \r\n]","[\\[\\]\\.,\\+\\-<> \r\n]",{returnEnd:!0,relevance:0}),{className:"title",begin:"[\\[\\]]",relevance:0},{className:"string",begin:"[\\.,]",relevance:0},{begin:/(?:\+\+|\-\-)/,contains:[b]},b]}}}());
-hljs.registerLanguage("c",function(){return function(a){a=a.getLanguage("c-like").rawDefinition();a.name="C";a.aliases=["c","h"];return a}}());
-hljs.registerLanguage("cal",function(){return function(a){var b=[a.C_LINE_COMMENT_MODE,a.COMMENT(/\{/,/\}/,{relevance:0}),a.COMMENT(/\(\*/,/\*\)/,{relevance:10})],c={className:"string",begin:/'/,end:/'/,contains:[{begin:/''/}]},d={className:"string",begin:/(#\d+)+/};b={className:"function",beginKeywords:"procedure",end:/[:;]/,keywords:"procedure|10",contains:[a.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:"div mod in and or not xor asserterror begin case do downto else end exit for if of repeat then to until while with var",
-contains:[c,d]}].concat(b)};return{name:"C/AL",case_insensitive:!0,keywords:{keyword:"div mod in and or not xor asserterror begin case do downto else end exit for if of repeat then to until while with var",literal:"false true"},illegal:/\/\*/,contains:[c,d,{className:"number",begin:"\\b\\d+(\\.\\d+)?(DT|D|T)",relevance:0},{className:"string",begin:'"',end:'"'},a.NUMBER_MODE,{className:"class",begin:"OBJECT (Table|Form|Report|Dataport|Codeunit|XMLport|MenuSuite|Page|Query) (\\d+) ([^\\r\\n]+)",returnBegin:!0,
-contains:[a.TITLE_MODE,b]},b]}}}());
-hljs.registerLanguage("capnproto",function(){return function(a){return{name:"Cap\u2019n Proto",aliases:["capnp"],keywords:{keyword:"struct enum interface union group import using const annotation extends in of on as with from fixed",built_in:"Void Bool Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64 Text Data AnyPointer AnyStruct Capability List",literal:"true false"},contains:[a.QUOTE_STRING_MODE,a.NUMBER_MODE,a.HASH_COMMENT_MODE,{className:"meta",begin:/@0x[\w\d]{16};/,illegal:/\n/},
-{className:"symbol",begin:/@\d+\b/},{className:"class",beginKeywords:"struct enum",end:/\{/,illegal:/\n/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,excludeEnd:!0}})]},{className:"class",beginKeywords:"interface",end:/\{/,illegal:/\n/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,excludeEnd:!0}})]}]}}}());
-hljs.registerLanguage("ceylon",function(){return function(a){var b={className:"subst",excludeBegin:!0,excludeEnd:!0,begin:/``/,end:/``/,keywords:"assembly module package import alias class interface object given value assign void function new of extends satisfies abstracts in out return break continue throw assert dynamic if else switch case for while try catch finally then let this outer super is exists nonempty",relevance:10},c=[{className:"string",begin:'"""',end:'"""',relevance:10},{className:"string",
-begin:'"',end:'"',contains:[b]},{className:"string",begin:"'",end:"'"},{className:"number",begin:"#[0-9a-fA-F_]+|\\$[01_]+|[0-9_]+(?:\\.[0-9_](?:[eE][+-]?\\d+)?)?[kMGTPmunpf]?",relevance:0}];b.contains=c;return{name:"Ceylon",keywords:{keyword:"assembly module package import alias class interface object given value assign void function new of extends satisfies abstracts in out return break continue throw assert dynamic if else switch case for while try catch finally then let this outer super is exists nonempty shared abstract formal default actual variable late native deprecated final sealed annotation suppressWarnings small",
-meta:"doc by license see throws tagged"},illegal:"\\$[^01]|#[^0-9a-fA-F]",contains:[a.C_LINE_COMMENT_MODE,a.COMMENT("/\\*","\\*/",{contains:["self"]}),{className:"meta",begin:'@[a-z]\\w*(?:\\:"[^"]*")?'}].concat(c)}}}());
-hljs.registerLanguage("clean",function(){return function(a){return{name:"Clean",aliases:["clean","icl","dcl"],keywords:{keyword:"if let in with where case of class instance otherwise implementation definition system module from import qualified as special code inline foreign export ccall stdcall generic derive infix infixl infixr",built_in:"Int Real Char Bool",literal:"True False"},contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,{begin:"->|<-[|:]?|#!?|>>=|\\{\\||\\|\\}|:==|=:|<>"}]}}}());
-hljs.registerLanguage("clojure",function(){return function(a){var b={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",relevance:0},c=a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),d=a.COMMENT(";","$",{relevance:0}),e={className:"literal",begin:/\b(true|false|nil)\b/},f={begin:"[\\[\\{]",end:"[\\]\\}]"},h={className:"comment",begin:"\\^[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},g=a.COMMENT("\\^\\{","\\}"),k={className:"symbol",begin:"[:]{1,2}[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},
-l={begin:"\\(",end:"\\)"},m={endsWithParent:!0,relevance:0},p={keywords:{$pattern:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*","builtin-name":"def defonce defprotocol defstruct defmulti defmethod defn- defn defmacro deftype defrecord cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy first rest cons cast coll last butlast sigs reify second ffirst fnext nfirst nnext meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},
-className:"name",begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",starts:m},q=[l,c,h,g,d,k,f,b,e,{begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",relevance:0}],r={beginKeywords:"def defonce defprotocol defstruct defmulti defmethod defn- defn defmacro deftype defrecord",lexemes:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",end:'(\\[|\\#|\\d|"|:|\\{|\\)|\\(|$)',contains:[{className:"title",begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",relevance:0,
-excludeEnd:!0,endsParent:!0}].concat(q)};l.contains=[a.COMMENT("comment",""),r,p,m];m.contains=q;f.contains=q;g.contains=[f];return{name:"Clojure",aliases:["clj"],illegal:/\S/,contains:[l,c,h,g,d,k,f,b,e]}}}());hljs.registerLanguage("clojure-repl",function(){return function(a){return{name:"Clojure REPL",contains:[{className:"meta",begin:/^([\w.-]+|\s*#_)?=>/,starts:{end:/$/,subLanguage:"clojure"}}]}}}());
-hljs.registerLanguage("cmake",function(){return function(a){return{name:"CMake",aliases:["cmake.in"],case_insensitive:!0,keywords:{keyword:"break cmake_host_system_information cmake_minimum_required cmake_parse_arguments cmake_policy configure_file continue elseif else endforeach endfunction endif endmacro endwhile execute_process file find_file find_library find_package find_path find_program foreach function get_cmake_property get_directory_property get_filename_component get_property if include include_guard list macro mark_as_advanced math message option return separate_arguments set_directory_properties set_property set site_name string unset variable_watch while add_compile_definitions add_compile_options add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_link_options add_subdirectory add_test aux_source_directory build_command create_test_sourcelist define_property enable_language enable_testing export fltk_wrap_ui get_source_file_property get_target_property get_test_property include_directories include_external_msproject include_regular_expression install link_directories link_libraries load_cache project qt_wrap_cpp qt_wrap_ui remove_definitions set_source_files_properties set_target_properties set_tests_properties source_group target_compile_definitions target_compile_features target_compile_options target_include_directories target_link_directories target_link_libraries target_link_options target_sources try_compile try_run ctest_build ctest_configure ctest_coverage ctest_empty_binary_directory ctest_memcheck ctest_read_custom_files ctest_run_script ctest_sleep ctest_start ctest_submit ctest_test ctest_update ctest_upload build_name exec_program export_library_dependencies install_files install_programs install_targets load_command make_directory output_required_files remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or not command policy target test exists is_newer_than is_directory is_symlink is_absolute matches less greater equal less_equal greater_equal strless strgreater strequal strless_equal strgreater_equal version_less version_greater version_equal version_less_equal version_greater_equal in_list defined"},
-contains:[{className:"variable",begin:"\\${",end:"}"},a.HASH_COMMENT_MODE,a.QUOTE_STRING_MODE,a.NUMBER_MODE]}}}());
-hljs.registerLanguage("coffeescript",function(){var a="as in of if for while finally var new function do return void else break catch instanceof with throw case default try switch continue typeof delete let yield const class debugger async await static import from export extends".split(" "),b="true false null undefined NaN Infinity".split(" "),c=[].concat("setInterval setTimeout clearInterval clearTimeout require exports eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape".split(" "),"arguments this super console window document localStorage module global".split(" "),
-"Intl DataView Number Math Date String RegExp Object Function Boolean Error Symbol Set Map WeakSet WeakMap Proxy Reflect JSON Promise Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Float32Array Array Uint8Array Uint8ClampedArray ArrayBuffer".split(" "),"EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError".split(" "));return function(d){var e={keyword:a.concat("then unless until loop by when and or is isnt not".split(" ")).filter(function(a){return function(b){return!a.includes(b)}}(["var",
-"const","let","function","static"])).join(" "),literal:b.concat(["yes","no","on","off"]).join(" "),built_in:c.concat(["npm","print"]).join(" ")},f={className:"subst",begin:/#\{/,end:/}/,keywords:e},h=[d.BINARY_NUMBER_MODE,d.inherit(d.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[d.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[d.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[d.BACKSLASH_ESCAPE,f]},{begin:/"/,end:/"/,contains:[d.BACKSLASH_ESCAPE,
-f]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[f,d.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];f.contains=h;f=d.inherit(d.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"});var g={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,
-keywords:e,contains:["self"].concat(h)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:e,illegal:/\/\*/,contains:h.concat([d.COMMENT("###","###"),d.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*[A-Za-z$_][0-9A-Za-z$_]*\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[f,g]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[g]}]},{className:"class",beginKeywords:"class",
-end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[f]},f]},{begin:"[A-Za-z$_][0-9A-Za-z$_]*:",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());
-hljs.registerLanguage("coq",function(){return function(a){return{name:"Coq",keywords:{keyword:"_|0 as at cofix else end exists exists2 fix for forall fun if IF in let match mod Prop return Set then Type using where with Abort About Add Admit Admitted All Arguments Assumptions Axiom Back BackTo Backtrack Bind Blacklist Canonical Cd Check Class Classes Close Coercion Coercions CoFixpoint CoInductive Collection Combined Compute Conjecture Conjectures Constant constr Constraint Constructors Context Corollary CreateHintDb Cut Declare Defined Definition Delimit Dependencies Dependent Derive Drop eauto End Equality Eval Example Existential Existentials Existing Export exporting Extern Extract Extraction Fact Field Fields File Fixpoint Focus for From Function Functional Generalizable Global Goal Grab Grammar Graph Guarded Heap Hint HintDb Hints Hypotheses Hypothesis ident Identity If Immediate Implicit Import Include Inductive Infix Info Initial Inline Inspect Instance Instances Intro Intros Inversion Inversion_clear Language Left Lemma Let Libraries Library Load LoadPath Local Locate Ltac ML Mode Module Modules Monomorphic Morphism Next NoInline Notation Obligation Obligations Opaque Open Optimize Options Parameter Parameters Parametric Path Paths pattern Polymorphic Preterm Print Printing Program Projections Proof Proposition Pwd Qed Quit Rec Record Recursive Redirect Relation Remark Remove Require Reserved Reset Resolve Restart Rewrite Right Ring Rings Save Scheme Scope Scopes Script Search SearchAbout SearchHead SearchPattern SearchRewrite Section Separate Set Setoid Show Solve Sorted Step Strategies Strategy Structure SubClass Table Tables Tactic Term Test Theorem Time Timeout Transparent Type Typeclasses Types Undelimit Undo Unfocus Unfocused Unfold Universe Universes Unset Unshelve using Variable Variables Variant Verbose Visibility where with",built_in:"abstract absurd admit after apply as assert assumption at auto autorewrite autounfold before bottom btauto by case case_eq cbn cbv change classical_left classical_right clear clearbody cofix compare compute congruence constr_eq constructor contradict contradiction cut cutrewrite cycle decide decompose dependent destruct destruction dintuition discriminate discrR do double dtauto eapply eassumption eauto ecase econstructor edestruct ediscriminate eelim eexact eexists einduction einjection eleft elim elimtype enough equality erewrite eright esimplify_eq esplit evar exact exactly_once exfalso exists f_equal fail field field_simplify field_simplify_eq first firstorder fix fold fourier functional generalize generalizing gfail give_up has_evar hnf idtac in induction injection instantiate intro intro_pattern intros intuition inversion inversion_clear is_evar is_var lapply lazy left lia lra move native_compute nia nsatz omega once pattern pose progress proof psatz quote record red refine reflexivity remember rename repeat replace revert revgoals rewrite rewrite_strat right ring ring_simplify rtauto set setoid_reflexivity setoid_replace setoid_rewrite setoid_symmetry setoid_transitivity shelve shelve_unifiable simpl simple simplify_eq solve specialize split split_Rabs split_Rmult stepl stepr subst sum swap symmetry tactic tauto time timeout top transitivity trivial try tryif unfold unify until using vm_compute with"},
-contains:[a.QUOTE_STRING_MODE,a.COMMENT("\\(\\*","\\*\\)"),a.C_NUMBER_MODE,{className:"type",excludeBegin:!0,begin:"\\|\\s*",end:"\\w+"},{begin:/[-=]>/}]}}}());
-hljs.registerLanguage("cos",function(){return function(a){return{name:"Cach\u00e9 Object Script",case_insensitive:!0,aliases:["cos","cls"],keywords:"property parameter class classmethod clientmethod extends as break catch close continue do d|0 else elseif for goto halt hang h|0 if job j|0 kill k|0 lock l|0 merge new open quit q|0 read r|0 return set s|0 tcommit throw trollback try tstart use view while write w|0 xecute x|0 zkill znspace zn ztrap zwrite zw zzdump zzwrite print zbreak zinsert zload zprint zremove zsave zzprint mv mvcall mvcrt mvdim mvprint zquit zsync ascii",contains:[{className:"number",
-begin:"\\b(\\d+(\\.\\d*)?|\\.\\d+)",relevance:0},{className:"string",variants:[{begin:'"',end:'"',contains:[{begin:'""',relevance:0}]}]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"comment",begin:/;/,end:"$",relevance:0},{className:"built_in",begin:/(?:\$\$?|\.\.)\^?[a-zA-Z]+/},{className:"built_in",begin:/\$\$\$[a-zA-Z]+/},{className:"built_in",begin:/%[a-z]+(?:\.[a-z]+)*/},{className:"symbol",begin:/\^%?[a-zA-Z][\w]*/},{className:"keyword",begin:/##class|##super|#define|#dim/},{begin:/&sql\(/,
-end:/\)/,excludeBegin:!0,excludeEnd:!0,subLanguage:"sql"},{begin:/&(js|jscript|javascript)</,end:/>/,excludeBegin:!0,excludeEnd:!0,subLanguage:"javascript"},{begin:/&html<\s*</,end:/>\s*>/,subLanguage:"xml"}]}}}());
-hljs.registerLanguage("crmsh",function(){return function(a){return{name:"crmsh",aliases:["crm","pcmk"],case_insensitive:!0,keywords:{keyword:"params meta operations op rule attributes utilization read write deny defined not_defined in_range date spec in ref reference attribute type xpath version and or lt gt tag lte gte eq ne \\ number string",literal:"Master Started Slave Stopped start promote demote stop monitor true false"},contains:[a.HASH_COMMENT_MODE,{beginKeywords:"node",starts:{end:"\\s*([\\w_-]+:)?",
-starts:{className:"title",end:"\\s*[\\$\\w_][\\w_-]*"}}},{beginKeywords:"primitive rsc_template",starts:{className:"title",end:"\\s*[\\$\\w_][\\w_-]*",starts:{end:"\\s*@?[\\w_][\\w_\\.:-]*"}}},{begin:"\\b(group|clone|ms|master|location|colocation|order|fencing_topology|rsc_ticket|acl_target|acl_group|user|role|tag|xml)\\s+",keywords:"group clone ms master location colocation order fencing_topology rsc_ticket acl_target acl_group user role tag xml",starts:{className:"title",end:"[\\$\\w_][\\w_-]*"}},
-{beginKeywords:"property rsc_defaults op_defaults",starts:{className:"title",end:"\\s*([\\w_-]+:)?"}},a.QUOTE_STRING_MODE,{className:"meta",begin:"(ocf|systemd|service|lsb):[\\w_:-]+",relevance:0},{className:"number",begin:"\\b\\d+(\\.\\d+)?(ms|s|h|m)?",relevance:0},{className:"literal",begin:"[-]?(infinity|inf)",relevance:0},{className:"attr",begin:/([A-Za-z\$_#][\w_-]+)=/,relevance:0},{className:"tag",begin:"</?",end:"/?>",relevance:0}]}}}());
-hljs.registerLanguage("crystal",function(){return function(a){function b(a,b){a=[{begin:a,end:b}];return a[0].contains=a}var c={$pattern:"[a-zA-Z_]\\w*[!?=]?",keyword:"abstract alias annotation as as? asm begin break case class def do else elsif end ensure enum extend for fun if include instance_sizeof is_a? lib macro module next nil? of out pointerof private protected rescue responds_to? return require select self sizeof struct super then type typeof union uninitialized unless until verbatim when while with yield __DIR__ __END_LINE__ __FILE__ __LINE__",
-literal:"false nil true"},d={className:"subst",begin:"#{",end:"}",keywords:c},e={className:"template-variable",variants:[{begin:"\\{\\{",end:"\\}\\}"},{begin:"\\{%",end:"%\\}"}],keywords:c},f={className:"string",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[Qwi]?\\(",end:"\\)",contains:b("\\(","\\)")},{begin:"%[Qwi]?\\[",end:"\\]",contains:b("\\[","\\]")},{begin:"%[Qwi]?{",end:"}",contains:b("{","}")},{begin:"%[Qwi]?<",end:">",contains:b("<",
-">")},{begin:"%[Qwi]?\\|",end:"\\|"},{begin:/<<-\w+$/,end:/^\s*\w+$/}],relevance:0},h={className:"string",variants:[{begin:"%q\\(",end:"\\)",contains:b("\\(","\\)")},{begin:"%q\\[",end:"\\]",contains:b("\\[","\\]")},{begin:"%q{",end:"}",contains:b("{","}")},{begin:"%q<",end:">",contains:b("<",">")},{begin:"%q\\|",end:"\\|"},{begin:/<<-'\w+'$/,end:/^\s*\w+$/}],relevance:0},g={begin:"(?!%})("+a.RE_STARTERS_RE+"|\\n|\\b(case|if|select|unless|until|when|while)\\b)\\s*",keywords:"case if select unless until when while",
-contains:[{className:"regexp",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:"//[a-z]*",relevance:0},{begin:"/(?!\\/)",end:"/[a-z]*"}]}],relevance:0},k={className:"regexp",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:"%r\\(",end:"\\)",contains:b("\\(","\\)")},{begin:"%r\\[",end:"\\]",contains:b("\\[","\\]")},{begin:"%r{",end:"}",contains:b("{","}")},{begin:"%r<",end:">",contains:b("<",">")},{begin:"%r\\|",end:"\\|"}],relevance:0},l={className:"meta",begin:"@\\[",end:"\\]",contains:[a.inherit(a.QUOTE_STRING_MODE,
-{className:"meta-string"})]};a=[e,f,h,k,g,l,a.HASH_COMMENT_MODE,{className:"class",beginKeywords:"class module struct",end:"$|;",illegal:/=/,contains:[a.HASH_COMMENT_MODE,a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<"}]},{className:"class",beginKeywords:"lib enum union",end:"$|;",illegal:/=/,contains:[a.HASH_COMMENT_MODE,a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"})],relevance:10},{beginKeywords:"annotation",end:"$|;",illegal:/=/,contains:[a.HASH_COMMENT_MODE,
-a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"})],relevance:10},{className:"function",beginKeywords:"def",end:/\B\b/,contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|[=!]~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?",endsParent:!0})]},{className:"function",beginKeywords:"fun macro",end:/\B\b/,contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|[=!]~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?",
-endsParent:!0})],relevance:5},{className:"symbol",begin:a.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":",contains:[f,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|[=!]~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?"}],relevance:0},{className:"number",variants:[{begin:"\\b0b([01_]+)(_*[ui](8|16|32|64|128))?"},{begin:"\\b0o([0-7_]+)(_*[ui](8|16|32|64|128))?"},{begin:"\\b0x([A-Fa-f0-9_]+)(_*[ui](8|16|32|64|128))?"},{begin:"\\b([1-9][0-9_]*[0-9]|[0-9])(\\.[0-9][0-9_]*)?([eE]_*[-+]?[0-9_]*)?(_*f(32|64))?(?!_)"},
-{begin:"\\b([1-9][0-9_]*|0)(_*[ui](8|16|32|64|128))?"}],relevance:0}];d.contains=a;e.contains=a.slice(1);return{name:"Crystal",aliases:["cr"],keywords:c,contains:a}}}());
-hljs.registerLanguage("csharp",function(){return function(a){var b={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},
-c=a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),d={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},e={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},f=a.inherit(e,{illegal:/\n/}),h={className:"subst",begin:"{",end:"}",keywords:b},g=a.inherit(h,{illegal:/\n/}),k={className:"string",begin:/\$"/,
-end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},a.BACKSLASH_ESCAPE,g]},l={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},h]},m=a.inherit(l,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},g]});h.contains=[l,k,e,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,d,a.C_BLOCK_COMMENT_MODE];g.contains=[m,k,f,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,d,a.inherit(a.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];e={variants:[l,k,e,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]};
-f=a.IDENT_RE+"(<"+a.IDENT_RE+"(\\s*,\\s*"+a.IDENT_RE+")*>)?(\\[\\])?";h={begin:"@"+a.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:b,illegal:/::/,contains:[a.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},
-e,d,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},c,{begin:"<",end:">",keywords:"in out"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[c,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",
-begin:"("+f+"\\s+)+"+a.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:b,contains:[{begin:a.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[a.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,relevance:0,contains:[e,d,a.C_BLOCK_COMMENT_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},h]}}}());
-hljs.registerLanguage("csp",function(){return function(a){return{name:"CSP",case_insensitive:!1,keywords:{$pattern:"[a-zA-Z][a-zA-Z0-9_-]*",keyword:"base-uri child-src connect-src default-src font-src form-action frame-ancestors frame-src img-src media-src object-src plugin-types report-uri sandbox script-src style-src"},contains:[{className:"string",begin:"'",end:"'"},{className:"attribute",begin:"^Content",end:":",excludeEnd:!0}]}}}());
-hljs.registerLanguage("css",function(){return function(a){return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[a.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},
-{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[a.C_BLOCK_COMMENT_MODE,{begin:/(?:[A-Z_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,
-end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.CSS_NUMBER_MODE]}]},a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]}]}]}}}());
-hljs.registerLanguage("d",function(){return function(a){var b={$pattern:a.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",
-built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},c=a.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:b,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},{className:"string",begin:'"',contains:[{begin:"\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",
-relevance:0}],end:'"[cwd]?'},{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},{className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))(i|[fF]i|Li))",
-relevance:0},{className:"number",begin:"\\b((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))(L|u|U|Lu|LU|uL|UL)?",relevance:0},{className:"string",begin:"'(\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};|.)",end:"'",illegal:"."},{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());
-hljs.registerLanguage("markdown",function(){return function(a){a={begin:"<",end:">",subLanguage:"xml",relevance:0};var b={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},c={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},
-{begin:/\*{2}/,end:/\*{2}/}]},d={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};c.contains.push(d);d.contains.push(c);var e=[a,b];c.contains=c.contains.concat(e);d.contains=d.contains.concat(e);e=e.concat(c,d);return{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:e},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:e}]}]},a,
-{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},c,d,{className:"quote",begin:"^>\\s+",contains:e,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},b,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",
-begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());
-hljs.registerLanguage("dart",function(){return function(a){var b={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"}]},c={className:"subst",variants:[{begin:"\\${",end:"}"}],keywords:"true false null this is new super"};b={className:"string",variants:[{begin:"r'''",end:"'''"},{begin:'r"""',end:'"""'},{begin:"r'",end:"'",illegal:"\\n"},{begin:'r"',end:'"',illegal:"\\n"},{begin:"'''",end:"'''",contains:[a.BACKSLASH_ESCAPE,b,c]},{begin:'"""',end:'"""',contains:[a.BACKSLASH_ESCAPE,b,c]},{begin:"'",
-end:"'",illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,b,c]},{begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,b,c]}]};c.contains=[a.C_NUMBER_MODE,b];return{name:"Dart",keywords:{keyword:"abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for Function get hide if implements import in inferface is library mixin new null on operator part rethrow return set show static super switch sync this throw true try typedef var void while with yield",
-built_in:"Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double dynamic int num print Element ElementList document querySelector querySelectorAll window"},contains:[b,a.COMMENT("/\\*\\*","\\*/",{subLanguage:"markdown",relevance:0}),a.COMMENT("///+\\s*","$",{contains:[{subLanguage:"markdown",begin:".",end:"$",relevance:0}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"class",
-beginKeywords:"class interface",end:"{",excludeEnd:!0,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"},{begin:"=>"}]}}}());
-hljs.registerLanguage("delphi",function(){return function(a){var b=[a.C_LINE_COMMENT_MODE,a.COMMENT(/\{/,/\}/,{relevance:0}),a.COMMENT(/\(\*/,/\*\)/,{relevance:10})],c={className:"meta",variants:[{begin:/\{\$/,end:/\}/},{begin:/\(\*\$/,end:/\*\)/}]},d={className:"string",begin:/'/,end:/'/,contains:[{begin:/''/}]},e={className:"string",begin:/(#\d+)+/},f={begin:a.IDENT_RE+"\\s*=\\s*class\\s*\\(",returnBegin:!0,contains:[a.TITLE_MODE]},h={className:"function",beginKeywords:"function constructor destructor procedure",
-end:/[:;]/,keywords:"function constructor|10 destructor|10 procedure|10",contains:[a.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:"exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure absolute reintroduce operator as is abstract alias assembler bitpacked break continue cppdecl cvar enumerator experimental platform deprecated unimplemented dynamic export far16 forward generic helper implements interrupt iochecks local name nodefault noreturn nostackframe oldfpccall otherwise saveregisters softfloat specialize strict unaligned varargs ",
-contains:[d,e,c].concat(b)},c].concat(b)};return{name:"Delphi",aliases:"dpr dfm pas pascal freepascal lazarus lpr lfm".split(" "),case_insensitive:!0,keywords:"exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure absolute reintroduce operator as is abstract alias assembler bitpacked break continue cppdecl cvar enumerator experimental platform deprecated unimplemented dynamic export far16 forward generic helper implements interrupt iochecks local name nodefault noreturn nostackframe oldfpccall otherwise saveregisters softfloat specialize strict unaligned varargs ",
-illegal:/"|\$[G-Zg-z]|\/\*|<\/|\|/,contains:[d,e,a.NUMBER_MODE,{className:"number",relevance:0,variants:[{begin:"\\$[0-9A-Fa-f]+"},{begin:"&[0-7]+"},{begin:"%[01]+"}]},f,h,c].concat(b)}}}());
-hljs.registerLanguage("diff",function(){return function(a){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",
-begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());
-hljs.registerLanguage("django",function(){return function(a){var b={begin:/\|[A-Za-z]+:?/,keywords:{name:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone"},contains:[a.QUOTE_STRING_MODE,
-a.APOS_STRING_MODE]};return{name:"Django",aliases:["jinja"],case_insensitive:!0,subLanguage:"xml",contains:[a.COMMENT(/\{%\s*comment\s*%}/,/\{%\s*endcomment\s*%}/),a.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:{name:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim"},
-starts:{endsWithParent:!0,keywords:"in by as",contains:[b],relevance:0}}]},{className:"template-variable",begin:/\{\{/,end:/}}/,contains:[b]}]}}}());
-hljs.registerLanguage("dns",function(){return function(a){return{name:"DNS Zone",aliases:["bind","zone"],keywords:{keyword:"IN A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT"},contains:[a.COMMENT(";","$",{relevance:0}),{className:"meta",begin:/^\$(TTL|GENERATE|INCLUDE|ORIGIN)\b/},{className:"number",begin:"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))\\b"},
-{className:"number",begin:"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\b"},a.inherit(a.NUMBER_MODE,{begin:/\b\d+[dhwm]?/})]}}}());
-hljs.registerLanguage("dockerfile",function(){return function(a){return{name:"Dockerfile",aliases:["docker"],case_insensitive:!0,keywords:"from maintainer expose env arg user onbuild stopsignal",contains:[a.HASH_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.NUMBER_MODE,{beginKeywords:"run cmd entrypoint volume add copy workdir label healthcheck shell",starts:{end:/[^\\]$/,subLanguage:"bash"}}],illegal:"</"}}}());
-hljs.registerLanguage("dos",function(){return function(a){var b=a.COMMENT(/^\s*@?rem\b/,/$/,{relevance:10});return{name:"Batch file (DOS)",aliases:["bat","cmd"],case_insensitive:!0,illegal:/\/\*/,keywords:{keyword:"if else goto for in do call exit not exist errorlevel defined equ neq lss leq gtr geq",built_in:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux shift cd dir echo setlocal endlocal set pause copy append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shift sort start subst time title tree type ver verify vol ping net ipconfig taskkill xcopy ren del"},
-contains:[{className:"variable",begin:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{className:"function",begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",end:"goto:eof",contains:[a.inherit(a.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),b]},{className:"number",begin:"\\b\\d+",relevance:0},b]}}}());
-hljs.registerLanguage("dsconfig",function(){return function(a){return{keywords:"dsconfig",contains:[{className:"keyword",begin:"^dsconfig",end:"\\s",excludeEnd:!0,relevance:10},{className:"built_in",begin:"(list|create|get|set|delete)-(\\w+)",end:"\\s",excludeEnd:!0,illegal:"!@#$%^&*()",relevance:10},{className:"built_in",begin:"--(\\w+)",end:"\\s",excludeEnd:!0},{className:"string",begin:/"/,end:/"/},{className:"string",begin:/'/,end:/'/},{className:"string",begin:"[\\w-?]+:\\w+",end:"\\W",relevance:0},
-{className:"string",begin:"\\w+-?\\w+",end:"\\W",relevance:0},a.HASH_COMMENT_MODE]}}}());
-hljs.registerLanguage("dts",function(){return function(a){var b={className:"string",variants:[a.inherit(a.QUOTE_STRING_MODE,{begin:'((u8?|U)|L)?"'}),{begin:'(u8?|U)?R"',end:'"',contains:[a.BACKSLASH_ESCAPE]},{begin:"'\\\\?.",end:"'",illegal:"."}]},c={className:"number",variants:[{begin:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{begin:a.C_NUMBER_RE}],relevance:0},d={className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef ifdef ifndef"},contains:[{begin:/\\\n/,
-relevance:0},{beginKeywords:"include",end:"$",keywords:{"meta-keyword":"include"},contains:[a.inherit(b,{className:"meta-string"}),{className:"meta-string",begin:"<",end:">",illegal:"\\n"}]},b,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},e={className:"variable",begin:"\\&[a-z\\d_]*\\b"},f={className:"meta-keyword",begin:"/[a-z][a-z\\d-]*/"},h={className:"symbol",begin:"^\\s*[a-zA-Z_][a-zA-Z\\d_]*:"},g={className:"params",begin:"<",end:">",contains:[c,e]},k={className:"class",begin:/[a-zA-Z_][a-zA-Z\d_@]*\s{/,
-end:/[{;=]/,returnBegin:!0,excludeEnd:!0};return{name:"Device Tree",keywords:"",contains:[{className:"class",begin:"/\\s*{",end:"};",relevance:10,contains:[e,f,h,k,g,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,b]},e,f,h,k,g,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,b,d,{begin:a.IDENT_RE+"::",keywords:""}]}}}());
-hljs.registerLanguage("dust",function(){return function(a){return{name:"Dust",aliases:["dst"],case_insensitive:!0,subLanguage:"xml",contains:[{className:"template-tag",begin:/\{[#\/]/,end:/\}/,illegal:/;/,contains:[{className:"name",begin:/[a-zA-Z\.-]+/,starts:{endsWithParent:!0,relevance:0,contains:[a.QUOTE_STRING_MODE]}}]},{className:"template-variable",begin:/\{/,end:/\}/,illegal:/;/,keywords:"if eq ne lt lte gt gte select default math sep"}]}}}());
-hljs.registerLanguage("ebnf",function(){return function(a){var b=a.COMMENT(/\(\*/,/\*\)/);return{name:"Extended Backus-Naur Form",illegal:/\S/,contains:[b,{className:"attribute",begin:/^[ ]*[a-zA-Z][a-zA-Z-_]*([\s-_]+[a-zA-Z][a-zA-Z]*)*/},{begin:/=/,end:/[.;]/,contains:[b,{className:"meta",begin:/\?.*\?/},{className:"string",variants:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{begin:"`",end:"`"}]}]}]}}}());
-hljs.registerLanguage("elixir",function(){return function(a){var b={$pattern:"[a-zA-Z_][a-zA-Z0-9_.]*(\\!|\\?)?",keyword:"and false then defined module in return redo retry end for true self when next until do begin unless nil break not case cond alias while ensure or include use alias fn quote require import with|0"},c={className:"subst",begin:"#\\{",end:"}",keywords:b},d={className:"number",begin:"(\\b0o[0-7_]+)|(\\b0b[01_]+)|(\\b0x[0-9a-fA-F_]+)|(-?\\b[1-9][0-9_]*(.[0-9_]+([eE][-+]?[0-9]+)?)?)",
-relevance:0},e={className:"string",begin:"~[a-z](?=[/|([{<\"'])",contains:[{endsParent:!0,contains:[{contains:[a.BACKSLASH_ESCAPE,c],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/\//,end:/\//},{begin:/\|/,end:/\|/},{begin:/\(/,end:/\)/},{begin:/\[/,end:/\]/},{begin:/\{/,end:/\}/},{begin:/</,end:/>/}]}]}]},f={className:"string",contains:[a.BACKSLASH_ESCAPE,c],variants:[{begin:/"""/,end:/"""/},{begin:/'''/,end:/'''/},{begin:/~S"""/,end:/"""/,contains:[]},{begin:/~S"/,end:/"/,contains:[]},
-{begin:/~S'''/,end:/'''/,contains:[]},{begin:/~S'/,end:/'/,contains:[]},{begin:/'/,end:/'/},{begin:/"/,end:/"/}]},h={className:"function",beginKeywords:"def defp defmacro",end:/\B\b/,contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_][a-zA-Z0-9_.]*(\\!|\\?)?",endsParent:!0})]},g=a.inherit(h,{className:"class",beginKeywords:"defimpl defmodule defprotocol defrecord",end:/\bdo\b|$|;/});a=[f,{className:"string",begin:"~[A-Z](?=[/|([{<\"'])",contains:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/\//,
-end:/\//},{begin:/\|/,end:/\|/},{begin:/\(/,end:/\)/},{begin:/\[/,end:/\]/},{begin:/\{/,end:/\}/},{begin:/</,end:/>/}]},e,a.HASH_COMMENT_MODE,g,h,{begin:"::"},{className:"symbol",begin:":(?![\\s:])",contains:[f,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"}],relevance:0},{className:"symbol",begin:"[a-zA-Z_][a-zA-Z0-9_.]*(\\!|\\?)?:(?!:)",relevance:0},d,{className:"variable",begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{begin:"->"},{begin:"("+a.RE_STARTERS_RE+
-")\\s*",contains:[a.HASH_COMMENT_MODE,{begin:/\/: (?=\d+\s*[,\]])/,relevance:0,contains:[d]},{className:"regexp",illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,c],variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}],relevance:0}];c.contains=a;return{name:"Elixir",keywords:b,contains:a}}}());
-hljs.registerLanguage("elm",function(){return function(a){var b={variants:[a.COMMENT("--","$"),a.COMMENT("{-","-}",{contains:["self"]})]},c={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},d={begin:"\\(",end:"\\)",illegal:'"',contains:[{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},b]};return{name:"Elm",keywords:"let in if then else case of where module import exposing type alias as infix infixl infixr port effect command subscription",contains:[{beginKeywords:"port effect module",
-end:"exposing",keywords:"port effect module where command subscription exposing",contains:[d,b],illegal:"\\W\\.|;"},{begin:"import",end:"$",keywords:"import as exposing",contains:[d,b],illegal:"\\W\\.|;"},{begin:"type",end:"$",keywords:"type alias",contains:[c,d,{begin:"{",end:"}",contains:d.contains},b]},{beginKeywords:"infix infixl infixr",end:"$",contains:[a.C_NUMBER_MODE,b]},{begin:"port",end:"$",keywords:"port",contains:[b]},{className:"string",begin:"'\\\\?.",end:"'",illegal:"."},a.QUOTE_STRING_MODE,
-a.C_NUMBER_MODE,c,a.inherit(a.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),b,{begin:"->|<-"}],illegal:/;/}}}());
-hljs.registerLanguage("ruby",function(){return function(a){var b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={className:"doctag",begin:"@[A-Za-z]+"},d={begin:"#<",end:">"};c=[a.COMMENT("#","$",{contains:[c]}),a.COMMENT("^\\=begin","^\\=end",{contains:[c],relevance:10}),
-a.COMMENT("^__END__","\\n$")];var e={className:"subst",begin:"#\\{",end:"}",keywords:b},f={className:"string",contains:[a.BACKSLASH_ESCAPE,e],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},
-{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},a.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[a.BACKSLASH_ESCAPE,e]})]}]},h={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:b};a=[f,d,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+a.IDENT_RE+"::)?"+a.IDENT_RE}]}].concat(c)},{className:"function",beginKeywords:"def",
-end:"$|;",contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"}),h].concat(c)},{begin:a.IDENT_RE+"::"},{className:"symbol",begin:a.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[f,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",
-relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:b},{begin:"("+a.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[d,{className:"regexp",contains:[a.BACKSLASH_ESCAPE,e],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(c),relevance:0}].concat(c);e.contains=a;h.contains=a;return{name:"Ruby",aliases:["rb","gemspec",
-"podspec","thor","irb"],keywords:b,illegal:/\/\*/,contains:c.concat([{begin:/^\s*=>/,starts:{end:"$",contains:a}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:a}}]).concat(a)}}}());hljs.registerLanguage("erb",function(){return function(a){return{name:"ERB",subLanguage:"xml",contains:[a.COMMENT("<%#","%>"),{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0}]}}}());
-hljs.registerLanguage("erlang-repl",function(){return function(a){return{name:"Erlang REPL",keywords:{built_in:"spawn spawn_link self",keyword:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"},contains:[{className:"meta",begin:"^[0-9]+> ",relevance:10},a.COMMENT("%","$"),{className:"number",begin:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",relevance:0},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{begin:"\\?(::)?([A-Z]\\w*(::)?)+"},
-{begin:"->"},{begin:"ok"},{begin:"!"},{begin:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",relevance:0},{begin:"[A-Z][a-zA-Z0-9_']*",relevance:0}]}}}());
-hljs.registerLanguage("erlang",function(){return function(a){var b={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},c=a.COMMENT("%","$"),d={className:"number",begin:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",relevance:0},e={begin:"fun\\s+[a-z'][a-zA-Z0-9_']*/\\d+"},f={begin:"([a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*|[a-z'][a-zA-Z0-9_']*)\\(",end:"\\)",returnBegin:!0,
-relevance:0,contains:[{begin:"([a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*|[a-z'][a-zA-Z0-9_']*)",relevance:0},{begin:"\\(",end:"\\)",endsWithParent:!0,returnEnd:!0,relevance:0}]},h={begin:"{",end:"}",relevance:0},g={begin:"\\b_([A-Z][A-Za-z0-9_]*)?",relevance:0},k={begin:"[A-Z][a-zA-Z0-9_]*",relevance:0},l={begin:"#"+a.UNDERSCORE_IDENT_RE,relevance:0,returnBegin:!0,contains:[{begin:"#"+a.UNDERSCORE_IDENT_RE,relevance:0},{begin:"{",end:"}",relevance:0}]},m={beginKeywords:"fun receive if try case",end:"end",
-keywords:b};m.contains=[c,e,a.inherit(a.APOS_STRING_MODE,{className:""}),m,f,a.QUOTE_STRING_MODE,d,h,g,k,l];e=[c,e,m,f,a.QUOTE_STRING_MODE,d,h,g,k,l];f.contains[1].contains=e;h.contains=e;l.contains[1].contains=e;f={className:"params",begin:"\\(",end:"\\)",contains:e};return{name:"Erlang",aliases:["erl"],keywords:b,illegal:"(</|\\*=|\\+=|-=|/\\*|\\*/|\\(\\*|\\*\\))",contains:[{className:"function",begin:"^[a-z'][a-zA-Z0-9_']*\\s*\\(",end:"->",returnBegin:!0,illegal:"\\(|#|//|/\\*|\\\\|:|;",contains:[f,
-a.inherit(a.TITLE_MODE,{begin:"[a-z'][a-zA-Z0-9_']*"})],starts:{end:";|\\.",keywords:b,contains:e}},c,{begin:"^-",end:"\\.",relevance:0,excludeEnd:!0,returnBegin:!0,keywords:{$pattern:"-"+a.IDENT_RE,keyword:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec"},contains:[f]},d,a.QUOTE_STRING_MODE,l,g,k,h,{begin:/\.$/}]}}}());
-hljs.registerLanguage("excel",function(){return function(a){return{name:"Excel formulae",aliases:["xlsx","xls"],case_insensitive:!0,keywords:{$pattern:/[a-zA-Z][\w\.]*/,built_in:"ABS ACCRINT ACCRINTM ACOS ACOSH ACOT ACOTH AGGREGATE ADDRESS AMORDEGRC AMORLINC AND ARABIC AREAS ASC ASIN ASINH ATAN ATAN2 ATANH AVEDEV AVERAGE AVERAGEA AVERAGEIF AVERAGEIFS BAHTTEXT BASE BESSELI BESSELJ BESSELK BESSELY BETADIST BETA.DIST BETAINV BETA.INV BIN2DEC BIN2HEX BIN2OCT BINOMDIST BINOM.DIST BINOM.DIST.RANGE BINOM.INV BITAND BITLSHIFT BITOR BITRSHIFT BITXOR CALL CEILING CEILING.MATH CEILING.PRECISE CELL CHAR CHIDIST CHIINV CHITEST CHISQ.DIST CHISQ.DIST.RT CHISQ.INV CHISQ.INV.RT CHISQ.TEST CHOOSE CLEAN CODE COLUMN COLUMNS COMBIN COMBINA COMPLEX CONCAT CONCATENATE CONFIDENCE CONFIDENCE.NORM CONFIDENCE.T CONVERT CORREL COS COSH COT COTH COUNT COUNTA COUNTBLANK COUNTIF COUNTIFS COUPDAYBS COUPDAYS COUPDAYSNC COUPNCD COUPNUM COUPPCD COVAR COVARIANCE.P COVARIANCE.S CRITBINOM CSC CSCH CUBEKPIMEMBER CUBEMEMBER CUBEMEMBERPROPERTY CUBERANKEDMEMBER CUBESET CUBESETCOUNT CUBEVALUE CUMIPMT CUMPRINC DATE DATEDIF DATEVALUE DAVERAGE DAY DAYS DAYS360 DB DBCS DCOUNT DCOUNTA DDB DEC2BIN DEC2HEX DEC2OCT DECIMAL DEGREES DELTA DEVSQ DGET DISC DMAX DMIN DOLLAR DOLLARDE DOLLARFR DPRODUCT DSTDEV DSTDEVP DSUM DURATION DVAR DVARP EDATE EFFECT ENCODEURL EOMONTH ERF ERF.PRECISE ERFC ERFC.PRECISE ERROR.TYPE EUROCONVERT EVEN EXACT EXP EXPON.DIST EXPONDIST FACT FACTDOUBLE FALSE|0 F.DIST FDIST F.DIST.RT FILTERXML FIND FINDB F.INV F.INV.RT FINV FISHER FISHERINV FIXED FLOOR FLOOR.MATH FLOOR.PRECISE FORECAST FORECAST.ETS FORECAST.ETS.CONFINT FORECAST.ETS.SEASONALITY FORECAST.ETS.STAT FORECAST.LINEAR FORMULATEXT FREQUENCY F.TEST FTEST FV FVSCHEDULE GAMMA GAMMA.DIST GAMMADIST GAMMA.INV GAMMAINV GAMMALN GAMMALN.PRECISE GAUSS GCD GEOMEAN GESTEP GETPIVOTDATA GROWTH HARMEAN HEX2BIN HEX2DEC HEX2OCT HLOOKUP HOUR HYPERLINK HYPGEOM.DIST HYPGEOMDIST IF IFERROR IFNA IFS IMABS IMAGINARY IMARGUMENT IMCONJUGATE IMCOS IMCOSH IMCOT IMCSC IMCSCH IMDIV IMEXP IMLN IMLOG10 IMLOG2 IMPOWER IMPRODUCT IMREAL IMSEC IMSECH IMSIN IMSINH IMSQRT IMSUB IMSUM IMTAN INDEX INDIRECT INFO INT INTERCEPT INTRATE IPMT IRR ISBLANK ISERR ISERROR ISEVEN ISFORMULA ISLOGICAL ISNA ISNONTEXT ISNUMBER ISODD ISREF ISTEXT ISO.CEILING ISOWEEKNUM ISPMT JIS KURT LARGE LCM LEFT LEFTB LEN LENB LINEST LN LOG LOG10 LOGEST LOGINV LOGNORM.DIST LOGNORMDIST LOGNORM.INV LOOKUP LOWER MATCH MAX MAXA MAXIFS MDETERM MDURATION MEDIAN MID MIDBs MIN MINIFS MINA MINUTE MINVERSE MIRR MMULT MOD MODE MODE.MULT MODE.SNGL MONTH MROUND MULTINOMIAL MUNIT N NA NEGBINOM.DIST NEGBINOMDIST NETWORKDAYS NETWORKDAYS.INTL NOMINAL NORM.DIST NORMDIST NORMINV NORM.INV NORM.S.DIST NORMSDIST NORM.S.INV NORMSINV NOT NOW NPER NPV NUMBERVALUE OCT2BIN OCT2DEC OCT2HEX ODD ODDFPRICE ODDFYIELD ODDLPRICE ODDLYIELD OFFSET OR PDURATION PEARSON PERCENTILE.EXC PERCENTILE.INC PERCENTILE PERCENTRANK.EXC PERCENTRANK.INC PERCENTRANK PERMUT PERMUTATIONA PHI PHONETIC PI PMT POISSON.DIST POISSON POWER PPMT PRICE PRICEDISC PRICEMAT PROB PRODUCT PROPER PV QUARTILE QUARTILE.EXC QUARTILE.INC QUOTIENT RADIANS RAND RANDBETWEEN RANK.AVG RANK.EQ RANK RATE RECEIVED REGISTER.ID REPLACE REPLACEB REPT RIGHT RIGHTB ROMAN ROUND ROUNDDOWN ROUNDUP ROW ROWS RRI RSQ RTD SEARCH SEARCHB SEC SECH SECOND SERIESSUM SHEET SHEETS SIGN SIN SINH SKEW SKEW.P SLN SLOPE SMALL SQL.REQUEST SQRT SQRTPI STANDARDIZE STDEV STDEV.P STDEV.S STDEVA STDEVP STDEVPA STEYX SUBSTITUTE SUBTOTAL SUM SUMIF SUMIFS SUMPRODUCT SUMSQ SUMX2MY2 SUMX2PY2 SUMXMY2 SWITCH SYD T TAN TANH TBILLEQ TBILLPRICE TBILLYIELD T.DIST T.DIST.2T T.DIST.RT TDIST TEXT TEXTJOIN TIME TIMEVALUE T.INV T.INV.2T TINV TODAY TRANSPOSE TREND TRIM TRIMMEAN TRUE|0 TRUNC T.TEST TTEST TYPE UNICHAR UNICODE UPPER VALUE VAR VAR.P VAR.S VARA VARP VARPA VDB VLOOKUP WEBSERVICE WEEKDAY WEEKNUM WEIBULL WEIBULL.DIST WORKDAY WORKDAY.INTL XIRR XNPV XOR YEAR YEARFRAC YIELD YIELDDISC YIELDMAT Z.TEST ZTEST"},
-contains:[{begin:/^=/,end:/[^=]/,returnEnd:!0,illegal:/=/,relevance:10},{className:"symbol",begin:/\b[A-Z]{1,2}\d+\b/,end:/[^\d]/,excludeEnd:!0,relevance:0},{className:"symbol",begin:/[A-Z]{0,2}\d*:[A-Z]{0,2}\d*/,relevance:0},a.BACKSLASH_ESCAPE,a.QUOTE_STRING_MODE,{className:"number",begin:a.NUMBER_RE+"(%)?",relevance:0},a.COMMENT(/\bN\(/,/\)/,{excludeBegin:!0,excludeEnd:!0,illegal:/\n/})]}}}());
-hljs.registerLanguage("fix",function(){return function(a){return{name:"FIX",contains:[{begin:/[^\u2401\u0001]+/,end:/[\u2401\u0001]/,excludeEnd:!0,returnBegin:!0,returnEnd:!1,contains:[{begin:/([^\u2401\u0001=]+)/,end:/=([^\u2401\u0001=]+)/,returnEnd:!0,returnBegin:!1,className:"attr"},{begin:/=/,end:/([\u2401\u0001])/,excludeEnd:!0,excludeBegin:!0,className:"string"}]}],case_insensitive:!0}}}());
-hljs.registerLanguage("flix",function(){return function(a){return{name:"Flix",keywords:{literal:"true false",keyword:"case class def else enum if impl import in lat rel index let match namespace switch type yield with"},contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},{className:"string",variants:[{begin:'"',end:'"'}]},{className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[{className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/}]},
-a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("fortran",function(){return function(a){var b={variants:[a.COMMENT("!","$",{relevance:0}),a.COMMENT("^C","$",{relevance:0})]};return{name:"Fortran",case_insensitive:!0,aliases:["f90","f95"],keywords:{literal:".False. .True.",keyword:"kind do concurrent local shared while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then block endblock endassociate public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure impure integer real character complex logical codimension dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image sync change team co_broadcast co_max co_min co_sum co_reduce"},
-illegal:/\/\*/,contains:[{className:"string",relevance:0,variants:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},{className:"function",beginKeywords:"subroutine function program",illegal:"[${=\\n]",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]},{begin:/^C\s*=(?!=)/,relevance:0},b,{className:"number",begin:"(?=\\b|\\+|\\-|\\.)(?:\\.|\\d+\\.?)\\d*([de][+-]?\\d+)?(_[a-z_\\d]+)?",relevance:0}]}}}());
-hljs.registerLanguage("fsharp",function(){return function(a){var b={begin:"<",end:">",contains:[a.inherit(a.TITLE_MODE,{begin:/'[a-zA-Z0-9_]+/})]};return{name:"F#",aliases:["fs"],keywords:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",
-illegal:/\/\*/,contains:[{className:"keyword",begin:/\b(yield|return|let|do)!/},{className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:'"""',end:'"""'},a.COMMENT("\\(\\*","\\*\\)"),{className:"class",beginKeywords:"type",end:"\\(|=|$",excludeEnd:!0,contains:[a.UNDERSCORE_TITLE_MODE,b]},{className:"meta",begin:"\\[<",end:">\\]",relevance:10},{className:"symbol",begin:"\\B('[A-Za-z])\\b",contains:[a.BACKSLASH_ESCAPE]},a.C_LINE_COMMENT_MODE,a.inherit(a.QUOTE_STRING_MODE,
-{illegal:null}),a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("gams",function(){return function(a){var b={keyword:"abort acronym acronyms alias all and assign binary card diag display else eq file files for free ge gt if integer le loop lt maximizing minimizing model models ne negative no not option options or ord positive prod put putpage puttl repeat sameas semicont semiint smax smin solve sos1 sos2 sum system table then until using while xor yes",literal:"eps inf na",built_in:"abs arccos arcsin arctan arctan2 Beta betaReg binomial ceil centropy cos cosh cvPower div div0 eDist entropy errorf execSeed exp fact floor frac gamma gammaReg log logBeta logGamma log10 log2 mapVal max min mod ncpCM ncpF ncpVUpow ncpVUsin normal pi poly power randBinomial randLinear randTriangle round rPower sigmoid sign signPower sin sinh slexp sllog10 slrec sqexp sqlog10 sqr sqrec sqrt tan tanh trunc uniform uniformInt vcPower bool_and bool_eqv bool_imp bool_not bool_or bool_xor ifThen rel_eq rel_ge rel_gt rel_le rel_lt rel_ne gday gdow ghour gleap gmillisec gminute gmonth gsecond gyear jdate jnow jstart jtime errorLevel execError gamsRelease gamsVersion handleCollect handleDelete handleStatus handleSubmit heapFree heapLimit heapSize jobHandle jobKill jobStatus jobTerminate licenseLevel licenseStatus maxExecError sleep timeClose timeComp timeElapsed timeExec timeStart"},c=
-{className:"symbol",variants:[{begin:/=[lgenxc]=/},{begin:/\$/}]},d={className:"comment",variants:[{begin:"'",end:"'"},{begin:'"',end:'"'}],illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},e={begin:"/",end:"/",keywords:b,contains:[d,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_NUMBER_MODE]};d={begin:/[a-z][a-z0-9_]*(\([a-z0-9_, ]*\))?[ \t]+/,excludeBegin:!0,end:"$",endsWithParent:!0,contains:[d,e,{className:"comment",begin:/([ ]*[a-z0-9&#*=?@>\\<:\-,()$\[\]_.{}!+%^]+)+/,
-relevance:0}]};return{name:"GAMS",aliases:["gms"],case_insensitive:!0,keywords:b,contains:[a.COMMENT(/^\$ontext/,/^\$offtext/),{className:"meta",begin:"^\\$[a-z0-9]+",end:"$",returnBegin:!0,contains:[{className:"meta-keyword",begin:"^\\$[a-z0-9]+"}]},a.COMMENT("^\\*","$"),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{beginKeywords:"set sets parameter parameters variable variables scalar scalars equation equations",end:";",contains:[a.COMMENT("^\\*","$"),a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,e,d]},{beginKeywords:"table",end:";",returnBegin:!0,contains:[{beginKeywords:"table",end:"$",contains:[d]},a.COMMENT("^\\*","$"),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_NUMBER_MODE]},{className:"function",begin:/^[a-z][a-z0-9_,\-+' ()$]+\.{2}/,returnBegin:!0,contains:[{className:"title",begin:/^[a-z0-9_]+/},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0},c]},a.C_NUMBER_MODE,
-c]}}}());
-hljs.registerLanguage("gauss",function(){return function(a){var b={keyword:"bool break call callexe checkinterrupt clear clearg closeall cls comlog compile continue create debug declare delete disable dlibrary dllcall do dos ed edit else elseif enable end endfor endif endp endo errorlog errorlogat expr external fn for format goto gosub graph if keyword let lib library line load loadarray loadexe loadf loadk loadm loadp loads loadx local locate loopnextindex lprint lpwidth lshow matrix msym ndpclex new open output outwidth plot plotsym pop prcsn print printdos proc push retp return rndcon rndmod rndmult rndseed run save saveall screen scroll setarray show sparse stop string struct system trace trap threadfor threadendfor threadbegin threadjoin threadstat threadend until use while winprint ne ge le gt lt and xor or not eq eqv",built_in:"abs acf aconcat aeye amax amean AmericanBinomCall AmericanBinomCall_Greeks AmericanBinomCall_ImpVol AmericanBinomPut AmericanBinomPut_Greeks AmericanBinomPut_ImpVol AmericanBSCall AmericanBSCall_Greeks AmericanBSCall_ImpVol AmericanBSPut AmericanBSPut_Greeks AmericanBSPut_ImpVol amin amult annotationGetDefaults annotationSetBkd annotationSetFont annotationSetLineColor annotationSetLineStyle annotationSetLineThickness annualTradingDays arccos arcsin areshape arrayalloc arrayindex arrayinit arraytomat asciiload asclabel astd astds asum atan atan2 atranspose axmargin balance band bandchol bandcholsol bandltsol bandrv bandsolpd bar base10 begwind besselj bessely beta box boxcox cdfBeta cdfBetaInv cdfBinomial cdfBinomialInv cdfBvn cdfBvn2 cdfBvn2e cdfCauchy cdfCauchyInv cdfChic cdfChii cdfChinc cdfChincInv cdfExp cdfExpInv cdfFc cdfFnc cdfFncInv cdfGam cdfGenPareto cdfHyperGeo cdfLaplace cdfLaplaceInv cdfLogistic cdfLogisticInv cdfmControlCreate cdfMvn cdfMvn2e cdfMvnce cdfMvne cdfMvt2e cdfMvtce cdfMvte cdfN cdfN2 cdfNc cdfNegBinomial cdfNegBinomialInv cdfNi cdfPoisson cdfPoissonInv cdfRayleigh cdfRayleighInv cdfTc cdfTci cdfTnc cdfTvn cdfWeibull cdfWeibullInv cdir ceil ChangeDir chdir chiBarSquare chol choldn cholsol cholup chrs close code cols colsf combinate combinated complex con cond conj cons ConScore contour conv convertsatostr convertstrtosa corrm corrms corrvc corrx corrxs cos cosh counts countwts crossprd crout croutp csrcol csrlin csvReadM csvReadSA cumprodc cumsumc curve cvtos datacreate datacreatecomplex datalist dataload dataloop dataopen datasave date datestr datestring datestrymd dayinyr dayofweek dbAddDatabase dbClose dbCommit dbCreateQuery dbExecQuery dbGetConnectOptions dbGetDatabaseName dbGetDriverName dbGetDrivers dbGetHostName dbGetLastErrorNum dbGetLastErrorText dbGetNumericalPrecPolicy dbGetPassword dbGetPort dbGetTableHeaders dbGetTables dbGetUserName dbHasFeature dbIsDriverAvailable dbIsOpen dbIsOpenError dbOpen dbQueryBindValue dbQueryClear dbQueryCols dbQueryExecPrepared dbQueryFetchAllM dbQueryFetchAllSA dbQueryFetchOneM dbQueryFetchOneSA dbQueryFinish dbQueryGetBoundValue dbQueryGetBoundValues dbQueryGetField dbQueryGetLastErrorNum dbQueryGetLastErrorText dbQueryGetLastInsertID dbQueryGetLastQuery dbQueryGetPosition dbQueryIsActive dbQueryIsForwardOnly dbQueryIsNull dbQueryIsSelect dbQueryIsValid dbQueryPrepare dbQueryRows dbQuerySeek dbQuerySeekFirst dbQuerySeekLast dbQuerySeekNext dbQuerySeekPrevious dbQuerySetForwardOnly dbRemoveDatabase dbRollback dbSetConnectOptions dbSetDatabaseName dbSetHostName dbSetNumericalPrecPolicy dbSetPort dbSetUserName dbTransaction DeleteFile delif delrows denseToSp denseToSpRE denToZero design det detl dfft dffti diag diagrv digamma doswin DOSWinCloseall DOSWinOpen dotfeq dotfeqmt dotfge dotfgemt dotfgt dotfgtmt dotfle dotflemt dotflt dotfltmt dotfne dotfnemt draw drop dsCreate dstat dstatmt dstatmtControlCreate dtdate dtday dttime dttodtv dttostr dttoutc dtvnormal dtvtodt dtvtoutc dummy dummybr dummydn eig eigh eighv eigv elapsedTradingDays endwind envget eof eqSolve eqSolvemt eqSolvemtControlCreate eqSolvemtOutCreate eqSolveset erf erfc erfccplx erfcplx error etdays ethsec etstr EuropeanBinomCall EuropeanBinomCall_Greeks EuropeanBinomCall_ImpVol EuropeanBinomPut EuropeanBinomPut_Greeks EuropeanBinomPut_ImpVol EuropeanBSCall EuropeanBSCall_Greeks EuropeanBSCall_ImpVol EuropeanBSPut EuropeanBSPut_Greeks EuropeanBSPut_ImpVol exctsmpl exec execbg exp extern eye fcheckerr fclearerr feq feqmt fflush fft ffti fftm fftmi fftn fge fgemt fgets fgetsa fgetsat fgetst fgt fgtmt fileinfo filesa fle flemt floor flt fltmt fmod fne fnemt fonts fopen formatcv formatnv fputs fputst fseek fstrerror ftell ftocv ftos ftostrC gamma gammacplx gammaii gausset gdaAppend gdaCreate gdaDStat gdaDStatMat gdaGetIndex gdaGetName gdaGetNames gdaGetOrders gdaGetType gdaGetTypes gdaGetVarInfo gdaIsCplx gdaLoad gdaPack gdaRead gdaReadByIndex gdaReadSome gdaReadSparse gdaReadStruct gdaReportVarInfo gdaSave gdaUpdate gdaUpdateAndPack gdaVars gdaWrite gdaWrite32 gdaWriteSome getarray getdims getf getGAUSShome getmatrix getmatrix4D getname getnamef getNextTradingDay getNextWeekDay getnr getorders getpath getPreviousTradingDay getPreviousWeekDay getRow getscalar3D getscalar4D getTrRow getwind glm gradcplx gradMT gradMTm gradMTT gradMTTm gradp graphprt graphset hasimag header headermt hess hessMT hessMTg hessMTgw hessMTm hessMTmw hessMTT hessMTTg hessMTTgw hessMTTm hessMTw hessp hist histf histp hsec imag indcv indexcat indices indices2 indicesf indicesfn indnv indsav integrate1d integrateControlCreate intgrat2 intgrat3 inthp1 inthp2 inthp3 inthp4 inthpControlCreate intquad1 intquad2 intquad3 intrleav intrleavsa intrsect intsimp inv invpd invswp iscplx iscplxf isden isinfnanmiss ismiss key keyav keyw lag lag1 lagn lapEighb lapEighi lapEighvb lapEighvi lapgEig lapgEigh lapgEighv lapgEigv lapgSchur lapgSvdcst lapgSvds lapgSvdst lapSvdcusv lapSvds lapSvdusv ldlp ldlsol linSolve listwise ln lncdfbvn lncdfbvn2 lncdfmvn lncdfn lncdfn2 lncdfnc lnfact lngammacplx lnpdfmvn lnpdfmvt lnpdfn lnpdft loadd loadstruct loadwind loess loessmt loessmtControlCreate log loglog logx logy lower lowmat lowmat1 ltrisol lu lusol machEpsilon make makevars makewind margin matalloc matinit mattoarray maxbytes maxc maxindc maxv maxvec mbesselei mbesselei0 mbesselei1 mbesseli mbesseli0 mbesseli1 meanc median mergeby mergevar minc minindc minv miss missex missrv moment momentd movingave movingaveExpwgt movingaveWgt nextindex nextn nextnevn nextwind ntos null null1 numCombinations ols olsmt olsmtControlCreate olsqr olsqr2 olsqrmt ones optn optnevn orth outtyp pacf packedToSp packr parse pause pdfCauchy pdfChi pdfExp pdfGenPareto pdfHyperGeo pdfLaplace pdfLogistic pdfn pdfPoisson pdfRayleigh pdfWeibull pi pinv pinvmt plotAddArrow plotAddBar plotAddBox plotAddHist plotAddHistF plotAddHistP plotAddPolar plotAddScatter plotAddShape plotAddTextbox plotAddTS plotAddXY plotArea plotBar plotBox plotClearLayout plotContour plotCustomLayout plotGetDefaults plotHist plotHistF plotHistP plotLayout plotLogLog plotLogX plotLogY plotOpenWindow plotPolar plotSave plotScatter plotSetAxesPen plotSetBar plotSetBarFill plotSetBarStacked plotSetBkdColor plotSetFill plotSetGrid plotSetLegend plotSetLineColor plotSetLineStyle plotSetLineSymbol plotSetLineThickness plotSetNewWindow plotSetTitle plotSetWhichYAxis plotSetXAxisShow plotSetXLabel plotSetXRange plotSetXTicInterval plotSetXTicLabel plotSetYAxisShow plotSetYLabel plotSetYRange plotSetZAxisShow plotSetZLabel plotSurface plotTS plotXY polar polychar polyeval polygamma polyint polymake polymat polymroot polymult polyroot pqgwin previousindex princomp printfm printfmt prodc psi putarray putf putvals pvCreate pvGetIndex pvGetParNames pvGetParVector pvLength pvList pvPack pvPacki pvPackm pvPackmi pvPacks pvPacksi pvPacksm pvPacksmi pvPutParVector pvTest pvUnpack QNewton QNewtonmt QNewtonmtControlCreate QNewtonmtOutCreate QNewtonSet QProg QProgmt QProgmtInCreate qqr qqre qqrep qr qre qrep qrsol qrtsol qtyr qtyre qtyrep quantile quantiled qyr qyre qyrep qz rank rankindx readr real reclassify reclassifyCuts recode recserar recsercp recserrc rerun rescale reshape rets rev rfft rffti rfftip rfftn rfftnp rfftp rndBernoulli rndBeta rndBinomial rndCauchy rndChiSquare rndCon rndCreateState rndExp rndGamma rndGeo rndGumbel rndHyperGeo rndi rndKMbeta rndKMgam rndKMi rndKMn rndKMnb rndKMp rndKMu rndKMvm rndLaplace rndLCbeta rndLCgam rndLCi rndLCn rndLCnb rndLCp rndLCu rndLCvm rndLogNorm rndMTu rndMVn rndMVt rndn rndnb rndNegBinomial rndp rndPoisson rndRayleigh rndStateSkip rndu rndvm rndWeibull rndWishart rotater round rows rowsf rref sampleData satostrC saved saveStruct savewind scale scale3d scalerr scalinfnanmiss scalmiss schtoc schur searchsourcepath seekr select selif seqa seqm setdif setdifsa setvars setvwrmode setwind shell shiftr sin singleindex sinh sleep solpd sortc sortcc sortd sorthc sorthcc sortind sortindc sortmc sortr sortrc spBiconjGradSol spChol spConjGradSol spCreate spDenseSubmat spDiagRvMat spEigv spEye spLDL spline spLU spNumNZE spOnes spreadSheetReadM spreadSheetReadSA spreadSheetWrite spScale spSubmat spToDense spTrTDense spTScalar spZeros sqpSolve sqpSolveMT sqpSolveMTControlCreate sqpSolveMTlagrangeCreate sqpSolveMToutCreate sqpSolveSet sqrt statements stdc stdsc stocv stof strcombine strindx strlen strput strrindx strsect strsplit strsplitPad strtodt strtof strtofcplx strtriml strtrimr strtrunc strtruncl strtruncpad strtruncr submat subscat substute subvec sumc sumr surface svd svd1 svd2 svdcusv svds svdusv sysstate tab tan tanh tempname time timedt timestr timeutc title tkf2eps tkf2ps tocart todaydt toeplitz token topolar trapchk trigamma trimr trunc type typecv typef union unionsa uniqindx uniqindxsa unique uniquesa upmat upmat1 upper utctodt utctodtv utrisol vals varCovMS varCovXS varget vargetl varmall varmares varput varputl vartypef vcm vcms vcx vcxs vec vech vecr vector vget view viewxyz vlist vnamecv volume vput vread vtypecv wait waitc walkindex where window writer xlabel xlsGetSheetCount xlsGetSheetSize xlsGetSheetTypes xlsMakeRange xlsReadM xlsReadSA xlsWrite xlsWriteM xlsWriteSA xpnd xtics xy xyz ylabel ytics zeros zeta zlabel ztics cdfEmpirical dot h5create h5open h5read h5readAttribute h5write h5writeAttribute ldl plotAddErrorBar plotAddSurface plotCDFEmpirical plotSetColormap plotSetContourLabels plotSetLegendFont plotSetTextInterpreter plotSetXTicCount plotSetYTicCount plotSetZLevels powerm strjoin sylvester strtrim",
-literal:"DB_AFTER_LAST_ROW DB_ALL_TABLES DB_BATCH_OPERATIONS DB_BEFORE_FIRST_ROW DB_BLOB DB_EVENT_NOTIFICATIONS DB_FINISH_QUERY DB_HIGH_PRECISION DB_LAST_INSERT_ID DB_LOW_PRECISION_DOUBLE DB_LOW_PRECISION_INT32 DB_LOW_PRECISION_INT64 DB_LOW_PRECISION_NUMBERS DB_MULTIPLE_RESULT_SETS DB_NAMED_PLACEHOLDERS DB_POSITIONAL_PLACEHOLDERS DB_PREPARED_QUERIES DB_QUERY_SIZE DB_SIMPLE_LOCKING DB_SYSTEM_TABLES DB_TABLES DB_TRANSACTIONS DB_UNICODE DB_VIEWS __STDIN __STDOUT __STDERR __FILE_DIR"},c=a.COMMENT("@",
-"@"),d={className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"define definecs|10 undef ifdef ifndef iflight ifdllcall ifmac ifos2win ifunix else endif lineson linesoff srcfile srcline"},contains:[{begin:/\\\n/,relevance:0},{beginKeywords:"include",end:"$",keywords:{"meta-keyword":"include"},contains:[{className:"meta-string",begin:'"',end:'"',illegal:"\\n"}]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c]},e={begin:/\bstruct\s+/,end:/\s/,keywords:"struct",contains:[{className:"type",begin:a.UNDERSCORE_IDENT_RE,
-relevance:0}]},f=[{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,endsWithParent:!0,relevance:0,contains:[{className:"literal",begin:/\.\.\./},a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE,c,e]}],h={className:"title",begin:a.UNDERSCORE_IDENT_RE,relevance:0},g=function(b,d,e){b=a.inherit({className:"function",beginKeywords:b,end:d,excludeEnd:!0,contains:[].concat(f)},e||{});b.contains.push(h);b.contains.push(a.C_NUMBER_MODE);b.contains.push(a.C_BLOCK_COMMENT_MODE);b.contains.push(c);
-return b},k={className:"built_in",begin:"\\b("+b.built_in.split(" ").join("|")+")\\b"},l={className:"string",begin:'"',end:'"',contains:[a.BACKSLASH_ESCAPE],relevance:0},m={begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,keywords:b,relevance:0,contains:[{beginKeywords:b.keyword},k,{className:"built_in",begin:a.UNDERSCORE_IDENT_RE,relevance:0}]};k={begin:/\(/,end:/\)/,relevance:0,keywords:{built_in:b.built_in,literal:b.literal},contains:[a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE,c,k,m,l,"self"]};
-m.contains.push(k);return{name:"GAUSS",aliases:["gss"],case_insensitive:!0,keywords:b,illegal:/(\{[%#]|[%#]\}| <- )/,contains:[a.C_NUMBER_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,l,d,{className:"keyword",begin:/\bexternal (matrix|string|array|sparse matrix|struct|proc|keyword|fn)/},g("proc keyword",";"),g("fn","="),{beginKeywords:"for threadfor",end:/;/,relevance:0,contains:[a.C_BLOCK_COMMENT_MODE,c,k]},{variants:[{begin:a.UNDERSCORE_IDENT_RE+"\\."+a.UNDERSCORE_IDENT_RE},{begin:a.UNDERSCORE_IDENT_RE+
-"\\s*="}],relevance:0},m,e]}}}());
-hljs.registerLanguage("gcode",function(){return function(a){a=[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.COMMENT(/\(/,/\)/),a.inherit(a.C_NUMBER_MODE,{begin:"([-+]?([0-9]*\\.?[0-9]+\\.?))|"+a.C_NUMBER_RE}),a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),{className:"name",begin:"([G])([0-9]+\\.?[0-9]?)"},{className:"name",begin:"([M])([0-9]+\\.?[0-9]?)"},{className:"attr",begin:"(VC|VS|#)",end:"(\\d+)"},{className:"attr",begin:"(VZOFX|VZOFY|VZOFZ)"},
-{className:"built_in",begin:"(ATAN|ABS|ACOS|ASIN|SIN|COS|EXP|FIX|FUP|ROUND|LN|TAN)(\\[)",end:"([-+]?([0-9]*\\.?[0-9]+\\.?))(\\])"},{className:"symbol",variants:[{begin:"N",end:"\\d+",illegal:"\\W"}]}];return{name:"G-code (ISO 6983)",aliases:["nc"],case_insensitive:!0,keywords:{$pattern:"[A-Z_][A-Z0-9_.]*",keyword:"IF DO WHILE ENDWHILE CALL ENDIF SUB ENDSUB GOTO REPEAT ENDREPEAT EQ LT GT NE GE LE OR XOR"},contains:[{className:"meta",begin:"\\%"},{className:"meta",begin:"([O])([0-9]+)"}].concat(a)}}}());
-hljs.registerLanguage("gherkin",function(){return function(a){return{name:"Gherkin",aliases:["feature"],keywords:"Feature Background Ability Business Need Scenario Scenarios Scenario Outline Scenario Template Examples Given And Then But When",contains:[{className:"symbol",begin:"\\*",relevance:0},{className:"meta",begin:"@[^@\\s]+"},{begin:"\\|",end:"\\|\\w*$",contains:[{className:"string",begin:"[^|]+"}]},{className:"variable",begin:"<",end:">"},a.HASH_COMMENT_MODE,{className:"string",begin:'"""',
-end:'"""'},a.QUOTE_STRING_MODE]}}}());
-hljs.registerLanguage("glsl",function(){return function(a){return{name:"GLSL",keywords:{keyword:"break continue discard do else for if return while switch case default attribute binding buffer ccw centroid centroid varying coherent column_major const cw depth_any depth_greater depth_less depth_unchanged early_fragment_tests equal_spacing flat fractional_even_spacing fractional_odd_spacing highp in index inout invariant invocations isolines layout line_strip lines lines_adjacency local_size_x local_size_y local_size_z location lowp max_vertices mediump noperspective offset origin_upper_left out packed patch pixel_center_integer point_mode points precise precision quads r11f_g11f_b10f r16 r16_snorm r16f r16i r16ui r32f r32i r32ui r8 r8_snorm r8i r8ui readonly restrict rg16 rg16_snorm rg16f rg16i rg16ui rg32f rg32i rg32ui rg8 rg8_snorm rg8i rg8ui rgb10_a2 rgb10_a2ui rgba16 rgba16_snorm rgba16f rgba16i rgba16ui rgba32f rgba32i rgba32ui rgba8 rgba8_snorm rgba8i rgba8ui row_major sample shared smooth std140 std430 stream triangle_strip triangles triangles_adjacency uniform varying vertices volatile writeonly",type:"atomic_uint bool bvec2 bvec3 bvec4 dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 double dvec2 dvec3 dvec4 float iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray int isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow image1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D samplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 vec2 vec3 vec4 void",
+literal:"true false nullptr NULL"},m=[l,a,r,t.C_BLOCK_COMMENT_MODE,o,s],p={
+variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{
+beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:m.concat([{
+begin:/\(/,end:/\)/,keywords:u,contains:m.concat(["self"]),relevance:0}]),
+relevance:0},g={className:"function",begin:"("+i+"[\\*&\\s]+)+"+d,
+returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>.]/,
+contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d,
+returnBegin:!0,contains:[c],relevance:0},{begin:/::/,relevance:0},{begin:/:/,
+endsWithParent:!0,contains:[s,o]},{className:"params",begin:/\(/,end:/\)/,
+keywords:u,relevance:0,contains:[r,t.C_BLOCK_COMMENT_MODE,s,o,a,{begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:["self",r,t.C_BLOCK_COMMENT_MODE,s,o,a]
+}]},a,r,t.C_BLOCK_COMMENT_MODE,l]};return{name:"C++",
+aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"</",
+contains:[].concat(p,g,m,[l,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",
+end:">",keywords:u,contains:["self",a]},{begin:t.IDENT_RE+"::",keywords:u},{
+className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/,
+contains:[{beginKeywords:"final class struct"},t.TITLE_MODE]}]),exports:{
+preprocessor:l,strings:s,keywords:u}}})(t),n=r.keywords
+;return n.keyword+=" boolean byte word String",
+n.literal+=" DIGITAL_MESSAGE FIRMATA_STRING ANALOG_MESSAGE REPORT_DIGITAL REPORT_ANALOG INPUT_PULLUP SET_PIN_MODE INTERNAL2V56 SYSTEM_RESET LED_BUILTIN INTERNAL1V1 SYSEX_START INTERNAL EXTERNAL DEFAULT OUTPUT INPUT HIGH LOW",
+n.built_in+=" setup loop KeyboardController MouseController SoftwareSerial EthernetServer EthernetClient LiquidCrystal RobotControl GSMVoiceCall EthernetUDP EsploraTFT HttpClient RobotMotor WiFiClient GSMScanner FileSystem Scheduler GSMServer YunClient YunServer IPAddress GSMClient GSMModem Keyboard Ethernet Console GSMBand Esplora Stepper Process WiFiUDP GSM_SMS Mailbox USBHost Firmata PImage Client Server GSMPIN FileIO Bridge Serial EEPROM Stream Mouse Audio Servo File Task GPRS WiFi Wire TFT GSM SPI SD runShellCommandAsynchronously analogWriteResolution retrieveCallingNumber printFirmwareVersion analogReadResolution sendDigitalPortPair noListenOnLocalhost readJoystickButton setFirmwareVersion readJoystickSwitch scrollDisplayRight getVoiceCallStatus scrollDisplayLeft writeMicroseconds delayMicroseconds beginTransmission getSignalStrength runAsynchronously getAsynchronously listenOnLocalhost getCurrentCarrier readAccelerometer messageAvailable sendDigitalPorts lineFollowConfig countryNameWrite runShellCommand readStringUntil rewindDirectory readTemperature setClockDivider readLightSensor endTransmission analogReference detachInterrupt countryNameRead attachInterrupt encryptionType readBytesUntil robotNameWrite readMicrophone robotNameRead cityNameWrite userNameWrite readJoystickY readJoystickX mouseReleased openNextFile scanNetworks noInterrupts digitalWrite beginSpeaker mousePressed isActionDone mouseDragged displayLogos noAutoscroll addParameter remoteNumber getModifiers keyboardRead userNameRead waitContinue processInput parseCommand printVersion readNetworks writeMessage blinkVersion cityNameRead readMessage setDataMode parsePacket isListening setBitOrder beginPacket isDirectory motorsWrite drawCompass digitalRead clearScreen serialEvent rightToLeft setTextSize leftToRight requestFrom keyReleased compassRead analogWrite interrupts WiFiServer disconnect playMelody parseFloat autoscroll getPINUsed setPINUsed setTimeout sendAnalog readSlider analogRead beginWrite createChar motorsStop keyPressed tempoWrite readButton subnetMask debugPrint macAddress writeGreen randomSeed attachGPRS readString sendString remotePort releaseAll mouseMoved background getXChange getYChange answerCall getResult voiceCall endPacket constrain getSocket writeJSON getButton available connected findUntil readBytes exitValue readGreen writeBlue startLoop IPAddress isPressed sendSysex pauseMode gatewayIP setCursor getOemKey tuneWrite noDisplay loadImage switchPIN onRequest onReceive changePIN playFile noBuffer parseInt overflow checkPIN knobRead beginTFT bitClear updateIR bitWrite position writeRGB highByte writeRed setSpeed readBlue noStroke remoteIP transfer shutdown hangCall beginSMS endWrite attached maintain noCursor checkReg checkPUK shiftOut isValid shiftIn pulseIn connect println localIP pinMode getIMEI display noBlink process getBand running beginSD drawBMP lowByte setBand release bitRead prepare pointTo readRed setMode noFill remove listen stroke detach attach noTone exists buffer height bitSet circle config cursor random IRread setDNS endSMS getKey micros millis begin print write ready flush width isPIN blink clear press mkdir rmdir close point yield image BSSID click delay read text move peek beep rect line open seek fill size turn stop home find step tone sqrt RSSI SSID end bit tan cos sin pow map abs max min get run put",
+r.name="Arduino",r.aliases=["ino"],r.supersetOf="cpp",r}})());
+hljs.registerLanguage("armasm",(()=>{"use strict";return s=>{const e={
+variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0
+}),s.COMMENT("[;@]","$",{relevance:0
+}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",
+case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,
+meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",
+built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"
+},contains:[{className:"keyword",
+begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"
+},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0
+},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{
+className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"
+},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",
+variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{
+begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}})());
+hljs.registerLanguage("xml",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")}
+function a(...n){return n.map((n=>e(n))).join("")}function s(...n){
+return"("+n.map((n=>e(n))).join("|")+")"}return e=>{
+const t=a(/[A-Z_]/,a("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),i={
+className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},r={begin:/\s/,
+contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]
+},c=e.inherit(r,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{
+className:"meta-string"}),g=e.inherit(e.QUOTE_STRING_MODE,{
+className:"meta-string"}),m={endsWithParent:!0,illegal:/</,relevance:0,
+contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/,
+relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,
+end:/"/,contains:[i]},{begin:/'/,end:/'/,contains:[i]},{begin:/[^\s"'=<>`]+/}]}]
+}]};return{name:"HTML, XML",
+aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],
+case_insensitive:!0,contains:[{className:"meta",begin:/<![a-z]/,end:/>/,
+relevance:10,contains:[r,g,l,c,{begin:/\[/,end:/\]/,contains:[{className:"meta",
+begin:/<![a-z]/,end:/>/,contains:[r,c,g,l]}]}]},e.COMMENT(/<!--/,/-->/,{
+relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,relevance:10},i,{
+className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",
+begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[m],starts:{
+end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",
+begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[m],starts:{
+end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{
+className:"tag",begin:/<>|<\/>/},{className:"tag",
+begin:a(/</,n(a(t,s(/\/>/,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",
+begin:t,relevance:0,starts:m}]},{className:"tag",begin:a(/<\//,n(a(t,/>/))),
+contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0}]}]}}
+})());
+hljs.registerLanguage("asciidoc",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const a=[{className:"strong",begin:/\*{2}([^\n]+?)\*{2}/
+},{className:"strong",
+begin:e(/\*\*/,/((\*(?!\*)|\\[^\n]|[^*\n\\])+\n)+/,/(\*(?!\*)|\\[^\n]|[^*\n\\])*/,/\*\*/),
+relevance:0},{className:"strong",begin:/\B\*(\S|\S[^\n]*?\S)\*(?!\w)/},{
+className:"strong",begin:/\*[^\s]([^\n]+\n)+([^\n]+)\*/}],s=[{
+className:"emphasis",begin:/_{2}([^\n]+?)_{2}/},{className:"emphasis",
+begin:e(/__/,/((_(?!_)|\\[^\n]|[^_\n\\])+\n)+/,/(_(?!_)|\\[^\n]|[^_\n\\])*/,/__/),
+relevance:0},{className:"emphasis",begin:/\b_(\S|\S[^\n]*?\S)_(?!\w)/},{
+className:"emphasis",begin:/_[^\s]([^\n]+\n)+([^\n]+)_/},{className:"emphasis",
+begin:"\\B'(?!['\\s])",end:"(\\n{2}|')",contains:[{begin:"\\\\'\\w",relevance:0
+}],relevance:0}];return{name:"AsciiDoc",aliases:["adoc"],
+contains:[n.COMMENT("^/{4,}\\n","\\n/{4,}$",{relevance:10
+}),n.COMMENT("^//","$",{relevance:0}),{className:"title",begin:"^\\.\\w.*$"},{
+begin:"^[=\\*]{4,}\\n",end:"\\n^[=\\*]{4,}$",relevance:10},{className:"section",
+relevance:10,variants:[{begin:"^(={1,6})[ \t].+?([ \t]\\1)?$"},{
+begin:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{className:"meta",
+begin:"^:.+?:",end:"\\s",excludeEnd:!0,relevance:10},{className:"meta",
+begin:"^\\[.+?\\]$",relevance:0},{className:"quote",begin:"^_{4,}\\n",
+end:"\\n_{4,}$",relevance:10},{className:"code",begin:"^[\\-\\.]{4,}\\n",
+end:"\\n[\\-\\.]{4,}$",relevance:10},{begin:"^\\+{4,}\\n",end:"\\n\\+{4,}$",
+contains:[{begin:"<",end:">",subLanguage:"xml",relevance:0}],relevance:10},{
+className:"bullet",begin:"^(\\*+|-+|\\.+|[^\\n]+?::)\\s+"},{className:"symbol",
+begin:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",relevance:10},{
+begin:/\\[*_`]/},{begin:/\\\\\*{2}[^\n]*?\*{2}/},{begin:/\\\\_{2}[^\n]*_{2}/},{
+begin:/\\\\`{2}[^\n]*`{2}/},{begin:/[:;}][*_`](?![*_`])/},...a,...s,{
+className:"string",variants:[{begin:"``.+?''"},{begin:"`.+?'"}]},{
+className:"code",begin:/`{2}/,end:/(\n{2}|`{2})/},{className:"code",
+begin:"(`.+?`|\\+.+?\\+)",relevance:0},{className:"code",begin:"^[ \\t]",
+end:"$",relevance:0},{begin:"^'{3,}[ \\t]*$",relevance:10},{
+begin:"(link:)?(http|https|ftp|file|irc|image:?):\\S+?\\[[^[]*?\\]",
+returnBegin:!0,contains:[{begin:"(link|image:?):",relevance:0},{
+className:"link",begin:"\\w",end:"[^\\[]+",relevance:0},{className:"string",
+begin:"\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0,relevance:0}],relevance:10}]
+}}})());
+hljs.registerLanguage("aspectj",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{
+const t="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",i="get set args call"
+;return{name:"AspectJ",keywords:t,illegal:/<\/|#/,
+contains:[n.COMMENT(/\/\*\*/,/\*\//,{relevance:0,contains:[{begin:/\w+@/,
+relevance:0},{className:"doctag",begin:/@[A-Za-z]+/}]
+}),n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,{
+className:"class",beginKeywords:"aspect",end:/[{;=]/,excludeEnd:!0,
+illegal:/[:;"\[\]]/,contains:[{
+beginKeywords:"extends implements pertypewithin perthis pertarget percflowbelow percflow issingleton"
+},n.UNDERSCORE_TITLE_MODE,{begin:/\([^\)]*/,end:/[)]+/,keywords:t+" "+i,
+excludeEnd:!1}]},{className:"class",beginKeywords:"class interface",end:/[{;=]/,
+excludeEnd:!0,relevance:0,keywords:"class interface",illegal:/[:"\[\]]/,
+contains:[{beginKeywords:"extends implements"},n.UNDERSCORE_TITLE_MODE]},{
+beginKeywords:"pointcut after before around throwing returning",end:/[)]/,
+excludeEnd:!1,illegal:/["\[\]]/,contains:[{
+begin:e(n.UNDERSCORE_IDENT_RE,/\s*\(/),returnBegin:!0,
+contains:[n.UNDERSCORE_TITLE_MODE]}]},{begin:/[:]/,returnBegin:!0,end:/[{;]/,
+relevance:0,excludeEnd:!1,keywords:t,illegal:/["\[\]]/,contains:[{
+begin:e(n.UNDERSCORE_IDENT_RE,/\s*\(/),keywords:t+" "+i,relevance:0
+},n.QUOTE_STRING_MODE]},{beginKeywords:"new throw",relevance:0},{
+className:"function",
+begin:/\w+ +\w+(\.\w+)?\s*\([^\)]*\)\s*((throws)[\w\s,]+)?[\{;]/,returnBegin:!0,
+end:/[{;=]/,keywords:t,excludeEnd:!0,contains:[{
+begin:e(n.UNDERSCORE_IDENT_RE,/\s*\(/),returnBegin:!0,relevance:0,
+contains:[n.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,
+relevance:0,keywords:t,
+contains:[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,n.C_NUMBER_MODE,n.C_BLOCK_COMMENT_MODE]
+},n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE]},n.C_NUMBER_MODE,{
+className:"meta",begin:/@[A-Za-z]+/}]}}})());
+hljs.registerLanguage("autohotkey",(()=>{"use strict";return e=>{const a={
+begin:"`[\\s\\S]"};return{name:"AutoHotkey",case_insensitive:!0,aliases:["ahk"],
+keywords:{
+keyword:"Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group",
+literal:"true false NOT AND OR",
+built_in:"ComSpec Clipboard ClipboardAll ErrorLevel"},
+contains:[a,e.inherit(e.QUOTE_STRING_MODE,{contains:[a]}),e.COMMENT(";","$",{
+relevance:0}),e.C_BLOCK_COMMENT_MODE,{className:"number",begin:e.NUMBER_RE,
+relevance:0},{className:"variable",begin:"%[a-zA-Z0-9#_$@]+%"},{
+className:"built_in",begin:"^\\s*\\w+\\s*(,|%)"},{className:"title",variants:[{
+begin:'^[^\\n";]+::(?!=)'},{begin:'^[^\\n";]+:(?!=)',relevance:0}]},{
+className:"meta",begin:"^\\s*#\\w+",end:"$",relevance:0},{className:"built_in",
+begin:"A_[a-zA-Z0-9]+"},{begin:",\\s*,"}]}}})());
+hljs.registerLanguage("autoit",(()=>{"use strict";return e=>{const t={
+variants:[e.COMMENT(";","$",{relevance:0
+}),e.COMMENT("#cs","#ce"),e.COMMENT("#comments-start","#comments-end")]},r={
+begin:"\\$[A-z0-9_]+"},i={className:"string",variants:[{begin:/"/,end:/"/,
+contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,
+relevance:0}]}]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]};return{
+name:"AutoIt",case_insensitive:!0,illegal:/\/\*/,keywords:{
+keyword:"ByRef Case Const ContinueCase ContinueLoop Dim Do Else ElseIf EndFunc EndIf EndSelect EndSwitch EndWith Enum Exit ExitLoop For Func Global If In Local Next ReDim Return Select Static Step Switch Then To Until Volatile WEnd While With",
+built_in:"Abs ACos AdlibRegister AdlibUnRegister Asc AscW ASin Assign ATan AutoItSetOption AutoItWinGetTitle AutoItWinSetTitle Beep Binary BinaryLen BinaryMid BinaryToString BitAND BitNOT BitOR BitRotate BitShift BitXOR BlockInput Break Call CDTray Ceiling Chr ChrW ClipGet ClipPut ConsoleRead ConsoleWrite ConsoleWriteError ControlClick ControlCommand ControlDisable ControlEnable ControlFocus ControlGetFocus ControlGetHandle ControlGetPos ControlGetText ControlHide ControlListView ControlMove ControlSend ControlSetText ControlShow ControlTreeView Cos Dec DirCopy DirCreate DirGetSize DirMove DirRemove DllCall DllCallAddress DllCallbackFree DllCallbackGetPtr DllCallbackRegister DllClose DllOpen DllStructCreate DllStructGetData DllStructGetPtr DllStructGetSize DllStructSetData DriveGetDrive DriveGetFileSystem DriveGetLabel DriveGetSerial DriveGetType DriveMapAdd DriveMapDel DriveMapGet DriveSetLabel DriveSpaceFree DriveSpaceTotal DriveStatus EnvGet EnvSet EnvUpdate Eval Execute Exp FileChangeDir FileClose FileCopy FileCreateNTFSLink FileCreateShortcut FileDelete FileExists FileFindFirstFile FileFindNextFile FileFlush FileGetAttrib FileGetEncoding FileGetLongName FileGetPos FileGetShortcut FileGetShortName FileGetSize FileGetTime FileGetVersion FileInstall FileMove FileOpen FileOpenDialog FileRead FileReadLine FileReadToArray FileRecycle FileRecycleEmpty FileSaveDialog FileSelectFolder FileSetAttrib FileSetEnd FileSetPos FileSetTime FileWrite FileWriteLine Floor FtpSetProxy FuncName GUICreate GUICtrlCreateAvi GUICtrlCreateButton GUICtrlCreateCheckbox GUICtrlCreateCombo GUICtrlCreateContextMenu GUICtrlCreateDate GUICtrlCreateDummy GUICtrlCreateEdit GUICtrlCreateGraphic GUICtrlCreateGroup GUICtrlCreateIcon GUICtrlCreateInput GUICtrlCreateLabel GUICtrlCreateList GUICtrlCreateListView GUICtrlCreateListViewItem GUICtrlCreateMenu GUICtrlCreateMenuItem GUICtrlCreateMonthCal GUICtrlCreateObj GUICtrlCreatePic GUICtrlCreateProgress GUICtrlCreateRadio GUICtrlCreateSlider GUICtrlCreateTab GUICtrlCreateTabItem GUICtrlCreateTreeView GUICtrlCreateTreeViewItem GUICtrlCreateUpdown GUICtrlDelete GUICtrlGetHandle GUICtrlGetState GUICtrlRead GUICtrlRecvMsg GUICtrlRegisterListViewSort GUICtrlSendMsg GUICtrlSendToDummy GUICtrlSetBkColor GUICtrlSetColor GUICtrlSetCursor GUICtrlSetData GUICtrlSetDefBkColor GUICtrlSetDefColor GUICtrlSetFont GUICtrlSetGraphic GUICtrlSetImage GUICtrlSetLimit GUICtrlSetOnEvent GUICtrlSetPos GUICtrlSetResizing GUICtrlSetState GUICtrlSetStyle GUICtrlSetTip GUIDelete GUIGetCursorInfo GUIGetMsg GUIGetStyle GUIRegisterMsg GUISetAccelerators GUISetBkColor GUISetCoord GUISetCursor GUISetFont GUISetHelp GUISetIcon GUISetOnEvent GUISetState GUISetStyle GUIStartGroup GUISwitch Hex HotKeySet HttpSetProxy HttpSetUserAgent HWnd InetClose InetGet InetGetInfo InetGetSize InetRead IniDelete IniRead IniReadSection IniReadSectionNames IniRenameSection IniWrite IniWriteSection InputBox Int IsAdmin IsArray IsBinary IsBool IsDeclared IsDllStruct IsFloat IsFunc IsHWnd IsInt IsKeyword IsNumber IsObj IsPtr IsString Log MemGetStats Mod MouseClick MouseClickDrag MouseDown MouseGetCursor MouseGetPos MouseMove MouseUp MouseWheel MsgBox Number ObjCreate ObjCreateInterface ObjEvent ObjGet ObjName OnAutoItExitRegister OnAutoItExitUnRegister Ping PixelChecksum PixelGetColor PixelSearch ProcessClose ProcessExists ProcessGetStats ProcessList ProcessSetPriority ProcessWait ProcessWaitClose ProgressOff ProgressOn ProgressSet Ptr Random RegDelete RegEnumKey RegEnumVal RegRead RegWrite Round Run RunAs RunAsWait RunWait Send SendKeepActive SetError SetExtended ShellExecute ShellExecuteWait Shutdown Sin Sleep SoundPlay SoundSetWaveVolume SplashImageOn SplashOff SplashTextOn Sqrt SRandom StatusbarGetText StderrRead StdinWrite StdioClose StdoutRead String StringAddCR StringCompare StringFormat StringFromASCIIArray StringInStr StringIsAlNum StringIsAlpha StringIsASCII StringIsDigit StringIsFloat StringIsInt StringIsLower StringIsSpace StringIsUpper StringIsXDigit StringLeft StringLen StringLower StringMid StringRegExp StringRegExpReplace StringReplace StringReverse StringRight StringSplit StringStripCR StringStripWS StringToASCIIArray StringToBinary StringTrimLeft StringTrimRight StringUpper Tan TCPAccept TCPCloseSocket TCPConnect TCPListen TCPNameToIP TCPRecv TCPSend TCPShutdown, UDPShutdown TCPStartup, UDPStartup TimerDiff TimerInit ToolTip TrayCreateItem TrayCreateMenu TrayGetMsg TrayItemDelete TrayItemGetHandle TrayItemGetState TrayItemGetText TrayItemSetOnEvent TrayItemSetState TrayItemSetText TraySetClick TraySetIcon TraySetOnEvent TraySetPauseIcon TraySetState TraySetToolTip TrayTip UBound UDPBind UDPCloseSocket UDPOpen UDPRecv UDPSend VarGetType WinActivate WinActive WinClose WinExists WinFlash WinGetCaretPos WinGetClassList WinGetClientSize WinGetHandle WinGetPos WinGetProcess WinGetState WinGetText WinGetTitle WinKill WinList WinMenuSelectItem WinMinimizeAll WinMinimizeAllUndo WinMove WinSetOnTop WinSetState WinSetTitle WinSetTrans WinWait WinWaitActive WinWaitClose WinWaitNotActive",
+literal:"True False And Null Not Or Default"},contains:[t,r,i,n,{
+className:"meta",begin:"#",end:"$",keywords:{
+"meta-keyword":["EndRegion","forcedef","forceref","ignorefunc","include","include-once","NoTrayIcon","OnAutoItStartRegister","pragma","Region","RequireAdmin","Tidy_Off","Tidy_On","Tidy_Parameters"]
+},contains:[{begin:/\\\n/,relevance:0},{beginKeywords:"include",keywords:{
+"meta-keyword":"include"},end:"$",contains:[i,{className:"meta-string",
+variants:[{begin:"<",end:">"},{begin:/"/,end:/"/,contains:[{begin:/""/,
+relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]}]},i,t]
+},{className:"symbol",begin:"@[A-z0-9_]+"},{className:"function",
+beginKeywords:"Func",end:"$",illegal:"\\$|\\[|%",
+contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",
+contains:[r,i,n]}]}]}}})());
+hljs.registerLanguage("avrasm",(()=>{"use strict";return r=>({
+name:"AVR Assembly",case_insensitive:!0,keywords:{$pattern:"\\.?"+r.IDENT_RE,
+keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",
+built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",
+meta:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"
+},contains:[r.C_BLOCK_COMMENT_MODE,r.COMMENT(";","$",{relevance:0
+}),r.C_NUMBER_MODE,r.BINARY_NUMBER_MODE,{className:"number",
+begin:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},r.QUOTE_STRING_MODE,{className:"string",
+begin:"'",end:"[^\\\\]'",illegal:"[^\\\\][^']"},{className:"symbol",
+begin:"^[A-Za-z0-9_.$]+:"},{className:"meta",begin:"#",end:"$"},{
+className:"subst",begin:"@[0-9]+"}]})})());
+hljs.registerLanguage("awk",(()=>{"use strict";return e=>({name:"Awk",keywords:{
+keyword:"BEGIN END if else while do for in break continue delete next nextfile function func exit|10"
+},contains:[{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{
+begin:/\$\{(.*?)\}/}]},{className:"string",contains:[e.BACKSLASH_ESCAPE],
+variants:[{begin:/(u|b)?r?'''/,end:/'''/,relevance:10},{begin:/(u|b)?r?"""/,
+end:/"""/,relevance:10},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{
+begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{
+begin:/(b|br)"/,end:/"/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},e.REGEXP_MODE,e.HASH_COMMENT_MODE,e.NUMBER_MODE]})})());
+hljs.registerLanguage("axapta",(()=>{"use strict";return e=>({name:"X++",
+aliases:["x++"],keywords:{
+keyword:["abstract","as","asc","avg","break","breakpoint","by","byref","case","catch","changecompany","class","client","client","common","const","continue","count","crosscompany","delegate","delete_from","desc","display","div","do","edit","else","eventhandler","exists","extends","final","finally","firstfast","firstonly","firstonly1","firstonly10","firstonly100","firstonly1000","flush","for","forceliterals","forcenestedloop","forceplaceholders","forceselectorder","forupdate","from","generateonly","group","hint","if","implements","in","index","insert_recordset","interface","internal","is","join","like","maxof","minof","mod","namespace","new","next","nofetch","notexists","optimisticlock","order","outer","pessimisticlock","print","private","protected","public","readonly","repeatableread","retry","return","reverse","select","server","setting","static","sum","super","switch","this","throw","try","ttsabort","ttsbegin","ttscommit","unchecked","update_recordset","using","validtimestate","void","where","while"],
+built_in:["anytype","boolean","byte","char","container","date","double","enum","guid","int","int64","long","real","short","str","utcdatetime","var"],
+literal:["default","false","null","true"]},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"#",end:"$"},{className:"class",
+beginKeywords:"class interface",end:/\{/,excludeEnd:!0,illegal:":",contains:[{
+beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]}]})})());
+hljs.registerLanguage("bash",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(s=e)?"string"==typeof s?s:s.source:null;var s
+})).join("")}return s=>{const n={},t={begin:/\$\{/,end:/\}/,contains:["self",{
+begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{
+begin:e(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},t]});const a={
+className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},i={
+begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,
+end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/,
+contains:[s.BACKSLASH_ESCAPE,n,a]};a.contains.push(c);const o={begin:/\$\(\(/,
+end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,n]
+},r=s.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10
+}),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,
+contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{
+name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,
+keyword:"if then else elif fi for while in do done case esac function",
+literal:"true false",
+built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"
+},contains:[r,s.SHEBANG(),l,o,s.HASH_COMMENT_MODE,i,c,{className:"",begin:/\\"/
+},{className:"string",begin:/'/,end:/'/},n]}}})());
+hljs.registerLanguage("basic",(()=>{"use strict";return E=>({name:"BASIC",
+case_insensitive:!0,illegal:"^.",keywords:{$pattern:"[a-zA-Z][a-zA-Z0-9_$%!#]*",
+keyword:"ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO HEX$ IF THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE WEND WIDTH WINDOW WRITE XOR"
+},contains:[E.QUOTE_STRING_MODE,E.COMMENT("REM","$",{relevance:10
+}),E.COMMENT("'","$",{relevance:0}),{className:"symbol",begin:"^[0-9]+ ",
+relevance:10},{className:"number",begin:"\\b\\d+(\\.\\d+)?([edED]\\d+)?[#!]?",
+relevance:0},{className:"number",begin:"(&[hH][0-9a-fA-F]{1,4})"},{
+className:"number",begin:"(&[oO][0-7]{1,6})"}]})})());
+hljs.registerLanguage("bnf",(()=>{"use strict";return e=>({
+name:"Backus\u2013Naur Form",contains:[{className:"attribute",begin:/</,end:/>/
+},{begin:/::=/,end:/$/,contains:[{begin:/</,end:/>/
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+}]})})());
+hljs.registerLanguage("brainfuck",(()=>{"use strict";return e=>{const n={
+className:"literal",begin:/[+-]/,relevance:0};return{name:"Brainfuck",
+aliases:["bf"],
+contains:[e.COMMENT("[^\\[\\]\\.,\\+\\-<> \r\n]","[\\[\\]\\.,\\+\\-<> \r\n]",{
+returnEnd:!0,relevance:0}),{className:"title",begin:"[\\[\\]]",relevance:0},{
+className:"string",begin:"[\\.,]",relevance:0},{begin:/(?:\+\+|--)/,contains:[n]
+},n]}}})());
+hljs.registerLanguage("c-like",(()=>{"use strict";function e(e){
+return((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(",e,")?")
+}return t=>{const n=(t=>{const n=t.COMMENT("//","$",{contains:[{begin:/\\\n/}]
+}),a="[a-zA-Z_]\\w*::",r="(decltype\\(auto\\)|"+e(a)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",s={
+className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},i={className:"string",
+variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",
+contains:[t.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},t.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},c={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},o={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},t.inherit(i,{className:"meta-string"}),{
+className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"
+},n,t.C_BLOCK_COMMENT_MODE]},l={className:"title",begin:e(a)+t.IDENT_RE,
+relevance:0},d=e(a)+t.IDENT_RE+"\\s*\\(",u={
+keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",
+built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",
+literal:"true false nullptr NULL"},p=[o,s,n,t.C_BLOCK_COMMENT_MODE,c,i],m={
+variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{
+beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:p.concat([{
+begin:/\(/,end:/\)/,keywords:u,contains:p.concat(["self"]),relevance:0}]),
+relevance:0},g={className:"function",begin:"("+r+"[\\*&\\s]+)+"+d,
+returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>.]/,
+contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d,
+returnBegin:!0,contains:[l],relevance:0},{begin:/::/,relevance:0},{begin:/:/,
+endsWithParent:!0,contains:[i,c]},{className:"params",begin:/\(/,end:/\)/,
+keywords:u,relevance:0,contains:[n,t.C_BLOCK_COMMENT_MODE,i,c,s,{begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:["self",n,t.C_BLOCK_COMMENT_MODE,i,c,s]
+}]},s,n,t.C_BLOCK_COMMENT_MODE,o]};return{name:"C++",
+aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"</",
+contains:[].concat(m,g,p,[o,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",
+end:">",keywords:u,contains:["self",s]},{begin:t.IDENT_RE+"::",keywords:u},{
+className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/,
+contains:[{beginKeywords:"final class struct"},t.TITLE_MODE]}]),exports:{
+preprocessor:o,strings:i,keywords:u}}})(t)
+;return n.disableAutodetect=!0,n.aliases=[],
+t.getLanguage("c")||n.aliases.push("c","h"),
+t.getLanguage("cpp")||n.aliases.push("cc","c++","h++","hpp","hh","hxx","cxx"),n}
+})());
+hljs.registerLanguage("c",(()=>{"use strict";function e(e){
+return((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(",e,")?")
+}return t=>{const n=t.COMMENT("//","$",{contains:[{begin:/\\\n/}]
+}),r="[a-zA-Z_]\\w*::",a="(decltype\\(auto\\)|"+e(r)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",i={
+className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},s={className:"string",
+variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",
+contains:[t.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},t.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},t.inherit(s,{className:"meta-string"}),{
+className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"
+},n,t.C_BLOCK_COMMENT_MODE]},l={className:"title",begin:e(r)+t.IDENT_RE,
+relevance:0},d=e(r)+t.IDENT_RE+"\\s*\\(",u={
+keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",
+built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",
+literal:"true false nullptr NULL"},m=[c,i,n,t.C_BLOCK_COMMENT_MODE,o,s],p={
+variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{
+beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:m.concat([{
+begin:/\(/,end:/\)/,keywords:u,contains:m.concat(["self"]),relevance:0}]),
+relevance:0},_={className:"function",begin:"("+a+"[\\*&\\s]+)+"+d,
+returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>.]/,
+contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d,
+returnBegin:!0,contains:[l],relevance:0},{className:"params",begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:[n,t.C_BLOCK_COMMENT_MODE,s,o,i,{
+begin:/\(/,end:/\)/,keywords:u,relevance:0,
+contains:["self",n,t.C_BLOCK_COMMENT_MODE,s,o,i]}]
+},i,n,t.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["c","h"],keywords:u,
+disableAutodetect:!0,illegal:"</",contains:[].concat(p,_,m,[c,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",
+end:">",keywords:u,contains:["self",i]},{begin:t.IDENT_RE+"::",keywords:u},{
+className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/,
+contains:[{beginKeywords:"final class struct"},t.TITLE_MODE]}]),exports:{
+preprocessor:c,strings:s,keywords:u}}}})());
+hljs.registerLanguage("cal",(()=>{"use strict";return e=>{
+const n="div mod in and or not xor asserterror begin case do downto else end exit for if of repeat then to until while with var",a=[e.C_LINE_COMMENT_MODE,e.COMMENT(/\{/,/\}/,{
+relevance:0}),e.COMMENT(/\(\*/,/\*\)/,{relevance:10})],r={className:"string",
+begin:/'/,end:/'/,contains:[{begin:/''/}]},s={className:"string",begin:/(#\d+)+/
+},i={className:"function",beginKeywords:"procedure",end:/[:;]/,
+keywords:"procedure|10",contains:[e.TITLE_MODE,{className:"params",begin:/\(/,
+end:/\)/,keywords:n,contains:[r,s]}].concat(a)},t={className:"class",
+begin:"OBJECT (Table|Form|Report|Dataport|Codeunit|XMLport|MenuSuite|Page|Query) (\\d+) ([^\\r\\n]+)",
+returnBegin:!0,contains:[e.TITLE_MODE,i]};return{name:"C/AL",
+case_insensitive:!0,keywords:{keyword:n,literal:"false true"},illegal:/\/\*/,
+contains:[r,s,{className:"number",begin:"\\b\\d+(\\.\\d+)?(DT|D|T)",relevance:0
+},{className:"string",begin:'"',end:'"'},e.NUMBER_MODE,t,i]}}})());
+hljs.registerLanguage("capnproto",(()=>{"use strict";return n=>({
+name:"Cap\u2019n Proto",aliases:["capnp"],keywords:{
+keyword:"struct enum interface union group import using const annotation extends in of on as with from fixed",
+built_in:"Void Bool Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64 Text Data AnyPointer AnyStruct Capability List",
+literal:"true false"},
+contains:[n.QUOTE_STRING_MODE,n.NUMBER_MODE,n.HASH_COMMENT_MODE,{
+className:"meta",begin:/@0x[\w\d]{16};/,illegal:/\n/},{className:"symbol",
+begin:/@\d+\b/},{className:"class",beginKeywords:"struct enum",end:/\{/,
+illegal:/\n/,contains:[n.inherit(n.TITLE_MODE,{starts:{endsWithParent:!0,
+excludeEnd:!0}})]},{className:"class",beginKeywords:"interface",end:/\{/,
+illegal:/\n/,contains:[n.inherit(n.TITLE_MODE,{starts:{endsWithParent:!0,
+excludeEnd:!0}})]}]})})());
+hljs.registerLanguage("ceylon",(()=>{"use strict";return e=>{
+const a="assembly module package import alias class interface object given value assign void function new of extends satisfies abstracts in out return break continue throw assert dynamic if else switch case for while try catch finally then let this outer super is exists nonempty",s={
+className:"subst",excludeBegin:!0,excludeEnd:!0,begin:/``/,end:/``/,keywords:a,
+relevance:10},n=[{className:"string",begin:'"""',end:'"""',relevance:10},{
+className:"string",begin:'"',end:'"',contains:[s]},{className:"string",
+begin:"'",end:"'"},{className:"number",
+begin:"#[0-9a-fA-F_]+|\\$[01_]+|[0-9_]+(?:\\.[0-9_](?:[eE][+-]?\\d+)?)?[kMGTPmunpf]?",
+relevance:0}];return s.contains=n,{name:"Ceylon",keywords:{
+keyword:a+" shared abstract formal default actual variable late native deprecated final sealed annotation suppressWarnings small",
+meta:"doc by license see throws tagged"},illegal:"\\$[^01]|#[^0-9a-fA-F]",
+contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]}),{
+className:"meta",begin:'@[a-z]\\w*(?::"[^"]*")?'}].concat(n)}}})());
+hljs.registerLanguage("clean",(()=>{"use strict";return e=>({name:"Clean",
+aliases:["icl","dcl"],keywords:{
+keyword:"if let in with where case of class instance otherwise implementation definition system module from import qualified as special code inline foreign export ccall stdcall generic derive infix infixl infixr",
+built_in:"Int Real Char Bool",literal:"True False"},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{
+begin:"->|<-[|:]?|#!?|>>=|\\{\\||\\|\\}|:==|=:|<>"}]})})());
+hljs.registerLanguage("clojure",(()=>{"use strict";return e=>{
+const t="a-zA-Z_\\-!.?+*=<>&#'",n="["+t+"]["+t+"0-9/;:]*",r="def defonce defprotocol defstruct defmulti defmethod defn- defn defmacro deftype defrecord",a={
+$pattern:n,
+"builtin-name":r+" cond apply if-not if-let if not not= =|0 <|0 >|0 <=|0 >=|0 ==|0 +|0 /|0 *|0 -|0 rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy first rest cons cast coll last butlast sigs reify second ffirst fnext nfirst nnext meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"
+},s={begin:n,relevance:0},o={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",
+relevance:0},i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null
+}),c=e.COMMENT(";","$",{relevance:0}),d={className:"literal",
+begin:/\b(true|false|nil)\b/},l={begin:"[\\[\\{]",end:"[\\]\\}]"},m={
+className:"comment",begin:"\\^"+n},p=e.COMMENT("\\^\\{","\\}"),u={
+className:"symbol",begin:"[:]{1,2}"+n},f={begin:"\\(",end:"\\)"},h={
+endsWithParent:!0,relevance:0},y={keywords:a,className:"name",begin:n,
+relevance:0,starts:h},g=[f,i,m,p,c,u,l,o,d,s],b={beginKeywords:r,lexemes:n,
+end:'(\\[|#|\\d|"|:|\\{|\\)|\\(|$)',contains:[{className:"title",begin:n,
+relevance:0,excludeEnd:!0,endsParent:!0}].concat(g)}
+;return f.contains=[e.COMMENT("comment",""),b,y,h],
+h.contains=g,l.contains=g,p.contains=[l],{name:"Clojure",aliases:["clj"],
+illegal:/\S/,contains:[f,i,m,p,c,u,l,o,d]}}})());
+hljs.registerLanguage("clojure-repl",(()=>{"use strict";return e=>({
+name:"Clojure REPL",contains:[{className:"meta",begin:/^([\w.-]+|\s*#_)?=>/,
+starts:{end:/$/,subLanguage:"clojure"}}]})})());
+hljs.registerLanguage("cmake",(()=>{"use strict";return e=>({name:"CMake",
+aliases:["cmake.in"],case_insensitive:!0,keywords:{
+keyword:"break cmake_host_system_information cmake_minimum_required cmake_parse_arguments cmake_policy configure_file continue elseif else endforeach endfunction endif endmacro endwhile execute_process file find_file find_library find_package find_path find_program foreach function get_cmake_property get_directory_property get_filename_component get_property if include include_guard list macro mark_as_advanced math message option return separate_arguments set_directory_properties set_property set site_name string unset variable_watch while add_compile_definitions add_compile_options add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_link_options add_subdirectory add_test aux_source_directory build_command create_test_sourcelist define_property enable_language enable_testing export fltk_wrap_ui get_source_file_property get_target_property get_test_property include_directories include_external_msproject include_regular_expression install link_directories link_libraries load_cache project qt_wrap_cpp qt_wrap_ui remove_definitions set_source_files_properties set_target_properties set_tests_properties source_group target_compile_definitions target_compile_features target_compile_options target_include_directories target_link_directories target_link_libraries target_link_options target_sources try_compile try_run ctest_build ctest_configure ctest_coverage ctest_empty_binary_directory ctest_memcheck ctest_read_custom_files ctest_run_script ctest_sleep ctest_start ctest_submit ctest_test ctest_update ctest_upload build_name exec_program export_library_dependencies install_files install_programs install_targets load_command make_directory output_required_files remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or not command policy target test exists is_newer_than is_directory is_symlink is_absolute matches less greater equal less_equal greater_equal strless strgreater strequal strless_equal strgreater_equal version_less version_greater version_equal version_less_equal version_greater_equal in_list defined"
+},contains:[{className:"variable",begin:/\$\{/,end:/\}/
+},e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE]})})());
+hljs.registerLanguage("coffeescript",(()=>{"use strict"
+;const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"])
+;return r=>{const t={
+keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((i=["var","const","let","function","static"],
+e=>!i.includes(e))),literal:n.concat(["yes","no","on","off"]),
+built_in:a.concat(["npm","print"])};var i;const s="[A-Za-z$_][0-9A-Za-z$_]*",o={
+className:"subst",begin:/#\{/,end:/\}/,keywords:t
+},c=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",
+relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,
+contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]
+},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,o]},{begin:/"/,end:/"/,
+contains:[r.BACKSLASH_ESCAPE,o]}]},{className:"regexp",variants:[{begin:"///",
+end:"///",contains:[o,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",
+relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+s
+},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{
+begin:"```",end:"```"},{begin:"`",end:"`"}]}];o.contains=c
+;const l=r.inherit(r.TITLE_MODE,{begin:s}),d="(\\(.*\\)\\s*)?\\B[-=]>",g={
+className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,
+end:/\)/,keywords:t,contains:["self"].concat(c)}]};return{name:"CoffeeScript",
+aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,
+contains:c.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{
+className:"function",begin:"^\\s*"+s+"\\s*=\\s*"+d,end:"[-=]>",returnBegin:!0,
+contains:[l,g]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",
+begin:d,end:"[-=]>",returnBegin:!0,contains:[g]}]},{className:"class",
+beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{
+beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[l]},l]
+},{begin:s+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}})());
+hljs.registerLanguage("coq",(()=>{"use strict";return e=>({name:"Coq",keywords:{
+keyword:"_|0 as at cofix else end exists exists2 fix for forall fun if IF in let match mod Prop return Set then Type using where with Abort About Add Admit Admitted All Arguments Assumptions Axiom Back BackTo Backtrack Bind Blacklist Canonical Cd Check Class Classes Close Coercion Coercions CoFixpoint CoInductive Collection Combined Compute Conjecture Conjectures Constant constr Constraint Constructors Context Corollary CreateHintDb Cut Declare Defined Definition Delimit Dependencies Dependent Derive Drop eauto End Equality Eval Example Existential Existentials Existing Export exporting Extern Extract Extraction Fact Field Fields File Fixpoint Focus for From Function Functional Generalizable Global Goal Grab Grammar Graph Guarded Heap Hint HintDb Hints Hypotheses Hypothesis ident Identity If Immediate Implicit Import Include Inductive Infix Info Initial Inline Inspect Instance Instances Intro Intros Inversion Inversion_clear Language Left Lemma Let Libraries Library Load LoadPath Local Locate Ltac ML Mode Module Modules Monomorphic Morphism Next NoInline Notation Obligation Obligations Opaque Open Optimize Options Parameter Parameters Parametric Path Paths pattern Polymorphic Preterm Print Printing Program Projections Proof Proposition Pwd Qed Quit Rec Record Recursive Redirect Relation Remark Remove Require Reserved Reset Resolve Restart Rewrite Right Ring Rings Save Scheme Scope Scopes Script Search SearchAbout SearchHead SearchPattern SearchRewrite Section Separate Set Setoid Show Solve Sorted Step Strategies Strategy Structure SubClass Table Tables Tactic Term Test Theorem Time Timeout Transparent Type Typeclasses Types Undelimit Undo Unfocus Unfocused Unfold Universe Universes Unset Unshelve using Variable Variables Variant Verbose Visibility where with",
+built_in:"abstract absurd admit after apply as assert assumption at auto autorewrite autounfold before bottom btauto by case case_eq cbn cbv change classical_left classical_right clear clearbody cofix compare compute congruence constr_eq constructor contradict contradiction cut cutrewrite cycle decide decompose dependent destruct destruction dintuition discriminate discrR do double dtauto eapply eassumption eauto ecase econstructor edestruct ediscriminate eelim eexact eexists einduction einjection eleft elim elimtype enough equality erewrite eright esimplify_eq esplit evar exact exactly_once exfalso exists f_equal fail field field_simplify field_simplify_eq first firstorder fix fold fourier functional generalize generalizing gfail give_up has_evar hnf idtac in induction injection instantiate intro intro_pattern intros intuition inversion inversion_clear is_evar is_var lapply lazy left lia lra move native_compute nia nsatz omega once pattern pose progress proof psatz quote record red refine reflexivity remember rename repeat replace revert revgoals rewrite rewrite_strat right ring ring_simplify rtauto set setoid_reflexivity setoid_replace setoid_rewrite setoid_symmetry setoid_transitivity shelve shelve_unifiable simpl simple simplify_eq solve specialize split split_Rabs split_Rmult stepl stepr subst sum swap symmetry tactic tauto time timeout top transitivity trivial try tryif unfold unify until using vm_compute with"
+},contains:[e.QUOTE_STRING_MODE,e.COMMENT("\\(\\*","\\*\\)"),e.C_NUMBER_MODE,{
+className:"type",excludeBegin:!0,begin:"\\|\\s*",end:"\\w+"},{begin:/[-=]>/}]})
+})());
+hljs.registerLanguage("cos",(()=>{"use strict";return e=>({
+name:"Cach\xe9 Object Script",case_insensitive:!0,aliases:["cls"],
+keywords:"property parameter class classmethod clientmethod extends as break catch close continue do d|0 else elseif for goto halt hang h|0 if job j|0 kill k|0 lock l|0 merge new open quit q|0 read r|0 return set s|0 tcommit throw trollback try tstart use view while write w|0 xecute x|0 zkill znspace zn ztrap zwrite zw zzdump zzwrite print zbreak zinsert zload zprint zremove zsave zzprint mv mvcall mvcrt mvdim mvprint zquit zsync ascii",
+contains:[{className:"number",begin:"\\b(\\d+(\\.\\d*)?|\\.\\d+)",relevance:0},{
+className:"string",variants:[{begin:'"',end:'"',contains:[{begin:'""',
+relevance:0}]}]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+className:"comment",begin:/;/,end:"$",relevance:0},{className:"built_in",
+begin:/(?:\$\$?|\.\.)\^?[a-zA-Z]+/},{className:"built_in",
+begin:/\$\$\$[a-zA-Z]+/},{className:"built_in",begin:/%[a-z]+(?:\.[a-z]+)*/},{
+className:"symbol",begin:/\^%?[a-zA-Z][\w]*/},{className:"keyword",
+begin:/##class|##super|#define|#dim/},{begin:/&sql\(/,end:/\)/,excludeBegin:!0,
+excludeEnd:!0,subLanguage:"sql"},{begin:/&(js|jscript|javascript)</,end:/>/,
+excludeBegin:!0,excludeEnd:!0,subLanguage:"javascript"},{begin:/&html<\s*</,
+end:/>\s*>/,subLanguage:"xml"}]})})());
+hljs.registerLanguage("cpp",(()=>{"use strict";function e(e){
+return((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(",e,")?")
+}return t=>{const n=t.COMMENT("//","$",{contains:[{begin:/\\\n/}]
+}),r="[a-zA-Z_]\\w*::",a="(decltype\\(auto\\)|"+e(r)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",i={
+className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},s={className:"string",
+variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",
+contains:[t.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},t.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},t.inherit(s,{className:"meta-string"}),{
+className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"
+},n,t.C_BLOCK_COMMENT_MODE]},l={className:"title",begin:e(r)+t.IDENT_RE,
+relevance:0},d=e(r)+t.IDENT_RE+"\\s*\\(",u={
+keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",
+built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",
+literal:"true false nullptr NULL"},m=[c,i,n,t.C_BLOCK_COMMENT_MODE,o,s],p={
+variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{
+beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:m.concat([{
+begin:/\(/,end:/\)/,keywords:u,contains:m.concat(["self"]),relevance:0}]),
+relevance:0},_={className:"function",begin:"("+a+"[\\*&\\s]+)+"+d,
+returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>.]/,
+contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d,
+returnBegin:!0,contains:[l],relevance:0},{begin:/::/,relevance:0},{begin:/:/,
+endsWithParent:!0,contains:[s,o]},{className:"params",begin:/\(/,end:/\)/,
+keywords:u,relevance:0,contains:[n,t.C_BLOCK_COMMENT_MODE,s,o,i,{begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:["self",n,t.C_BLOCK_COMMENT_MODE,s,o,i]
+}]},i,n,t.C_BLOCK_COMMENT_MODE,c]};return{name:"C++",
+aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"</",
+contains:[].concat(p,_,m,[c,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",
+end:">",keywords:u,contains:["self",i]},{begin:t.IDENT_RE+"::",keywords:u},{
+className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/,
+contains:[{beginKeywords:"final class struct"},t.TITLE_MODE]}]),exports:{
+preprocessor:c,strings:s,keywords:u}}}})());
+hljs.registerLanguage("crmsh",(()=>{"use strict";return e=>{
+const t="group clone ms master location colocation order fencing_topology rsc_ticket acl_target acl_group user role tag xml"
+;return{name:"crmsh",aliases:["crm","pcmk"],case_insensitive:!0,keywords:{
+keyword:"params meta operations op rule attributes utilization read write deny defined not_defined in_range date spec in ref reference attribute type xpath version and or lt gt tag lte gte eq ne \\ number string",
+literal:"Master Started Slave Stopped start promote demote stop monitor true false"
+},contains:[e.HASH_COMMENT_MODE,{beginKeywords:"node",starts:{
+end:"\\s*([\\w_-]+:)?",starts:{className:"title",end:"\\s*[\\$\\w_][\\w_-]*"}}
+},{beginKeywords:"primitive rsc_template",starts:{className:"title",
+end:"\\s*[\\$\\w_][\\w_-]*",starts:{end:"\\s*@?[\\w_][\\w_\\.:-]*"}}},{
+begin:"\\b("+t.split(" ").join("|")+")\\s+",keywords:t,starts:{
+className:"title",end:"[\\$\\w_][\\w_-]*"}},{
+beginKeywords:"property rsc_defaults op_defaults",starts:{className:"title",
+end:"\\s*([\\w_-]+:)?"}},e.QUOTE_STRING_MODE,{className:"meta",
+begin:"(ocf|systemd|service|lsb):[\\w_:-]+",relevance:0},{className:"number",
+begin:"\\b\\d+(\\.\\d+)?(ms|s|h|m)?",relevance:0},{className:"literal",
+begin:"[-]?(infinity|inf)",relevance:0},{className:"attr",
+begin:/([A-Za-z$_#][\w_-]+)=/,relevance:0},{className:"tag",begin:"</?",
+end:"/?>",relevance:0}]}}})());
+hljs.registerLanguage("crystal",(()=>{"use strict";return e=>{
+const n="(_?[ui](8|16|32|64|128))?",i="[a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|[=!]~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?",s="[A-Za-z_]\\w*(::\\w+)*(\\?|!)?",a={
+$pattern:"[a-zA-Z_]\\w*[!?=]?",
+keyword:"abstract alias annotation as as? asm begin break case class def do else elsif end ensure enum extend for fun if include instance_sizeof is_a? lib macro module next nil? of out pointerof private protected rescue responds_to? return require select self sizeof struct super then type typeof union uninitialized unless until verbatim when while with yield __DIR__ __END_LINE__ __FILE__ __LINE__",
+literal:"false nil true"},t={className:"subst",begin:/#\{/,end:/\}/,keywords:a
+},c={className:"template-variable",variants:[{begin:"\\{\\{",end:"\\}\\}"},{
+begin:"\\{%",end:"%\\}"}],keywords:a};function r(e,n){const i=[{begin:e,end:n}]
+;return i[0].contains=i,i}const l={className:"string",
+contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/
+},{begin:/`/,end:/`/},{begin:"%[Qwi]?\\(",end:"\\)",contains:r("\\(","\\)")},{
+begin:"%[Qwi]?\\[",end:"\\]",contains:r("\\[","\\]")},{begin:"%[Qwi]?\\{",
+end:/\}/,contains:r(/\{/,/\}/)},{begin:"%[Qwi]?<",end:">",contains:r("<",">")},{
+begin:"%[Qwi]?\\|",end:"\\|"},{begin:/<<-\w+$/,end:/^\s*\w+$/}],relevance:0},b={
+className:"string",variants:[{begin:"%q\\(",end:"\\)",contains:r("\\(","\\)")},{
+begin:"%q\\[",end:"\\]",contains:r("\\[","\\]")},{begin:"%q\\{",end:/\}/,
+contains:r(/\{/,/\}/)},{begin:"%q<",end:">",contains:r("<",">")},{begin:"%q\\|",
+end:"\\|"},{begin:/<<-'\w+'$/,end:/^\s*\w+$/}],relevance:0},o={
+begin:"(?!%\\})("+e.RE_STARTERS_RE+"|\\n|\\b(case|if|select|unless|until|when|while)\\b)\\s*",
+keywords:"case if select unless until when while",contains:[{className:"regexp",
+contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:"//[a-z]*",relevance:0},{
+begin:"/(?!\\/)",end:"/[a-z]*"}]}],relevance:0},g=[c,l,b,{className:"regexp",
+contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:"%r\\(",end:"\\)",
+contains:r("\\(","\\)")},{begin:"%r\\[",end:"\\]",contains:r("\\[","\\]")},{
+begin:"%r\\{",end:/\}/,contains:r(/\{/,/\}/)},{begin:"%r<",end:">",
+contains:r("<",">")},{begin:"%r\\|",end:"\\|"}],relevance:0},o,{
+className:"meta",begin:"@\\[",end:"\\]",
+contains:[e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"})]
+},e.HASH_COMMENT_MODE,{className:"class",beginKeywords:"class module struct",
+end:"$|;",illegal:/=/,contains:[e.HASH_COMMENT_MODE,e.inherit(e.TITLE_MODE,{
+begin:s}),{begin:"<"}]},{className:"class",beginKeywords:"lib enum union",
+end:"$|;",illegal:/=/,contains:[e.HASH_COMMENT_MODE,e.inherit(e.TITLE_MODE,{
+begin:s})]},{beginKeywords:"annotation",end:"$|;",illegal:/=/,
+contains:[e.HASH_COMMENT_MODE,e.inherit(e.TITLE_MODE,{begin:s})],relevance:2},{
+className:"function",beginKeywords:"def",end:/\B\b/,
+contains:[e.inherit(e.TITLE_MODE,{begin:i,endsParent:!0})]},{
+className:"function",beginKeywords:"fun macro",end:/\B\b/,
+contains:[e.inherit(e.TITLE_MODE,{begin:i,endsParent:!0})],relevance:2},{
+className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{
+className:"symbol",begin:":",contains:[l,{begin:i}],relevance:0},{
+className:"number",variants:[{begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n
+},{begin:"\\b0x([A-Fa-f0-9_]+)"+n},{
+begin:"\\b([1-9][0-9_]*[0-9]|[0-9])(\\.[0-9][0-9_]*)?([eE]_?[-+]?[0-9_]*)?(_?f(32|64))?(?!_)"
+},{begin:"\\b([1-9][0-9_]*|0)"+n}],relevance:0}]
+;return t.contains=g,c.contains=g.slice(1),{name:"Crystal",aliases:["cr"],
+keywords:a,contains:g}}})());
+hljs.registerLanguage("csharp",(()=>{"use strict";return e=>{var n={
+keyword:["abstract","as","base","break","case","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]),
+built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","unit","ushort"],
+literal:["default","false","null","true"]},a=e.inherit(e.TITLE_MODE,{
+begin:"[a-zA-Z](\\.?\\w)*"}),i={className:"number",variants:[{
+begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]
+},t=e.inherit(s,{illegal:/\n/}),r={className:"subst",begin:/\{/,end:/\}/,
+keywords:n},l=e.inherit(r,{illegal:/\n/}),c={className:"string",begin:/\$"/,
+end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/
+},e.BACKSLASH_ESCAPE,l]},o={className:"string",begin:/\$@"/,end:'"',contains:[{
+begin:/\{\{/},{begin:/\}\}/},{begin:'""'},r]},d=e.inherit(o,{illegal:/\n/,
+contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},l]})
+;r.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.C_BLOCK_COMMENT_MODE],
+l.contains=[d,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.inherit(e.C_BLOCK_COMMENT_MODE,{
+illegal:/\n/})];var g={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},a]
+},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={
+begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],
+keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,
+contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{
+begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]
+}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",
+end:"$",keywords:{
+"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"
+}},g,i,{beginKeywords:"class interface",relevance:0,end:/[{;=]/,
+illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"
+},a,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",
+relevance:0,end:/[{;=]/,illegal:/[^\s:]/,
+contains:[a,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{
+beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,
+contains:[a,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",
+begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{
+className:"meta-string",begin:/"/,end:/"/}]},{
+beginKeywords:"new return throw await else",relevance:0},{className:"function",
+begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0,
+end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{
+beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial",
+relevance:0},{begin:e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0,
+contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,
+excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,
+contains:[g,i,e.C_BLOCK_COMMENT_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}})());
+hljs.registerLanguage("csp",(()=>{"use strict";return e=>({name:"CSP",
+case_insensitive:!1,keywords:{$pattern:"[a-zA-Z][a-zA-Z0-9_-]*",
+keyword:"base-uri child-src connect-src default-src font-src form-action frame-ancestors frame-src img-src media-src object-src plugin-types report-uri sandbox script-src style-src"
+},contains:[{className:"string",begin:"'",end:"'"},{className:"attribute",
+begin:"^Content",end:":",excludeEnd:!0}]})})());
+hljs.registerLanguage("css",(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],r=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse()
+;return n=>{const a=(e=>({IMPORTANT:{className:"meta",begin:"!important"},
+HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"},
+ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/,
+illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}
+}))(n),l=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS",
+case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},
+classNameAliases:{keyframePosition:"selector-tag"},
+contains:[n.C_BLOCK_COMMENT_MODE,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/
+},n.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0
+},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0
+},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{
+begin:":("+i.join("|")+")"},{begin:"::("+o.join("|")+")"}]},{
+className:"attribute",begin:"\\b("+r.join("|")+")\\b"},{begin:":",end:"[;}]",
+contains:[a.HEXCOLOR,a.IMPORTANT,n.CSS_NUMBER_MODE,...l,{
+begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"
+},contains:[{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]
+},{className:"built_in",begin:/[\w-]+(?=\()/}]},{
+begin:(s=/@/,((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(?=",s,")")),
+end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword",
+begin:/@-?\w[\w]*(-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,
+relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:"and or not only",
+attribute:t.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute"
+},...l,n.CSS_NUMBER_MODE]}]},{className:"selector-tag",
+begin:"\\b("+e.join("|")+")\\b"}]};var s}})());
+hljs.registerLanguage("d",(()=>{"use strict";return e=>{const a={
+$pattern:e.UNDERSCORE_IDENT_RE,
+keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",
+built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",
+literal:"false null true"
+},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={
+className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={
+className:"number",
+begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",
+relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={
+className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'
+},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{
+name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{
+className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{
+className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",
+begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{
+className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",
+begin:"#(line)",end:"$",relevance:5},{className:"keyword",
+begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}})());
+hljs.registerLanguage("markdown",(()=>{"use strict";function n(...n){
+return n.map((n=>{return(e=n)?"string"==typeof e?e:e.source:null;var e
+})).join("")}return e=>{const a={begin:/<\/?[A-Za-z_]/,end:">",
+subLanguage:"xml",relevance:0},i={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0
+},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
+relevance:2},{begin:n(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),
+relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{
+begin:/\[.+?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{
+className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,
+returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",
+excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",
+end:"\\]",excludeBegin:!0,excludeEnd:!0}]},s={className:"strong",contains:[],
+variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},c={
+className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{
+begin:/_(?!_)/,end:/_/,relevance:0}]};s.contains.push(c),c.contains.push(s)
+;let t=[a,i]
+;return s.contains=s.contains.concat(t),c.contains=c.contains.concat(t),
+t=t.concat(s,c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{
+className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:t},{
+begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",
+contains:t}]}]},a,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",
+end:"\\s+",excludeEnd:!0},s,c,{className:"quote",begin:"^>\\s+",contains:t,
+end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{
+begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{
+begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",
+contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{
+begin:"^[-\\*]{3,}",end:"$"},i,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{
+className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{
+className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})());
+hljs.registerLanguage("dart",(()=>{"use strict";return e=>{const n={
+className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"}]},a={className:"subst",
+variants:[{begin:/\$\{/,end:/\}/}],keywords:"true false null this is new super"
+},t={className:"string",variants:[{begin:"r'''",end:"'''"},{begin:'r"""',
+end:'"""'},{begin:"r'",end:"'",illegal:"\\n"},{begin:'r"',end:'"',illegal:"\\n"
+},{begin:"'''",end:"'''",contains:[e.BACKSLASH_ESCAPE,n,a]},{begin:'"""',
+end:'"""',contains:[e.BACKSLASH_ESCAPE,n,a]},{begin:"'",end:"'",illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE,n,a]},{begin:'"',end:'"',illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE,n,a]}]};a.contains=[e.C_NUMBER_MODE,t]
+;const i=["Comparable","DateTime","Duration","Function","Iterable","Iterator","List","Map","Match","Object","Pattern","RegExp","Set","Stopwatch","String","StringBuffer","StringSink","Symbol","Type","Uri","bool","double","int","num","Element","ElementList"],r=i.map((e=>e+"?"))
+;return{name:"Dart",keywords:{
+keyword:"abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for Function get hide if implements import in inferface is late library mixin new null on operator part required rethrow return set show static super switch sync this throw true try typedef var void while with yield",
+built_in:i.concat(r).concat(["Never","Null","dynamic","print","document","querySelector","querySelectorAll","window"]),
+$pattern:/[A-Za-z][A-Za-z0-9_]*\??/},
+contains:[t,e.COMMENT(/\/\*\*(?!\/)/,/\*\//,{subLanguage:"markdown",relevance:0
+}),e.COMMENT(/\/{3,} ?/,/$/,{contains:[{subLanguage:"markdown",begin:".",
+end:"$",relevance:0}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+className:"class",beginKeywords:"class interface",end:/\{/,excludeEnd:!0,
+contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]
+},e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"},{begin:"=>"}]}}})());
+hljs.registerLanguage("delphi",(()=>{"use strict";return e=>{
+const r="exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure absolute reintroduce operator as is abstract alias assembler bitpacked break continue cppdecl cvar enumerator experimental platform deprecated unimplemented dynamic export far16 forward generic helper implements interrupt iochecks local name nodefault noreturn nostackframe oldfpccall otherwise saveregisters softfloat specialize strict unaligned varargs ",a=[e.C_LINE_COMMENT_MODE,e.COMMENT(/\{/,/\}/,{
+relevance:0}),e.COMMENT(/\(\*/,/\*\)/,{relevance:10})],t={className:"meta",
+variants:[{begin:/\{\$/,end:/\}/},{begin:/\(\*\$/,end:/\*\)/}]},n={
+className:"string",begin:/'/,end:/'/,contains:[{begin:/''/}]},s={
+className:"string",begin:/(#\d+)+/},i={begin:e.IDENT_RE+"\\s*=\\s*class\\s*\\(",
+returnBegin:!0,contains:[e.TITLE_MODE]},c={className:"function",
+beginKeywords:"function constructor destructor procedure",end:/[:;]/,
+keywords:"function constructor|10 destructor|10 procedure|10",
+contains:[e.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:r,
+contains:[n,s,t].concat(a)},t].concat(a)};return{name:"Delphi",
+aliases:["dpr","dfm","pas","pascal","freepascal","lazarus","lpr","lfm"],
+case_insensitive:!0,keywords:r,illegal:/"|\$[G-Zg-z]|\/\*|<\/|\|/,
+contains:[n,s,e.NUMBER_MODE,{className:"number",relevance:0,variants:[{
+begin:"\\$[0-9A-Fa-f]+"},{begin:"&[0-7]+"},{begin:"%[01]+"}]},i,c,t].concat(a)}}
+})());
+hljs.registerLanguage("diff",(()=>{"use strict";return e=>({name:"Diff",
+aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{
+begin:/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{
+begin:/^--- +\d+,\d+ +----$/}]},{className:"comment",variants:[{begin:/Index: /,
+end:/$/},{begin:/^index/,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^-{3}/,end:/$/
+},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/},{
+begin:/^diff --git/,end:/$/}]},{className:"addition",begin:/^\+/,end:/$/},{
+className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,
+end:/$/}]})})());
+hljs.registerLanguage("django",(()=>{"use strict";return e=>{const t={
+begin:/\|[A-Za-z]+:?/,keywords:{
+name:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone"
+},contains:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE]};return{name:"Django",
+aliases:["jinja"],case_insensitive:!0,subLanguage:"xml",
+contains:[e.COMMENT(/\{%\s*comment\s*%\}/,/\{%\s*endcomment\s*%\}/),e.COMMENT(/\{#/,/#\}/),{
+className:"template-tag",begin:/\{%/,end:/%\}/,contains:[{className:"name",
+begin:/\w+/,keywords:{
+name:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim"
+},starts:{endsWithParent:!0,keywords:"in by as",contains:[t],relevance:0}}]},{
+className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[t]}]}}})());
+hljs.registerLanguage("dns",(()=>{"use strict";return d=>({name:"DNS Zone",
+aliases:["bind","zone"],keywords:{
+keyword:"IN A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT"
+},contains:[d.COMMENT(";","$",{relevance:0}),{className:"meta",
+begin:/^\$(TTL|GENERATE|INCLUDE|ORIGIN)\b/},{className:"number",
+begin:"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))\\b"
+},{className:"number",
+begin:"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\b"
+},d.inherit(d.NUMBER_MODE,{begin:/\b\d+[dhwm]?/})]})})());
+hljs.registerLanguage("dockerfile",(()=>{"use strict";return e=>({
+name:"Dockerfile",aliases:["docker"],case_insensitive:!0,
+keywords:"from maintainer expose env arg user onbuild stopsignal",
+contains:[e.HASH_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{
+beginKeywords:"run cmd entrypoint volume add copy workdir label healthcheck shell",
+starts:{end:/[^\\]$/,subLanguage:"bash"}}],illegal:"</"})})());
+hljs.registerLanguage("dos",(()=>{"use strict";return e=>{
+const t=e.COMMENT(/^\s*@?rem\b/,/$/,{relevance:10});return{
+name:"Batch file (DOS)",aliases:["bat","cmd"],case_insensitive:!0,
+illegal:/\/\*/,keywords:{
+keyword:"if else goto for in do call exit not exist errorlevel defined equ neq lss leq gtr geq",
+built_in:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux shift cd dir echo setlocal endlocal set pause copy append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shift sort start subst time title tree type ver verify vol ping net ipconfig taskkill xcopy ren del"
+},contains:[{className:"variable",begin:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{
+className:"function",begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",
+end:"goto:eof",contains:[e.inherit(e.TITLE_MODE,{
+begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),t]},{
+className:"number",begin:"\\b\\d+",relevance:0},t]}}})());
+hljs.registerLanguage("dsconfig",(()=>{"use strict";return e=>({
+keywords:"dsconfig",contains:[{className:"keyword",begin:"^dsconfig",end:/\s/,
+excludeEnd:!0,relevance:10},{className:"built_in",
+begin:/(list|create|get|set|delete)-(\w+)/,end:/\s/,excludeEnd:!0,
+illegal:"!@#$%^&*()",relevance:10},{className:"built_in",begin:/--(\w+)/,
+end:/\s/,excludeEnd:!0},{className:"string",begin:/"/,end:/"/},{
+className:"string",begin:/'/,end:/'/},{className:"string",begin:/[\w\-?]+:\w+/,
+end:/\W/,relevance:0},{className:"string",begin:/\w+(\-\w+)*/,end:/(?=\W)/,
+relevance:0},e.HASH_COMMENT_MODE]})})());
+hljs.registerLanguage("dts",(()=>{"use strict";return e=>{const n={
+className:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{
+begin:'((u8?|U)|L)?"'}),{begin:'(u8?|U)?R"',end:'"',
+contains:[e.BACKSLASH_ESCAPE]},{begin:"'\\\\?.",end:"'",illegal:"."}]},a={
+className:"number",variants:[{
+begin:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{begin:e.C_NUMBER_RE}],
+relevance:0},s={className:"meta",begin:"#",end:"$",keywords:{
+"meta-keyword":"if else elif endif define undef ifdef ifndef"},contains:[{
+begin:/\\\n/,relevance:0},{beginKeywords:"include",end:"$",keywords:{
+"meta-keyword":"include"},contains:[e.inherit(n,{className:"meta-string"}),{
+className:"meta-string",begin:"<",end:">",illegal:"\\n"}]
+},n,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},i={className:"variable",
+begin:/&[a-z\d_]*\b/},d={className:"meta-keyword",begin:"/[a-z][a-z\\d-]*/"},l={
+className:"symbol",begin:"^\\s*[a-zA-Z_][a-zA-Z\\d_]*:"},r={className:"params",
+begin:"<",end:">",contains:[a,i]},_={className:"class",
+begin:/[a-zA-Z_][a-zA-Z\d_@]*\s\{/,end:/[{;=]/,returnBegin:!0,excludeEnd:!0}
+;return{name:"Device Tree",keywords:"",contains:[{className:"class",
+begin:"/\\s*\\{",end:/\};/,relevance:10,
+contains:[i,d,l,_,r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,n]
+},i,d,l,_,r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,n,s,{
+begin:e.IDENT_RE+"::",keywords:""}]}}})());
+hljs.registerLanguage("dust",(()=>{"use strict";return e=>({name:"Dust",
+aliases:["dst"],case_insensitive:!0,subLanguage:"xml",contains:[{
+className:"template-tag",begin:/\{[#\/]/,end:/\}/,illegal:/;/,contains:[{
+className:"name",begin:/[a-zA-Z\.-]+/,starts:{endsWithParent:!0,relevance:0,
+contains:[e.QUOTE_STRING_MODE]}}]},{className:"template-variable",begin:/\{/,
+end:/\}/,illegal:/;/,keywords:"if eq ne lt lte gt gte select default math sep"}]
+})})());
+hljs.registerLanguage("ebnf",(()=>{"use strict";return e=>{
+const a=e.COMMENT(/\(\*/,/\*\)/);return{name:"Extended Backus-Naur Form",
+illegal:/\S/,contains:[a,{className:"attribute",
+begin:/^[ ]*[a-zA-Z]+([\s_-]+[a-zA-Z]+)*/},{begin:/=/,end:/[.;]/,contains:[a,{
+className:"meta",begin:/\?.*\?/},{className:"string",
+variants:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{begin:"`",end:"`"}]}]}]}}
+})());
+hljs.registerLanguage("elixir",(()=>{"use strict";return e=>{
+const n="[a-zA-Z_][a-zA-Z0-9_.]*(!|\\?)?",i={$pattern:n,
+keyword:"and false then defined module in return redo retry end for true self when next until do begin unless nil break not case cond alias while ensure or include use alias fn quote require import with|0"
+},a={className:"subst",begin:/#\{/,end:/\}/,keywords:i},s={className:"number",
+begin:"(\\b0o[0-7_]+)|(\\b0b[01_]+)|(\\b0x[0-9a-fA-F_]+)|(-?\\b[1-9][0-9_]*(\\.[0-9_]+([eE][-+]?[0-9]+)?)?)",
+relevance:0},b={className:"string",begin:"~[a-z](?=[/|([{<\"'])",contains:[{
+endsParent:!0,contains:[{contains:[e.BACKSLASH_ESCAPE,a],variants:[{begin:/"/,
+end:/"/},{begin:/'/,end:/'/},{begin:/\//,end:/\//},{begin:/\|/,end:/\|/},{
+begin:/\(/,end:/\)/},{begin:/\[/,end:/\]/},{begin:/\{/,end:/\}/},{begin:/</,
+end:/>/}]}]}]},d={className:"string",contains:[e.BACKSLASH_ESCAPE,a],variants:[{
+begin:/"""/,end:/"""/},{begin:/'''/,end:/'''/},{begin:/~S"""/,end:/"""/,
+contains:[]},{begin:/~S"/,end:/"/,contains:[]},{begin:/~S'''/,end:/'''/,
+contains:[]},{begin:/~S'/,end:/'/,contains:[]},{begin:/'/,end:/'/},{begin:/"/,
+end:/"/}]},r={className:"function",beginKeywords:"def defp defmacro",end:/\B\b/,
+contains:[e.inherit(e.TITLE_MODE,{begin:n,endsParent:!0})]},g=e.inherit(r,{
+className:"class",beginKeywords:"defimpl defmodule defprotocol defrecord",
+end:/\bdo\b|$|;/}),t=[d,{className:"string",begin:"~[A-Z](?=[/|([{<\"'])",
+contains:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/\//,end:/\//},{
+begin:/\|/,end:/\|/},{begin:/\(/,end:/\)/},{begin:/\[/,end:/\]/},{begin:/\{/,
+end:/\}/},{begin:/</,end:/>/}]},b,e.HASH_COMMENT_MODE,g,r,{begin:"::"},{
+className:"symbol",begin:":(?![\\s:])",contains:[d,{
+begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"
+}],relevance:0},{className:"symbol",begin:n+":(?!:)",relevance:0},s,{
+className:"variable",begin:"(\\$\\W)|((\\$|@@?)(\\w+))"},{begin:"->"},{
+begin:"("+e.RE_STARTERS_RE+")\\s*",contains:[e.HASH_COMMENT_MODE,{
+begin:/\/: (?=\d+\s*[,\]])/,relevance:0,contains:[s]},{className:"regexp",
+illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,a],variants:[{begin:"/",end:"/[a-z]*"
+},{begin:"%r\\[",end:"\\][a-z]*"}]}],relevance:0}];return a.contains=t,{
+name:"Elixir",keywords:i,contains:t}}})());
+hljs.registerLanguage("elm",(()=>{"use strict";return e=>{const n={
+variants:[e.COMMENT("--","$"),e.COMMENT(/\{-/,/-\}/,{contains:["self"]})]},i={
+className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},s={begin:"\\(",end:"\\)",
+illegal:'"',contains:[{className:"type",
+begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},n]};return{name:"Elm",
+keywords:"let in if then else case of where module import exposing type alias as infix infixl infixr port effect command subscription",
+contains:[{beginKeywords:"port effect module",end:"exposing",
+keywords:"port effect module where command subscription exposing",
+contains:[s,n],illegal:"\\W\\.|;"},{begin:"import",end:"$",
+keywords:"import as exposing",contains:[s,n],illegal:"\\W\\.|;"},{begin:"type",
+end:"$",keywords:"type alias",contains:[i,s,{begin:/\{/,end:/\}/,
+contains:s.contains},n]},{beginKeywords:"infix infixl infixr",end:"$",
+contains:[e.C_NUMBER_MODE,n]},{begin:"port",end:"$",keywords:"port",contains:[n]
+},{className:"string",begin:"'\\\\?.",end:"'",illegal:"."
+},e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,i,e.inherit(e.TITLE_MODE,{
+begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}],illegal:/;/}}})());
+hljs.registerLanguage("ruby",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{
+const a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",i={
+keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor __FILE__",
+built_in:"proc lambda",literal:"true false nil"},s={className:"doctag",
+begin:"@[A-Za-z]+"},r={begin:"#<",end:">"},b=[n.COMMENT("#","$",{contains:[s]
+}),n.COMMENT("^=begin","^=end",{contains:[s],relevance:10
+}),n.COMMENT("^__END__","\\n$")],c={className:"subst",begin:/#\{/,end:/\}/,
+keywords:i},t={className:"string",contains:[n.BACKSLASH_ESCAPE,c],variants:[{
+begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,
+end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{
+begin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,
+end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{
+begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{
+begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{
+begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{
+begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{
+begin:/<<[-~]?'?(\w+)\n(?:[^\n]*\n)*?\s*\1\b/,returnBegin:!0,contains:[{
+begin:/<<[-~]?'?/},n.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,
+contains:[n.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",d={className:"number",
+relevance:0,variants:[{
+begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{
+begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"
+},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{
+begin:"\\b0(_?[0-7])+r?i?\\b"}]},l={className:"params",begin:"\\(",end:"\\)",
+endsParent:!0,keywords:i},o=[t,{className:"class",beginKeywords:"class module",
+end:"$|;",illegal:/=/,contains:[n.inherit(n.TITLE_MODE,{
+begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|!)?"}),{begin:"<\\s*",contains:[{
+begin:"("+n.IDENT_RE+"::)?"+n.IDENT_RE,relevance:0}]}].concat(b)},{
+className:"function",begin:e(/def\s*/,(_=a+"\\s*(\\(|;|$)",e("(?=",_,")"))),
+relevance:0,keywords:"def",end:"$|;",contains:[n.inherit(n.TITLE_MODE,{begin:a
+}),l].concat(b)},{begin:n.IDENT_RE+"::"},{className:"symbol",
+begin:n.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",
+begin:":(?!\\s)",contains:[t,{begin:a}],relevance:0},d,{className:"variable",
+begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{
+className:"params",begin:/\|/,end:/\|/,relevance:0,keywords:i},{
+begin:"("+n.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{
+className:"regexp",contains:[n.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{
+begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(",
+end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]
+}].concat(r,b),relevance:0}].concat(r,b);var _;c.contains=o,l.contains=o
+;const E=[{begin:/^\s*=>/,starts:{end:"$",contains:o}},{className:"meta",
+begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])",
+starts:{end:"$",contains:o}}];return b.unshift(r),{name:"Ruby",
+aliases:["rb","gemspec","podspec","thor","irb"],keywords:i,illegal:/\/\*/,
+contains:[n.SHEBANG({binary:"ruby"})].concat(E).concat(b).concat(o)}}})());
+hljs.registerLanguage("erb",(()=>{"use strict";return e=>({name:"ERB",
+subLanguage:"xml",contains:[e.COMMENT("<%#","%>"),{begin:"<%[%=-]?",
+end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0}]})})());
+hljs.registerLanguage("erlang-repl",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>({name:"Erlang REPL",keywords:{
+built_in:"spawn spawn_link self",
+keyword:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"
+},contains:[{className:"meta",begin:"^[0-9]+> ",relevance:10
+},n.COMMENT("%","$"),{className:"number",
+begin:"\\b(\\d+(_\\d+)*#[a-fA-F0-9]+(_[a-fA-F0-9]+)*|\\d+(_\\d+)*(\\.\\d+(_\\d+)*)?([eE][-+]?\\d+)?)",
+relevance:0},n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,{
+begin:e(/\?(::)?/,/([A-Z]\w*)/,/((::)[A-Z]\w*)*/)},{begin:"->"},{begin:"ok"},{
+begin:"!"},{
+begin:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",
+relevance:0},{begin:"[A-Z][a-zA-Z0-9_']*",relevance:0}]})})());
+hljs.registerLanguage("erlang",(()=>{"use strict";return e=>{
+const n="[a-z'][a-zA-Z0-9_']*",r="("+n+":"+n+"|"+n+")",a={
+keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",
+literal:"false true"},i=e.COMMENT("%","$"),s={className:"number",
+begin:"\\b(\\d+(_\\d+)*#[a-fA-F0-9]+(_[a-fA-F0-9]+)*|\\d+(_\\d+)*(\\.\\d+(_\\d+)*)?([eE][-+]?\\d+)?)",
+relevance:0},c={begin:"fun\\s+"+n+"/\\d+"},t={begin:r+"\\(",end:"\\)",
+returnBegin:!0,relevance:0,contains:[{begin:r,relevance:0},{begin:"\\(",
+end:"\\)",endsWithParent:!0,returnEnd:!0,relevance:0}]},d={begin:/\{/,end:/\}/,
+relevance:0},o={begin:"\\b_([A-Z][A-Za-z0-9_]*)?",relevance:0},l={
+begin:"[A-Z][a-zA-Z0-9_]*",relevance:0},b={begin:"#"+e.UNDERSCORE_IDENT_RE,
+relevance:0,returnBegin:!0,contains:[{begin:"#"+e.UNDERSCORE_IDENT_RE,
+relevance:0},{begin:/\{/,end:/\}/,relevance:0}]},g={
+beginKeywords:"fun receive if try case",end:"end",keywords:a}
+;g.contains=[i,c,e.inherit(e.APOS_STRING_MODE,{className:""
+}),g,t,e.QUOTE_STRING_MODE,s,d,o,l,b]
+;const E=[i,c,g,t,e.QUOTE_STRING_MODE,s,d,o,l,b]
+;t.contains[1].contains=E,d.contains=E,b.contains[1].contains=E;const u={
+className:"params",begin:"\\(",end:"\\)",contains:E};return{name:"Erlang",
+aliases:["erl"],keywords:a,illegal:"(</|\\*=|\\+=|-=|/\\*|\\*/|\\(\\*|\\*\\))",
+contains:[{className:"function",begin:"^"+n+"\\s*\\(",end:"->",returnBegin:!0,
+illegal:"\\(|#|//|/\\*|\\\\|:|;",contains:[u,e.inherit(e.TITLE_MODE,{begin:n})],
+starts:{end:";|\\.",keywords:a,contains:E}},i,{begin:"^-",end:"\\.",relevance:0,
+excludeEnd:!0,returnBegin:!0,keywords:{$pattern:"-"+e.IDENT_RE,
+keyword:["-module","-record","-undef","-export","-ifdef","-ifndef","-author","-copyright","-doc","-vsn","-import","-include","-include_lib","-compile","-define","-else","-endif","-file","-behaviour","-behavior","-spec"].map((e=>e+"|1.5")).join(" ")
+},contains:[u]},s,e.QUOTE_STRING_MODE,b,o,l,d,{begin:/\.$/}]}}})());
+hljs.registerLanguage("excel",(()=>{"use strict";return E=>({
+name:"Excel formulae",aliases:["xlsx","xls"],case_insensitive:!0,keywords:{
+$pattern:/[a-zA-Z][\w\.]*/,
+built_in:"ABS ACCRINT ACCRINTM ACOS ACOSH ACOT ACOTH AGGREGATE ADDRESS AMORDEGRC AMORLINC AND ARABIC AREAS ASC ASIN ASINH ATAN ATAN2 ATANH AVEDEV AVERAGE AVERAGEA AVERAGEIF AVERAGEIFS BAHTTEXT BASE BESSELI BESSELJ BESSELK BESSELY BETADIST BETA.DIST BETAINV BETA.INV BIN2DEC BIN2HEX BIN2OCT BINOMDIST BINOM.DIST BINOM.DIST.RANGE BINOM.INV BITAND BITLSHIFT BITOR BITRSHIFT BITXOR CALL CEILING CEILING.MATH CEILING.PRECISE CELL CHAR CHIDIST CHIINV CHITEST CHISQ.DIST CHISQ.DIST.RT CHISQ.INV CHISQ.INV.RT CHISQ.TEST CHOOSE CLEAN CODE COLUMN COLUMNS COMBIN COMBINA COMPLEX CONCAT CONCATENATE CONFIDENCE CONFIDENCE.NORM CONFIDENCE.T CONVERT CORREL COS COSH COT COTH COUNT COUNTA COUNTBLANK COUNTIF COUNTIFS COUPDAYBS COUPDAYS COUPDAYSNC COUPNCD COUPNUM COUPPCD COVAR COVARIANCE.P COVARIANCE.S CRITBINOM CSC CSCH CUBEKPIMEMBER CUBEMEMBER CUBEMEMBERPROPERTY CUBERANKEDMEMBER CUBESET CUBESETCOUNT CUBEVALUE CUMIPMT CUMPRINC DATE DATEDIF DATEVALUE DAVERAGE DAY DAYS DAYS360 DB DBCS DCOUNT DCOUNTA DDB DEC2BIN DEC2HEX DEC2OCT DECIMAL DEGREES DELTA DEVSQ DGET DISC DMAX DMIN DOLLAR DOLLARDE DOLLARFR DPRODUCT DSTDEV DSTDEVP DSUM DURATION DVAR DVARP EDATE EFFECT ENCODEURL EOMONTH ERF ERF.PRECISE ERFC ERFC.PRECISE ERROR.TYPE EUROCONVERT EVEN EXACT EXP EXPON.DIST EXPONDIST FACT FACTDOUBLE FALSE|0 F.DIST FDIST F.DIST.RT FILTERXML FIND FINDB F.INV F.INV.RT FINV FISHER FISHERINV FIXED FLOOR FLOOR.MATH FLOOR.PRECISE FORECAST FORECAST.ETS FORECAST.ETS.CONFINT FORECAST.ETS.SEASONALITY FORECAST.ETS.STAT FORECAST.LINEAR FORMULATEXT FREQUENCY F.TEST FTEST FV FVSCHEDULE GAMMA GAMMA.DIST GAMMADIST GAMMA.INV GAMMAINV GAMMALN GAMMALN.PRECISE GAUSS GCD GEOMEAN GESTEP GETPIVOTDATA GROWTH HARMEAN HEX2BIN HEX2DEC HEX2OCT HLOOKUP HOUR HYPERLINK HYPGEOM.DIST HYPGEOMDIST IF IFERROR IFNA IFS IMABS IMAGINARY IMARGUMENT IMCONJUGATE IMCOS IMCOSH IMCOT IMCSC IMCSCH IMDIV IMEXP IMLN IMLOG10 IMLOG2 IMPOWER IMPRODUCT IMREAL IMSEC IMSECH IMSIN IMSINH IMSQRT IMSUB IMSUM IMTAN INDEX INDIRECT INFO INT INTERCEPT INTRATE IPMT IRR ISBLANK ISERR ISERROR ISEVEN ISFORMULA ISLOGICAL ISNA ISNONTEXT ISNUMBER ISODD ISREF ISTEXT ISO.CEILING ISOWEEKNUM ISPMT JIS KURT LARGE LCM LEFT LEFTB LEN LENB LINEST LN LOG LOG10 LOGEST LOGINV LOGNORM.DIST LOGNORMDIST LOGNORM.INV LOOKUP LOWER MATCH MAX MAXA MAXIFS MDETERM MDURATION MEDIAN MID MIDBs MIN MINIFS MINA MINUTE MINVERSE MIRR MMULT MOD MODE MODE.MULT MODE.SNGL MONTH MROUND MULTINOMIAL MUNIT N NA NEGBINOM.DIST NEGBINOMDIST NETWORKDAYS NETWORKDAYS.INTL NOMINAL NORM.DIST NORMDIST NORMINV NORM.INV NORM.S.DIST NORMSDIST NORM.S.INV NORMSINV NOT NOW NPER NPV NUMBERVALUE OCT2BIN OCT2DEC OCT2HEX ODD ODDFPRICE ODDFYIELD ODDLPRICE ODDLYIELD OFFSET OR PDURATION PEARSON PERCENTILE.EXC PERCENTILE.INC PERCENTILE PERCENTRANK.EXC PERCENTRANK.INC PERCENTRANK PERMUT PERMUTATIONA PHI PHONETIC PI PMT POISSON.DIST POISSON POWER PPMT PRICE PRICEDISC PRICEMAT PROB PRODUCT PROPER PV QUARTILE QUARTILE.EXC QUARTILE.INC QUOTIENT RADIANS RAND RANDBETWEEN RANK.AVG RANK.EQ RANK RATE RECEIVED REGISTER.ID REPLACE REPLACEB REPT RIGHT RIGHTB ROMAN ROUND ROUNDDOWN ROUNDUP ROW ROWS RRI RSQ RTD SEARCH SEARCHB SEC SECH SECOND SERIESSUM SHEET SHEETS SIGN SIN SINH SKEW SKEW.P SLN SLOPE SMALL SQL.REQUEST SQRT SQRTPI STANDARDIZE STDEV STDEV.P STDEV.S STDEVA STDEVP STDEVPA STEYX SUBSTITUTE SUBTOTAL SUM SUMIF SUMIFS SUMPRODUCT SUMSQ SUMX2MY2 SUMX2PY2 SUMXMY2 SWITCH SYD T TAN TANH TBILLEQ TBILLPRICE TBILLYIELD T.DIST T.DIST.2T T.DIST.RT TDIST TEXT TEXTJOIN TIME TIMEVALUE T.INV T.INV.2T TINV TODAY TRANSPOSE TREND TRIM TRIMMEAN TRUE|0 TRUNC T.TEST TTEST TYPE UNICHAR UNICODE UPPER VALUE VAR VAR.P VAR.S VARA VARP VARPA VDB VLOOKUP WEBSERVICE WEEKDAY WEEKNUM WEIBULL WEIBULL.DIST WORKDAY WORKDAY.INTL XIRR XNPV XOR YEAR YEARFRAC YIELD YIELDDISC YIELDMAT Z.TEST ZTEST"
+},contains:[{begin:/^=/,end:/[^=]/,returnEnd:!0,illegal:/=/,relevance:10},{
+className:"symbol",begin:/\b[A-Z]{1,2}\d+\b/,end:/[^\d]/,excludeEnd:!0,
+relevance:0},{className:"symbol",begin:/[A-Z]{0,2}\d*:[A-Z]{0,2}\d*/,relevance:0
+},E.BACKSLASH_ESCAPE,E.QUOTE_STRING_MODE,{className:"number",
+begin:E.NUMBER_RE+"(%)?",relevance:0},E.COMMENT(/\bN\(/,/\)/,{excludeBegin:!0,
+excludeEnd:!0,illegal:/\n/})]})})());
+hljs.registerLanguage("fix",(()=>{"use strict";return e=>({name:"FIX",
+contains:[{begin:/[^\u2401\u0001]+/,end:/[\u2401\u0001]/,excludeEnd:!0,
+returnBegin:!0,returnEnd:!1,contains:[{begin:/([^\u2401\u0001=]+)/,
+end:/=([^\u2401\u0001=]+)/,returnEnd:!0,returnBegin:!1,className:"attr"},{
+begin:/=/,end:/([\u2401\u0001])/,excludeEnd:!0,excludeBegin:!0,
+className:"string"}]}],case_insensitive:!0})})());
+hljs.registerLanguage("flix",(()=>{"use strict";return e=>({name:"Flix",
+keywords:{literal:"true false",
+keyword:"case class def else enum if impl import in lat rel index let match namespace switch type yield with"
+},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",
+begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},{className:"string",variants:[{begin:'"',
+end:'"'}]},{className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,
+excludeEnd:!0,contains:[{className:"title",relevance:0,
+begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/
+}]},e.C_NUMBER_MODE]})})());
+hljs.registerLanguage("fortran",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const a={variants:[n.COMMENT("!","$",{relevance:0
+}),n.COMMENT("^C[ ]","$",{relevance:0}),n.COMMENT("^C$","$",{relevance:0})]
+},t=/(_[a-z_\d]+)?/,i=/([de][+-]?\d+)?/,c={className:"number",variants:[{
+begin:e(/\b\d+/,/\.(\d*)/,i,t)},{begin:e(/\b\d+/,i,t)},{begin:e(/\.\d+/,i,t)}],
+relevance:0},o={className:"function",
+beginKeywords:"subroutine function program",illegal:"[${=\\n]",
+contains:[n.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]}
+;return{name:"Fortran",case_insensitive:!0,aliases:["f90","f95"],keywords:{
+literal:".False. .True.",
+keyword:"kind do concurrent local shared while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then block endblock endassociate public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure impure integer real character complex logical codimension dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",
+built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image sync change team co_broadcast co_max co_min co_sum co_reduce"
+},illegal:/\/\*/,contains:[{className:"string",relevance:0,
+variants:[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE]},o,{begin:/^C\s*=(?!=)/,
+relevance:0},a,c]}}})());
+hljs.registerLanguage("fsharp",(()=>{"use strict";return e=>{const n={begin:"<",
+end:">",contains:[e.inherit(e.TITLE_MODE,{begin:/'[a-zA-Z0-9_]+/})]};return{
+name:"F#",aliases:["fs"],
+keywords:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",
+illegal:/\/\*/,contains:[{className:"keyword",begin:/\b(yield|return|let|do)!/
+},{className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},{
+className:"string",begin:'"""',end:'"""'},e.COMMENT("\\(\\*(\\s)","\\*\\)",{
+contains:["self"]}),{className:"class",beginKeywords:"type",end:"\\(|=|$",
+excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE,n]},{className:"meta",
+begin:"\\[<",end:">\\]",relevance:10},{className:"symbol",
+begin:"\\B('[A-Za-z])\\b",contains:[e.BACKSLASH_ESCAPE]
+},e.C_LINE_COMMENT_MODE,e.inherit(e.QUOTE_STRING_MODE,{illegal:null
+}),e.C_NUMBER_MODE]}}})());
+hljs.registerLanguage("gams",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const a={
+keyword:"abort acronym acronyms alias all and assign binary card diag display else eq file files for free ge gt if integer le loop lt maximizing minimizing model models ne negative no not option options or ord positive prod put putpage puttl repeat sameas semicont semiint smax smin solve sos1 sos2 sum system table then until using while xor yes",
+literal:"eps inf na",
+built_in:"abs arccos arcsin arctan arctan2 Beta betaReg binomial ceil centropy cos cosh cvPower div div0 eDist entropy errorf execSeed exp fact floor frac gamma gammaReg log logBeta logGamma log10 log2 mapVal max min mod ncpCM ncpF ncpVUpow ncpVUsin normal pi poly power randBinomial randLinear randTriangle round rPower sigmoid sign signPower sin sinh slexp sllog10 slrec sqexp sqlog10 sqr sqrec sqrt tan tanh trunc uniform uniformInt vcPower bool_and bool_eqv bool_imp bool_not bool_or bool_xor ifThen rel_eq rel_ge rel_gt rel_le rel_lt rel_ne gday gdow ghour gleap gmillisec gminute gmonth gsecond gyear jdate jnow jstart jtime errorLevel execError gamsRelease gamsVersion handleCollect handleDelete handleStatus handleSubmit heapFree heapLimit heapSize jobHandle jobKill jobStatus jobTerminate licenseLevel licenseStatus maxExecError sleep timeClose timeComp timeElapsed timeExec timeStart"
+},i={className:"symbol",variants:[{begin:/=[lgenxc]=/},{begin:/\$/}]},s={
+className:"comment",variants:[{begin:"'",end:"'"},{begin:'"',end:'"'}],
+illegal:"\\n",contains:[n.BACKSLASH_ESCAPE]},o={begin:"/",end:"/",keywords:a,
+contains:[s,n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,n.C_NUMBER_MODE]
+},t=/[a-z0-9&#*=?@\\><:,()$[\]_.{}!+%^-]+/,r={
+begin:/[a-z][a-z0-9_]*(\([a-z0-9_, ]*\))?[ \t]+/,excludeBegin:!0,end:"$",
+endsWithParent:!0,contains:[s,o,{className:"comment",
+begin:e(t,(l=e(/[ ]+/,t),e("(",l,")*"))),relevance:0}]};var l;return{
+name:"GAMS",aliases:["gms"],case_insensitive:!0,keywords:a,
+contains:[n.COMMENT(/^\$ontext/,/^\$offtext/),{className:"meta",
+begin:"^\\$[a-z0-9]+",end:"$",returnBegin:!0,contains:[{
+className:"meta-keyword",begin:"^\\$[a-z0-9]+"}]
+},n.COMMENT("^\\*","$"),n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{
+beginKeywords:"set sets parameter parameters variable variables scalar scalars equation equations",
+end:";",
+contains:[n.COMMENT("^\\*","$"),n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,o,r]
+},{beginKeywords:"table",end:";",returnBegin:!0,contains:[{
+beginKeywords:"table",end:"$",contains:[r]
+},n.COMMENT("^\\*","$"),n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,n.C_NUMBER_MODE]
+},{className:"function",begin:/^[a-z][a-z0-9_,\-+' ()$]+\.{2}/,returnBegin:!0,
+contains:[{className:"title",begin:/^[a-z0-9_]+/},{className:"params",
+begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0},i]},n.C_NUMBER_MODE,i]}}
+})());
+hljs.registerLanguage("gauss",(()=>{"use strict";return e=>{const t={
+keyword:"bool break call callexe checkinterrupt clear clearg closeall cls comlog compile continue create debug declare delete disable dlibrary dllcall do dos ed edit else elseif enable end endfor endif endp endo errorlog errorlogat expr external fn for format goto gosub graph if keyword let lib library line load loadarray loadexe loadf loadk loadm loadp loads loadx local locate loopnextindex lprint lpwidth lshow matrix msym ndpclex new open output outwidth plot plotsym pop prcsn print printdos proc push retp return rndcon rndmod rndmult rndseed run save saveall screen scroll setarray show sparse stop string struct system trace trap threadfor threadendfor threadbegin threadjoin threadstat threadend until use while winprint ne ge le gt lt and xor or not eq eqv",
+built_in:"abs acf aconcat aeye amax amean AmericanBinomCall AmericanBinomCall_Greeks AmericanBinomCall_ImpVol AmericanBinomPut AmericanBinomPut_Greeks AmericanBinomPut_ImpVol AmericanBSCall AmericanBSCall_Greeks AmericanBSCall_ImpVol AmericanBSPut AmericanBSPut_Greeks AmericanBSPut_ImpVol amin amult annotationGetDefaults annotationSetBkd annotationSetFont annotationSetLineColor annotationSetLineStyle annotationSetLineThickness annualTradingDays arccos arcsin areshape arrayalloc arrayindex arrayinit arraytomat asciiload asclabel astd astds asum atan atan2 atranspose axmargin balance band bandchol bandcholsol bandltsol bandrv bandsolpd bar base10 begwind besselj bessely beta box boxcox cdfBeta cdfBetaInv cdfBinomial cdfBinomialInv cdfBvn cdfBvn2 cdfBvn2e cdfCauchy cdfCauchyInv cdfChic cdfChii cdfChinc cdfChincInv cdfExp cdfExpInv cdfFc cdfFnc cdfFncInv cdfGam cdfGenPareto cdfHyperGeo cdfLaplace cdfLaplaceInv cdfLogistic cdfLogisticInv cdfmControlCreate cdfMvn cdfMvn2e cdfMvnce cdfMvne cdfMvt2e cdfMvtce cdfMvte cdfN cdfN2 cdfNc cdfNegBinomial cdfNegBinomialInv cdfNi cdfPoisson cdfPoissonInv cdfRayleigh cdfRayleighInv cdfTc cdfTci cdfTnc cdfTvn cdfWeibull cdfWeibullInv cdir ceil ChangeDir chdir chiBarSquare chol choldn cholsol cholup chrs close code cols colsf combinate combinated complex con cond conj cons ConScore contour conv convertsatostr convertstrtosa corrm corrms corrvc corrx corrxs cos cosh counts countwts crossprd crout croutp csrcol csrlin csvReadM csvReadSA cumprodc cumsumc curve cvtos datacreate datacreatecomplex datalist dataload dataloop dataopen datasave date datestr datestring datestrymd dayinyr dayofweek dbAddDatabase dbClose dbCommit dbCreateQuery dbExecQuery dbGetConnectOptions dbGetDatabaseName dbGetDriverName dbGetDrivers dbGetHostName dbGetLastErrorNum dbGetLastErrorText dbGetNumericalPrecPolicy dbGetPassword dbGetPort dbGetTableHeaders dbGetTables dbGetUserName dbHasFeature dbIsDriverAvailable dbIsOpen dbIsOpenError dbOpen dbQueryBindValue dbQueryClear dbQueryCols dbQueryExecPrepared dbQueryFetchAllM dbQueryFetchAllSA dbQueryFetchOneM dbQueryFetchOneSA dbQueryFinish dbQueryGetBoundValue dbQueryGetBoundValues dbQueryGetField dbQueryGetLastErrorNum dbQueryGetLastErrorText dbQueryGetLastInsertID dbQueryGetLastQuery dbQueryGetPosition dbQueryIsActive dbQueryIsForwardOnly dbQueryIsNull dbQueryIsSelect dbQueryIsValid dbQueryPrepare dbQueryRows dbQuerySeek dbQuerySeekFirst dbQuerySeekLast dbQuerySeekNext dbQuerySeekPrevious dbQuerySetForwardOnly dbRemoveDatabase dbRollback dbSetConnectOptions dbSetDatabaseName dbSetHostName dbSetNumericalPrecPolicy dbSetPort dbSetUserName dbTransaction DeleteFile delif delrows denseToSp denseToSpRE denToZero design det detl dfft dffti diag diagrv digamma doswin DOSWinCloseall DOSWinOpen dotfeq dotfeqmt dotfge dotfgemt dotfgt dotfgtmt dotfle dotflemt dotflt dotfltmt dotfne dotfnemt draw drop dsCreate dstat dstatmt dstatmtControlCreate dtdate dtday dttime dttodtv dttostr dttoutc dtvnormal dtvtodt dtvtoutc dummy dummybr dummydn eig eigh eighv eigv elapsedTradingDays endwind envget eof eqSolve eqSolvemt eqSolvemtControlCreate eqSolvemtOutCreate eqSolveset erf erfc erfccplx erfcplx error etdays ethsec etstr EuropeanBinomCall EuropeanBinomCall_Greeks EuropeanBinomCall_ImpVol EuropeanBinomPut EuropeanBinomPut_Greeks EuropeanBinomPut_ImpVol EuropeanBSCall EuropeanBSCall_Greeks EuropeanBSCall_ImpVol EuropeanBSPut EuropeanBSPut_Greeks EuropeanBSPut_ImpVol exctsmpl exec execbg exp extern eye fcheckerr fclearerr feq feqmt fflush fft ffti fftm fftmi fftn fge fgemt fgets fgetsa fgetsat fgetst fgt fgtmt fileinfo filesa fle flemt floor flt fltmt fmod fne fnemt fonts fopen formatcv formatnv fputs fputst fseek fstrerror ftell ftocv ftos ftostrC gamma gammacplx gammaii gausset gdaAppend gdaCreate gdaDStat gdaDStatMat gdaGetIndex gdaGetName gdaGetNames gdaGetOrders gdaGetType gdaGetTypes gdaGetVarInfo gdaIsCplx gdaLoad gdaPack gdaRead gdaReadByIndex gdaReadSome gdaReadSparse gdaReadStruct gdaReportVarInfo gdaSave gdaUpdate gdaUpdateAndPack gdaVars gdaWrite gdaWrite32 gdaWriteSome getarray getdims getf getGAUSShome getmatrix getmatrix4D getname getnamef getNextTradingDay getNextWeekDay getnr getorders getpath getPreviousTradingDay getPreviousWeekDay getRow getscalar3D getscalar4D getTrRow getwind glm gradcplx gradMT gradMTm gradMTT gradMTTm gradp graphprt graphset hasimag header headermt hess hessMT hessMTg hessMTgw hessMTm hessMTmw hessMTT hessMTTg hessMTTgw hessMTTm hessMTw hessp hist histf histp hsec imag indcv indexcat indices indices2 indicesf indicesfn indnv indsav integrate1d integrateControlCreate intgrat2 intgrat3 inthp1 inthp2 inthp3 inthp4 inthpControlCreate intquad1 intquad2 intquad3 intrleav intrleavsa intrsect intsimp inv invpd invswp iscplx iscplxf isden isinfnanmiss ismiss key keyav keyw lag lag1 lagn lapEighb lapEighi lapEighvb lapEighvi lapgEig lapgEigh lapgEighv lapgEigv lapgSchur lapgSvdcst lapgSvds lapgSvdst lapSvdcusv lapSvds lapSvdusv ldlp ldlsol linSolve listwise ln lncdfbvn lncdfbvn2 lncdfmvn lncdfn lncdfn2 lncdfnc lnfact lngammacplx lnpdfmvn lnpdfmvt lnpdfn lnpdft loadd loadstruct loadwind loess loessmt loessmtControlCreate log loglog logx logy lower lowmat lowmat1 ltrisol lu lusol machEpsilon make makevars makewind margin matalloc matinit mattoarray maxbytes maxc maxindc maxv maxvec mbesselei mbesselei0 mbesselei1 mbesseli mbesseli0 mbesseli1 meanc median mergeby mergevar minc minindc minv miss missex missrv moment momentd movingave movingaveExpwgt movingaveWgt nextindex nextn nextnevn nextwind ntos null null1 numCombinations ols olsmt olsmtControlCreate olsqr olsqr2 olsqrmt ones optn optnevn orth outtyp pacf packedToSp packr parse pause pdfCauchy pdfChi pdfExp pdfGenPareto pdfHyperGeo pdfLaplace pdfLogistic pdfn pdfPoisson pdfRayleigh pdfWeibull pi pinv pinvmt plotAddArrow plotAddBar plotAddBox plotAddHist plotAddHistF plotAddHistP plotAddPolar plotAddScatter plotAddShape plotAddTextbox plotAddTS plotAddXY plotArea plotBar plotBox plotClearLayout plotContour plotCustomLayout plotGetDefaults plotHist plotHistF plotHistP plotLayout plotLogLog plotLogX plotLogY plotOpenWindow plotPolar plotSave plotScatter plotSetAxesPen plotSetBar plotSetBarFill plotSetBarStacked plotSetBkdColor plotSetFill plotSetGrid plotSetLegend plotSetLineColor plotSetLineStyle plotSetLineSymbol plotSetLineThickness plotSetNewWindow plotSetTitle plotSetWhichYAxis plotSetXAxisShow plotSetXLabel plotSetXRange plotSetXTicInterval plotSetXTicLabel plotSetYAxisShow plotSetYLabel plotSetYRange plotSetZAxisShow plotSetZLabel plotSurface plotTS plotXY polar polychar polyeval polygamma polyint polymake polymat polymroot polymult polyroot pqgwin previousindex princomp printfm printfmt prodc psi putarray putf putvals pvCreate pvGetIndex pvGetParNames pvGetParVector pvLength pvList pvPack pvPacki pvPackm pvPackmi pvPacks pvPacksi pvPacksm pvPacksmi pvPutParVector pvTest pvUnpack QNewton QNewtonmt QNewtonmtControlCreate QNewtonmtOutCreate QNewtonSet QProg QProgmt QProgmtInCreate qqr qqre qqrep qr qre qrep qrsol qrtsol qtyr qtyre qtyrep quantile quantiled qyr qyre qyrep qz rank rankindx readr real reclassify reclassifyCuts recode recserar recsercp recserrc rerun rescale reshape rets rev rfft rffti rfftip rfftn rfftnp rfftp rndBernoulli rndBeta rndBinomial rndCauchy rndChiSquare rndCon rndCreateState rndExp rndGamma rndGeo rndGumbel rndHyperGeo rndi rndKMbeta rndKMgam rndKMi rndKMn rndKMnb rndKMp rndKMu rndKMvm rndLaplace rndLCbeta rndLCgam rndLCi rndLCn rndLCnb rndLCp rndLCu rndLCvm rndLogNorm rndMTu rndMVn rndMVt rndn rndnb rndNegBinomial rndp rndPoisson rndRayleigh rndStateSkip rndu rndvm rndWeibull rndWishart rotater round rows rowsf rref sampleData satostrC saved saveStruct savewind scale scale3d scalerr scalinfnanmiss scalmiss schtoc schur searchsourcepath seekr select selif seqa seqm setdif setdifsa setvars setvwrmode setwind shell shiftr sin singleindex sinh sleep solpd sortc sortcc sortd sorthc sorthcc sortind sortindc sortmc sortr sortrc spBiconjGradSol spChol spConjGradSol spCreate spDenseSubmat spDiagRvMat spEigv spEye spLDL spline spLU spNumNZE spOnes spreadSheetReadM spreadSheetReadSA spreadSheetWrite spScale spSubmat spToDense spTrTDense spTScalar spZeros sqpSolve sqpSolveMT sqpSolveMTControlCreate sqpSolveMTlagrangeCreate sqpSolveMToutCreate sqpSolveSet sqrt statements stdc stdsc stocv stof strcombine strindx strlen strput strrindx strsect strsplit strsplitPad strtodt strtof strtofcplx strtriml strtrimr strtrunc strtruncl strtruncpad strtruncr submat subscat substute subvec sumc sumr surface svd svd1 svd2 svdcusv svds svdusv sysstate tab tan tanh tempname time timedt timestr timeutc title tkf2eps tkf2ps tocart todaydt toeplitz token topolar trapchk trigamma trimr trunc type typecv typef union unionsa uniqindx uniqindxsa unique uniquesa upmat upmat1 upper utctodt utctodtv utrisol vals varCovMS varCovXS varget vargetl varmall varmares varput varputl vartypef vcm vcms vcx vcxs vec vech vecr vector vget view viewxyz vlist vnamecv volume vput vread vtypecv wait waitc walkindex where window writer xlabel xlsGetSheetCount xlsGetSheetSize xlsGetSheetTypes xlsMakeRange xlsReadM xlsReadSA xlsWrite xlsWriteM xlsWriteSA xpnd xtics xy xyz ylabel ytics zeros zeta zlabel ztics cdfEmpirical dot h5create h5open h5read h5readAttribute h5write h5writeAttribute ldl plotAddErrorBar plotAddSurface plotCDFEmpirical plotSetColormap plotSetContourLabels plotSetLegendFont plotSetTextInterpreter plotSetXTicCount plotSetYTicCount plotSetZLevels powerm strjoin sylvester strtrim",
+literal:"DB_AFTER_LAST_ROW DB_ALL_TABLES DB_BATCH_OPERATIONS DB_BEFORE_FIRST_ROW DB_BLOB DB_EVENT_NOTIFICATIONS DB_FINISH_QUERY DB_HIGH_PRECISION DB_LAST_INSERT_ID DB_LOW_PRECISION_DOUBLE DB_LOW_PRECISION_INT32 DB_LOW_PRECISION_INT64 DB_LOW_PRECISION_NUMBERS DB_MULTIPLE_RESULT_SETS DB_NAMED_PLACEHOLDERS DB_POSITIONAL_PLACEHOLDERS DB_PREPARED_QUERIES DB_QUERY_SIZE DB_SIMPLE_LOCKING DB_SYSTEM_TABLES DB_TABLES DB_TRANSACTIONS DB_UNICODE DB_VIEWS __STDIN __STDOUT __STDERR __FILE_DIR"
+},a=e.COMMENT("@","@"),r={className:"meta",begin:"#",end:"$",keywords:{
+"meta-keyword":"define definecs|10 undef ifdef ifndef iflight ifdllcall ifmac ifos2win ifunix else endif lineson linesoff srcfile srcline"
+},contains:[{begin:/\\\n/,relevance:0},{beginKeywords:"include",end:"$",
+keywords:{"meta-keyword":"include"},contains:[{className:"meta-string",
+begin:'"',end:'"',illegal:"\\n"}]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a]},n={begin:/\bstruct\s+/,
+end:/\s/,keywords:"struct",contains:[{className:"type",
+begin:e.UNDERSCORE_IDENT_RE,relevance:0}]},s=[{className:"params",begin:/\(/,
+end:/\)/,excludeBegin:!0,excludeEnd:!0,endsWithParent:!0,relevance:0,contains:[{
+className:"literal",begin:/\.\.\./},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,a,n]
+}],o={className:"title",begin:e.UNDERSCORE_IDENT_RE,relevance:0},d=(t,r,n)=>{
+const d=e.inherit({className:"function",beginKeywords:t,end:r,excludeEnd:!0,
+contains:[].concat(s)},n||{})
+;return d.contains.push(o),d.contains.push(e.C_NUMBER_MODE),
+d.contains.push(e.C_BLOCK_COMMENT_MODE),d.contains.push(a),d},l={
+className:"built_in",begin:"\\b("+t.built_in.split(" ").join("|")+")\\b"},i={
+className:"string",begin:'"',end:'"',contains:[e.BACKSLASH_ESCAPE],relevance:0
+},c={begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,keywords:t,
+relevance:0,contains:[{beginKeywords:t.keyword},l,{className:"built_in",
+begin:e.UNDERSCORE_IDENT_RE,relevance:0}]},p={begin:/\(/,end:/\)/,relevance:0,
+keywords:{built_in:t.built_in,literal:t.literal},
+contains:[e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,a,l,c,i,"self"]}
+;return c.contains.push(p),{name:"GAUSS",aliases:["gss"],case_insensitive:!0,
+keywords:t,illegal:/(\{[%#]|[%#]\}| <- )/,
+contains:[e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{
+className:"keyword",
+begin:/\bexternal (matrix|string|array|sparse matrix|struct|proc|keyword|fn)/
+},d("proc keyword",";"),d("fn","="),{beginKeywords:"for threadfor",end:/;/,
+relevance:0,contains:[e.C_BLOCK_COMMENT_MODE,a,p]},{variants:[{
+begin:e.UNDERSCORE_IDENT_RE+"\\."+e.UNDERSCORE_IDENT_RE},{
+begin:e.UNDERSCORE_IDENT_RE+"\\s*="}],relevance:0},c,n]}}})());
+hljs.registerLanguage("gcode",(()=>{"use strict";return e=>{
+const a=e.inherit(e.C_NUMBER_MODE,{
+begin:"([-+]?((\\.\\d+)|(\\d+)(\\.\\d*)?))|"+e.C_NUMBER_RE
+}),n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.COMMENT(/\(/,/\)/),a,e.inherit(e.APOS_STRING_MODE,{
+illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{className:"name",
+begin:"([G])([0-9]+\\.?[0-9]?)"},{className:"name",
+begin:"([M])([0-9]+\\.?[0-9]?)"},{className:"attr",begin:"(VC|VS|#)",
+end:"(\\d+)"},{className:"attr",begin:"(VZOFX|VZOFY|VZOFZ)"},{
+className:"built_in",
+begin:"(ATAN|ABS|ACOS|ASIN|SIN|COS|EXP|FIX|FUP|ROUND|LN|TAN)(\\[)",contains:[a],
+end:"\\]"},{className:"symbol",variants:[{begin:"N",end:"\\d+",illegal:"\\W"}]}]
+;return{name:"G-code (ISO 6983)",aliases:["nc"],case_insensitive:!0,keywords:{
+$pattern:"[A-Z_][A-Z0-9_.]*",
+keyword:"IF DO WHILE ENDWHILE CALL ENDIF SUB ENDSUB GOTO REPEAT ENDREPEAT EQ LT GT NE GE LE OR XOR"
+},contains:[{className:"meta",begin:"%"},{className:"meta",begin:"([O])([0-9]+)"
+}].concat(n)}}})());
+hljs.registerLanguage("gherkin",(()=>{"use strict";return e=>({name:"Gherkin",
+aliases:["feature"],
+keywords:"Feature Background Ability Business Need Scenario Scenarios Scenario Outline Scenario Template Examples Given And Then But When",
+contains:[{className:"symbol",begin:"\\*",relevance:0},{className:"meta",
+begin:"@[^@\\s]+"},{begin:"\\|",end:"\\|\\w*$",contains:[{className:"string",
+begin:"[^|]+"}]},{className:"variable",begin:"<",end:">"},e.HASH_COMMENT_MODE,{
+className:"string",begin:'"""',end:'"""'},e.QUOTE_STRING_MODE]})})());
+hljs.registerLanguage("glsl",(()=>{"use strict";return e=>({name:"GLSL",
+keywords:{
+keyword:"break continue discard do else for if return while switch case default attribute binding buffer ccw centroid centroid varying coherent column_major const cw depth_any depth_greater depth_less depth_unchanged early_fragment_tests equal_spacing flat fractional_even_spacing fractional_odd_spacing highp in index inout invariant invocations isolines layout line_strip lines lines_adjacency local_size_x local_size_y local_size_z location lowp max_vertices mediump noperspective offset origin_upper_left out packed patch pixel_center_integer point_mode points precise precision quads r11f_g11f_b10f r16 r16_snorm r16f r16i r16ui r32f r32i r32ui r8 r8_snorm r8i r8ui readonly restrict rg16 rg16_snorm rg16f rg16i rg16ui rg32f rg32i rg32ui rg8 rg8_snorm rg8i rg8ui rgb10_a2 rgb10_a2ui rgba16 rgba16_snorm rgba16f rgba16i rgba16ui rgba32f rgba32i rgba32ui rgba8 rgba8_snorm rgba8i rgba8ui row_major sample shared smooth std140 std430 stream triangle_strip triangles triangles_adjacency uniform varying vertices volatile writeonly",
+type:"atomic_uint bool bvec2 bvec3 bvec4 dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 double dvec2 dvec3 dvec4 float iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray int isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow image1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D samplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 vec2 vec3 vec4 void",
built_in:"gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxComputeAtomicCounterBuffers gl_MaxComputeAtomicCounters gl_MaxComputeImageUniforms gl_MaxComputeTextureImageUnits gl_MaxComputeUniformComponents gl_MaxComputeWorkGroupCount gl_MaxComputeWorkGroupSize gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentInputVectors gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms gl_MaxVertexOutputComponents gl_MaxVertexOutputVectors gl_MaxVertexTextureImageUnits gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffset gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial gl_FrontSecondaryColor gl_GlobalInvocationID gl_InstanceID gl_InvocationID gl_Layer gl_LightModel gl_LightSource gl_LocalInvocationID gl_LocalInvocationIndex gl_ModelViewMatrix gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_Normal gl_NormalMatrix gl_NormalScale gl_NumSamples gl_NumWorkGroups gl_ObjectPlaneQ gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_Point gl_PointCoord gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter gl_TexCoord gl_TextureEnvColor gl_TextureMatrix gl_TextureMatrixInverse gl_TextureMatrixInverseTranspose gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_WorkGroupID gl_WorkGroupSize gl_in gl_out EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin asinh atan atanh atomicAdd atomicAnd atomicCompSwap atomicCounter atomicCounterDecrement atomicCounterIncrement atomicExchange atomicMax atomicMin atomicOr atomicXor barrier bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan greaterThanEqual groupMemoryBarrier imageAtomicAdd imageAtomicAnd imageAtomicCompSwap imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad imageSize imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log log2 matrixCompMult max memoryBarrier memoryBarrierAtomicCounter memoryBarrierBuffer memoryBarrierImage memoryBarrierShared min mix mod modf noise1 noise2 noise3 noise4 normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset textureProjLod textureProjLodOffset textureProjOffset textureQueryLevels textureQueryLod textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow",
-literal:"true false"},illegal:'"',contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.C_NUMBER_MODE,{className:"meta",begin:"#",end:"$"}]}}}());
-hljs.registerLanguage("gml",function(){return function(a){return{name:"GML",aliases:["gml","GML"],case_insensitive:!1,keywords:{keyword:"begin end if then else while do for break continue with until repeat exit and or xor not return mod div switch case default var globalvar enum #macro #region #endregion",built_in:"is_real is_string is_array is_undefined is_int32 is_int64 is_ptr is_vec3 is_vec4 is_matrix is_bool typeof variable_global_exists variable_global_get variable_global_set variable_instance_exists variable_instance_get variable_instance_set variable_instance_get_names array_length_1d array_length_2d array_height_2d array_equals array_create array_copy random random_range irandom irandom_range random_set_seed random_get_seed randomize randomise choose abs round floor ceil sign frac sqrt sqr exp ln log2 log10 sin cos tan arcsin arccos arctan arctan2 dsin dcos dtan darcsin darccos darctan darctan2 degtorad radtodeg power logn min max mean median clamp lerp dot_product dot_product_3d dot_product_normalised dot_product_3d_normalised dot_product_normalized dot_product_3d_normalized math_set_epsilon math_get_epsilon angle_difference point_distance_3d point_distance point_direction lengthdir_x lengthdir_y real string int64 ptr string_format chr ansi_char ord string_length string_byte_length string_pos string_copy string_char_at string_ord_at string_byte_at string_set_byte_at string_delete string_insert string_lower string_upper string_repeat string_letters string_digits string_lettersdigits string_replace string_replace_all string_count string_hash_to_newline clipboard_has_text clipboard_set_text clipboard_get_text date_current_datetime date_create_datetime date_valid_datetime date_inc_year date_inc_month date_inc_week date_inc_day date_inc_hour date_inc_minute date_inc_second date_get_year date_get_month date_get_week date_get_day date_get_hour date_get_minute date_get_second date_get_weekday date_get_day_of_year date_get_hour_of_year date_get_minute_of_year date_get_second_of_year date_year_span date_month_span date_week_span date_day_span date_hour_span date_minute_span date_second_span date_compare_datetime date_compare_date date_compare_time date_date_of date_time_of date_datetime_string date_date_string date_time_string date_days_in_month date_days_in_year date_leap_year date_is_today date_set_timezone date_get_timezone game_set_speed game_get_speed motion_set motion_add place_free place_empty place_meeting place_snapped move_random move_snap move_towards_point move_contact_solid move_contact_all move_outside_solid move_outside_all move_bounce_solid move_bounce_all move_wrap distance_to_point distance_to_object position_empty position_meeting path_start path_end mp_linear_step mp_potential_step mp_linear_step_object mp_potential_step_object mp_potential_settings mp_linear_path mp_potential_path mp_linear_path_object mp_potential_path_object mp_grid_create mp_grid_destroy mp_grid_clear_all mp_grid_clear_cell mp_grid_clear_rectangle mp_grid_add_cell mp_grid_get_cell mp_grid_add_rectangle mp_grid_add_instances mp_grid_path mp_grid_draw mp_grid_to_ds_grid collision_point collision_rectangle collision_circle collision_ellipse collision_line collision_point_list collision_rectangle_list collision_circle_list collision_ellipse_list collision_line_list instance_position_list instance_place_list point_in_rectangle point_in_triangle point_in_circle rectangle_in_rectangle rectangle_in_triangle rectangle_in_circle instance_find instance_exists instance_number instance_position instance_nearest instance_furthest instance_place instance_create_depth instance_create_layer instance_copy instance_change instance_destroy position_destroy position_change instance_id_get instance_deactivate_all instance_deactivate_object instance_deactivate_region instance_activate_all instance_activate_object instance_activate_region room_goto room_goto_previous room_goto_next room_previous room_next room_restart game_end game_restart game_load game_save game_save_buffer game_load_buffer event_perform event_user event_perform_object event_inherited show_debug_message show_debug_overlay debug_event debug_get_callstack alarm_get alarm_set font_texture_page_size keyboard_set_map keyboard_get_map keyboard_unset_map keyboard_check keyboard_check_pressed keyboard_check_released keyboard_check_direct keyboard_get_numlock keyboard_set_numlock keyboard_key_press keyboard_key_release keyboard_clear io_clear mouse_check_button mouse_check_button_pressed mouse_check_button_released mouse_wheel_up mouse_wheel_down mouse_clear draw_self draw_sprite draw_sprite_pos draw_sprite_ext draw_sprite_stretched draw_sprite_stretched_ext draw_sprite_tiled draw_sprite_tiled_ext draw_sprite_part draw_sprite_part_ext draw_sprite_general draw_clear draw_clear_alpha draw_point draw_line draw_line_width draw_rectangle draw_roundrect draw_roundrect_ext draw_triangle draw_circle draw_ellipse draw_set_circle_precision draw_arrow draw_button draw_path draw_healthbar draw_getpixel draw_getpixel_ext draw_set_colour draw_set_color draw_set_alpha draw_get_colour draw_get_color draw_get_alpha merge_colour make_colour_rgb make_colour_hsv colour_get_red colour_get_green colour_get_blue colour_get_hue colour_get_saturation colour_get_value merge_color make_color_rgb make_color_hsv color_get_red color_get_green color_get_blue color_get_hue color_get_saturation color_get_value merge_color screen_save screen_save_part draw_set_font draw_set_halign draw_set_valign draw_text draw_text_ext string_width string_height string_width_ext string_height_ext draw_text_transformed draw_text_ext_transformed draw_text_colour draw_text_ext_colour draw_text_transformed_colour draw_text_ext_transformed_colour draw_text_color draw_text_ext_color draw_text_transformed_color draw_text_ext_transformed_color draw_point_colour draw_line_colour draw_line_width_colour draw_rectangle_colour draw_roundrect_colour draw_roundrect_colour_ext draw_triangle_colour draw_circle_colour draw_ellipse_colour draw_point_color draw_line_color draw_line_width_color draw_rectangle_color draw_roundrect_color draw_roundrect_color_ext draw_triangle_color draw_circle_color draw_ellipse_color draw_primitive_begin draw_vertex draw_vertex_colour draw_vertex_color draw_primitive_end sprite_get_uvs font_get_uvs sprite_get_texture font_get_texture texture_get_width texture_get_height texture_get_uvs draw_primitive_begin_texture draw_vertex_texture draw_vertex_texture_colour draw_vertex_texture_color texture_global_scale surface_create surface_create_ext surface_resize surface_free surface_exists surface_get_width surface_get_height surface_get_texture surface_set_target surface_set_target_ext surface_reset_target surface_depth_disable surface_get_depth_disable draw_surface draw_surface_stretched draw_surface_tiled draw_surface_part draw_surface_ext draw_surface_stretched_ext draw_surface_tiled_ext draw_surface_part_ext draw_surface_general surface_getpixel surface_getpixel_ext surface_save surface_save_part surface_copy surface_copy_part application_surface_draw_enable application_get_position application_surface_enable application_surface_is_enabled display_get_width display_get_height display_get_orientation display_get_gui_width display_get_gui_height display_reset display_mouse_get_x display_mouse_get_y display_mouse_set display_set_ui_visibility window_set_fullscreen window_get_fullscreen window_set_caption window_set_min_width window_set_max_width window_set_min_height window_set_max_height window_get_visible_rects window_get_caption window_set_cursor window_get_cursor window_set_colour window_get_colour window_set_color window_get_color window_set_position window_set_size window_set_rectangle window_center window_get_x window_get_y window_get_width window_get_height window_mouse_get_x window_mouse_get_y window_mouse_set window_view_mouse_get_x window_view_mouse_get_y window_views_mouse_get_x window_views_mouse_get_y audio_listener_position audio_listener_velocity audio_listener_orientation audio_emitter_position audio_emitter_create audio_emitter_free audio_emitter_exists audio_emitter_pitch audio_emitter_velocity audio_emitter_falloff audio_emitter_gain audio_play_sound audio_play_sound_on audio_play_sound_at audio_stop_sound audio_resume_music audio_music_is_playing audio_resume_sound audio_pause_sound audio_pause_music audio_channel_num audio_sound_length audio_get_type audio_falloff_set_model audio_play_music audio_stop_music audio_master_gain audio_music_gain audio_sound_gain audio_sound_pitch audio_stop_all audio_resume_all audio_pause_all audio_is_playing audio_is_paused audio_exists audio_sound_set_track_position audio_sound_get_track_position audio_emitter_get_gain audio_emitter_get_pitch audio_emitter_get_x audio_emitter_get_y audio_emitter_get_z audio_emitter_get_vx audio_emitter_get_vy audio_emitter_get_vz audio_listener_set_position audio_listener_set_velocity audio_listener_set_orientation audio_listener_get_data audio_set_master_gain audio_get_master_gain audio_sound_get_gain audio_sound_get_pitch audio_get_name audio_sound_set_track_position audio_sound_get_track_position audio_create_stream audio_destroy_stream audio_create_sync_group audio_destroy_sync_group audio_play_in_sync_group audio_start_sync_group audio_stop_sync_group audio_pause_sync_group audio_resume_sync_group audio_sync_group_get_track_pos audio_sync_group_debug audio_sync_group_is_playing audio_debug audio_group_load audio_group_unload audio_group_is_loaded audio_group_load_progress audio_group_name audio_group_stop_all audio_group_set_gain audio_create_buffer_sound audio_free_buffer_sound audio_create_play_queue audio_free_play_queue audio_queue_sound audio_get_recorder_count audio_get_recorder_info audio_start_recording audio_stop_recording audio_sound_get_listener_mask audio_emitter_get_listener_mask audio_get_listener_mask audio_sound_set_listener_mask audio_emitter_set_listener_mask audio_set_listener_mask audio_get_listener_count audio_get_listener_info audio_system show_message show_message_async clickable_add clickable_add_ext clickable_change clickable_change_ext clickable_delete clickable_exists clickable_set_style show_question show_question_async get_integer get_string get_integer_async get_string_async get_login_async get_open_filename get_save_filename get_open_filename_ext get_save_filename_ext show_error highscore_clear highscore_add highscore_value highscore_name draw_highscore sprite_exists sprite_get_name sprite_get_number sprite_get_width sprite_get_height sprite_get_xoffset sprite_get_yoffset sprite_get_bbox_left sprite_get_bbox_right sprite_get_bbox_top sprite_get_bbox_bottom sprite_save sprite_save_strip sprite_set_cache_size sprite_set_cache_size_ext sprite_get_tpe sprite_prefetch sprite_prefetch_multi sprite_flush sprite_flush_multi sprite_set_speed sprite_get_speed_type sprite_get_speed font_exists font_get_name font_get_fontname font_get_bold font_get_italic font_get_first font_get_last font_get_size font_set_cache_size path_exists path_get_name path_get_length path_get_time path_get_kind path_get_closed path_get_precision path_get_number path_get_point_x path_get_point_y path_get_point_speed path_get_x path_get_y path_get_speed script_exists script_get_name timeline_add timeline_delete timeline_clear timeline_exists timeline_get_name timeline_moment_clear timeline_moment_add_script timeline_size timeline_max_moment object_exists object_get_name object_get_sprite object_get_solid object_get_visible object_get_persistent object_get_mask object_get_parent object_get_physics object_is_ancestor room_exists room_get_name sprite_set_offset sprite_duplicate sprite_assign sprite_merge sprite_add sprite_replace sprite_create_from_surface sprite_add_from_surface sprite_delete sprite_set_alpha_from_sprite sprite_collision_mask font_add_enable_aa font_add_get_enable_aa font_add font_add_sprite font_add_sprite_ext font_replace font_replace_sprite font_replace_sprite_ext font_delete path_set_kind path_set_closed path_set_precision path_add path_assign path_duplicate path_append path_delete path_add_point path_insert_point path_change_point path_delete_point path_clear_points path_reverse path_mirror path_flip path_rotate path_rescale path_shift script_execute object_set_sprite object_set_solid object_set_visible object_set_persistent object_set_mask room_set_width room_set_height room_set_persistent room_set_background_colour room_set_background_color room_set_view room_set_viewport room_get_viewport room_set_view_enabled room_add room_duplicate room_assign room_instance_add room_instance_clear room_get_camera room_set_camera asset_get_index asset_get_type file_text_open_from_string file_text_open_read file_text_open_write file_text_open_append file_text_close file_text_write_string file_text_write_real file_text_writeln file_text_read_string file_text_read_real file_text_readln file_text_eof file_text_eoln file_exists file_delete file_rename file_copy directory_exists directory_create directory_destroy file_find_first file_find_next file_find_close file_attributes filename_name filename_path filename_dir filename_drive filename_ext filename_change_ext file_bin_open file_bin_rewrite file_bin_close file_bin_position file_bin_size file_bin_seek file_bin_write_byte file_bin_read_byte parameter_count parameter_string environment_get_variable ini_open_from_string ini_open ini_close ini_read_string ini_read_real ini_write_string ini_write_real ini_key_exists ini_section_exists ini_key_delete ini_section_delete ds_set_precision ds_exists ds_stack_create ds_stack_destroy ds_stack_clear ds_stack_copy ds_stack_size ds_stack_empty ds_stack_push ds_stack_pop ds_stack_top ds_stack_write ds_stack_read ds_queue_create ds_queue_destroy ds_queue_clear ds_queue_copy ds_queue_size ds_queue_empty ds_queue_enqueue ds_queue_dequeue ds_queue_head ds_queue_tail ds_queue_write ds_queue_read ds_list_create ds_list_destroy ds_list_clear ds_list_copy ds_list_size ds_list_empty ds_list_add ds_list_insert ds_list_replace ds_list_delete ds_list_find_index ds_list_find_value ds_list_mark_as_list ds_list_mark_as_map ds_list_sort ds_list_shuffle ds_list_write ds_list_read ds_list_set ds_map_create ds_map_destroy ds_map_clear ds_map_copy ds_map_size ds_map_empty ds_map_add ds_map_add_list ds_map_add_map ds_map_replace ds_map_replace_map ds_map_replace_list ds_map_delete ds_map_exists ds_map_find_value ds_map_find_previous ds_map_find_next ds_map_find_first ds_map_find_last ds_map_write ds_map_read ds_map_secure_save ds_map_secure_load ds_map_secure_load_buffer ds_map_secure_save_buffer ds_map_set ds_priority_create ds_priority_destroy ds_priority_clear ds_priority_copy ds_priority_size ds_priority_empty ds_priority_add ds_priority_change_priority ds_priority_find_priority ds_priority_delete_value ds_priority_delete_min ds_priority_find_min ds_priority_delete_max ds_priority_find_max ds_priority_write ds_priority_read ds_grid_create ds_grid_destroy ds_grid_copy ds_grid_resize ds_grid_width ds_grid_height ds_grid_clear ds_grid_set ds_grid_add ds_grid_multiply ds_grid_set_region ds_grid_add_region ds_grid_multiply_region ds_grid_set_disk ds_grid_add_disk ds_grid_multiply_disk ds_grid_set_grid_region ds_grid_add_grid_region ds_grid_multiply_grid_region ds_grid_get ds_grid_get_sum ds_grid_get_max ds_grid_get_min ds_grid_get_mean ds_grid_get_disk_sum ds_grid_get_disk_min ds_grid_get_disk_max ds_grid_get_disk_mean ds_grid_value_exists ds_grid_value_x ds_grid_value_y ds_grid_value_disk_exists ds_grid_value_disk_x ds_grid_value_disk_y ds_grid_shuffle ds_grid_write ds_grid_read ds_grid_sort ds_grid_set ds_grid_get effect_create_below effect_create_above effect_clear part_type_create part_type_destroy part_type_exists part_type_clear part_type_shape part_type_sprite part_type_size part_type_scale part_type_orientation part_type_life part_type_step part_type_death part_type_speed part_type_direction part_type_gravity part_type_colour1 part_type_colour2 part_type_colour3 part_type_colour_mix part_type_colour_rgb part_type_colour_hsv part_type_color1 part_type_color2 part_type_color3 part_type_color_mix part_type_color_rgb part_type_color_hsv part_type_alpha1 part_type_alpha2 part_type_alpha3 part_type_blend part_system_create part_system_create_layer part_system_destroy part_system_exists part_system_clear part_system_draw_order part_system_depth part_system_position part_system_automatic_update part_system_automatic_draw part_system_update part_system_drawit part_system_get_layer part_system_layer part_particles_create part_particles_create_colour part_particles_create_color part_particles_clear part_particles_count part_emitter_create part_emitter_destroy part_emitter_destroy_all part_emitter_exists part_emitter_clear part_emitter_region part_emitter_burst part_emitter_stream external_call external_define external_free window_handle window_device matrix_get matrix_set matrix_build_identity matrix_build matrix_build_lookat matrix_build_projection_ortho matrix_build_projection_perspective matrix_build_projection_perspective_fov matrix_multiply matrix_transform_vertex matrix_stack_push matrix_stack_pop matrix_stack_multiply matrix_stack_set matrix_stack_clear matrix_stack_top matrix_stack_is_empty browser_input_capture os_get_config os_get_info os_get_language os_get_region os_lock_orientation display_get_dpi_x display_get_dpi_y display_set_gui_size display_set_gui_maximise display_set_gui_maximize device_mouse_dbclick_enable display_set_timing_method display_get_timing_method display_set_sleep_margin display_get_sleep_margin virtual_key_add virtual_key_hide virtual_key_delete virtual_key_show draw_enable_drawevent draw_enable_swf_aa draw_set_swf_aa_level draw_get_swf_aa_level draw_texture_flush draw_flush gpu_set_blendenable gpu_set_ztestenable gpu_set_zfunc gpu_set_zwriteenable gpu_set_lightingenable gpu_set_fog gpu_set_cullmode gpu_set_blendmode gpu_set_blendmode_ext gpu_set_blendmode_ext_sepalpha gpu_set_colorwriteenable gpu_set_colourwriteenable gpu_set_alphatestenable gpu_set_alphatestref gpu_set_alphatestfunc gpu_set_texfilter gpu_set_texfilter_ext gpu_set_texrepeat gpu_set_texrepeat_ext gpu_set_tex_filter gpu_set_tex_filter_ext gpu_set_tex_repeat gpu_set_tex_repeat_ext gpu_set_tex_mip_filter gpu_set_tex_mip_filter_ext gpu_set_tex_mip_bias gpu_set_tex_mip_bias_ext gpu_set_tex_min_mip gpu_set_tex_min_mip_ext gpu_set_tex_max_mip gpu_set_tex_max_mip_ext gpu_set_tex_max_aniso gpu_set_tex_max_aniso_ext gpu_set_tex_mip_enable gpu_set_tex_mip_enable_ext gpu_get_blendenable gpu_get_ztestenable gpu_get_zfunc gpu_get_zwriteenable gpu_get_lightingenable gpu_get_fog gpu_get_cullmode gpu_get_blendmode gpu_get_blendmode_ext gpu_get_blendmode_ext_sepalpha gpu_get_blendmode_src gpu_get_blendmode_dest gpu_get_blendmode_srcalpha gpu_get_blendmode_destalpha gpu_get_colorwriteenable gpu_get_colourwriteenable gpu_get_alphatestenable gpu_get_alphatestref gpu_get_alphatestfunc gpu_get_texfilter gpu_get_texfilter_ext gpu_get_texrepeat gpu_get_texrepeat_ext gpu_get_tex_filter gpu_get_tex_filter_ext gpu_get_tex_repeat gpu_get_tex_repeat_ext gpu_get_tex_mip_filter gpu_get_tex_mip_filter_ext gpu_get_tex_mip_bias gpu_get_tex_mip_bias_ext gpu_get_tex_min_mip gpu_get_tex_min_mip_ext gpu_get_tex_max_mip gpu_get_tex_max_mip_ext gpu_get_tex_max_aniso gpu_get_tex_max_aniso_ext gpu_get_tex_mip_enable gpu_get_tex_mip_enable_ext gpu_push_state gpu_pop_state gpu_get_state gpu_set_state draw_light_define_ambient draw_light_define_direction draw_light_define_point draw_light_enable draw_set_lighting draw_light_get_ambient draw_light_get draw_get_lighting shop_leave_rating url_get_domain url_open url_open_ext url_open_full get_timer achievement_login achievement_logout achievement_post achievement_increment achievement_post_score achievement_available achievement_show_achievements achievement_show_leaderboards achievement_load_friends achievement_load_leaderboard achievement_send_challenge achievement_load_progress achievement_reset achievement_login_status achievement_get_pic achievement_show_challenge_notifications achievement_get_challenges achievement_event achievement_show achievement_get_info cloud_file_save cloud_string_save cloud_synchronise ads_enable ads_disable ads_setup ads_engagement_launch ads_engagement_available ads_engagement_active ads_event ads_event_preload ads_set_reward_callback ads_get_display_height ads_get_display_width ads_move ads_interstitial_available ads_interstitial_display device_get_tilt_x device_get_tilt_y device_get_tilt_z device_is_keypad_open device_mouse_check_button device_mouse_check_button_pressed device_mouse_check_button_released device_mouse_x device_mouse_y device_mouse_raw_x device_mouse_raw_y device_mouse_x_to_gui device_mouse_y_to_gui iap_activate iap_status iap_enumerate_products iap_restore_all iap_acquire iap_consume iap_product_details iap_purchase_details facebook_init facebook_login facebook_status facebook_graph_request facebook_dialog facebook_logout facebook_launch_offerwall facebook_post_message facebook_send_invite facebook_user_id facebook_accesstoken facebook_check_permission facebook_request_read_permissions facebook_request_publish_permissions gamepad_is_supported gamepad_get_device_count gamepad_is_connected gamepad_get_description gamepad_get_button_threshold gamepad_set_button_threshold gamepad_get_axis_deadzone gamepad_set_axis_deadzone gamepad_button_count gamepad_button_check gamepad_button_check_pressed gamepad_button_check_released gamepad_button_value gamepad_axis_count gamepad_axis_value gamepad_set_vibration gamepad_set_colour gamepad_set_color os_is_paused window_has_focus code_is_compiled http_get http_get_file http_post_string http_request json_encode json_decode zip_unzip load_csv base64_encode base64_decode md5_string_unicode md5_string_utf8 md5_file os_is_network_connected sha1_string_unicode sha1_string_utf8 sha1_file os_powersave_enable analytics_event analytics_event_ext win8_livetile_tile_notification win8_livetile_tile_clear win8_livetile_badge_notification win8_livetile_badge_clear win8_livetile_queue_enable win8_secondarytile_pin win8_secondarytile_badge_notification win8_secondarytile_delete win8_livetile_notification_begin win8_livetile_notification_secondary_begin win8_livetile_notification_expiry win8_livetile_notification_tag win8_livetile_notification_text_add win8_livetile_notification_image_add win8_livetile_notification_end win8_appbar_enable win8_appbar_add_element win8_appbar_remove_element win8_settingscharm_add_entry win8_settingscharm_add_html_entry win8_settingscharm_add_xaml_entry win8_settingscharm_set_xaml_property win8_settingscharm_get_xaml_property win8_settingscharm_remove_entry win8_share_image win8_share_screenshot win8_share_file win8_share_url win8_share_text win8_search_enable win8_search_disable win8_search_add_suggestions win8_device_touchscreen_available win8_license_initialize_sandbox win8_license_trial_version winphone_license_trial_version winphone_tile_title winphone_tile_count winphone_tile_back_title winphone_tile_back_content winphone_tile_back_content_wide winphone_tile_front_image winphone_tile_front_image_small winphone_tile_front_image_wide winphone_tile_back_image winphone_tile_back_image_wide winphone_tile_background_colour winphone_tile_background_color winphone_tile_icon_image winphone_tile_small_icon_image winphone_tile_wide_content winphone_tile_cycle_images winphone_tile_small_background_image physics_world_create physics_world_gravity physics_world_update_speed physics_world_update_iterations physics_world_draw_debug physics_pause_enable physics_fixture_create physics_fixture_set_kinematic physics_fixture_set_density physics_fixture_set_awake physics_fixture_set_restitution physics_fixture_set_friction physics_fixture_set_collision_group physics_fixture_set_sensor physics_fixture_set_linear_damping physics_fixture_set_angular_damping physics_fixture_set_circle_shape physics_fixture_set_box_shape physics_fixture_set_edge_shape physics_fixture_set_polygon_shape physics_fixture_set_chain_shape physics_fixture_add_point physics_fixture_bind physics_fixture_bind_ext physics_fixture_delete physics_apply_force physics_apply_impulse physics_apply_angular_impulse physics_apply_local_force physics_apply_local_impulse physics_apply_torque physics_mass_properties physics_draw_debug physics_test_overlap physics_remove_fixture physics_set_friction physics_set_density physics_set_restitution physics_get_friction physics_get_density physics_get_restitution physics_joint_distance_create physics_joint_rope_create physics_joint_revolute_create physics_joint_prismatic_create physics_joint_pulley_create physics_joint_wheel_create physics_joint_weld_create physics_joint_friction_create physics_joint_gear_create physics_joint_enable_motor physics_joint_get_value physics_joint_set_value physics_joint_delete physics_particle_create physics_particle_delete physics_particle_delete_region_circle physics_particle_delete_region_box physics_particle_delete_region_poly physics_particle_set_flags physics_particle_set_category_flags physics_particle_draw physics_particle_draw_ext physics_particle_count physics_particle_get_data physics_particle_get_data_particle physics_particle_group_begin physics_particle_group_circle physics_particle_group_box physics_particle_group_polygon physics_particle_group_add_point physics_particle_group_end physics_particle_group_join physics_particle_group_delete physics_particle_group_count physics_particle_group_get_data physics_particle_group_get_mass physics_particle_group_get_inertia physics_particle_group_get_centre_x physics_particle_group_get_centre_y physics_particle_group_get_vel_x physics_particle_group_get_vel_y physics_particle_group_get_ang_vel physics_particle_group_get_x physics_particle_group_get_y physics_particle_group_get_angle physics_particle_set_group_flags physics_particle_get_group_flags physics_particle_get_max_count physics_particle_get_radius physics_particle_get_density physics_particle_get_damping physics_particle_get_gravity_scale physics_particle_set_max_count physics_particle_set_radius physics_particle_set_density physics_particle_set_damping physics_particle_set_gravity_scale network_create_socket network_create_socket_ext network_create_server network_create_server_raw network_connect network_connect_raw network_send_packet network_send_raw network_send_broadcast network_send_udp network_send_udp_raw network_set_timeout network_set_config network_resolve network_destroy buffer_create buffer_write buffer_read buffer_seek buffer_get_surface buffer_set_surface buffer_delete buffer_exists buffer_get_type buffer_get_alignment buffer_poke buffer_peek buffer_save buffer_save_ext buffer_load buffer_load_ext buffer_load_partial buffer_copy buffer_fill buffer_get_size buffer_tell buffer_resize buffer_md5 buffer_sha1 buffer_base64_encode buffer_base64_decode buffer_base64_decode_ext buffer_sizeof buffer_get_address buffer_create_from_vertex_buffer buffer_create_from_vertex_buffer_ext buffer_copy_from_vertex_buffer buffer_async_group_begin buffer_async_group_option buffer_async_group_end buffer_load_async buffer_save_async gml_release_mode gml_pragma steam_activate_overlay steam_is_overlay_enabled steam_is_overlay_activated steam_get_persona_name steam_initialised steam_is_cloud_enabled_for_app steam_is_cloud_enabled_for_account steam_file_persisted steam_get_quota_total steam_get_quota_free steam_file_write steam_file_write_file steam_file_read steam_file_delete steam_file_exists steam_file_size steam_file_share steam_is_screenshot_requested steam_send_screenshot steam_is_user_logged_on steam_get_user_steam_id steam_user_owns_dlc steam_user_installed_dlc steam_set_achievement steam_get_achievement steam_clear_achievement steam_set_stat_int steam_set_stat_float steam_set_stat_avg_rate steam_get_stat_int steam_get_stat_float steam_get_stat_avg_rate steam_reset_all_stats steam_reset_all_stats_achievements steam_stats_ready steam_create_leaderboard steam_upload_score steam_upload_score_ext steam_download_scores_around_user steam_download_scores steam_download_friends_scores steam_upload_score_buffer steam_upload_score_buffer_ext steam_current_game_language steam_available_languages steam_activate_overlay_browser steam_activate_overlay_user steam_activate_overlay_store steam_get_user_persona_name steam_get_app_id steam_get_user_account_id steam_ugc_download steam_ugc_create_item steam_ugc_start_item_update steam_ugc_set_item_title steam_ugc_set_item_description steam_ugc_set_item_visibility steam_ugc_set_item_tags steam_ugc_set_item_content steam_ugc_set_item_preview steam_ugc_submit_item_update steam_ugc_get_item_update_progress steam_ugc_subscribe_item steam_ugc_unsubscribe_item steam_ugc_num_subscribed_items steam_ugc_get_subscribed_items steam_ugc_get_item_install_info steam_ugc_get_item_update_info steam_ugc_request_item_details steam_ugc_create_query_user steam_ugc_create_query_user_ex steam_ugc_create_query_all steam_ugc_create_query_all_ex steam_ugc_query_set_cloud_filename_filter steam_ugc_query_set_match_any_tag steam_ugc_query_set_search_text steam_ugc_query_set_ranked_by_trend_days steam_ugc_query_add_required_tag steam_ugc_query_add_excluded_tag steam_ugc_query_set_return_long_description steam_ugc_query_set_return_total_only steam_ugc_query_set_allow_cached_response steam_ugc_send_query shader_set shader_get_name shader_reset shader_current shader_is_compiled shader_get_sampler_index shader_get_uniform shader_set_uniform_i shader_set_uniform_i_array shader_set_uniform_f shader_set_uniform_f_array shader_set_uniform_matrix shader_set_uniform_matrix_array shader_enable_corner_id texture_set_stage texture_get_texel_width texture_get_texel_height shaders_are_supported vertex_format_begin vertex_format_end vertex_format_delete vertex_format_add_position vertex_format_add_position_3d vertex_format_add_colour vertex_format_add_color vertex_format_add_normal vertex_format_add_texcoord vertex_format_add_textcoord vertex_format_add_custom vertex_create_buffer vertex_create_buffer_ext vertex_delete_buffer vertex_begin vertex_end vertex_position vertex_position_3d vertex_colour vertex_color vertex_argb vertex_texcoord vertex_normal vertex_float1 vertex_float2 vertex_float3 vertex_float4 vertex_ubyte4 vertex_submit vertex_freeze vertex_get_number vertex_get_buffer_size vertex_create_buffer_from_buffer vertex_create_buffer_from_buffer_ext push_local_notification push_get_first_local_notification push_get_next_local_notification push_cancel_local_notification skeleton_animation_set skeleton_animation_get skeleton_animation_mix skeleton_animation_set_ext skeleton_animation_get_ext skeleton_animation_get_duration skeleton_animation_get_frames skeleton_animation_clear skeleton_skin_set skeleton_skin_get skeleton_attachment_set skeleton_attachment_get skeleton_attachment_create skeleton_collision_draw_set skeleton_bone_data_get skeleton_bone_data_set skeleton_bone_state_get skeleton_bone_state_set skeleton_get_minmax skeleton_get_num_bounds skeleton_get_bounds skeleton_animation_get_frame skeleton_animation_set_frame draw_skeleton draw_skeleton_time draw_skeleton_instance draw_skeleton_collision skeleton_animation_list skeleton_skin_list skeleton_slot_data layer_get_id layer_get_id_at_depth layer_get_depth layer_create layer_destroy layer_destroy_instances layer_add_instance layer_has_instance layer_set_visible layer_get_visible layer_exists layer_x layer_y layer_get_x layer_get_y layer_hspeed layer_vspeed layer_get_hspeed layer_get_vspeed layer_script_begin layer_script_end layer_shader layer_get_script_begin layer_get_script_end layer_get_shader layer_set_target_room layer_get_target_room layer_reset_target_room layer_get_all layer_get_all_elements layer_get_name layer_depth layer_get_element_layer layer_get_element_type layer_element_move layer_force_draw_depth layer_is_draw_depth_forced layer_get_forced_depth layer_background_get_id layer_background_exists layer_background_create layer_background_destroy layer_background_visible layer_background_change layer_background_sprite layer_background_htiled layer_background_vtiled layer_background_stretch layer_background_yscale layer_background_xscale layer_background_blend layer_background_alpha layer_background_index layer_background_speed layer_background_get_visible layer_background_get_sprite layer_background_get_htiled layer_background_get_vtiled layer_background_get_stretch layer_background_get_yscale layer_background_get_xscale layer_background_get_blend layer_background_get_alpha layer_background_get_index layer_background_get_speed layer_sprite_get_id layer_sprite_exists layer_sprite_create layer_sprite_destroy layer_sprite_change layer_sprite_index layer_sprite_speed layer_sprite_xscale layer_sprite_yscale layer_sprite_angle layer_sprite_blend layer_sprite_alpha layer_sprite_x layer_sprite_y layer_sprite_get_sprite layer_sprite_get_index layer_sprite_get_speed layer_sprite_get_xscale layer_sprite_get_yscale layer_sprite_get_angle layer_sprite_get_blend layer_sprite_get_alpha layer_sprite_get_x layer_sprite_get_y layer_tilemap_get_id layer_tilemap_exists layer_tilemap_create layer_tilemap_destroy tilemap_tileset tilemap_x tilemap_y tilemap_set tilemap_set_at_pixel tilemap_get_tileset tilemap_get_tile_width tilemap_get_tile_height tilemap_get_width tilemap_get_height tilemap_get_x tilemap_get_y tilemap_get tilemap_get_at_pixel tilemap_get_cell_x_at_pixel tilemap_get_cell_y_at_pixel tilemap_clear draw_tilemap draw_tile tilemap_set_global_mask tilemap_get_global_mask tilemap_set_mask tilemap_get_mask tilemap_get_frame tile_set_empty tile_set_index tile_set_flip tile_set_mirror tile_set_rotate tile_get_empty tile_get_index tile_get_flip tile_get_mirror tile_get_rotate layer_tile_exists layer_tile_create layer_tile_destroy layer_tile_change layer_tile_xscale layer_tile_yscale layer_tile_blend layer_tile_alpha layer_tile_x layer_tile_y layer_tile_region layer_tile_visible layer_tile_get_sprite layer_tile_get_xscale layer_tile_get_yscale layer_tile_get_blend layer_tile_get_alpha layer_tile_get_x layer_tile_get_y layer_tile_get_region layer_tile_get_visible layer_instance_get_instance instance_activate_layer instance_deactivate_layer camera_create camera_create_view camera_destroy camera_apply camera_get_active camera_get_default camera_set_default camera_set_view_mat camera_set_proj_mat camera_set_update_script camera_set_begin_script camera_set_end_script camera_set_view_pos camera_set_view_size camera_set_view_speed camera_set_view_border camera_set_view_angle camera_set_view_target camera_get_view_mat camera_get_proj_mat camera_get_update_script camera_get_begin_script camera_get_end_script camera_get_view_x camera_get_view_y camera_get_view_width camera_get_view_height camera_get_view_speed_x camera_get_view_speed_y camera_get_view_border_x camera_get_view_border_y camera_get_view_angle camera_get_view_target view_get_camera view_get_visible view_get_xport view_get_yport view_get_wport view_get_hport view_get_surface_id view_set_camera view_set_visible view_set_xport view_set_yport view_set_wport view_set_hport view_set_surface_id gesture_drag_time gesture_drag_distance gesture_flick_speed gesture_double_tap_time gesture_double_tap_distance gesture_pinch_distance gesture_pinch_angle_towards gesture_pinch_angle_away gesture_rotate_time gesture_rotate_angle gesture_tap_count gesture_get_drag_time gesture_get_drag_distance gesture_get_flick_speed gesture_get_double_tap_time gesture_get_double_tap_distance gesture_get_pinch_distance gesture_get_pinch_angle_towards gesture_get_pinch_angle_away gesture_get_rotate_time gesture_get_rotate_angle gesture_get_tap_count keyboard_virtual_show keyboard_virtual_hide keyboard_virtual_status keyboard_virtual_height",literal:"self other all noone global local undefined pointer_invalid pointer_null path_action_stop path_action_restart path_action_continue path_action_reverse true false pi GM_build_date GM_version GM_runtime_version timezone_local timezone_utc gamespeed_fps gamespeed_microseconds ev_create ev_destroy ev_step ev_alarm ev_keyboard ev_mouse ev_collision ev_other ev_draw ev_draw_begin ev_draw_end ev_draw_pre ev_draw_post ev_keypress ev_keyrelease ev_trigger ev_left_button ev_right_button ev_middle_button ev_no_button ev_left_press ev_right_press ev_middle_press ev_left_release ev_right_release ev_middle_release ev_mouse_enter ev_mouse_leave ev_mouse_wheel_up ev_mouse_wheel_down ev_global_left_button ev_global_right_button ev_global_middle_button ev_global_left_press ev_global_right_press ev_global_middle_press ev_global_left_release ev_global_right_release ev_global_middle_release ev_joystick1_left ev_joystick1_right ev_joystick1_up ev_joystick1_down ev_joystick1_button1 ev_joystick1_button2 ev_joystick1_button3 ev_joystick1_button4 ev_joystick1_button5 ev_joystick1_button6 ev_joystick1_button7 ev_joystick1_button8 ev_joystick2_left ev_joystick2_right ev_joystick2_up ev_joystick2_down ev_joystick2_button1 ev_joystick2_button2 ev_joystick2_button3 ev_joystick2_button4 ev_joystick2_button5 ev_joystick2_button6 ev_joystick2_button7 ev_joystick2_button8 ev_outside ev_boundary ev_game_start ev_game_end ev_room_start ev_room_end ev_no_more_lives ev_animation_end ev_end_of_path ev_no_more_health ev_close_button ev_user0 ev_user1 ev_user2 ev_user3 ev_user4 ev_user5 ev_user6 ev_user7 ev_user8 ev_user9 ev_user10 ev_user11 ev_user12 ev_user13 ev_user14 ev_user15 ev_step_normal ev_step_begin ev_step_end ev_gui ev_gui_begin ev_gui_end ev_cleanup ev_gesture ev_gesture_tap ev_gesture_double_tap ev_gesture_drag_start ev_gesture_dragging ev_gesture_drag_end ev_gesture_flick ev_gesture_pinch_start ev_gesture_pinch_in ev_gesture_pinch_out ev_gesture_pinch_end ev_gesture_rotate_start ev_gesture_rotating ev_gesture_rotate_end ev_global_gesture_tap ev_global_gesture_double_tap ev_global_gesture_drag_start ev_global_gesture_dragging ev_global_gesture_drag_end ev_global_gesture_flick ev_global_gesture_pinch_start ev_global_gesture_pinch_in ev_global_gesture_pinch_out ev_global_gesture_pinch_end ev_global_gesture_rotate_start ev_global_gesture_rotating ev_global_gesture_rotate_end vk_nokey vk_anykey vk_enter vk_return vk_shift vk_control vk_alt vk_escape vk_space vk_backspace vk_tab vk_pause vk_printscreen vk_left vk_right vk_up vk_down vk_home vk_end vk_delete vk_insert vk_pageup vk_pagedown vk_f1 vk_f2 vk_f3 vk_f4 vk_f5 vk_f6 vk_f7 vk_f8 vk_f9 vk_f10 vk_f11 vk_f12 vk_numpad0 vk_numpad1 vk_numpad2 vk_numpad3 vk_numpad4 vk_numpad5 vk_numpad6 vk_numpad7 vk_numpad8 vk_numpad9 vk_divide vk_multiply vk_subtract vk_add vk_decimal vk_lshift vk_lcontrol vk_lalt vk_rshift vk_rcontrol vk_ralt mb_any mb_none mb_left mb_right mb_middle c_aqua c_black c_blue c_dkgray c_fuchsia c_gray c_green c_lime c_ltgray c_maroon c_navy c_olive c_purple c_red c_silver c_teal c_white c_yellow c_orange fa_left fa_center fa_right fa_top fa_middle fa_bottom pr_pointlist pr_linelist pr_linestrip pr_trianglelist pr_trianglestrip pr_trianglefan bm_complex bm_normal bm_add bm_max bm_subtract bm_zero bm_one bm_src_colour bm_inv_src_colour bm_src_color bm_inv_src_color bm_src_alpha bm_inv_src_alpha bm_dest_alpha bm_inv_dest_alpha bm_dest_colour bm_inv_dest_colour bm_dest_color bm_inv_dest_color bm_src_alpha_sat tf_point tf_linear tf_anisotropic mip_off mip_on mip_markedonly audio_falloff_none audio_falloff_inverse_distance audio_falloff_inverse_distance_clamped audio_falloff_linear_distance audio_falloff_linear_distance_clamped audio_falloff_exponent_distance audio_falloff_exponent_distance_clamped audio_old_system audio_new_system audio_mono audio_stereo audio_3d cr_default cr_none cr_arrow cr_cross cr_beam cr_size_nesw cr_size_ns cr_size_nwse cr_size_we cr_uparrow cr_hourglass cr_drag cr_appstart cr_handpoint cr_size_all spritespeed_framespersecond spritespeed_framespergameframe asset_object asset_unknown asset_sprite asset_sound asset_room asset_path asset_script asset_font asset_timeline asset_tiles asset_shader fa_readonly fa_hidden fa_sysfile fa_volumeid fa_directory fa_archive ds_type_map ds_type_list ds_type_stack ds_type_queue ds_type_grid ds_type_priority ef_explosion ef_ring ef_ellipse ef_firework ef_smoke ef_smokeup ef_star ef_spark ef_flare ef_cloud ef_rain ef_snow pt_shape_pixel pt_shape_disk pt_shape_square pt_shape_line pt_shape_star pt_shape_circle pt_shape_ring pt_shape_sphere pt_shape_flare pt_shape_spark pt_shape_explosion pt_shape_cloud pt_shape_smoke pt_shape_snow ps_distr_linear ps_distr_gaussian ps_distr_invgaussian ps_shape_rectangle ps_shape_ellipse ps_shape_diamond ps_shape_line ty_real ty_string dll_cdecl dll_stdcall matrix_view matrix_projection matrix_world os_win32 os_windows os_macosx os_ios os_android os_symbian os_linux os_unknown os_winphone os_tizen os_win8native os_wiiu os_3ds os_psvita os_bb10 os_ps4 os_xboxone os_ps3 os_xbox360 os_uwp os_tvos os_switch browser_not_a_browser browser_unknown browser_ie browser_firefox browser_chrome browser_safari browser_safari_mobile browser_opera browser_tizen browser_edge browser_windows_store browser_ie_mobile device_ios_unknown device_ios_iphone device_ios_iphone_retina device_ios_ipad device_ios_ipad_retina device_ios_iphone5 device_ios_iphone6 device_ios_iphone6plus device_emulator device_tablet display_landscape display_landscape_flipped display_portrait display_portrait_flipped tm_sleep tm_countvsyncs of_challenge_win of_challen ge_lose of_challenge_tie leaderboard_type_number leaderboard_type_time_mins_secs cmpfunc_never cmpfunc_less cmpfunc_equal cmpfunc_lessequal cmpfunc_greater cmpfunc_notequal cmpfunc_greaterequal cmpfunc_always cull_noculling cull_clockwise cull_counterclockwise lighttype_dir lighttype_point iap_ev_storeload iap_ev_product iap_ev_purchase iap_ev_consume iap_ev_restore iap_storeload_ok iap_storeload_failed iap_status_uninitialised iap_status_unavailable iap_status_loading iap_status_available iap_status_processing iap_status_restoring iap_failed iap_unavailable iap_available iap_purchased iap_canceled iap_refunded fb_login_default fb_login_fallback_to_webview fb_login_no_fallback_to_webview fb_login_forcing_webview fb_login_use_system_account fb_login_forcing_safari phy_joint_anchor_1_x phy_joint_anchor_1_y phy_joint_anchor_2_x phy_joint_anchor_2_y phy_joint_reaction_force_x phy_joint_reaction_force_y phy_joint_reaction_torque phy_joint_motor_speed phy_joint_angle phy_joint_motor_torque phy_joint_max_motor_torque phy_joint_translation phy_joint_speed phy_joint_motor_force phy_joint_max_motor_force phy_joint_length_1 phy_joint_length_2 phy_joint_damping_ratio phy_joint_frequency phy_joint_lower_angle_limit phy_joint_upper_angle_limit phy_joint_angle_limits phy_joint_max_length phy_joint_max_torque phy_joint_max_force phy_debug_render_aabb phy_debug_render_collision_pairs phy_debug_render_coms phy_debug_render_core_shapes phy_debug_render_joints phy_debug_render_obb phy_debug_render_shapes phy_particle_flag_water phy_particle_flag_zombie phy_particle_flag_wall phy_particle_flag_spring phy_particle_flag_elastic phy_particle_flag_viscous phy_particle_flag_powder phy_particle_flag_tensile phy_particle_flag_colourmixing phy_particle_flag_colormixing phy_particle_group_flag_solid phy_particle_group_flag_rigid phy_particle_data_flag_typeflags phy_particle_data_flag_position phy_particle_data_flag_velocity phy_particle_data_flag_colour phy_particle_data_flag_color phy_particle_data_flag_category achievement_our_info achievement_friends_info achievement_leaderboard_info achievement_achievement_info achievement_filter_all_players achievement_filter_friends_only achievement_filter_favorites_only achievement_type_achievement_challenge achievement_type_score_challenge achievement_pic_loaded achievement_show_ui achievement_show_profile achievement_show_leaderboard achievement_show_achievement achievement_show_bank achievement_show_friend_picker achievement_show_purchase_prompt network_socket_tcp network_socket_udp network_socket_bluetooth network_type_connect network_type_disconnect network_type_data network_type_non_blocking_connect network_config_connect_timeout network_config_use_non_blocking_socket network_config_enable_reliable_udp network_config_disable_reliable_udp buffer_fixed buffer_grow buffer_wrap buffer_fast buffer_vbuffer buffer_network buffer_u8 buffer_s8 buffer_u16 buffer_s16 buffer_u32 buffer_s32 buffer_u64 buffer_f16 buffer_f32 buffer_f64 buffer_bool buffer_text buffer_string buffer_surface_copy buffer_seek_start buffer_seek_relative buffer_seek_end buffer_generalerror buffer_outofspace buffer_outofbounds buffer_invalidtype text_type button_type input_type ANSI_CHARSET DEFAULT_CHARSET EASTEUROPE_CHARSET RUSSIAN_CHARSET SYMBOL_CHARSET SHIFTJIS_CHARSET HANGEUL_CHARSET GB2312_CHARSET CHINESEBIG5_CHARSET JOHAB_CHARSET HEBREW_CHARSET ARABIC_CHARSET GREEK_CHARSET TURKISH_CHARSET VIETNAMESE_CHARSET THAI_CHARSET MAC_CHARSET BALTIC_CHARSET OEM_CHARSET gp_face1 gp_face2 gp_face3 gp_face4 gp_shoulderl gp_shoulderr gp_shoulderlb gp_shoulderrb gp_select gp_start gp_stickl gp_stickr gp_padu gp_padd gp_padl gp_padr gp_axislh gp_axislv gp_axisrh gp_axisrv ov_friends ov_community ov_players ov_settings ov_gamegroup ov_achievements lb_sort_none lb_sort_ascending lb_sort_descending lb_disp_none lb_disp_numeric lb_disp_time_sec lb_disp_time_ms ugc_result_success ugc_filetype_community ugc_filetype_microtrans ugc_visibility_public ugc_visibility_friends_only ugc_visibility_private ugc_query_RankedByVote ugc_query_RankedByPublicationDate ugc_query_AcceptedForGameRankedByAcceptanceDate ugc_query_RankedByTrend ugc_query_FavoritedByFriendsRankedByPublicationDate ugc_query_CreatedByFriendsRankedByPublicationDate ugc_query_RankedByNumTimesReported ugc_query_CreatedByFollowedUsersRankedByPublicationDate ugc_query_NotYetRated ugc_query_RankedByTotalVotesAsc ugc_query_RankedByVotesUp ugc_query_RankedByTextSearch ugc_sortorder_CreationOrderDesc ugc_sortorder_CreationOrderAsc ugc_sortorder_TitleAsc ugc_sortorder_LastUpdatedDesc ugc_sortorder_SubscriptionDateDesc ugc_sortorder_VoteScoreDesc ugc_sortorder_ForModeration ugc_list_Published ugc_list_VotedOn ugc_list_VotedUp ugc_list_VotedDown ugc_list_WillVoteLater ugc_list_Favorited ugc_list_Subscribed ugc_list_UsedOrPlayed ugc_list_Followed ugc_match_Items ugc_match_Items_Mtx ugc_match_Items_ReadyToUse ugc_match_Collections ugc_match_Artwork ugc_match_Videos ugc_match_Screenshots ugc_match_AllGuides ugc_match_WebGuides ugc_match_IntegratedGuides ugc_match_UsableInGame ugc_match_ControllerBindings vertex_usage_position vertex_usage_colour vertex_usage_color vertex_usage_normal vertex_usage_texcoord vertex_usage_textcoord vertex_usage_blendweight vertex_usage_blendindices vertex_usage_psize vertex_usage_tangent vertex_usage_binormal vertex_usage_fog vertex_usage_depth vertex_usage_sample vertex_type_float1 vertex_type_float2 vertex_type_float3 vertex_type_float4 vertex_type_colour vertex_type_color vertex_type_ubyte4 layerelementtype_undefined layerelementtype_background layerelementtype_instance layerelementtype_oldtilemap layerelementtype_sprite layerelementtype_tilemap layerelementtype_particlesystem layerelementtype_tile tile_rotate tile_flip tile_mirror tile_index_mask kbv_type_default kbv_type_ascii kbv_type_url kbv_type_email kbv_type_numbers kbv_type_phone kbv_type_phone_name kbv_returnkey_default kbv_returnkey_go kbv_returnkey_google kbv_returnkey_join kbv_returnkey_next kbv_returnkey_route kbv_returnkey_search kbv_returnkey_send kbv_returnkey_yahoo kbv_returnkey_done kbv_returnkey_continue kbv_returnkey_emergency kbv_autocapitalize_none kbv_autocapitalize_words kbv_autocapitalize_sentences kbv_autocapitalize_characters",
-symbol:"argument_relative argument argument0 argument1 argument2 argument3 argument4 argument5 argument6 argument7 argument8 argument9 argument10 argument11 argument12 argument13 argument14 argument15 argument_count x y xprevious yprevious xstart ystart hspeed vspeed direction speed friction gravity gravity_direction path_index path_position path_positionprevious path_speed path_scale path_orientation path_endaction object_index id solid persistent mask_index instance_count instance_id room_speed fps fps_real current_time current_year current_month current_day current_weekday current_hour current_minute current_second alarm timeline_index timeline_position timeline_speed timeline_running timeline_loop room room_first room_last room_width room_height room_caption room_persistent score lives health show_score show_lives show_health caption_score caption_lives caption_health event_type event_number event_object event_action application_surface gamemaker_pro gamemaker_registered gamemaker_version error_occurred error_last debug_mode keyboard_key keyboard_lastkey keyboard_lastchar keyboard_string mouse_x mouse_y mouse_button mouse_lastbutton cursor_sprite visible sprite_index sprite_width sprite_height sprite_xoffset sprite_yoffset image_number image_index image_speed depth image_xscale image_yscale image_angle image_alpha image_blend bbox_left bbox_right bbox_top bbox_bottom layer background_colour background_showcolour background_color background_showcolor view_enabled view_current view_visible view_xview view_yview view_wview view_hview view_xport view_yport view_wport view_hport view_angle view_hborder view_vborder view_hspeed view_vspeed view_object view_surface_id view_camera game_id game_display_name game_project_name game_save_id working_directory temp_directory program_directory browser_width browser_height os_type os_device os_browser os_version display_aa async_load delta_time webgl_enabled event_data iap_data phy_rotation phy_position_x phy_position_y phy_angular_velocity phy_linear_velocity_x phy_linear_velocity_y phy_speed_x phy_speed_y phy_speed phy_angular_damping phy_linear_damping phy_bullet phy_fixed_rotation phy_active phy_mass phy_inertia phy_com_x phy_com_y phy_dynamic phy_kinematic phy_sleeping phy_collision_points phy_collision_x phy_collision_y phy_col_normal_x phy_col_normal_y phy_position_xprevious phy_position_yprevious"},
-contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("go",function(){return function(a){var b={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",
-aliases:["golang"],keywords:b,illegal:"</",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",variants:[a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{begin:"`",end:"`"}]},{className:"number",variants:[{begin:a.C_NUMBER_RE+"[i]",relevance:1},a.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:"\\s*(\\{|$)",excludeEnd:!0,contains:[a.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:b,illegal:/["']/}]}]}}}());
-hljs.registerLanguage("golo",function(){return function(a){return{name:"Golo",keywords:{keyword:"println readln print import module function local return let var while for foreach times in case when match with break continue augment augmentation each find filter reduce if then else otherwise try catch finally raise throw orIfNull DynamicObject|10 DynamicVariable struct Observable map set vector list array",literal:"true false null"},contains:[a.HASH_COMMENT_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,
-{className:"meta",begin:"@[A-Za-z]+"}]}}}());
-hljs.registerLanguage("gradle",function(){return function(a){return{name:"Gradle",case_insensitive:!0,keywords:{keyword:"task project allprojects subprojects artifacts buildscript configurations dependencies repositories sourceSets description delete from into include exclude source classpath destinationDir includes options sourceCompatibility targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant def abstract break case catch continue default do else extends final finally for if implements instanceof native new private protected public return static switch synchronized throw throws transient try volatile while strictfp package import false null super this true antlrtask checkstyle codenarc copy boolean byte char class double float int interface long short void compile runTime file fileTree abs any append asList asWritable call collect compareTo count div dump each eachByte eachFile eachLine every find findAll flatten getAt getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter newReader newWriter next plus pop power previous print println push putAt read readBytes readLines reverse reverseEach round size sort splitEachLine step subMap times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader withStream withWriter withWriterAppend write writeLine"},contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.NUMBER_MODE,a.REGEXP_MODE]}}}());
-hljs.registerLanguage("groovy",function(){return function(a){return{name:"Groovy",keywords:{literal:"true false null",keyword:"byte short char int long boolean float double void def as in assert trait super this abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"},contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,
-contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",begin:'"""',end:'"""'},{className:"string",begin:"'''",end:"'''"},{className:"string",begin:"\\$/",end:"/\\$",relevance:10},a.APOS_STRING_MODE,{className:"regexp",begin:/~?\/[^\/\n]+\//,contains:[a.BACKSLASH_ESCAPE]},a.QUOTE_STRING_MODE,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},a.BINARY_NUMBER_MODE,{className:"class",beginKeywords:"class interface trait enum",
-end:"{",illegal:":",contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"},{className:"string",begin:/[^\?]{0}[A-Za-z0-9_$]+ *:/},{begin:/\?/,end:/:/},{className:"symbol",begin:"^\\s*[A-Za-z0-9_$]+:",relevance:0}],illegal:/#|<\//}}}());
-hljs.registerLanguage("haml",function(){return function(a){return{name:"HAML",case_insensitive:!0,contains:[{className:"meta",begin:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",relevance:10},a.COMMENT("^\\s*(!=#|=#|-#|/).*$",!1,{relevance:0}),{begin:"^\\s*(-|=|!=)(?!#)",starts:{end:"\\n",subLanguage:"ruby"}},{className:"tag",begin:"^\\s*%",contains:[{className:"selector-tag",begin:"\\w+"},{className:"selector-id",begin:"#[\\w-]+"},{className:"selector-class",begin:"\\.[\\w-]+"},
-{begin:"{\\s*",end:"\\s*}",contains:[{begin:":\\w+\\s*=>",end:",\\s+",returnBegin:!0,endsWithParent:!0,contains:[{className:"attr",begin:":\\w+"},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{begin:"\\w+",relevance:0}]}]},{begin:"\\(\\s*",end:"\\s*\\)",excludeEnd:!0,contains:[{begin:"\\w+\\s*=",end:"\\s+",returnBegin:!0,endsWithParent:!0,contains:[{className:"attr",begin:"\\w+",relevance:0},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{begin:"\\w+",relevance:0}]}]}]},{begin:"^\\s*[=~]\\s*"},{begin:"#{",starts:{end:"}",
-subLanguage:"ruby"}}]}}}());
-hljs.registerLanguage("handlebars",function(){return function(a){var b={"builtin-name":"each in with if else unless bindattr action collection debugger log outlet template unbound view yield lookup"},c={begin:/".*?"|'.*?'|\[.*?\]|\w+/},d=a.inherit(c,{keywords:b,starts:{endsWithParent:!0,relevance:0,contains:[a.inherit(c,{relevance:0})]}});c=a.inherit(d,{className:"name"});d=a.inherit(d,{relevance:0});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars"],case_insensitive:!0,subLanguage:"xml",
-contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},a.COMMENT(/\{\{!--/,/--\}\}/),a.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[c],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[c]},{className:"template-tag",begin:/\{\{[#\/]/,end:/\}\}/,contains:[c]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,keywords:b,contains:[d]},{className:"template-variable",
-begin:/\{\{/,end:/\}\}/,keywords:b,contains:[d]}]}}}());
-hljs.registerLanguage("haskell",function(){return function(a){var b={variants:[a.COMMENT("--","$"),a.COMMENT("{-","-}",{contains:["self"]})]},c={className:"meta",begin:"{-#",end:"#-}"},d={className:"meta",begin:"^#",end:"$"},e={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},f={begin:"\\(",end:"\\)",illegal:'"',contains:[c,d,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},a.inherit(a.TITLE_MODE,{begin:"[_a-z][\\w']*"}),b]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",
-contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[f,b],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[f,b],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[e,f,b]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[c,e,f,{begin:"{",end:"}",contains:f.contains},b]},{beginKeywords:"default",
-end:"$",contains:[e,f,b]},{beginKeywords:"infix infixl infixr",end:"$",contains:[a.C_NUMBER_MODE,b]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[e,a.QUOTE_STRING_MODE,b]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},c,d,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,e,a.inherit(a.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),b,{begin:"->|<-"}]}}}());
-hljs.registerLanguage("haxe",function(){return function(a){return{name:"Haxe",aliases:["hx"],keywords:{keyword:"break case cast catch continue default do dynamic else enum extern for function here if import in inline never new override package private get set public return static super switch this throw trace try typedef untyped using var while Int Float String Bool Dynamic Void Array ",built_in:"trace this",literal:"true false null _"},contains:[{className:"string",begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE,
-{className:"subst",begin:"\\$\\{",end:"\\}"},{className:"subst",begin:"\\$",end:"\\W}"}]},a.QUOTE_STRING_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.C_NUMBER_MODE,{className:"meta",begin:"@:",end:"$"},{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elseif end error"}},{className:"type",begin:":[ \t]*",end:"[^A-Za-z0-9_ \t\\->]",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:":[ \t]*",end:"\\W",excludeBegin:!0,excludeEnd:!0},{className:"type",begin:"new *",
-end:"\\W",excludeBegin:!0,excludeEnd:!0},{className:"class",beginKeywords:"enum",end:"\\{",contains:[a.TITLE_MODE]},{className:"class",beginKeywords:"abstract",end:"[\\{$]",contains:[{className:"type",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"type",begin:"from +",end:"\\W",excludeBegin:!0,excludeEnd:!0},{className:"type",begin:"to +",end:"\\W",excludeBegin:!0,excludeEnd:!0},a.TITLE_MODE],keywords:{keyword:"abstract from to"}},{className:"class",begin:"\\b(class|interface) +",
-end:"[\\{$]",excludeEnd:!0,keywords:"class interface",contains:[{className:"keyword",begin:"\\b(extends|implements) +",keywords:"extends implements",contains:[{className:"type",begin:a.IDENT_RE,relevance:0}]},a.TITLE_MODE]},{className:"function",beginKeywords:"function",end:"\\(",excludeEnd:!0,illegal:"\\S",contains:[a.TITLE_MODE]}],illegal:/<\//}}}());
-hljs.registerLanguage("hsp",function(){return function(a){return{name:"HSP",case_insensitive:!0,keywords:{$pattern:/[\w._]+/,keyword:"goto gosub return break repeat loop continue wait await dim sdim foreach dimtype dup dupptr end stop newmod delmod mref run exgoto on mcall assert logmes newlab resume yield onexit onerror onkey onclick oncmd exist delete mkdir chdir dirlist bload bsave bcopy memfile if else poke wpoke lpoke getstr chdpm memexpand memcpy memset notesel noteadd notedel noteload notesave randomize noteunsel noteget split strrep setease button chgdisp exec dialog mmload mmplay mmstop mci pset pget syscolor mes print title pos circle cls font sysfont objsize picload color palcolor palette redraw width gsel gcopy gzoom gmode bmpsave hsvcolor getkey listbox chkbox combox input mesbox buffer screen bgscr mouse objsel groll line clrobj boxf objprm objmode stick grect grotate gsquare gradf objimage objskip objenable celload celdiv celput newcom querycom delcom cnvstow comres axobj winobj sendmsg comevent comevarg sarrayconv callfunc cnvwtos comevdisp libptr system hspstat hspver stat cnt err strsize looplev sublev iparam wparam lparam refstr refdval int rnd strlen length length2 length3 length4 vartype gettime peek wpeek lpeek varptr varuse noteinfo instr abs limit getease str strmid strf getpath strtrim sin cos tan atan sqrt double absf expf logf limitf powf geteasef mousex mousey mousew hwnd hinstance hdc ginfo objinfo dirinfo sysinfo thismod __hspver__ __hsp30__ __date__ __time__ __line__ __file__ _debug __hspdef__ and or xor not screen_normal screen_palette screen_hide screen_fixedsize screen_tool screen_frame gmode_gdi gmode_mem gmode_rgb0 gmode_alpha gmode_rgb0alpha gmode_add gmode_sub gmode_pixela ginfo_mx ginfo_my ginfo_act ginfo_sel ginfo_wx1 ginfo_wy1 ginfo_wx2 ginfo_wy2 ginfo_vx ginfo_vy ginfo_sizex ginfo_sizey ginfo_winx ginfo_winy ginfo_mesx ginfo_mesy ginfo_r ginfo_g ginfo_b ginfo_paluse ginfo_dispx ginfo_dispy ginfo_cx ginfo_cy ginfo_intid ginfo_newid ginfo_sx ginfo_sy objinfo_mode objinfo_bmscr objinfo_hwnd notemax notesize dir_cur dir_exe dir_win dir_sys dir_cmdline dir_desktop dir_mydoc dir_tv font_normal font_bold font_italic font_underline font_strikeout font_antialias objmode_normal objmode_guifont objmode_usefont gsquare_grad msgothic msmincho do until while wend for next _break _continue switch case default swbreak swend ddim ldim alloc m_pi rad2deg deg2rad ease_linear ease_quad_in ease_quad_out ease_quad_inout ease_cubic_in ease_cubic_out ease_cubic_inout ease_quartic_in ease_quartic_out ease_quartic_inout ease_bounce_in ease_bounce_out ease_bounce_inout ease_shake_in ease_shake_out ease_shake_inout ease_loop"},contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{className:"string",begin:'{"',end:'"}',contains:[a.BACKSLASH_ESCAPE]},a.COMMENT(";","$",{relevance:0}),{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"addion cfunc cmd cmpopt comfunc const defcfunc deffunc define else endif enum epack func global if ifdef ifndef include modcfunc modfunc modinit modterm module pack packopt regcmd runtime undef usecom uselib"},contains:[a.inherit(a.QUOTE_STRING_MODE,{className:"meta-string"}),
-a.NUMBER_MODE,a.C_NUMBER_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"symbol",begin:"^\\*(\\w+|@)"},a.NUMBER_MODE,a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("htmlbars",function(){return function(a){a.requireLanguage("handlebars");var b={endsWithParent:!0,relevance:0,keywords:{keyword:"as",built_in:"action collection component concat debugger each each-in else get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with yield view"},contains:[a.QUOTE_STRING_MODE,{illegal:/\}\}/,begin:/[a-zA-Z0-9_]+=/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/[a-zA-Z0-9_]+/}]},a.NUMBER_MODE]};
-return{name:"HTMLBars",case_insensitive:!0,subLanguage:"xml",contains:[a.COMMENT("{{!(--)?","(--)?}}"),{className:"template-tag",begin:/\{\{[#\/]/,end:/\}\}/,contains:[{className:"name",begin:/[a-zA-Z\.\-]+/,keywords:{"builtin-name":"action collection component concat debugger each each-in else get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with yield view"},starts:b}]},{className:"template-variable",begin:/\{\{[a-zA-Z][a-zA-Z\-]+/,end:/\}\}/,keywords:{keyword:"as",
-built_in:"action collection component concat debugger each each-in else get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with yield view"},contains:[a.QUOTE_STRING_MODE]}]}}}());
-hljs.registerLanguage("http",function(){return function(a){return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^HTTP/[0-9\\.]+",end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:"HTTP/[0-9\\.]+"},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",
-relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());
-hljs.registerLanguage("hy",function(){return function(a){var b={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",relevance:0},c=a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),d=a.COMMENT(";","$",{relevance:0}),e={className:"literal",begin:/\b([Tt]rue|[Ff]alse|nil|None)\b/},f={begin:"[\\[\\{]",end:"[\\]\\}]"},h={className:"comment",begin:"\\^[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},g=a.COMMENT("\\^\\{","\\}"),k={className:"symbol",begin:"[:]{1,2}[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},
-l={begin:"\\(",end:"\\)"},m={endsWithParent:!0,relevance:0},p={keywords:{$pattern:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*","builtin-name":"!= % %= & &= * ** **= *= *map + += , --build-class-- --import-- -= . / // //= /= < << <<= <= = > >= >> >>= @ @= ^ ^= abs accumulate all and any ap-compose ap-dotimes ap-each ap-each-while ap-filter ap-first ap-if ap-last ap-map ap-map-when ap-pipe ap-reduce ap-reject apply as-> ascii assert assoc bin break butlast callable calling-module-name car case cdr chain chr coll? combinations compile compress cond cons cons? continue count curry cut cycle dec def default-method defclass defmacro defmacro-alias defmacro/g! defmain defmethod defmulti defn defn-alias defnc defnr defreader defseq del delattr delete-route dict-comp dir disassemble dispatch-reader-macro distinct divmod do doto drop drop-last drop-while empty? end-sequence eval eval-and-compile eval-when-compile even? every? except exec filter first flatten float? fn fnc fnr for for* format fraction genexpr gensym get getattr global globals group-by hasattr hash hex id identity if if* if-not if-python2 import in inc input instance? integer integer-char? integer? interleave interpose is is-coll is-cons is-empty is-even is-every is-float is-instance is-integer is-integer-char is-iterable is-iterator is-keyword is-neg is-none is-not is-numeric is-odd is-pos is-string is-symbol is-zero isinstance islice issubclass iter iterable? iterate iterator? keyword keyword? lambda last len let lif lif-not list* list-comp locals loop macro-error macroexpand macroexpand-1 macroexpand-all map max merge-with method-decorator min multi-decorator multicombinations name neg? next none? nonlocal not not-in not? nth numeric? oct odd? open or ord partition permutations pos? post-route postwalk pow prewalk print product profile/calls profile/cpu put-route quasiquote quote raise range read read-str recursive-replace reduce remove repeat repeatedly repr require rest round route route-with-methods rwm second seq set-comp setattr setv some sorted string string? sum switch symbol? take take-nth take-while tee try unless unquote unquote-splicing vars walk when while with with* with-decorator with-gensyms xi xor yield yield-from zero? zip zip-longest | |= ~"},
-className:"name",begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",starts:m},q=[l,c,h,g,d,k,f,b,e,{begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",relevance:0}];l.contains=[a.COMMENT("comment",""),p,m];m.contains=q;f.contains=q;return{name:"Hy",aliases:["hylang"],illegal:/\S/,contains:[a.SHEBANG(),l,c,h,g,d,k,f,b,e]}}}());
-hljs.registerLanguage("inform7",function(){return function(a){return{name:"Inform 7",aliases:["i7"],case_insensitive:!0,keywords:{keyword:"thing room person man woman animal container supporter backdrop door scenery open closed locked inside gender is are say understand kind of rule"},contains:[{className:"string",begin:'"',end:'"',relevance:0,contains:[{className:"subst",begin:"\\[",end:"\\]"}]},{className:"section",begin:/^(Volume|Book|Part|Chapter|Section|Table)\b/,end:"$"},{begin:/^(Check|Carry out|Report|Instead of|To|Rule|When|Before|After)\b/,
-end:":",contains:[{begin:"\\(This",end:"\\)"}]},{className:"comment",begin:"\\[",end:"\\]",contains:["self"]}]}}}());
-hljs.registerLanguage("ini",function(){return function(a){var b={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},c=a.COMMENT();c.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var d={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},e={className:"literal",begin:/\bon|off|true|false|yes|no\b/};a={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},
-{begin:'"',end:'"'},{begin:"'",end:"'"}]};return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[c,{className:"section",begin:/\[+/,end:/\]+/},{begin:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,className:"attr",starts:{end:/$/,contains:[c,{begin:/\[/,end:/\]/,contains:[c,e,d,a,b,"self"],relevance:0},e,d,a,b]}}]}}}());
-hljs.registerLanguage("irpf90",function(){return function(a){return{name:"IRPF90",case_insensitive:!0,keywords:{literal:".False. .True.",keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data begin_provider &begin_provider end_provider begin_shell end_shell begin_template end_template subst assert touch soft_touch provide no_dep free irp_if irp_else irp_endif irp_write irp_read",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image IRP_ALIGN irp_here"},
-illegal:/\/\*/,contains:[a.inherit(a.APOS_STRING_MODE,{className:"string",relevance:0}),a.inherit(a.QUOTE_STRING_MODE,{className:"string",relevance:0}),{className:"function",beginKeywords:"subroutine function program",illegal:"[${=\\n]",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]},a.COMMENT("!","$",{relevance:0}),a.COMMENT("begin_doc","end_doc",{relevance:10}),{className:"number",begin:"(?=\\b|\\+|\\-|\\.)(?:\\.|\\d+\\.?)\\d*([de][+-]?\\d+)?(_[a-z_\\d]+)?",relevance:0}]}}}());
-hljs.registerLanguage("isbl",function(){return function(a){var b={className:"number",begin:a.NUMBER_RE,relevance:0},c={className:"string",variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]},d={className:"doctag",begin:"\\b(?:TODO|DONE|BEGIN|END|STUB|CHG|FIXME|NOTE|BUG|XXX)\\b",relevance:0};d={variants:[{className:"comment",begin:"//",end:"$",relevance:0,contains:[a.PHRASAL_WORDS_MODE,d]},{className:"comment",begin:"/\\*",end:"\\*/",relevance:0,contains:[a.PHRASAL_WORDS_MODE,d]}]};var e={$pattern:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_!][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*",
-keyword:"and \u0438 else \u0438\u043d\u0430\u0447\u0435 endexcept endfinally endforeach \u043a\u043e\u043d\u0435\u0446\u0432\u0441\u0435 endif \u043a\u043e\u043d\u0435\u0446\u0435\u0441\u043b\u0438 endwhile \u043a\u043e\u043d\u0435\u0446\u043f\u043e\u043a\u0430 except exitfor finally foreach \u0432\u0441\u0435 if \u0435\u0441\u043b\u0438 in \u0432 not \u043d\u0435 or \u0438\u043b\u0438 try while \u043f\u043e\u043a\u0430 ",built_in:"SYSRES_CONST_ACCES_RIGHT_TYPE_EDIT SYSRES_CONST_ACCES_RIGHT_TYPE_FULL SYSRES_CONST_ACCES_RIGHT_TYPE_VIEW SYSRES_CONST_ACCESS_MODE_REQUISITE_CODE SYSRES_CONST_ACCESS_NO_ACCESS_VIEW SYSRES_CONST_ACCESS_NO_ACCESS_VIEW_CODE SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW SYSRES_CONST_ACCESS_RIGHTS_VIEW_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_TYPE_CHANGE SYSRES_CONST_ACCESS_TYPE_CHANGE_CODE SYSRES_CONST_ACCESS_TYPE_EXISTS SYSRES_CONST_ACCESS_TYPE_EXISTS_CODE SYSRES_CONST_ACCESS_TYPE_FULL SYSRES_CONST_ACCESS_TYPE_FULL_CODE SYSRES_CONST_ACCESS_TYPE_VIEW SYSRES_CONST_ACCESS_TYPE_VIEW_CODE SYSRES_CONST_ACTION_TYPE_ABORT SYSRES_CONST_ACTION_TYPE_ACCEPT SYSRES_CONST_ACTION_TYPE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ADD_ATTACHMENT SYSRES_CONST_ACTION_TYPE_CHANGE_CARD SYSRES_CONST_ACTION_TYPE_CHANGE_KIND SYSRES_CONST_ACTION_TYPE_CHANGE_STORAGE SYSRES_CONST_ACTION_TYPE_CONTINUE SYSRES_CONST_ACTION_TYPE_COPY SYSRES_CONST_ACTION_TYPE_CREATE SYSRES_CONST_ACTION_TYPE_CREATE_VERSION SYSRES_CONST_ACTION_TYPE_DELETE SYSRES_CONST_ACTION_TYPE_DELETE_ATTACHMENT SYSRES_CONST_ACTION_TYPE_DELETE_VERSION SYSRES_CONST_ACTION_TYPE_DISABLE_DELEGATE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ENABLE_DELEGATE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE_AND_PASSWORD SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_PASSWORD SYSRES_CONST_ACTION_TYPE_EXPORT_WITH_LOCK SYSRES_CONST_ACTION_TYPE_EXPORT_WITHOUT_LOCK SYSRES_CONST_ACTION_TYPE_IMPORT_WITH_UNLOCK SYSRES_CONST_ACTION_TYPE_IMPORT_WITHOUT_UNLOCK SYSRES_CONST_ACTION_TYPE_LIFE_CYCLE_STAGE SYSRES_CONST_ACTION_TYPE_LOCK SYSRES_CONST_ACTION_TYPE_LOCK_FOR_SERVER SYSRES_CONST_ACTION_TYPE_LOCK_MODIFY SYSRES_CONST_ACTION_TYPE_MARK_AS_READED SYSRES_CONST_ACTION_TYPE_MARK_AS_UNREADED SYSRES_CONST_ACTION_TYPE_MODIFY SYSRES_CONST_ACTION_TYPE_MODIFY_CARD SYSRES_CONST_ACTION_TYPE_MOVE_TO_ARCHIVE SYSRES_CONST_ACTION_TYPE_OFF_ENCRYPTION SYSRES_CONST_ACTION_TYPE_PASSWORD_CHANGE SYSRES_CONST_ACTION_TYPE_PERFORM SYSRES_CONST_ACTION_TYPE_RECOVER_FROM_LOCAL_COPY SYSRES_CONST_ACTION_TYPE_RESTART SYSRES_CONST_ACTION_TYPE_RESTORE_FROM_ARCHIVE SYSRES_CONST_ACTION_TYPE_REVISION SYSRES_CONST_ACTION_TYPE_SEND_BY_MAIL SYSRES_CONST_ACTION_TYPE_SIGN SYSRES_CONST_ACTION_TYPE_START SYSRES_CONST_ACTION_TYPE_UNLOCK SYSRES_CONST_ACTION_TYPE_UNLOCK_FROM_SERVER SYSRES_CONST_ACTION_TYPE_VERSION_STATE SYSRES_CONST_ACTION_TYPE_VERSION_VISIBILITY SYSRES_CONST_ACTION_TYPE_VIEW SYSRES_CONST_ACTION_TYPE_VIEW_SHADOW_COPY SYSRES_CONST_ACTION_TYPE_WORKFLOW_DESCRIPTION_MODIFY SYSRES_CONST_ACTION_TYPE_WRITE_HISTORY SYSRES_CONST_ACTIVE_VERSION_STATE_PICK_VALUE SYSRES_CONST_ADD_REFERENCE_MODE_NAME SYSRES_CONST_ADDITION_REQUISITE_CODE SYSRES_CONST_ADDITIONAL_PARAMS_REQUISITE_CODE SYSRES_CONST_ADITIONAL_JOB_END_DATE_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_READ_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_START_DATE_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_STATE_REQUISITE_NAME SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE_ACTION SYSRES_CONST_ALL_ACCEPT_CONDITION_RUS SYSRES_CONST_ALL_USERS_GROUP SYSRES_CONST_ALL_USERS_GROUP_NAME SYSRES_CONST_ALL_USERS_SERVER_GROUP_NAME SYSRES_CONST_ALLOWED_ACCESS_TYPE_CODE SYSRES_CONST_ALLOWED_ACCESS_TYPE_NAME SYSRES_CONST_APP_VIEWER_TYPE_REQUISITE_CODE SYSRES_CONST_APPROVING_SIGNATURE_NAME SYSRES_CONST_APPROVING_SIGNATURE_REQUISITE_CODE SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE_CODE SYSRES_CONST_ATTACH_TYPE_COMPONENT_TOKEN SYSRES_CONST_ATTACH_TYPE_DOC SYSRES_CONST_ATTACH_TYPE_EDOC SYSRES_CONST_ATTACH_TYPE_FOLDER SYSRES_CONST_ATTACH_TYPE_JOB SYSRES_CONST_ATTACH_TYPE_REFERENCE SYSRES_CONST_ATTACH_TYPE_TASK SYSRES_CONST_AUTH_ENCODED_PASSWORD SYSRES_CONST_AUTH_ENCODED_PASSWORD_CODE SYSRES_CONST_AUTH_NOVELL SYSRES_CONST_AUTH_PASSWORD SYSRES_CONST_AUTH_PASSWORD_CODE SYSRES_CONST_AUTH_WINDOWS SYSRES_CONST_AUTHENTICATING_SIGNATURE_NAME SYSRES_CONST_AUTHENTICATING_SIGNATURE_REQUISITE_CODE SYSRES_CONST_AUTO_ENUM_METHOD_FLAG SYSRES_CONST_AUTO_NUMERATION_CODE SYSRES_CONST_AUTO_STRONG_ENUM_METHOD_FLAG SYSRES_CONST_AUTOTEXT_NAME_REQUISITE_CODE SYSRES_CONST_AUTOTEXT_TEXT_REQUISITE_CODE SYSRES_CONST_AUTOTEXT_USAGE_ALL SYSRES_CONST_AUTOTEXT_USAGE_ALL_CODE SYSRES_CONST_AUTOTEXT_USAGE_SIGN SYSRES_CONST_AUTOTEXT_USAGE_SIGN_CODE SYSRES_CONST_AUTOTEXT_USAGE_WORK SYSRES_CONST_AUTOTEXT_USAGE_WORK_CODE SYSRES_CONST_AUTOTEXT_USE_ANYWHERE_CODE SYSRES_CONST_AUTOTEXT_USE_ON_SIGNING_CODE SYSRES_CONST_AUTOTEXT_USE_ON_WORK_CODE SYSRES_CONST_BEGIN_DATE_REQUISITE_CODE SYSRES_CONST_BLACK_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_BLUE_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_BTN_PART SYSRES_CONST_CALCULATED_ROLE_TYPE_CODE SYSRES_CONST_CALL_TYPE_VARIABLE_BUTTON_VALUE SYSRES_CONST_CALL_TYPE_VARIABLE_PROGRAM_VALUE SYSRES_CONST_CANCEL_MESSAGE_FUNCTION_RESULT SYSRES_CONST_CARD_PART SYSRES_CONST_CARD_REFERENCE_MODE_NAME SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_ENCRYPT_VALUE SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_AND_ENCRYPT_VALUE SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_VALUE SYSRES_CONST_CHECK_PARAM_VALUE_DATE_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_FLOAT_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_INTEGER_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_PICK_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_REEFRENCE_PARAM_TYPE SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_FEMININE SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_CODE_COMPONENT_TYPE_ADMIN SYSRES_CONST_CODE_COMPONENT_TYPE_DEVELOPER SYSRES_CONST_CODE_COMPONENT_TYPE_DOCS SYSRES_CONST_CODE_COMPONENT_TYPE_EDOC_CARDS SYSRES_CONST_CODE_COMPONENT_TYPE_EXTERNAL_EXECUTABLE SYSRES_CONST_CODE_COMPONENT_TYPE_OTHER SYSRES_CONST_CODE_COMPONENT_TYPE_REFERENCE SYSRES_CONST_CODE_COMPONENT_TYPE_REPORT SYSRES_CONST_CODE_COMPONENT_TYPE_SCRIPT SYSRES_CONST_CODE_COMPONENT_TYPE_URL SYSRES_CONST_CODE_REQUISITE_ACCESS SYSRES_CONST_CODE_REQUISITE_CODE SYSRES_CONST_CODE_REQUISITE_COMPONENT SYSRES_CONST_CODE_REQUISITE_DESCRIPTION SYSRES_CONST_CODE_REQUISITE_EXCLUDE_COMPONENT SYSRES_CONST_CODE_REQUISITE_RECORD SYSRES_CONST_COMMENT_REQ_CODE SYSRES_CONST_COMMON_SETTINGS_REQUISITE_CODE SYSRES_CONST_COMP_CODE_GRD SYSRES_CONST_COMPONENT_GROUP_TYPE_REQUISITE_CODE SYSRES_CONST_COMPONENT_TYPE_ADMIN_COMPONENTS SYSRES_CONST_COMPONENT_TYPE_DEVELOPER_COMPONENTS SYSRES_CONST_COMPONENT_TYPE_DOCS SYSRES_CONST_COMPONENT_TYPE_EDOC_CARDS SYSRES_CONST_COMPONENT_TYPE_EDOCS SYSRES_CONST_COMPONENT_TYPE_EXTERNAL_EXECUTABLE SYSRES_CONST_COMPONENT_TYPE_OTHER SYSRES_CONST_COMPONENT_TYPE_REFERENCE_TYPES SYSRES_CONST_COMPONENT_TYPE_REFERENCES SYSRES_CONST_COMPONENT_TYPE_REPORTS SYSRES_CONST_COMPONENT_TYPE_SCRIPTS SYSRES_CONST_COMPONENT_TYPE_URL SYSRES_CONST_COMPONENTS_REMOTE_SERVERS_VIEW_CODE SYSRES_CONST_CONDITION_BLOCK_DESCRIPTION SYSRES_CONST_CONST_FIRM_STATUS_COMMON SYSRES_CONST_CONST_FIRM_STATUS_INDIVIDUAL SYSRES_CONST_CONST_NEGATIVE_VALUE SYSRES_CONST_CONST_POSITIVE_VALUE SYSRES_CONST_CONST_SERVER_STATUS_DONT_REPLICATE SYSRES_CONST_CONST_SERVER_STATUS_REPLICATE SYSRES_CONST_CONTENTS_REQUISITE_CODE SYSRES_CONST_DATA_TYPE_BOOLEAN SYSRES_CONST_DATA_TYPE_DATE SYSRES_CONST_DATA_TYPE_FLOAT SYSRES_CONST_DATA_TYPE_INTEGER SYSRES_CONST_DATA_TYPE_PICK SYSRES_CONST_DATA_TYPE_REFERENCE SYSRES_CONST_DATA_TYPE_STRING SYSRES_CONST_DATA_TYPE_TEXT SYSRES_CONST_DATA_TYPE_VARIANT SYSRES_CONST_DATE_CLOSE_REQ_CODE SYSRES_CONST_DATE_FORMAT_DATE_ONLY_CHAR SYSRES_CONST_DATE_OPEN_REQ_CODE SYSRES_CONST_DATE_REQUISITE SYSRES_CONST_DATE_REQUISITE_CODE SYSRES_CONST_DATE_REQUISITE_NAME SYSRES_CONST_DATE_REQUISITE_TYPE SYSRES_CONST_DATE_TYPE_CHAR SYSRES_CONST_DATETIME_FORMAT_VALUE SYSRES_CONST_DEA_ACCESS_RIGHTS_ACTION_CODE SYSRES_CONST_DESCRIPTION_LOCALIZE_ID_REQUISITE_CODE SYSRES_CONST_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_DET1_PART SYSRES_CONST_DET2_PART SYSRES_CONST_DET3_PART SYSRES_CONST_DET4_PART SYSRES_CONST_DET5_PART SYSRES_CONST_DET6_PART SYSRES_CONST_DETAIL_DATASET_KEY_REQUISITE_CODE SYSRES_CONST_DETAIL_PICK_REQUISITE_CODE SYSRES_CONST_DETAIL_REQ_CODE SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_CODE SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_NAME SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_CODE SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_NAME SYSRES_CONST_DOCUMENT_STORAGES_CODE SYSRES_CONST_DOCUMENT_TEMPLATES_TYPE_NAME SYSRES_CONST_DOUBLE_REQUISITE_CODE SYSRES_CONST_EDITOR_CLOSE_FILE_OBSERV_TYPE_CODE SYSRES_CONST_EDITOR_CLOSE_PROCESS_OBSERV_TYPE_CODE SYSRES_CONST_EDITOR_TYPE_REQUISITE_CODE SYSRES_CONST_EDITORS_APPLICATION_NAME_REQUISITE_CODE SYSRES_CONST_EDITORS_CREATE_SEVERAL_PROCESSES_REQUISITE_CODE SYSRES_CONST_EDITORS_EXTENSION_REQUISITE_CODE SYSRES_CONST_EDITORS_OBSERVER_BY_PROCESS_TYPE SYSRES_CONST_EDITORS_REFERENCE_CODE SYSRES_CONST_EDITORS_REPLACE_SPEC_CHARS_REQUISITE_CODE SYSRES_CONST_EDITORS_USE_PLUGINS_REQUISITE_CODE SYSRES_CONST_EDITORS_VIEW_DOCUMENT_OPENED_TO_EDIT_CODE SYSRES_CONST_EDOC_CARD_TYPE_REQUISITE_CODE SYSRES_CONST_EDOC_CARD_TYPES_LINK_REQUISITE_CODE SYSRES_CONST_EDOC_CERTIFICATE_AND_PASSWORD_ENCODE_CODE SYSRES_CONST_EDOC_CERTIFICATE_ENCODE_CODE SYSRES_CONST_EDOC_DATE_REQUISITE_CODE SYSRES_CONST_EDOC_KIND_REFERENCE_CODE SYSRES_CONST_EDOC_KINDS_BY_TEMPLATE_ACTION_CODE SYSRES_CONST_EDOC_MANAGE_ACCESS_CODE SYSRES_CONST_EDOC_NONE_ENCODE_CODE SYSRES_CONST_EDOC_NUMBER_REQUISITE_CODE SYSRES_CONST_EDOC_PASSWORD_ENCODE_CODE SYSRES_CONST_EDOC_READONLY_ACCESS_CODE SYSRES_CONST_EDOC_SHELL_LIFE_TYPE_VIEW_VALUE SYSRES_CONST_EDOC_SIZE_RESTRICTION_PRIORITY_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_CHECK_ACCESS_RIGHTS_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_COMPUTER_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_DATABASE_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_EDIT_IN_STORAGE_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_LOCAL_PATH_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_SHARED_SOURCE_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_TEMPLATE_REQUISITE_CODE SYSRES_CONST_EDOC_TYPES_REFERENCE_CODE SYSRES_CONST_EDOC_VERSION_ACTIVE_STAGE_CODE SYSRES_CONST_EDOC_VERSION_DESIGN_STAGE_CODE SYSRES_CONST_EDOC_VERSION_OBSOLETE_STAGE_CODE SYSRES_CONST_EDOC_WRITE_ACCES_CODE SYSRES_CONST_EDOCUMENT_CARD_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE SYSRES_CONST_ENCODE_CERTIFICATE_TYPE_CODE SYSRES_CONST_END_DATE_REQUISITE_CODE SYSRES_CONST_ENUMERATION_TYPE_REQUISITE_CODE SYSRES_CONST_EXECUTE_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_EXECUTIVE_FILE_STORAGE_TYPE SYSRES_CONST_EXIST_CONST SYSRES_CONST_EXIST_VALUE SYSRES_CONST_EXPORT_LOCK_TYPE_ASK SYSRES_CONST_EXPORT_LOCK_TYPE_WITH_LOCK SYSRES_CONST_EXPORT_LOCK_TYPE_WITHOUT_LOCK SYSRES_CONST_EXPORT_VERSION_TYPE_ASK SYSRES_CONST_EXPORT_VERSION_TYPE_LAST SYSRES_CONST_EXPORT_VERSION_TYPE_LAST_ACTIVE SYSRES_CONST_EXTENSION_REQUISITE_CODE SYSRES_CONST_FILTER_NAME_REQUISITE_CODE SYSRES_CONST_FILTER_REQUISITE_CODE SYSRES_CONST_FILTER_TYPE_COMMON_CODE SYSRES_CONST_FILTER_TYPE_COMMON_NAME SYSRES_CONST_FILTER_TYPE_USER_CODE SYSRES_CONST_FILTER_TYPE_USER_NAME SYSRES_CONST_FILTER_VALUE_REQUISITE_NAME SYSRES_CONST_FLOAT_NUMBER_FORMAT_CHAR SYSRES_CONST_FLOAT_REQUISITE_TYPE SYSRES_CONST_FOLDER_AUTHOR_VALUE SYSRES_CONST_FOLDER_KIND_ANY_OBJECTS SYSRES_CONST_FOLDER_KIND_COMPONENTS SYSRES_CONST_FOLDER_KIND_EDOCS SYSRES_CONST_FOLDER_KIND_JOBS SYSRES_CONST_FOLDER_KIND_TASKS SYSRES_CONST_FOLDER_TYPE_COMMON SYSRES_CONST_FOLDER_TYPE_COMPONENT SYSRES_CONST_FOLDER_TYPE_FAVORITES SYSRES_CONST_FOLDER_TYPE_INBOX SYSRES_CONST_FOLDER_TYPE_OUTBOX SYSRES_CONST_FOLDER_TYPE_QUICK_LAUNCH SYSRES_CONST_FOLDER_TYPE_SEARCH SYSRES_CONST_FOLDER_TYPE_SHORTCUTS SYSRES_CONST_FOLDER_TYPE_USER SYSRES_CONST_FROM_DICTIONARY_ENUM_METHOD_FLAG SYSRES_CONST_FULL_SUBSTITUTE_TYPE SYSRES_CONST_FULL_SUBSTITUTE_TYPE_CODE SYSRES_CONST_FUNCTION_CANCEL_RESULT SYSRES_CONST_FUNCTION_CATEGORY_SYSTEM SYSRES_CONST_FUNCTION_CATEGORY_USER SYSRES_CONST_FUNCTION_FAILURE_RESULT SYSRES_CONST_FUNCTION_SAVE_RESULT SYSRES_CONST_GENERATED_REQUISITE SYSRES_CONST_GREEN_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_GROUP_ACCOUNT_TYPE_VALUE_CODE SYSRES_CONST_GROUP_CATEGORY_NORMAL_CODE SYSRES_CONST_GROUP_CATEGORY_NORMAL_NAME SYSRES_CONST_GROUP_CATEGORY_SERVICE_CODE SYSRES_CONST_GROUP_CATEGORY_SERVICE_NAME SYSRES_CONST_GROUP_COMMON_CATEGORY_FIELD_VALUE SYSRES_CONST_GROUP_FULL_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_RIGHTS_T_REQUISITE_CODE SYSRES_CONST_GROUP_SERVER_CODES_REQUISITE_CODE SYSRES_CONST_GROUP_SERVER_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_SERVICE_CATEGORY_FIELD_VALUE SYSRES_CONST_GROUP_USER_REQUISITE_CODE SYSRES_CONST_GROUPS_REFERENCE_CODE SYSRES_CONST_GROUPS_REQUISITE_CODE SYSRES_CONST_HIDDEN_MODE_NAME SYSRES_CONST_HIGH_LVL_REQUISITE_CODE SYSRES_CONST_HISTORY_ACTION_CREATE_CODE SYSRES_CONST_HISTORY_ACTION_DELETE_CODE SYSRES_CONST_HISTORY_ACTION_EDIT_CODE SYSRES_CONST_HOUR_CHAR SYSRES_CONST_ID_REQUISITE_CODE SYSRES_CONST_IDSPS_REQUISITE_CODE SYSRES_CONST_IMAGE_MODE_COLOR SYSRES_CONST_IMAGE_MODE_GREYSCALE SYSRES_CONST_IMAGE_MODE_MONOCHROME SYSRES_CONST_IMPORTANCE_HIGH SYSRES_CONST_IMPORTANCE_LOW SYSRES_CONST_IMPORTANCE_NORMAL SYSRES_CONST_IN_DESIGN_VERSION_STATE_PICK_VALUE SYSRES_CONST_INCOMING_WORK_RULE_TYPE_CODE SYSRES_CONST_INT_REQUISITE SYSRES_CONST_INT_REQUISITE_TYPE SYSRES_CONST_INTEGER_NUMBER_FORMAT_CHAR SYSRES_CONST_INTEGER_TYPE_CHAR SYSRES_CONST_IS_GENERATED_REQUISITE_NEGATIVE_VALUE SYSRES_CONST_IS_PUBLIC_ROLE_REQUISITE_CODE SYSRES_CONST_IS_REMOTE_USER_NEGATIVE_VALUE SYSRES_CONST_IS_REMOTE_USER_POSITIVE_VALUE SYSRES_CONST_IS_STORED_REQUISITE_NEGATIVE_VALUE SYSRES_CONST_IS_STORED_REQUISITE_STORED_VALUE SYSRES_CONST_ITALIC_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_JOB_BLOCK_DESCRIPTION SYSRES_CONST_JOB_KIND_CONTROL_JOB SYSRES_CONST_JOB_KIND_JOB SYSRES_CONST_JOB_KIND_NOTICE SYSRES_CONST_JOB_STATE_ABORTED SYSRES_CONST_JOB_STATE_COMPLETE SYSRES_CONST_JOB_STATE_WORKING SYSRES_CONST_KIND_REQUISITE_CODE SYSRES_CONST_KIND_REQUISITE_NAME SYSRES_CONST_KINDS_CREATE_SHADOW_COPIES_REQUISITE_CODE SYSRES_CONST_KINDS_DEFAULT_EDOC_LIFE_STAGE_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALL_TEPLATES_ALLOWED_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALLOW_LIFE_CYCLE_STAGE_CHANGING_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALLOW_MULTIPLE_ACTIVE_VERSIONS_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_SHARE_ACCES_RIGHTS_BY_DEFAULT_CODE SYSRES_CONST_KINDS_EDOC_TEMPLATE_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_TYPE_REQUISITE_CODE SYSRES_CONST_KINDS_SIGNERS_REQUISITES_CODE SYSRES_CONST_KOD_INPUT_TYPE SYSRES_CONST_LAST_UPDATE_DATE_REQUISITE_CODE SYSRES_CONST_LIFE_CYCLE_START_STAGE_REQUISITE_CODE SYSRES_CONST_LILAC_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_LINK_OBJECT_KIND_COMPONENT SYSRES_CONST_LINK_OBJECT_KIND_DOCUMENT SYSRES_CONST_LINK_OBJECT_KIND_EDOC SYSRES_CONST_LINK_OBJECT_KIND_FOLDER SYSRES_CONST_LINK_OBJECT_KIND_JOB SYSRES_CONST_LINK_OBJECT_KIND_REFERENCE SYSRES_CONST_LINK_OBJECT_KIND_TASK SYSRES_CONST_LINK_REF_TYPE_REQUISITE_CODE SYSRES_CONST_LIST_REFERENCE_MODE_NAME SYSRES_CONST_LOCALIZATION_DICTIONARY_MAIN_VIEW_CODE SYSRES_CONST_MAIN_VIEW_CODE SYSRES_CONST_MANUAL_ENUM_METHOD_FLAG SYSRES_CONST_MASTER_COMP_TYPE_REQUISITE_CODE SYSRES_CONST_MASTER_TABLE_REC_ID_REQUISITE_CODE SYSRES_CONST_MAXIMIZED_MODE_NAME SYSRES_CONST_ME_VALUE SYSRES_CONST_MESSAGE_ATTENTION_CAPTION SYSRES_CONST_MESSAGE_CONFIRMATION_CAPTION SYSRES_CONST_MESSAGE_ERROR_CAPTION SYSRES_CONST_MESSAGE_INFORMATION_CAPTION SYSRES_CONST_MINIMIZED_MODE_NAME SYSRES_CONST_MINUTE_CHAR SYSRES_CONST_MODULE_REQUISITE_CODE SYSRES_CONST_MONITORING_BLOCK_DESCRIPTION SYSRES_CONST_MONTH_FORMAT_VALUE SYSRES_CONST_NAME_LOCALIZE_ID_REQUISITE_CODE SYSRES_CONST_NAME_REQUISITE_CODE SYSRES_CONST_NAME_SINGULAR_REQUISITE_CODE SYSRES_CONST_NAMEAN_INPUT_TYPE SYSRES_CONST_NEGATIVE_PICK_VALUE SYSRES_CONST_NEGATIVE_VALUE SYSRES_CONST_NO SYSRES_CONST_NO_PICK_VALUE SYSRES_CONST_NO_SIGNATURE_REQUISITE_CODE SYSRES_CONST_NO_VALUE SYSRES_CONST_NONE_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_NORMAL_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_NORMAL_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_NORMAL_MODE_NAME SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_CODE SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_NAME SYSRES_CONST_NOTE_REQUISITE_CODE SYSRES_CONST_NOTICE_BLOCK_DESCRIPTION SYSRES_CONST_NUM_REQUISITE SYSRES_CONST_NUM_STR_REQUISITE_CODE SYSRES_CONST_NUMERATION_AUTO_NOT_STRONG SYSRES_CONST_NUMERATION_AUTO_STRONG SYSRES_CONST_NUMERATION_FROM_DICTONARY SYSRES_CONST_NUMERATION_MANUAL SYSRES_CONST_NUMERIC_TYPE_CHAR SYSRES_CONST_NUMREQ_REQUISITE_CODE SYSRES_CONST_OBSOLETE_VERSION_STATE_PICK_VALUE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_CODE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_FEMININE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_OPTIONAL_FORM_COMP_REQCODE_PREFIX SYSRES_CONST_ORANGE_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_ORIGINALREF_REQUISITE_CODE SYSRES_CONST_OURFIRM_REF_CODE SYSRES_CONST_OURFIRM_REQUISITE_CODE SYSRES_CONST_OURFIRM_VAR SYSRES_CONST_OUTGOING_WORK_RULE_TYPE_CODE SYSRES_CONST_PICK_NEGATIVE_RESULT SYSRES_CONST_PICK_POSITIVE_RESULT SYSRES_CONST_PICK_REQUISITE SYSRES_CONST_PICK_REQUISITE_TYPE SYSRES_CONST_PICK_TYPE_CHAR SYSRES_CONST_PLAN_STATUS_REQUISITE_CODE SYSRES_CONST_PLATFORM_VERSION_COMMENT SYSRES_CONST_PLUGINS_SETTINGS_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_POSITIVE_PICK_VALUE SYSRES_CONST_POWER_TO_CREATE_ACTION_CODE SYSRES_CONST_POWER_TO_SIGN_ACTION_CODE SYSRES_CONST_PRIORITY_REQUISITE_CODE SYSRES_CONST_QUALIFIED_TASK_TYPE SYSRES_CONST_QUALIFIED_TASK_TYPE_CODE SYSRES_CONST_RECSTAT_REQUISITE_CODE SYSRES_CONST_RED_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_REF_ID_T_REF_TYPE_REQUISITE_CODE SYSRES_CONST_REF_REQUISITE SYSRES_CONST_REF_REQUISITE_TYPE SYSRES_CONST_REF_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE SYSRES_CONST_REFERENCE_RECORD_HISTORY_CREATE_ACTION_CODE SYSRES_CONST_REFERENCE_RECORD_HISTORY_DELETE_ACTION_CODE SYSRES_CONST_REFERENCE_RECORD_HISTORY_MODIFY_ACTION_CODE SYSRES_CONST_REFERENCE_TYPE_CHAR SYSRES_CONST_REFERENCE_TYPE_REQUISITE_NAME SYSRES_CONST_REFERENCES_ADD_PARAMS_REQUISITE_CODE SYSRES_CONST_REFERENCES_DISPLAY_REQUISITE_REQUISITE_CODE SYSRES_CONST_REMOTE_SERVER_STATUS_WORKING SYSRES_CONST_REMOTE_SERVER_TYPE_MAIN SYSRES_CONST_REMOTE_SERVER_TYPE_SECONDARY SYSRES_CONST_REMOTE_USER_FLAG_VALUE_CODE SYSRES_CONST_REPORT_APP_EDITOR_INTERNAL SYSRES_CONST_REPORT_BASE_REPORT_ID_REQUISITE_CODE SYSRES_CONST_REPORT_BASE_REPORT_REQUISITE_CODE SYSRES_CONST_REPORT_SCRIPT_REQUISITE_CODE SYSRES_CONST_REPORT_TEMPLATE_REQUISITE_CODE SYSRES_CONST_REPORT_VIEWER_CODE_REQUISITE_CODE SYSRES_CONST_REQ_ALLOW_COMPONENT_DEFAULT_VALUE SYSRES_CONST_REQ_ALLOW_RECORD_DEFAULT_VALUE SYSRES_CONST_REQ_ALLOW_SERVER_COMPONENT_DEFAULT_VALUE SYSRES_CONST_REQ_MODE_AVAILABLE_CODE SYSRES_CONST_REQ_MODE_EDIT_CODE SYSRES_CONST_REQ_MODE_HIDDEN_CODE SYSRES_CONST_REQ_MODE_NOT_AVAILABLE_CODE SYSRES_CONST_REQ_MODE_VIEW_CODE SYSRES_CONST_REQ_NUMBER_REQUISITE_CODE SYSRES_CONST_REQ_SECTION_VALUE SYSRES_CONST_REQ_TYPE_VALUE SYSRES_CONST_REQUISITE_FORMAT_BY_UNIT SYSRES_CONST_REQUISITE_FORMAT_DATE_FULL SYSRES_CONST_REQUISITE_FORMAT_DATE_TIME SYSRES_CONST_REQUISITE_FORMAT_LEFT SYSRES_CONST_REQUISITE_FORMAT_RIGHT SYSRES_CONST_REQUISITE_FORMAT_WITHOUT_UNIT SYSRES_CONST_REQUISITE_NUMBER_REQUISITE_CODE SYSRES_CONST_REQUISITE_SECTION_ACTIONS SYSRES_CONST_REQUISITE_SECTION_BUTTON SYSRES_CONST_REQUISITE_SECTION_BUTTONS SYSRES_CONST_REQUISITE_SECTION_CARD SYSRES_CONST_REQUISITE_SECTION_TABLE SYSRES_CONST_REQUISITE_SECTION_TABLE10 SYSRES_CONST_REQUISITE_SECTION_TABLE11 SYSRES_CONST_REQUISITE_SECTION_TABLE12 SYSRES_CONST_REQUISITE_SECTION_TABLE13 SYSRES_CONST_REQUISITE_SECTION_TABLE14 SYSRES_CONST_REQUISITE_SECTION_TABLE15 SYSRES_CONST_REQUISITE_SECTION_TABLE16 SYSRES_CONST_REQUISITE_SECTION_TABLE17 SYSRES_CONST_REQUISITE_SECTION_TABLE18 SYSRES_CONST_REQUISITE_SECTION_TABLE19 SYSRES_CONST_REQUISITE_SECTION_TABLE2 SYSRES_CONST_REQUISITE_SECTION_TABLE20 SYSRES_CONST_REQUISITE_SECTION_TABLE21 SYSRES_CONST_REQUISITE_SECTION_TABLE22 SYSRES_CONST_REQUISITE_SECTION_TABLE23 SYSRES_CONST_REQUISITE_SECTION_TABLE24 SYSRES_CONST_REQUISITE_SECTION_TABLE3 SYSRES_CONST_REQUISITE_SECTION_TABLE4 SYSRES_CONST_REQUISITE_SECTION_TABLE5 SYSRES_CONST_REQUISITE_SECTION_TABLE6 SYSRES_CONST_REQUISITE_SECTION_TABLE7 SYSRES_CONST_REQUISITE_SECTION_TABLE8 SYSRES_CONST_REQUISITE_SECTION_TABLE9 SYSRES_CONST_REQUISITES_PSEUDOREFERENCE_REQUISITE_NUMBER_REQUISITE_CODE SYSRES_CONST_RIGHT_ALIGNMENT_CODE SYSRES_CONST_ROLES_REFERENCE_CODE SYSRES_CONST_ROUTE_STEP_AFTER_RUS SYSRES_CONST_ROUTE_STEP_AND_CONDITION_RUS SYSRES_CONST_ROUTE_STEP_OR_CONDITION_RUS SYSRES_CONST_ROUTE_TYPE_COMPLEX SYSRES_CONST_ROUTE_TYPE_PARALLEL SYSRES_CONST_ROUTE_TYPE_SERIAL SYSRES_CONST_SBDATASETDESC_NEGATIVE_VALUE SYSRES_CONST_SBDATASETDESC_POSITIVE_VALUE SYSRES_CONST_SBVIEWSDESC_POSITIVE_VALUE SYSRES_CONST_SCRIPT_BLOCK_DESCRIPTION SYSRES_CONST_SEARCH_BY_TEXT_REQUISITE_CODE SYSRES_CONST_SEARCHES_COMPONENT_CONTENT SYSRES_CONST_SEARCHES_CRITERIA_ACTION_NAME SYSRES_CONST_SEARCHES_EDOC_CONTENT SYSRES_CONST_SEARCHES_FOLDER_CONTENT SYSRES_CONST_SEARCHES_JOB_CONTENT SYSRES_CONST_SEARCHES_REFERENCE_CODE SYSRES_CONST_SEARCHES_TASK_CONTENT SYSRES_CONST_SECOND_CHAR SYSRES_CONST_SECTION_REQUISITE_ACTIONS_VALUE SYSRES_CONST_SECTION_REQUISITE_CARD_VALUE SYSRES_CONST_SECTION_REQUISITE_CODE SYSRES_CONST_SECTION_REQUISITE_DETAIL_1_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_2_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_3_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_4_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_5_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_6_VALUE SYSRES_CONST_SELECT_REFERENCE_MODE_NAME SYSRES_CONST_SELECT_TYPE_SELECTABLE SYSRES_CONST_SELECT_TYPE_SELECTABLE_ONLY_CHILD SYSRES_CONST_SELECT_TYPE_SELECTABLE_WITH_CHILD SYSRES_CONST_SELECT_TYPE_UNSLECTABLE SYSRES_CONST_SERVER_TYPE_MAIN SYSRES_CONST_SERVICE_USER_CATEGORY_FIELD_VALUE SYSRES_CONST_SETTINGS_USER_REQUISITE_CODE SYSRES_CONST_SIGNATURE_AND_ENCODE_CERTIFICATE_TYPE_CODE SYSRES_CONST_SIGNATURE_CERTIFICATE_TYPE_CODE SYSRES_CONST_SINGULAR_TITLE_REQUISITE_CODE SYSRES_CONST_SQL_SERVER_AUTHENTIFICATION_FLAG_VALUE_CODE SYSRES_CONST_SQL_SERVER_ENCODE_AUTHENTIFICATION_FLAG_VALUE_CODE SYSRES_CONST_STANDART_ROUTE_REFERENCE_CODE SYSRES_CONST_STANDART_ROUTE_REFERENCE_COMMENT_REQUISITE_CODE SYSRES_CONST_STANDART_ROUTES_GROUPS_REFERENCE_CODE SYSRES_CONST_STATE_REQ_NAME SYSRES_CONST_STATE_REQUISITE_ACTIVE_VALUE SYSRES_CONST_STATE_REQUISITE_CLOSED_VALUE SYSRES_CONST_STATE_REQUISITE_CODE SYSRES_CONST_STATIC_ROLE_TYPE_CODE SYSRES_CONST_STATUS_PLAN_DEFAULT_VALUE SYSRES_CONST_STATUS_VALUE_AUTOCLEANING SYSRES_CONST_STATUS_VALUE_BLUE_SQUARE SYSRES_CONST_STATUS_VALUE_COMPLETE SYSRES_CONST_STATUS_VALUE_GREEN_SQUARE SYSRES_CONST_STATUS_VALUE_ORANGE_SQUARE SYSRES_CONST_STATUS_VALUE_PURPLE_SQUARE SYSRES_CONST_STATUS_VALUE_RED_SQUARE SYSRES_CONST_STATUS_VALUE_SUSPEND SYSRES_CONST_STATUS_VALUE_YELLOW_SQUARE SYSRES_CONST_STDROUTE_SHOW_TO_USERS_REQUISITE_CODE SYSRES_CONST_STORAGE_TYPE_FILE SYSRES_CONST_STORAGE_TYPE_SQL_SERVER SYSRES_CONST_STR_REQUISITE SYSRES_CONST_STRIKEOUT_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_STRING_FORMAT_LEFT_ALIGN_CHAR SYSRES_CONST_STRING_FORMAT_RIGHT_ALIGN_CHAR SYSRES_CONST_STRING_REQUISITE_CODE SYSRES_CONST_STRING_REQUISITE_TYPE SYSRES_CONST_STRING_TYPE_CHAR SYSRES_CONST_SUBSTITUTES_PSEUDOREFERENCE_CODE SYSRES_CONST_SUBTASK_BLOCK_DESCRIPTION SYSRES_CONST_SYSTEM_SETTING_CURRENT_USER_PARAM_VALUE SYSRES_CONST_SYSTEM_SETTING_EMPTY_VALUE_PARAM_VALUE SYSRES_CONST_SYSTEM_VERSION_COMMENT SYSRES_CONST_TASK_ACCESS_TYPE_ALL SYSRES_CONST_TASK_ACCESS_TYPE_ALL_MEMBERS SYSRES_CONST_TASK_ACCESS_TYPE_MANUAL SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION_AND_PASSWORD SYSRES_CONST_TASK_ENCODE_TYPE_NONE SYSRES_CONST_TASK_ENCODE_TYPE_PASSWORD SYSRES_CONST_TASK_ROUTE_ALL_CONDITION SYSRES_CONST_TASK_ROUTE_AND_CONDITION SYSRES_CONST_TASK_ROUTE_OR_CONDITION SYSRES_CONST_TASK_STATE_ABORTED SYSRES_CONST_TASK_STATE_COMPLETE SYSRES_CONST_TASK_STATE_CONTINUED SYSRES_CONST_TASK_STATE_CONTROL SYSRES_CONST_TASK_STATE_INIT SYSRES_CONST_TASK_STATE_WORKING SYSRES_CONST_TASK_TITLE SYSRES_CONST_TASK_TYPES_GROUPS_REFERENCE_CODE SYSRES_CONST_TASK_TYPES_REFERENCE_CODE SYSRES_CONST_TEMPLATES_REFERENCE_CODE SYSRES_CONST_TEST_DATE_REQUISITE_NAME SYSRES_CONST_TEST_DEV_DATABASE_NAME SYSRES_CONST_TEST_DEV_SYSTEM_CODE SYSRES_CONST_TEST_EDMS_DATABASE_NAME SYSRES_CONST_TEST_EDMS_MAIN_CODE SYSRES_CONST_TEST_EDMS_MAIN_DB_NAME SYSRES_CONST_TEST_EDMS_SECOND_CODE SYSRES_CONST_TEST_EDMS_SECOND_DB_NAME SYSRES_CONST_TEST_EDMS_SYSTEM_CODE SYSRES_CONST_TEST_NUMERIC_REQUISITE_NAME SYSRES_CONST_TEXT_REQUISITE SYSRES_CONST_TEXT_REQUISITE_CODE SYSRES_CONST_TEXT_REQUISITE_TYPE SYSRES_CONST_TEXT_TYPE_CHAR SYSRES_CONST_TYPE_CODE_REQUISITE_CODE SYSRES_CONST_TYPE_REQUISITE_CODE SYSRES_CONST_UNDEFINED_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_UNITS_SECTION_ID_REQUISITE_CODE SYSRES_CONST_UNITS_SECTION_REQUISITE_CODE SYSRES_CONST_UNOPERATING_RECORD_FLAG_VALUE_CODE SYSRES_CONST_UNSTORED_DATA_REQUISITE_CODE SYSRES_CONST_UNSTORED_DATA_REQUISITE_NAME SYSRES_CONST_USE_ACCESS_TYPE_CODE SYSRES_CONST_USE_ACCESS_TYPE_NAME SYSRES_CONST_USER_ACCOUNT_TYPE_VALUE_CODE SYSRES_CONST_USER_ADDITIONAL_INFORMATION_REQUISITE_CODE SYSRES_CONST_USER_AND_GROUP_ID_FROM_PSEUDOREFERENCE_REQUISITE_CODE SYSRES_CONST_USER_CATEGORY_NORMAL SYSRES_CONST_USER_CERTIFICATE_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_STATE_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_SUBJECT_NAME_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_THUMBPRINT_REQUISITE_CODE SYSRES_CONST_USER_COMMON_CATEGORY SYSRES_CONST_USER_COMMON_CATEGORY_CODE SYSRES_CONST_USER_FULL_NAME_REQUISITE_CODE SYSRES_CONST_USER_GROUP_TYPE_REQUISITE_CODE SYSRES_CONST_USER_LOGIN_REQUISITE_CODE SYSRES_CONST_USER_REMOTE_CONTROLLER_REQUISITE_CODE SYSRES_CONST_USER_REMOTE_SYSTEM_REQUISITE_CODE SYSRES_CONST_USER_RIGHTS_T_REQUISITE_CODE SYSRES_CONST_USER_SERVER_NAME_REQUISITE_CODE SYSRES_CONST_USER_SERVICE_CATEGORY SYSRES_CONST_USER_SERVICE_CATEGORY_CODE SYSRES_CONST_USER_STATUS_ADMINISTRATOR_CODE SYSRES_CONST_USER_STATUS_ADMINISTRATOR_NAME SYSRES_CONST_USER_STATUS_DEVELOPER_CODE SYSRES_CONST_USER_STATUS_DEVELOPER_NAME SYSRES_CONST_USER_STATUS_DISABLED_CODE SYSRES_CONST_USER_STATUS_DISABLED_NAME SYSRES_CONST_USER_STATUS_SYSTEM_DEVELOPER_CODE SYSRES_CONST_USER_STATUS_USER_CODE SYSRES_CONST_USER_STATUS_USER_NAME SYSRES_CONST_USER_STATUS_USER_NAME_DEPRECATED SYSRES_CONST_USER_TYPE_FIELD_VALUE_USER SYSRES_CONST_USER_TYPE_REQUISITE_CODE SYSRES_CONST_USERS_CONTROLLER_REQUISITE_CODE SYSRES_CONST_USERS_IS_MAIN_SERVER_REQUISITE_CODE SYSRES_CONST_USERS_REFERENCE_CODE SYSRES_CONST_USERS_REGISTRATION_CERTIFICATES_ACTION_NAME SYSRES_CONST_USERS_REQUISITE_CODE SYSRES_CONST_USERS_SYSTEM_REQUISITE_CODE SYSRES_CONST_USERS_USER_ACCESS_RIGHTS_TYPR_REQUISITE_CODE SYSRES_CONST_USERS_USER_AUTHENTICATION_REQUISITE_CODE SYSRES_CONST_USERS_USER_COMPONENT_REQUISITE_CODE SYSRES_CONST_USERS_USER_GROUP_REQUISITE_CODE SYSRES_CONST_USERS_VIEW_CERTIFICATES_ACTION_NAME SYSRES_CONST_VIEW_DEFAULT_CODE SYSRES_CONST_VIEW_DEFAULT_NAME SYSRES_CONST_VIEWER_REQUISITE_CODE SYSRES_CONST_WAITING_BLOCK_DESCRIPTION SYSRES_CONST_WIZARD_FORM_LABEL_TEST_STRING SYSRES_CONST_WIZARD_QUERY_PARAM_HEIGHT_ETALON_STRING SYSRES_CONST_WIZARD_REFERENCE_COMMENT_REQUISITE_CODE SYSRES_CONST_WORK_RULES_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_WORK_TIME_CALENDAR_REFERENCE_CODE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE_RUS SYSRES_CONST_WORK_WORKFLOW_SOFT_ROUTE_TYPE_VALUE_CODE_RUS SYSRES_CONST_WORKFLOW_ROUTE_TYPR_HARD SYSRES_CONST_WORKFLOW_ROUTE_TYPR_SOFT SYSRES_CONST_XML_ENCODING SYSRES_CONST_XREC_STAT_REQUISITE_CODE SYSRES_CONST_XRECID_FIELD_NAME SYSRES_CONST_YES SYSRES_CONST_YES_NO_2_REQUISITE_CODE SYSRES_CONST_YES_NO_REQUISITE_CODE SYSRES_CONST_YES_NO_T_REF_TYPE_REQUISITE_CODE SYSRES_CONST_YES_PICK_VALUE SYSRES_CONST_YES_VALUE CR FALSE nil NO_VALUE NULL TAB TRUE YES_VALUE ADMINISTRATORS_GROUP_NAME CUSTOMIZERS_GROUP_NAME DEVELOPERS_GROUP_NAME SERVICE_USERS_GROUP_NAME DECISION_BLOCK_FIRST_OPERAND_PROPERTY DECISION_BLOCK_NAME_PROPERTY DECISION_BLOCK_OPERATION_PROPERTY DECISION_BLOCK_RESULT_TYPE_PROPERTY DECISION_BLOCK_SECOND_OPERAND_PROPERTY ANY_FILE_EXTENTION COMPRESSED_DOCUMENT_EXTENSION EXTENDED_DOCUMENT_EXTENSION SHORT_COMPRESSED_DOCUMENT_EXTENSION SHORT_EXTENDED_DOCUMENT_EXTENSION JOB_BLOCK_ABORT_DEADLINE_PROPERTY JOB_BLOCK_AFTER_FINISH_EVENT JOB_BLOCK_AFTER_QUERY_PARAMETERS_EVENT JOB_BLOCK_ATTACHMENT_PROPERTY JOB_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY JOB_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY JOB_BLOCK_BEFORE_QUERY_PARAMETERS_EVENT JOB_BLOCK_BEFORE_START_EVENT JOB_BLOCK_CREATED_JOBS_PROPERTY JOB_BLOCK_DEADLINE_PROPERTY JOB_BLOCK_EXECUTION_RESULTS_PROPERTY JOB_BLOCK_IS_PARALLEL_PROPERTY JOB_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY JOB_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY JOB_BLOCK_JOB_TEXT_PROPERTY JOB_BLOCK_NAME_PROPERTY JOB_BLOCK_NEED_SIGN_ON_PERFORM_PROPERTY JOB_BLOCK_PERFORMER_PROPERTY JOB_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY JOB_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY JOB_BLOCK_SUBJECT_PROPERTY ENGLISH_LANGUAGE_CODE RUSSIAN_LANGUAGE_CODE smHidden smMaximized smMinimized smNormal wmNo wmYes COMPONENT_TOKEN_LINK_KIND DOCUMENT_LINK_KIND EDOCUMENT_LINK_KIND FOLDER_LINK_KIND JOB_LINK_KIND REFERENCE_LINK_KIND TASK_LINK_KIND COMPONENT_TOKEN_LOCK_TYPE EDOCUMENT_VERSION_LOCK_TYPE MONITOR_BLOCK_AFTER_FINISH_EVENT MONITOR_BLOCK_BEFORE_START_EVENT MONITOR_BLOCK_DEADLINE_PROPERTY MONITOR_BLOCK_INTERVAL_PROPERTY MONITOR_BLOCK_INTERVAL_TYPE_PROPERTY MONITOR_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY MONITOR_BLOCK_NAME_PROPERTY MONITOR_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY MONITOR_BLOCK_SEARCH_SCRIPT_PROPERTY NOTICE_BLOCK_AFTER_FINISH_EVENT NOTICE_BLOCK_ATTACHMENT_PROPERTY NOTICE_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY NOTICE_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY NOTICE_BLOCK_BEFORE_START_EVENT NOTICE_BLOCK_CREATED_NOTICES_PROPERTY NOTICE_BLOCK_DEADLINE_PROPERTY NOTICE_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY NOTICE_BLOCK_NAME_PROPERTY NOTICE_BLOCK_NOTICE_TEXT_PROPERTY NOTICE_BLOCK_PERFORMER_PROPERTY NOTICE_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY NOTICE_BLOCK_SUBJECT_PROPERTY dseAfterCancel dseAfterClose dseAfterDelete dseAfterDeleteOutOfTransaction dseAfterInsert dseAfterOpen dseAfterScroll dseAfterUpdate dseAfterUpdateOutOfTransaction dseBeforeCancel dseBeforeClose dseBeforeDelete dseBeforeDetailUpdate dseBeforeInsert dseBeforeOpen dseBeforeUpdate dseOnAnyRequisiteChange dseOnCloseRecord dseOnDeleteError dseOnOpenRecord dseOnPrepareUpdate dseOnUpdateError dseOnUpdateRatifiedRecord dseOnValidDelete dseOnValidUpdate reOnChange reOnChangeValues SELECTION_BEGIN_ROUTE_EVENT SELECTION_END_ROUTE_EVENT CURRENT_PERIOD_IS_REQUIRED PREVIOUS_CARD_TYPE_NAME SHOW_RECORD_PROPERTIES_FORM ACCESS_RIGHTS_SETTING_DIALOG_CODE ADMINISTRATOR_USER_CODE ANALYTIC_REPORT_TYPE asrtHideLocal asrtHideRemote CALCULATED_ROLE_TYPE_CODE COMPONENTS_REFERENCE_DEVELOPER_VIEW_CODE DCTS_TEST_PROTOCOLS_FOLDER_PATH E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED_BY_USER E_EDOC_VERSION_ALREDY_SIGNED E_EDOC_VERSION_ALREDY_SIGNED_BY_USER EDOC_TYPES_CODE_REQUISITE_FIELD_NAME EDOCUMENTS_ALIAS_NAME FILES_FOLDER_PATH FILTER_OPERANDS_DELIMITER FILTER_OPERATIONS_DELIMITER FORMCARD_NAME FORMLIST_NAME GET_EXTENDED_DOCUMENT_EXTENSION_CREATION_MODE GET_EXTENDED_DOCUMENT_EXTENSION_IMPORT_MODE INTEGRATED_REPORT_TYPE IS_BUILDER_APPLICATION_ROLE IS_BUILDER_APPLICATION_ROLE2 IS_BUILDER_USERS ISBSYSDEV LOG_FOLDER_PATH mbCancel mbNo mbNoToAll mbOK mbYes mbYesToAll MEMORY_DATASET_DESRIPTIONS_FILENAME mrNo mrNoToAll mrYes mrYesToAll MULTIPLE_SELECT_DIALOG_CODE NONOPERATING_RECORD_FLAG_FEMININE NONOPERATING_RECORD_FLAG_MASCULINE OPERATING_RECORD_FLAG_FEMININE OPERATING_RECORD_FLAG_MASCULINE PROFILING_SETTINGS_COMMON_SETTINGS_CODE_VALUE PROGRAM_INITIATED_LOOKUP_ACTION ratDelete ratEdit ratInsert REPORT_TYPE REQUIRED_PICK_VALUES_VARIABLE rmCard rmList SBRTE_PROGID_DEV SBRTE_PROGID_RELEASE STATIC_ROLE_TYPE_CODE SUPPRESS_EMPTY_TEMPLATE_CREATION SYSTEM_USER_CODE UPDATE_DIALOG_DATASET USED_IN_OBJECT_HINT_PARAM USER_INITIATED_LOOKUP_ACTION USER_NAME_FORMAT USER_SELECTION_RESTRICTIONS WORKFLOW_TEST_PROTOCOLS_FOLDER_PATH ELS_SUBTYPE_CONTROL_NAME ELS_FOLDER_KIND_CONTROL_NAME REPEAT_PROCESS_CURRENT_OBJECT_EXCEPTION_NAME PRIVILEGE_COMPONENT_FULL_ACCESS PRIVILEGE_DEVELOPMENT_EXPORT PRIVILEGE_DEVELOPMENT_IMPORT PRIVILEGE_DOCUMENT_DELETE PRIVILEGE_ESD PRIVILEGE_FOLDER_DELETE PRIVILEGE_MANAGE_ACCESS_RIGHTS PRIVILEGE_MANAGE_REPLICATION PRIVILEGE_MANAGE_SESSION_SERVER PRIVILEGE_OBJECT_FULL_ACCESS PRIVILEGE_OBJECT_VIEW PRIVILEGE_RESERVE_LICENSE PRIVILEGE_SYSTEM_CUSTOMIZE PRIVILEGE_SYSTEM_DEVELOP PRIVILEGE_SYSTEM_INSTALL PRIVILEGE_TASK_DELETE PRIVILEGE_USER_PLUGIN_SETTINGS_CUSTOMIZE PRIVILEGES_PSEUDOREFERENCE_CODE ACCESS_TYPES_PSEUDOREFERENCE_CODE ALL_AVAILABLE_COMPONENTS_PSEUDOREFERENCE_CODE ALL_AVAILABLE_PRIVILEGES_PSEUDOREFERENCE_CODE ALL_REPLICATE_COMPONENTS_PSEUDOREFERENCE_CODE AVAILABLE_DEVELOPERS_COMPONENTS_PSEUDOREFERENCE_CODE COMPONENTS_PSEUDOREFERENCE_CODE FILTRATER_SETTINGS_CONFLICTS_PSEUDOREFERENCE_CODE GROUPS_PSEUDOREFERENCE_CODE RECEIVE_PROTOCOL_PSEUDOREFERENCE_CODE REFERENCE_REQUISITE_PSEUDOREFERENCE_CODE REFERENCE_REQUISITES_PSEUDOREFERENCE_CODE REFTYPES_PSEUDOREFERENCE_CODE REPLICATION_SEANCES_DIARY_PSEUDOREFERENCE_CODE SEND_PROTOCOL_PSEUDOREFERENCE_CODE SUBSTITUTES_PSEUDOREFERENCE_CODE SYSTEM_SETTINGS_PSEUDOREFERENCE_CODE UNITS_PSEUDOREFERENCE_CODE USERS_PSEUDOREFERENCE_CODE VIEWERS_PSEUDOREFERENCE_CODE CERTIFICATE_TYPE_ENCRYPT CERTIFICATE_TYPE_SIGN CERTIFICATE_TYPE_SIGN_AND_ENCRYPT STORAGE_TYPE_FILE STORAGE_TYPE_NAS_CIFS STORAGE_TYPE_SAPERION STORAGE_TYPE_SQL_SERVER COMPTYPE2_REQUISITE_DOCUMENTS_VALUE COMPTYPE2_REQUISITE_TASKS_VALUE COMPTYPE2_REQUISITE_FOLDERS_VALUE COMPTYPE2_REQUISITE_REFERENCES_VALUE SYSREQ_CODE SYSREQ_COMPTYPE2 SYSREQ_CONST_AVAILABLE_FOR_WEB SYSREQ_CONST_COMMON_CODE SYSREQ_CONST_COMMON_VALUE SYSREQ_CONST_FIRM_CODE SYSREQ_CONST_FIRM_STATUS SYSREQ_CONST_FIRM_VALUE SYSREQ_CONST_SERVER_STATUS SYSREQ_CONTENTS SYSREQ_DATE_OPEN SYSREQ_DATE_CLOSE SYSREQ_DESCRIPTION SYSREQ_DESCRIPTION_LOCALIZE_ID SYSREQ_DOUBLE SYSREQ_EDOC_ACCESS_TYPE SYSREQ_EDOC_AUTHOR SYSREQ_EDOC_CREATED SYSREQ_EDOC_DELEGATE_RIGHTS_REQUISITE_CODE SYSREQ_EDOC_EDITOR SYSREQ_EDOC_ENCODE_TYPE SYSREQ_EDOC_ENCRYPTION_PLUGIN_NAME SYSREQ_EDOC_ENCRYPTION_PLUGIN_VERSION SYSREQ_EDOC_EXPORT_DATE SYSREQ_EDOC_EXPORTER SYSREQ_EDOC_KIND SYSREQ_EDOC_LIFE_STAGE_NAME SYSREQ_EDOC_LOCKED_FOR_SERVER_CODE SYSREQ_EDOC_MODIFIED SYSREQ_EDOC_NAME SYSREQ_EDOC_NOTE SYSREQ_EDOC_QUALIFIED_ID SYSREQ_EDOC_SESSION_KEY SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_NAME SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_VERSION SYSREQ_EDOC_SIGNATURE_TYPE SYSREQ_EDOC_SIGNED SYSREQ_EDOC_STORAGE SYSREQ_EDOC_STORAGES_ARCHIVE_STORAGE SYSREQ_EDOC_STORAGES_CHECK_RIGHTS SYSREQ_EDOC_STORAGES_COMPUTER_NAME SYSREQ_EDOC_STORAGES_EDIT_IN_STORAGE SYSREQ_EDOC_STORAGES_EXECUTIVE_STORAGE SYSREQ_EDOC_STORAGES_FUNCTION SYSREQ_EDOC_STORAGES_INITIALIZED SYSREQ_EDOC_STORAGES_LOCAL_PATH SYSREQ_EDOC_STORAGES_SAPERION_DATABASE_NAME SYSREQ_EDOC_STORAGES_SEARCH_BY_TEXT SYSREQ_EDOC_STORAGES_SERVER_NAME SYSREQ_EDOC_STORAGES_SHARED_SOURCE_NAME SYSREQ_EDOC_STORAGES_TYPE SYSREQ_EDOC_TEXT_MODIFIED SYSREQ_EDOC_TYPE_ACT_CODE SYSREQ_EDOC_TYPE_ACT_DESCRIPTION SYSREQ_EDOC_TYPE_ACT_DESCRIPTION_LOCALIZE_ID SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE_EXISTS SYSREQ_EDOC_TYPE_ACT_SECTION SYSREQ_EDOC_TYPE_ADD_PARAMS SYSREQ_EDOC_TYPE_COMMENT SYSREQ_EDOC_TYPE_EVENT_TEXT SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID SYSREQ_EDOC_TYPE_NAME_LOCALIZE_ID SYSREQ_EDOC_TYPE_NUMERATION_METHOD SYSREQ_EDOC_TYPE_PSEUDO_REQUISITE_CODE SYSREQ_EDOC_TYPE_REQ_CODE SYSREQ_EDOC_TYPE_REQ_DESCRIPTION SYSREQ_EDOC_TYPE_REQ_DESCRIPTION_LOCALIZE_ID SYSREQ_EDOC_TYPE_REQ_IS_LEADING SYSREQ_EDOC_TYPE_REQ_IS_REQUIRED SYSREQ_EDOC_TYPE_REQ_NUMBER SYSREQ_EDOC_TYPE_REQ_ON_CHANGE SYSREQ_EDOC_TYPE_REQ_ON_CHANGE_EXISTS SYSREQ_EDOC_TYPE_REQ_ON_SELECT SYSREQ_EDOC_TYPE_REQ_ON_SELECT_KIND SYSREQ_EDOC_TYPE_REQ_SECTION SYSREQ_EDOC_TYPE_VIEW_CARD SYSREQ_EDOC_TYPE_VIEW_CODE SYSREQ_EDOC_TYPE_VIEW_COMMENT SYSREQ_EDOC_TYPE_VIEW_IS_MAIN SYSREQ_EDOC_TYPE_VIEW_NAME SYSREQ_EDOC_TYPE_VIEW_NAME_LOCALIZE_ID SYSREQ_EDOC_VERSION_AUTHOR SYSREQ_EDOC_VERSION_CRC SYSREQ_EDOC_VERSION_DATA SYSREQ_EDOC_VERSION_EDITOR SYSREQ_EDOC_VERSION_EXPORT_DATE SYSREQ_EDOC_VERSION_EXPORTER SYSREQ_EDOC_VERSION_HIDDEN SYSREQ_EDOC_VERSION_LIFE_STAGE SYSREQ_EDOC_VERSION_MODIFIED SYSREQ_EDOC_VERSION_NOTE SYSREQ_EDOC_VERSION_SIGNATURE_TYPE SYSREQ_EDOC_VERSION_SIGNED SYSREQ_EDOC_VERSION_SIZE SYSREQ_EDOC_VERSION_SOURCE SYSREQ_EDOC_VERSION_TEXT_MODIFIED SYSREQ_EDOCKIND_DEFAULT_VERSION_STATE_CODE SYSREQ_FOLDER_KIND SYSREQ_FUNC_CATEGORY SYSREQ_FUNC_COMMENT SYSREQ_FUNC_GROUP SYSREQ_FUNC_GROUP_COMMENT SYSREQ_FUNC_GROUP_NUMBER SYSREQ_FUNC_HELP SYSREQ_FUNC_PARAM_DEF_VALUE SYSREQ_FUNC_PARAM_IDENT SYSREQ_FUNC_PARAM_NUMBER SYSREQ_FUNC_PARAM_TYPE SYSREQ_FUNC_TEXT SYSREQ_GROUP_CATEGORY SYSREQ_ID SYSREQ_LAST_UPDATE SYSREQ_LEADER_REFERENCE SYSREQ_LINE_NUMBER SYSREQ_MAIN_RECORD_ID SYSREQ_NAME SYSREQ_NAME_LOCALIZE_ID SYSREQ_NOTE SYSREQ_ORIGINAL_RECORD SYSREQ_OUR_FIRM SYSREQ_PROFILING_SETTINGS_BATCH_LOGING SYSREQ_PROFILING_SETTINGS_BATCH_SIZE SYSREQ_PROFILING_SETTINGS_PROFILING_ENABLED SYSREQ_PROFILING_SETTINGS_SQL_PROFILING_ENABLED SYSREQ_PROFILING_SETTINGS_START_LOGGED SYSREQ_RECORD_STATUS SYSREQ_REF_REQ_FIELD_NAME SYSREQ_REF_REQ_FORMAT SYSREQ_REF_REQ_GENERATED SYSREQ_REF_REQ_LENGTH SYSREQ_REF_REQ_PRECISION SYSREQ_REF_REQ_REFERENCE SYSREQ_REF_REQ_SECTION SYSREQ_REF_REQ_STORED SYSREQ_REF_REQ_TOKENS SYSREQ_REF_REQ_TYPE SYSREQ_REF_REQ_VIEW SYSREQ_REF_TYPE_ACT_CODE SYSREQ_REF_TYPE_ACT_DESCRIPTION SYSREQ_REF_TYPE_ACT_DESCRIPTION_LOCALIZE_ID SYSREQ_REF_TYPE_ACT_ON_EXECUTE SYSREQ_REF_TYPE_ACT_ON_EXECUTE_EXISTS SYSREQ_REF_TYPE_ACT_SECTION SYSREQ_REF_TYPE_ADD_PARAMS SYSREQ_REF_TYPE_COMMENT SYSREQ_REF_TYPE_COMMON_SETTINGS SYSREQ_REF_TYPE_DISPLAY_REQUISITE_NAME SYSREQ_REF_TYPE_EVENT_TEXT SYSREQ_REF_TYPE_MAIN_LEADING_REF SYSREQ_REF_TYPE_NAME_IN_SINGULAR SYSREQ_REF_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID SYSREQ_REF_TYPE_NAME_LOCALIZE_ID SYSREQ_REF_TYPE_NUMERATION_METHOD SYSREQ_REF_TYPE_REQ_CODE SYSREQ_REF_TYPE_REQ_DESCRIPTION SYSREQ_REF_TYPE_REQ_DESCRIPTION_LOCALIZE_ID SYSREQ_REF_TYPE_REQ_IS_CONTROL SYSREQ_REF_TYPE_REQ_IS_FILTER SYSREQ_REF_TYPE_REQ_IS_LEADING SYSREQ_REF_TYPE_REQ_IS_REQUIRED SYSREQ_REF_TYPE_REQ_NUMBER SYSREQ_REF_TYPE_REQ_ON_CHANGE SYSREQ_REF_TYPE_REQ_ON_CHANGE_EXISTS SYSREQ_REF_TYPE_REQ_ON_SELECT SYSREQ_REF_TYPE_REQ_ON_SELECT_KIND SYSREQ_REF_TYPE_REQ_SECTION SYSREQ_REF_TYPE_VIEW_CARD SYSREQ_REF_TYPE_VIEW_CODE SYSREQ_REF_TYPE_VIEW_COMMENT SYSREQ_REF_TYPE_VIEW_IS_MAIN SYSREQ_REF_TYPE_VIEW_NAME SYSREQ_REF_TYPE_VIEW_NAME_LOCALIZE_ID SYSREQ_REFERENCE_TYPE_ID SYSREQ_STATE SYSREQ_STAT\u0415 SYSREQ_SYSTEM_SETTINGS_VALUE SYSREQ_TYPE SYSREQ_UNIT SYSREQ_UNIT_ID SYSREQ_USER_GROUPS_GROUP_FULL_NAME SYSREQ_USER_GROUPS_GROUP_NAME SYSREQ_USER_GROUPS_GROUP_SERVER_NAME SYSREQ_USERS_ACCESS_RIGHTS SYSREQ_USERS_AUTHENTICATION SYSREQ_USERS_CATEGORY SYSREQ_USERS_COMPONENT SYSREQ_USERS_COMPONENT_USER_IS_PUBLIC SYSREQ_USERS_DOMAIN SYSREQ_USERS_FULL_USER_NAME SYSREQ_USERS_GROUP SYSREQ_USERS_IS_MAIN_SERVER SYSREQ_USERS_LOGIN SYSREQ_USERS_REFERENCE_USER_IS_PUBLIC SYSREQ_USERS_STATUS SYSREQ_USERS_USER_CERTIFICATE SYSREQ_USERS_USER_CERTIFICATE_INFO SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_NAME SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_VERSION SYSREQ_USERS_USER_CERTIFICATE_STATE SYSREQ_USERS_USER_CERTIFICATE_SUBJECT_NAME SYSREQ_USERS_USER_CERTIFICATE_THUMBPRINT SYSREQ_USERS_USER_DEFAULT_CERTIFICATE SYSREQ_USERS_USER_DESCRIPTION SYSREQ_USERS_USER_GLOBAL_NAME SYSREQ_USERS_USER_LOGIN SYSREQ_USERS_USER_MAIN_SERVER SYSREQ_USERS_USER_TYPE SYSREQ_WORK_RULES_FOLDER_ID RESULT_VAR_NAME RESULT_VAR_NAME_ENG AUTO_NUMERATION_RULE_ID CANT_CHANGE_ID_REQUISITE_RULE_ID CANT_CHANGE_OURFIRM_REQUISITE_RULE_ID CHECK_CHANGING_REFERENCE_RECORD_USE_RULE_ID CHECK_CODE_REQUISITE_RULE_ID CHECK_DELETING_REFERENCE_RECORD_USE_RULE_ID CHECK_FILTRATER_CHANGES_RULE_ID CHECK_RECORD_INTERVAL_RULE_ID CHECK_REFERENCE_INTERVAL_RULE_ID CHECK_REQUIRED_DATA_FULLNESS_RULE_ID CHECK_REQUIRED_REQUISITES_FULLNESS_RULE_ID MAKE_RECORD_UNRATIFIED_RULE_ID RESTORE_AUTO_NUMERATION_RULE_ID SET_FIRM_CONTEXT_FROM_RECORD_RULE_ID SET_FIRST_RECORD_IN_LIST_FORM_RULE_ID SET_IDSPS_VALUE_RULE_ID SET_NEXT_CODE_VALUE_RULE_ID SET_OURFIRM_BOUNDS_RULE_ID SET_OURFIRM_REQUISITE_RULE_ID SCRIPT_BLOCK_AFTER_FINISH_EVENT SCRIPT_BLOCK_BEFORE_START_EVENT SCRIPT_BLOCK_EXECUTION_RESULTS_PROPERTY SCRIPT_BLOCK_NAME_PROPERTY SCRIPT_BLOCK_SCRIPT_PROPERTY SUBTASK_BLOCK_ABORT_DEADLINE_PROPERTY SUBTASK_BLOCK_AFTER_FINISH_EVENT SUBTASK_BLOCK_ASSIGN_PARAMS_EVENT SUBTASK_BLOCK_ATTACHMENTS_PROPERTY SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY SUBTASK_BLOCK_BEFORE_START_EVENT SUBTASK_BLOCK_CREATED_TASK_PROPERTY SUBTASK_BLOCK_CREATION_EVENT SUBTASK_BLOCK_DEADLINE_PROPERTY SUBTASK_BLOCK_IMPORTANCE_PROPERTY SUBTASK_BLOCK_INITIATOR_PROPERTY SUBTASK_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY SUBTASK_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY SUBTASK_BLOCK_JOBS_TYPE_PROPERTY SUBTASK_BLOCK_NAME_PROPERTY SUBTASK_BLOCK_PARALLEL_ROUTE_PROPERTY SUBTASK_BLOCK_PERFORMERS_PROPERTY SUBTASK_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY SUBTASK_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY SUBTASK_BLOCK_REQUIRE_SIGN_PROPERTY SUBTASK_BLOCK_STANDARD_ROUTE_PROPERTY SUBTASK_BLOCK_START_EVENT SUBTASK_BLOCK_STEP_CONTROL_PROPERTY SUBTASK_BLOCK_SUBJECT_PROPERTY SUBTASK_BLOCK_TASK_CONTROL_PROPERTY SUBTASK_BLOCK_TEXT_PROPERTY SUBTASK_BLOCK_UNLOCK_ATTACHMENTS_ON_STOP_PROPERTY SUBTASK_BLOCK_USE_STANDARD_ROUTE_PROPERTY SUBTASK_BLOCK_WAIT_FOR_TASK_COMPLETE_PROPERTY SYSCOMP_CONTROL_JOBS SYSCOMP_FOLDERS SYSCOMP_JOBS SYSCOMP_NOTICES SYSCOMP_TASKS SYSDLG_CREATE_EDOCUMENT SYSDLG_CREATE_EDOCUMENT_VERSION SYSDLG_CURRENT_PERIOD SYSDLG_EDIT_FUNCTION_HELP SYSDLG_EDOCUMENT_KINDS_FOR_TEMPLATE SYSDLG_EXPORT_MULTIPLE_EDOCUMENTS SYSDLG_EXPORT_SINGLE_EDOCUMENT SYSDLG_IMPORT_EDOCUMENT SYSDLG_MULTIPLE_SELECT SYSDLG_SETUP_ACCESS_RIGHTS SYSDLG_SETUP_DEFAULT_RIGHTS SYSDLG_SETUP_FILTER_CONDITION SYSDLG_SETUP_SIGN_RIGHTS SYSDLG_SETUP_TASK_OBSERVERS SYSDLG_SETUP_TASK_ROUTE SYSDLG_SETUP_USERS_LIST SYSDLG_SIGN_EDOCUMENT SYSDLG_SIGN_MULTIPLE_EDOCUMENTS SYSREF_ACCESS_RIGHTS_TYPES SYSREF_ADMINISTRATION_HISTORY SYSREF_ALL_AVAILABLE_COMPONENTS SYSREF_ALL_AVAILABLE_PRIVILEGES SYSREF_ALL_REPLICATING_COMPONENTS SYSREF_AVAILABLE_DEVELOPERS_COMPONENTS SYSREF_CALENDAR_EVENTS SYSREF_COMPONENT_TOKEN_HISTORY SYSREF_COMPONENT_TOKENS SYSREF_COMPONENTS SYSREF_CONSTANTS SYSREF_DATA_RECEIVE_PROTOCOL SYSREF_DATA_SEND_PROTOCOL SYSREF_DIALOGS SYSREF_DIALOGS_REQUISITES SYSREF_EDITORS SYSREF_EDOC_CARDS SYSREF_EDOC_TYPES SYSREF_EDOCUMENT_CARD_REQUISITES SYSREF_EDOCUMENT_CARD_TYPES SYSREF_EDOCUMENT_CARD_TYPES_REFERENCE SYSREF_EDOCUMENT_CARDS SYSREF_EDOCUMENT_HISTORY SYSREF_EDOCUMENT_KINDS SYSREF_EDOCUMENT_REQUISITES SYSREF_EDOCUMENT_SIGNATURES SYSREF_EDOCUMENT_TEMPLATES SYSREF_EDOCUMENT_TEXT_STORAGES SYSREF_EDOCUMENT_VIEWS SYSREF_FILTERER_SETUP_CONFLICTS SYSREF_FILTRATER_SETTING_CONFLICTS SYSREF_FOLDER_HISTORY SYSREF_FOLDERS SYSREF_FUNCTION_GROUPS SYSREF_FUNCTION_PARAMS SYSREF_FUNCTIONS SYSREF_JOB_HISTORY SYSREF_LINKS SYSREF_LOCALIZATION_DICTIONARY SYSREF_LOCALIZATION_LANGUAGES SYSREF_MODULES SYSREF_PRIVILEGES SYSREF_RECORD_HISTORY SYSREF_REFERENCE_REQUISITES SYSREF_REFERENCE_TYPE_VIEWS SYSREF_REFERENCE_TYPES SYSREF_REFERENCES SYSREF_REFERENCES_REQUISITES SYSREF_REMOTE_SERVERS SYSREF_REPLICATION_SESSIONS_LOG SYSREF_REPLICATION_SESSIONS_PROTOCOL SYSREF_REPORTS SYSREF_ROLES SYSREF_ROUTE_BLOCK_GROUPS SYSREF_ROUTE_BLOCKS SYSREF_SCRIPTS SYSREF_SEARCHES SYSREF_SERVER_EVENTS SYSREF_SERVER_EVENTS_HISTORY SYSREF_STANDARD_ROUTE_GROUPS SYSREF_STANDARD_ROUTES SYSREF_STATUSES SYSREF_SYSTEM_SETTINGS SYSREF_TASK_HISTORY SYSREF_TASK_KIND_GROUPS SYSREF_TASK_KINDS SYSREF_TASK_RIGHTS SYSREF_TASK_SIGNATURES SYSREF_TASKS SYSREF_UNITS SYSREF_USER_GROUPS SYSREF_USER_GROUPS_REFERENCE SYSREF_USER_SUBSTITUTION SYSREF_USERS SYSREF_USERS_REFERENCE SYSREF_VIEWERS SYSREF_WORKING_TIME_CALENDARS ACCESS_RIGHTS_TABLE_NAME EDMS_ACCESS_TABLE_NAME EDOC_TYPES_TABLE_NAME TEST_DEV_DB_NAME TEST_DEV_SYSTEM_CODE TEST_EDMS_DB_NAME TEST_EDMS_MAIN_CODE TEST_EDMS_MAIN_DB_NAME TEST_EDMS_SECOND_CODE TEST_EDMS_SECOND_DB_NAME TEST_EDMS_SYSTEM_CODE TEST_ISB5_MAIN_CODE TEST_ISB5_SECOND_CODE TEST_SQL_SERVER_2005_NAME TEST_SQL_SERVER_NAME ATTENTION_CAPTION cbsCommandLinks cbsDefault CONFIRMATION_CAPTION ERROR_CAPTION INFORMATION_CAPTION mrCancel mrOk EDOC_VERSION_ACTIVE_STAGE_CODE EDOC_VERSION_DESIGN_STAGE_CODE EDOC_VERSION_OBSOLETE_STAGE_CODE cpDataEnciphermentEnabled cpDigitalSignatureEnabled cpID cpIssuer cpPluginVersion cpSerial cpSubjectName cpSubjSimpleName cpValidFromDate cpValidToDate ISBL_SYNTAX NO_SYNTAX XML_SYNTAX WAIT_BLOCK_AFTER_FINISH_EVENT WAIT_BLOCK_BEFORE_START_EVENT WAIT_BLOCK_DEADLINE_PROPERTY WAIT_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY WAIT_BLOCK_NAME_PROPERTY WAIT_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY SYSRES_COMMON SYSRES_CONST SYSRES_MBFUNC SYSRES_SBDATA SYSRES_SBGUI SYSRES_SBINTF SYSRES_SBREFDSC SYSRES_SQLERRORS SYSRES_SYSCOMP atUser atGroup atRole aemEnabledAlways aemDisabledAlways aemEnabledOnBrowse aemEnabledOnEdit aemDisabledOnBrowseEmpty apBegin apEnd alLeft alRight asmNever asmNoButCustomize asmAsLastTime asmYesButCustomize asmAlways cirCommon cirRevoked ctSignature ctEncode ctSignatureEncode clbUnchecked clbChecked clbGrayed ceISB ceAlways ceNever ctDocument ctReference ctScript ctUnknown ctReport ctDialog ctFunction ctFolder ctEDocument ctTask ctJob ctNotice ctControlJob cfInternal cfDisplay ciUnspecified ciWrite ciRead ckFolder ckEDocument ckTask ckJob ckComponentToken ckAny ckReference ckScript ckReport ckDialog ctISBLEditor ctBevel ctButton ctCheckListBox ctComboBox ctComboEdit ctGrid ctDBCheckBox ctDBComboBox ctDBEdit ctDBEllipsis ctDBMemo ctDBNavigator ctDBRadioGroup ctDBStatusLabel ctEdit ctGroupBox ctInplaceHint ctMemo ctPanel ctListBox ctRadioButton ctRichEdit ctTabSheet ctWebBrowser ctImage ctHyperLink ctLabel ctDBMultiEllipsis ctRibbon ctRichView ctInnerPanel ctPanelGroup ctBitButton cctDate cctInteger cctNumeric cctPick cctReference cctString cctText cltInternal cltPrimary cltGUI dseBeforeOpen dseAfterOpen dseBeforeClose dseAfterClose dseOnValidDelete dseBeforeDelete dseAfterDelete dseAfterDeleteOutOfTransaction dseOnDeleteError dseBeforeInsert dseAfterInsert dseOnValidUpdate dseBeforeUpdate dseOnUpdateRatifiedRecord dseAfterUpdate dseAfterUpdateOutOfTransaction dseOnUpdateError dseAfterScroll dseOnOpenRecord dseOnCloseRecord dseBeforeCancel dseAfterCancel dseOnUpdateDeadlockError dseBeforeDetailUpdate dseOnPrepareUpdate dseOnAnyRequisiteChange dssEdit dssInsert dssBrowse dssInActive dftDate dftShortDate dftDateTime dftTimeStamp dotDays dotHours dotMinutes dotSeconds dtkndLocal dtkndUTC arNone arView arEdit arFull ddaView ddaEdit emLock emEdit emSign emExportWithLock emImportWithUnlock emChangeVersionNote emOpenForModify emChangeLifeStage emDelete emCreateVersion emImport emUnlockExportedWithLock emStart emAbort emReInit emMarkAsReaded emMarkAsUnreaded emPerform emAccept emResume emChangeRights emEditRoute emEditObserver emRecoveryFromLocalCopy emChangeWorkAccessType emChangeEncodeTypeToCertificate emChangeEncodeTypeToPassword emChangeEncodeTypeToNone emChangeEncodeTypeToCertificatePassword emChangeStandardRoute emGetText emOpenForView emMoveToStorage emCreateObject emChangeVersionHidden emDeleteVersion emChangeLifeCycleStage emApprovingSign emExport emContinue emLockFromEdit emUnLockForEdit emLockForServer emUnlockFromServer emDelegateAccessRights emReEncode ecotFile ecotProcess eaGet eaCopy eaCreate eaCreateStandardRoute edltAll edltNothing edltQuery essmText essmCard esvtLast esvtLastActive esvtSpecified edsfExecutive edsfArchive edstSQLServer edstFile edvstNone edvstEDocumentVersionCopy edvstFile edvstTemplate edvstScannedFile vsDefault vsDesign vsActive vsObsolete etNone etCertificate etPassword etCertificatePassword ecException ecWarning ecInformation estAll estApprovingOnly evtLast evtLastActive evtQuery fdtString fdtNumeric fdtInteger fdtDate fdtText fdtUnknown fdtWideString fdtLargeInteger ftInbox ftOutbox ftFavorites ftCommonFolder ftUserFolder ftComponents ftQuickLaunch ftShortcuts ftSearch grhAuto grhX1 grhX2 grhX3 hltText hltRTF hltHTML iffBMP iffJPEG iffMultiPageTIFF iffSinglePageTIFF iffTIFF iffPNG im8bGrayscale im24bRGB im1bMonochrome itBMP itJPEG itWMF itPNG ikhInformation ikhWarning ikhError ikhNoIcon icUnknown icScript icFunction icIntegratedReport icAnalyticReport icDataSetEventHandler icActionHandler icFormEventHandler icLookUpEventHandler icRequisiteChangeEventHandler icBeforeSearchEventHandler icRoleCalculation icSelectRouteEventHandler icBlockPropertyCalculation icBlockQueryParamsEventHandler icChangeSearchResultEventHandler icBlockEventHandler icSubTaskInitEventHandler icEDocDataSetEventHandler icEDocLookUpEventHandler icEDocActionHandler icEDocFormEventHandler icEDocRequisiteChangeEventHandler icStructuredConversionRule icStructuredConversionEventBefore icStructuredConversionEventAfter icWizardEventHandler icWizardFinishEventHandler icWizardStepEventHandler icWizardStepFinishEventHandler icWizardActionEnableEventHandler icWizardActionExecuteEventHandler icCreateJobsHandler icCreateNoticesHandler icBeforeLookUpEventHandler icAfterLookUpEventHandler icTaskAbortEventHandler icWorkflowBlockActionHandler icDialogDataSetEventHandler icDialogActionHandler icDialogLookUpEventHandler icDialogRequisiteChangeEventHandler icDialogFormEventHandler icDialogValidCloseEventHandler icBlockFormEventHandler icTaskFormEventHandler icReferenceMethod icEDocMethod icDialogMethod icProcessMessageHandler isShow isHide isByUserSettings jkJob jkNotice jkControlJob jtInner jtLeft jtRight jtFull jtCross lbpAbove lbpBelow lbpLeft lbpRight eltPerConnection eltPerUser sfcUndefined sfcBlack sfcGreen sfcRed sfcBlue sfcOrange sfcLilac sfsItalic sfsStrikeout sfsNormal ldctStandardRoute ldctWizard ldctScript ldctFunction ldctRouteBlock ldctIntegratedReport ldctAnalyticReport ldctReferenceType ldctEDocumentType ldctDialog ldctServerEvents mrcrtNone mrcrtUser mrcrtMaximal mrcrtCustom vtEqual vtGreaterOrEqual vtLessOrEqual vtRange rdYesterday rdToday rdTomorrow rdThisWeek rdThisMonth rdThisYear rdNextMonth rdNextWeek rdLastWeek rdLastMonth rdWindow rdFile rdPrinter rdtString rdtNumeric rdtInteger rdtDate rdtReference rdtAccount rdtText rdtPick rdtUnknown rdtLargeInteger rdtDocument reOnChange reOnChangeValues ttGlobal ttLocal ttUser ttSystem ssmBrowse ssmSelect ssmMultiSelect ssmBrowseModal smSelect smLike smCard stNone stAuthenticating stApproving sctString sctStream sstAnsiSort sstNaturalSort svtEqual svtContain soatString soatNumeric soatInteger soatDatetime soatReferenceRecord soatText soatPick soatBoolean soatEDocument soatAccount soatIntegerCollection soatNumericCollection soatStringCollection soatPickCollection soatDatetimeCollection soatBooleanCollection soatReferenceRecordCollection soatEDocumentCollection soatAccountCollection soatContents soatUnknown tarAbortByUser tarAbortByWorkflowException tvtAllWords tvtExactPhrase tvtAnyWord usNone usCompleted usRedSquare usBlueSquare usYellowSquare usGreenSquare usOrangeSquare usPurpleSquare usFollowUp utUnknown utUser utDeveloper utAdministrator utSystemDeveloper utDisconnected btAnd btDetailAnd btOr btNotOr btOnly vmView vmSelect vmNavigation vsmSingle vsmMultiple vsmMultipleCheck vsmNoSelection wfatPrevious wfatNext wfatCancel wfatFinish wfepUndefined wfepText3 wfepText6 wfepText9 wfepSpinEdit wfepDropDown wfepRadioGroup wfepFlag wfepText12 wfepText15 wfepText18 wfepText21 wfepText24 wfepText27 wfepText30 wfepRadioGroupColumn1 wfepRadioGroupColumn2 wfepRadioGroupColumn3 wfetQueryParameter wfetText wfetDelimiter wfetLabel wptString wptInteger wptNumeric wptBoolean wptDateTime wptPick wptText wptUser wptUserList wptEDocumentInfo wptEDocumentInfoList wptReferenceRecordInfo wptReferenceRecordInfoList wptFolderInfo wptTaskInfo wptContents wptFileName wptDate wsrComplete wsrGoNext wsrGoPrevious wsrCustom wsrCancel wsrGoFinal wstForm wstEDocument wstTaskCard wstReferenceRecordCard wstFinal waAll waPerformers waManual wsbStart wsbFinish wsbNotice wsbStep wsbDecision wsbWait wsbMonitor wsbScript wsbConnector wsbSubTask wsbLifeCycleStage wsbPause wdtInteger wdtFloat wdtString wdtPick wdtDateTime wdtBoolean wdtTask wdtJob wdtFolder wdtEDocument wdtReferenceRecord wdtUser wdtGroup wdtRole wdtIntegerCollection wdtFloatCollection wdtStringCollection wdtPickCollection wdtDateTimeCollection wdtBooleanCollection wdtTaskCollection wdtJobCollection wdtFolderCollection wdtEDocumentCollection wdtReferenceRecordCollection wdtUserCollection wdtGroupCollection wdtRoleCollection wdtContents wdtUserList wdtSearchDescription wdtDeadLine wdtPickSet wdtAccountCollection wiLow wiNormal wiHigh wrtSoft wrtHard wsInit wsRunning wsDone wsControlled wsAborted wsContinued wtmFull wtmFromCurrent wtmOnlyCurrent ",
+literal:"true false"},illegal:'"',
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"#",end:"$"}]})})());
+hljs.registerLanguage("gml",(()=>{"use strict";return e=>({name:"GML",
+aliases:["GML"],case_insensitive:!1,keywords:{
+keyword:"begin end if then else while do for break continue with until repeat exit and or xor not return mod div switch case default var globalvar enum function constructor delete #macro #region #endregion",
+built_in:"is_real is_string is_array is_undefined is_int32 is_int64 is_ptr is_vec3 is_vec4 is_matrix is_bool is_method is_struct is_infinity is_nan is_numeric typeof variable_global_exists variable_global_get variable_global_set variable_instance_exists variable_instance_get variable_instance_set variable_instance_get_names variable_struct_exists variable_struct_get variable_struct_get_names variable_struct_names_count variable_struct_remove variable_struct_set array_delete array_insert array_length array_length_1d array_length_2d array_height_2d array_equals array_create array_copy array_pop array_push array_resize array_sort random random_range irandom irandom_range random_set_seed random_get_seed randomize randomise choose abs round floor ceil sign frac sqrt sqr exp ln log2 log10 sin cos tan arcsin arccos arctan arctan2 dsin dcos dtan darcsin darccos darctan darctan2 degtorad radtodeg power logn min max mean median clamp lerp dot_product dot_product_3d dot_product_normalised dot_product_3d_normalised dot_product_normalized dot_product_3d_normalized math_set_epsilon math_get_epsilon angle_difference point_distance_3d point_distance point_direction lengthdir_x lengthdir_y real string int64 ptr string_format chr ansi_char ord string_length string_byte_length string_pos string_copy string_char_at string_ord_at string_byte_at string_set_byte_at string_delete string_insert string_lower string_upper string_repeat string_letters string_digits string_lettersdigits string_replace string_replace_all string_count string_hash_to_newline clipboard_has_text clipboard_set_text clipboard_get_text date_current_datetime date_create_datetime date_valid_datetime date_inc_year date_inc_month date_inc_week date_inc_day date_inc_hour date_inc_minute date_inc_second date_get_year date_get_month date_get_week date_get_day date_get_hour date_get_minute date_get_second date_get_weekday date_get_day_of_year date_get_hour_of_year date_get_minute_of_year date_get_second_of_year date_year_span date_month_span date_week_span date_day_span date_hour_span date_minute_span date_second_span date_compare_datetime date_compare_date date_compare_time date_date_of date_time_of date_datetime_string date_date_string date_time_string date_days_in_month date_days_in_year date_leap_year date_is_today date_set_timezone date_get_timezone game_set_speed game_get_speed motion_set motion_add place_free place_empty place_meeting place_snapped move_random move_snap move_towards_point move_contact_solid move_contact_all move_outside_solid move_outside_all move_bounce_solid move_bounce_all move_wrap distance_to_point distance_to_object position_empty position_meeting path_start path_end mp_linear_step mp_potential_step mp_linear_step_object mp_potential_step_object mp_potential_settings mp_linear_path mp_potential_path mp_linear_path_object mp_potential_path_object mp_grid_create mp_grid_destroy mp_grid_clear_all mp_grid_clear_cell mp_grid_clear_rectangle mp_grid_add_cell mp_grid_get_cell mp_grid_add_rectangle mp_grid_add_instances mp_grid_path mp_grid_draw mp_grid_to_ds_grid collision_point collision_rectangle collision_circle collision_ellipse collision_line collision_point_list collision_rectangle_list collision_circle_list collision_ellipse_list collision_line_list instance_position_list instance_place_list point_in_rectangle point_in_triangle point_in_circle rectangle_in_rectangle rectangle_in_triangle rectangle_in_circle instance_find instance_exists instance_number instance_position instance_nearest instance_furthest instance_place instance_create_depth instance_create_layer instance_copy instance_change instance_destroy position_destroy position_change instance_id_get instance_deactivate_all instance_deactivate_object instance_deactivate_region instance_activate_all instance_activate_object instance_activate_region room_goto room_goto_previous room_goto_next room_previous room_next room_restart game_end game_restart game_load game_save game_save_buffer game_load_buffer event_perform event_user event_perform_object event_inherited show_debug_message show_debug_overlay debug_event debug_get_callstack alarm_get alarm_set font_texture_page_size keyboard_set_map keyboard_get_map keyboard_unset_map keyboard_check keyboard_check_pressed keyboard_check_released keyboard_check_direct keyboard_get_numlock keyboard_set_numlock keyboard_key_press keyboard_key_release keyboard_clear io_clear mouse_check_button mouse_check_button_pressed mouse_check_button_released mouse_wheel_up mouse_wheel_down mouse_clear draw_self draw_sprite draw_sprite_pos draw_sprite_ext draw_sprite_stretched draw_sprite_stretched_ext draw_sprite_tiled draw_sprite_tiled_ext draw_sprite_part draw_sprite_part_ext draw_sprite_general draw_clear draw_clear_alpha draw_point draw_line draw_line_width draw_rectangle draw_roundrect draw_roundrect_ext draw_triangle draw_circle draw_ellipse draw_set_circle_precision draw_arrow draw_button draw_path draw_healthbar draw_getpixel draw_getpixel_ext draw_set_colour draw_set_color draw_set_alpha draw_get_colour draw_get_color draw_get_alpha merge_colour make_colour_rgb make_colour_hsv colour_get_red colour_get_green colour_get_blue colour_get_hue colour_get_saturation colour_get_value merge_color make_color_rgb make_color_hsv color_get_red color_get_green color_get_blue color_get_hue color_get_saturation color_get_value merge_color screen_save screen_save_part draw_set_font draw_set_halign draw_set_valign draw_text draw_text_ext string_width string_height string_width_ext string_height_ext draw_text_transformed draw_text_ext_transformed draw_text_colour draw_text_ext_colour draw_text_transformed_colour draw_text_ext_transformed_colour draw_text_color draw_text_ext_color draw_text_transformed_color draw_text_ext_transformed_color draw_point_colour draw_line_colour draw_line_width_colour draw_rectangle_colour draw_roundrect_colour draw_roundrect_colour_ext draw_triangle_colour draw_circle_colour draw_ellipse_colour draw_point_color draw_line_color draw_line_width_color draw_rectangle_color draw_roundrect_color draw_roundrect_color_ext draw_triangle_color draw_circle_color draw_ellipse_color draw_primitive_begin draw_vertex draw_vertex_colour draw_vertex_color draw_primitive_end sprite_get_uvs font_get_uvs sprite_get_texture font_get_texture texture_get_width texture_get_height texture_get_uvs draw_primitive_begin_texture draw_vertex_texture draw_vertex_texture_colour draw_vertex_texture_color texture_global_scale surface_create surface_create_ext surface_resize surface_free surface_exists surface_get_width surface_get_height surface_get_texture surface_set_target surface_set_target_ext surface_reset_target surface_depth_disable surface_get_depth_disable draw_surface draw_surface_stretched draw_surface_tiled draw_surface_part draw_surface_ext draw_surface_stretched_ext draw_surface_tiled_ext draw_surface_part_ext draw_surface_general surface_getpixel surface_getpixel_ext surface_save surface_save_part surface_copy surface_copy_part application_surface_draw_enable application_get_position application_surface_enable application_surface_is_enabled display_get_width display_get_height display_get_orientation display_get_gui_width display_get_gui_height display_reset display_mouse_get_x display_mouse_get_y display_mouse_set display_set_ui_visibility window_set_fullscreen window_get_fullscreen window_set_caption window_set_min_width window_set_max_width window_set_min_height window_set_max_height window_get_visible_rects window_get_caption window_set_cursor window_get_cursor window_set_colour window_get_colour window_set_color window_get_color window_set_position window_set_size window_set_rectangle window_center window_get_x window_get_y window_get_width window_get_height window_mouse_get_x window_mouse_get_y window_mouse_set window_view_mouse_get_x window_view_mouse_get_y window_views_mouse_get_x window_views_mouse_get_y audio_listener_position audio_listener_velocity audio_listener_orientation audio_emitter_position audio_emitter_create audio_emitter_free audio_emitter_exists audio_emitter_pitch audio_emitter_velocity audio_emitter_falloff audio_emitter_gain audio_play_sound audio_play_sound_on audio_play_sound_at audio_stop_sound audio_resume_music audio_music_is_playing audio_resume_sound audio_pause_sound audio_pause_music audio_channel_num audio_sound_length audio_get_type audio_falloff_set_model audio_play_music audio_stop_music audio_master_gain audio_music_gain audio_sound_gain audio_sound_pitch audio_stop_all audio_resume_all audio_pause_all audio_is_playing audio_is_paused audio_exists audio_sound_set_track_position audio_sound_get_track_position audio_emitter_get_gain audio_emitter_get_pitch audio_emitter_get_x audio_emitter_get_y audio_emitter_get_z audio_emitter_get_vx audio_emitter_get_vy audio_emitter_get_vz audio_listener_set_position audio_listener_set_velocity audio_listener_set_orientation audio_listener_get_data audio_set_master_gain audio_get_master_gain audio_sound_get_gain audio_sound_get_pitch audio_get_name audio_sound_set_track_position audio_sound_get_track_position audio_create_stream audio_destroy_stream audio_create_sync_group audio_destroy_sync_group audio_play_in_sync_group audio_start_sync_group audio_stop_sync_group audio_pause_sync_group audio_resume_sync_group audio_sync_group_get_track_pos audio_sync_group_debug audio_sync_group_is_playing audio_debug audio_group_load audio_group_unload audio_group_is_loaded audio_group_load_progress audio_group_name audio_group_stop_all audio_group_set_gain audio_create_buffer_sound audio_free_buffer_sound audio_create_play_queue audio_free_play_queue audio_queue_sound audio_get_recorder_count audio_get_recorder_info audio_start_recording audio_stop_recording audio_sound_get_listener_mask audio_emitter_get_listener_mask audio_get_listener_mask audio_sound_set_listener_mask audio_emitter_set_listener_mask audio_set_listener_mask audio_get_listener_count audio_get_listener_info audio_system show_message show_message_async clickable_add clickable_add_ext clickable_change clickable_change_ext clickable_delete clickable_exists clickable_set_style show_question show_question_async get_integer get_string get_integer_async get_string_async get_login_async get_open_filename get_save_filename get_open_filename_ext get_save_filename_ext show_error highscore_clear highscore_add highscore_value highscore_name draw_highscore sprite_exists sprite_get_name sprite_get_number sprite_get_width sprite_get_height sprite_get_xoffset sprite_get_yoffset sprite_get_bbox_left sprite_get_bbox_right sprite_get_bbox_top sprite_get_bbox_bottom sprite_save sprite_save_strip sprite_set_cache_size sprite_set_cache_size_ext sprite_get_tpe sprite_prefetch sprite_prefetch_multi sprite_flush sprite_flush_multi sprite_set_speed sprite_get_speed_type sprite_get_speed font_exists font_get_name font_get_fontname font_get_bold font_get_italic font_get_first font_get_last font_get_size font_set_cache_size path_exists path_get_name path_get_length path_get_time path_get_kind path_get_closed path_get_precision path_get_number path_get_point_x path_get_point_y path_get_point_speed path_get_x path_get_y path_get_speed script_exists script_get_name timeline_add timeline_delete timeline_clear timeline_exists timeline_get_name timeline_moment_clear timeline_moment_add_script timeline_size timeline_max_moment object_exists object_get_name object_get_sprite object_get_solid object_get_visible object_get_persistent object_get_mask object_get_parent object_get_physics object_is_ancestor room_exists room_get_name sprite_set_offset sprite_duplicate sprite_assign sprite_merge sprite_add sprite_replace sprite_create_from_surface sprite_add_from_surface sprite_delete sprite_set_alpha_from_sprite sprite_collision_mask font_add_enable_aa font_add_get_enable_aa font_add font_add_sprite font_add_sprite_ext font_replace font_replace_sprite font_replace_sprite_ext font_delete path_set_kind path_set_closed path_set_precision path_add path_assign path_duplicate path_append path_delete path_add_point path_insert_point path_change_point path_delete_point path_clear_points path_reverse path_mirror path_flip path_rotate path_rescale path_shift script_execute object_set_sprite object_set_solid object_set_visible object_set_persistent object_set_mask room_set_width room_set_height room_set_persistent room_set_background_colour room_set_background_color room_set_view room_set_viewport room_get_viewport room_set_view_enabled room_add room_duplicate room_assign room_instance_add room_instance_clear room_get_camera room_set_camera asset_get_index asset_get_type file_text_open_from_string file_text_open_read file_text_open_write file_text_open_append file_text_close file_text_write_string file_text_write_real file_text_writeln file_text_read_string file_text_read_real file_text_readln file_text_eof file_text_eoln file_exists file_delete file_rename file_copy directory_exists directory_create directory_destroy file_find_first file_find_next file_find_close file_attributes filename_name filename_path filename_dir filename_drive filename_ext filename_change_ext file_bin_open file_bin_rewrite file_bin_close file_bin_position file_bin_size file_bin_seek file_bin_write_byte file_bin_read_byte parameter_count parameter_string environment_get_variable ini_open_from_string ini_open ini_close ini_read_string ini_read_real ini_write_string ini_write_real ini_key_exists ini_section_exists ini_key_delete ini_section_delete ds_set_precision ds_exists ds_stack_create ds_stack_destroy ds_stack_clear ds_stack_copy ds_stack_size ds_stack_empty ds_stack_push ds_stack_pop ds_stack_top ds_stack_write ds_stack_read ds_queue_create ds_queue_destroy ds_queue_clear ds_queue_copy ds_queue_size ds_queue_empty ds_queue_enqueue ds_queue_dequeue ds_queue_head ds_queue_tail ds_queue_write ds_queue_read ds_list_create ds_list_destroy ds_list_clear ds_list_copy ds_list_size ds_list_empty ds_list_add ds_list_insert ds_list_replace ds_list_delete ds_list_find_index ds_list_find_value ds_list_mark_as_list ds_list_mark_as_map ds_list_sort ds_list_shuffle ds_list_write ds_list_read ds_list_set ds_map_create ds_map_destroy ds_map_clear ds_map_copy ds_map_size ds_map_empty ds_map_add ds_map_add_list ds_map_add_map ds_map_replace ds_map_replace_map ds_map_replace_list ds_map_delete ds_map_exists ds_map_find_value ds_map_find_previous ds_map_find_next ds_map_find_first ds_map_find_last ds_map_write ds_map_read ds_map_secure_save ds_map_secure_load ds_map_secure_load_buffer ds_map_secure_save_buffer ds_map_set ds_priority_create ds_priority_destroy ds_priority_clear ds_priority_copy ds_priority_size ds_priority_empty ds_priority_add ds_priority_change_priority ds_priority_find_priority ds_priority_delete_value ds_priority_delete_min ds_priority_find_min ds_priority_delete_max ds_priority_find_max ds_priority_write ds_priority_read ds_grid_create ds_grid_destroy ds_grid_copy ds_grid_resize ds_grid_width ds_grid_height ds_grid_clear ds_grid_set ds_grid_add ds_grid_multiply ds_grid_set_region ds_grid_add_region ds_grid_multiply_region ds_grid_set_disk ds_grid_add_disk ds_grid_multiply_disk ds_grid_set_grid_region ds_grid_add_grid_region ds_grid_multiply_grid_region ds_grid_get ds_grid_get_sum ds_grid_get_max ds_grid_get_min ds_grid_get_mean ds_grid_get_disk_sum ds_grid_get_disk_min ds_grid_get_disk_max ds_grid_get_disk_mean ds_grid_value_exists ds_grid_value_x ds_grid_value_y ds_grid_value_disk_exists ds_grid_value_disk_x ds_grid_value_disk_y ds_grid_shuffle ds_grid_write ds_grid_read ds_grid_sort ds_grid_set ds_grid_get effect_create_below effect_create_above effect_clear part_type_create part_type_destroy part_type_exists part_type_clear part_type_shape part_type_sprite part_type_size part_type_scale part_type_orientation part_type_life part_type_step part_type_death part_type_speed part_type_direction part_type_gravity part_type_colour1 part_type_colour2 part_type_colour3 part_type_colour_mix part_type_colour_rgb part_type_colour_hsv part_type_color1 part_type_color2 part_type_color3 part_type_color_mix part_type_color_rgb part_type_color_hsv part_type_alpha1 part_type_alpha2 part_type_alpha3 part_type_blend part_system_create part_system_create_layer part_system_destroy part_system_exists part_system_clear part_system_draw_order part_system_depth part_system_position part_system_automatic_update part_system_automatic_draw part_system_update part_system_drawit part_system_get_layer part_system_layer part_particles_create part_particles_create_colour part_particles_create_color part_particles_clear part_particles_count part_emitter_create part_emitter_destroy part_emitter_destroy_all part_emitter_exists part_emitter_clear part_emitter_region part_emitter_burst part_emitter_stream external_call external_define external_free window_handle window_device matrix_get matrix_set matrix_build_identity matrix_build matrix_build_lookat matrix_build_projection_ortho matrix_build_projection_perspective matrix_build_projection_perspective_fov matrix_multiply matrix_transform_vertex matrix_stack_push matrix_stack_pop matrix_stack_multiply matrix_stack_set matrix_stack_clear matrix_stack_top matrix_stack_is_empty browser_input_capture os_get_config os_get_info os_get_language os_get_region os_lock_orientation display_get_dpi_x display_get_dpi_y display_set_gui_size display_set_gui_maximise display_set_gui_maximize device_mouse_dbclick_enable display_set_timing_method display_get_timing_method display_set_sleep_margin display_get_sleep_margin virtual_key_add virtual_key_hide virtual_key_delete virtual_key_show draw_enable_drawevent draw_enable_swf_aa draw_set_swf_aa_level draw_get_swf_aa_level draw_texture_flush draw_flush gpu_set_blendenable gpu_set_ztestenable gpu_set_zfunc gpu_set_zwriteenable gpu_set_lightingenable gpu_set_fog gpu_set_cullmode gpu_set_blendmode gpu_set_blendmode_ext gpu_set_blendmode_ext_sepalpha gpu_set_colorwriteenable gpu_set_colourwriteenable gpu_set_alphatestenable gpu_set_alphatestref gpu_set_alphatestfunc gpu_set_texfilter gpu_set_texfilter_ext gpu_set_texrepeat gpu_set_texrepeat_ext gpu_set_tex_filter gpu_set_tex_filter_ext gpu_set_tex_repeat gpu_set_tex_repeat_ext gpu_set_tex_mip_filter gpu_set_tex_mip_filter_ext gpu_set_tex_mip_bias gpu_set_tex_mip_bias_ext gpu_set_tex_min_mip gpu_set_tex_min_mip_ext gpu_set_tex_max_mip gpu_set_tex_max_mip_ext gpu_set_tex_max_aniso gpu_set_tex_max_aniso_ext gpu_set_tex_mip_enable gpu_set_tex_mip_enable_ext gpu_get_blendenable gpu_get_ztestenable gpu_get_zfunc gpu_get_zwriteenable gpu_get_lightingenable gpu_get_fog gpu_get_cullmode gpu_get_blendmode gpu_get_blendmode_ext gpu_get_blendmode_ext_sepalpha gpu_get_blendmode_src gpu_get_blendmode_dest gpu_get_blendmode_srcalpha gpu_get_blendmode_destalpha gpu_get_colorwriteenable gpu_get_colourwriteenable gpu_get_alphatestenable gpu_get_alphatestref gpu_get_alphatestfunc gpu_get_texfilter gpu_get_texfilter_ext gpu_get_texrepeat gpu_get_texrepeat_ext gpu_get_tex_filter gpu_get_tex_filter_ext gpu_get_tex_repeat gpu_get_tex_repeat_ext gpu_get_tex_mip_filter gpu_get_tex_mip_filter_ext gpu_get_tex_mip_bias gpu_get_tex_mip_bias_ext gpu_get_tex_min_mip gpu_get_tex_min_mip_ext gpu_get_tex_max_mip gpu_get_tex_max_mip_ext gpu_get_tex_max_aniso gpu_get_tex_max_aniso_ext gpu_get_tex_mip_enable gpu_get_tex_mip_enable_ext gpu_push_state gpu_pop_state gpu_get_state gpu_set_state draw_light_define_ambient draw_light_define_direction draw_light_define_point draw_light_enable draw_set_lighting draw_light_get_ambient draw_light_get draw_get_lighting shop_leave_rating url_get_domain url_open url_open_ext url_open_full get_timer achievement_login achievement_logout achievement_post achievement_increment achievement_post_score achievement_available achievement_show_achievements achievement_show_leaderboards achievement_load_friends achievement_load_leaderboard achievement_send_challenge achievement_load_progress achievement_reset achievement_login_status achievement_get_pic achievement_show_challenge_notifications achievement_get_challenges achievement_event achievement_show achievement_get_info cloud_file_save cloud_string_save cloud_synchronise ads_enable ads_disable ads_setup ads_engagement_launch ads_engagement_available ads_engagement_active ads_event ads_event_preload ads_set_reward_callback ads_get_display_height ads_get_display_width ads_move ads_interstitial_available ads_interstitial_display device_get_tilt_x device_get_tilt_y device_get_tilt_z device_is_keypad_open device_mouse_check_button device_mouse_check_button_pressed device_mouse_check_button_released device_mouse_x device_mouse_y device_mouse_raw_x device_mouse_raw_y device_mouse_x_to_gui device_mouse_y_to_gui iap_activate iap_status iap_enumerate_products iap_restore_all iap_acquire iap_consume iap_product_details iap_purchase_details facebook_init facebook_login facebook_status facebook_graph_request facebook_dialog facebook_logout facebook_launch_offerwall facebook_post_message facebook_send_invite facebook_user_id facebook_accesstoken facebook_check_permission facebook_request_read_permissions facebook_request_publish_permissions gamepad_is_supported gamepad_get_device_count gamepad_is_connected gamepad_get_description gamepad_get_button_threshold gamepad_set_button_threshold gamepad_get_axis_deadzone gamepad_set_axis_deadzone gamepad_button_count gamepad_button_check gamepad_button_check_pressed gamepad_button_check_released gamepad_button_value gamepad_axis_count gamepad_axis_value gamepad_set_vibration gamepad_set_colour gamepad_set_color os_is_paused window_has_focus code_is_compiled http_get http_get_file http_post_string http_request json_encode json_decode zip_unzip load_csv base64_encode base64_decode md5_string_unicode md5_string_utf8 md5_file os_is_network_connected sha1_string_unicode sha1_string_utf8 sha1_file os_powersave_enable analytics_event analytics_event_ext win8_livetile_tile_notification win8_livetile_tile_clear win8_livetile_badge_notification win8_livetile_badge_clear win8_livetile_queue_enable win8_secondarytile_pin win8_secondarytile_badge_notification win8_secondarytile_delete win8_livetile_notification_begin win8_livetile_notification_secondary_begin win8_livetile_notification_expiry win8_livetile_notification_tag win8_livetile_notification_text_add win8_livetile_notification_image_add win8_livetile_notification_end win8_appbar_enable win8_appbar_add_element win8_appbar_remove_element win8_settingscharm_add_entry win8_settingscharm_add_html_entry win8_settingscharm_add_xaml_entry win8_settingscharm_set_xaml_property win8_settingscharm_get_xaml_property win8_settingscharm_remove_entry win8_share_image win8_share_screenshot win8_share_file win8_share_url win8_share_text win8_search_enable win8_search_disable win8_search_add_suggestions win8_device_touchscreen_available win8_license_initialize_sandbox win8_license_trial_version winphone_license_trial_version winphone_tile_title winphone_tile_count winphone_tile_back_title winphone_tile_back_content winphone_tile_back_content_wide winphone_tile_front_image winphone_tile_front_image_small winphone_tile_front_image_wide winphone_tile_back_image winphone_tile_back_image_wide winphone_tile_background_colour winphone_tile_background_color winphone_tile_icon_image winphone_tile_small_icon_image winphone_tile_wide_content winphone_tile_cycle_images winphone_tile_small_background_image physics_world_create physics_world_gravity physics_world_update_speed physics_world_update_iterations physics_world_draw_debug physics_pause_enable physics_fixture_create physics_fixture_set_kinematic physics_fixture_set_density physics_fixture_set_awake physics_fixture_set_restitution physics_fixture_set_friction physics_fixture_set_collision_group physics_fixture_set_sensor physics_fixture_set_linear_damping physics_fixture_set_angular_damping physics_fixture_set_circle_shape physics_fixture_set_box_shape physics_fixture_set_edge_shape physics_fixture_set_polygon_shape physics_fixture_set_chain_shape physics_fixture_add_point physics_fixture_bind physics_fixture_bind_ext physics_fixture_delete physics_apply_force physics_apply_impulse physics_apply_angular_impulse physics_apply_local_force physics_apply_local_impulse physics_apply_torque physics_mass_properties physics_draw_debug physics_test_overlap physics_remove_fixture physics_set_friction physics_set_density physics_set_restitution physics_get_friction physics_get_density physics_get_restitution physics_joint_distance_create physics_joint_rope_create physics_joint_revolute_create physics_joint_prismatic_create physics_joint_pulley_create physics_joint_wheel_create physics_joint_weld_create physics_joint_friction_create physics_joint_gear_create physics_joint_enable_motor physics_joint_get_value physics_joint_set_value physics_joint_delete physics_particle_create physics_particle_delete physics_particle_delete_region_circle physics_particle_delete_region_box physics_particle_delete_region_poly physics_particle_set_flags physics_particle_set_category_flags physics_particle_draw physics_particle_draw_ext physics_particle_count physics_particle_get_data physics_particle_get_data_particle physics_particle_group_begin physics_particle_group_circle physics_particle_group_box physics_particle_group_polygon physics_particle_group_add_point physics_particle_group_end physics_particle_group_join physics_particle_group_delete physics_particle_group_count physics_particle_group_get_data physics_particle_group_get_mass physics_particle_group_get_inertia physics_particle_group_get_centre_x physics_particle_group_get_centre_y physics_particle_group_get_vel_x physics_particle_group_get_vel_y physics_particle_group_get_ang_vel physics_particle_group_get_x physics_particle_group_get_y physics_particle_group_get_angle physics_particle_set_group_flags physics_particle_get_group_flags physics_particle_get_max_count physics_particle_get_radius physics_particle_get_density physics_particle_get_damping physics_particle_get_gravity_scale physics_particle_set_max_count physics_particle_set_radius physics_particle_set_density physics_particle_set_damping physics_particle_set_gravity_scale network_create_socket network_create_socket_ext network_create_server network_create_server_raw network_connect network_connect_raw network_send_packet network_send_raw network_send_broadcast network_send_udp network_send_udp_raw network_set_timeout network_set_config network_resolve network_destroy buffer_create buffer_write buffer_read buffer_seek buffer_get_surface buffer_set_surface buffer_delete buffer_exists buffer_get_type buffer_get_alignment buffer_poke buffer_peek buffer_save buffer_save_ext buffer_load buffer_load_ext buffer_load_partial buffer_copy buffer_fill buffer_get_size buffer_tell buffer_resize buffer_md5 buffer_sha1 buffer_base64_encode buffer_base64_decode buffer_base64_decode_ext buffer_sizeof buffer_get_address buffer_create_from_vertex_buffer buffer_create_from_vertex_buffer_ext buffer_copy_from_vertex_buffer buffer_async_group_begin buffer_async_group_option buffer_async_group_end buffer_load_async buffer_save_async gml_release_mode gml_pragma steam_activate_overlay steam_is_overlay_enabled steam_is_overlay_activated steam_get_persona_name steam_initialised steam_is_cloud_enabled_for_app steam_is_cloud_enabled_for_account steam_file_persisted steam_get_quota_total steam_get_quota_free steam_file_write steam_file_write_file steam_file_read steam_file_delete steam_file_exists steam_file_size steam_file_share steam_is_screenshot_requested steam_send_screenshot steam_is_user_logged_on steam_get_user_steam_id steam_user_owns_dlc steam_user_installed_dlc steam_set_achievement steam_get_achievement steam_clear_achievement steam_set_stat_int steam_set_stat_float steam_set_stat_avg_rate steam_get_stat_int steam_get_stat_float steam_get_stat_avg_rate steam_reset_all_stats steam_reset_all_stats_achievements steam_stats_ready steam_create_leaderboard steam_upload_score steam_upload_score_ext steam_download_scores_around_user steam_download_scores steam_download_friends_scores steam_upload_score_buffer steam_upload_score_buffer_ext steam_current_game_language steam_available_languages steam_activate_overlay_browser steam_activate_overlay_user steam_activate_overlay_store steam_get_user_persona_name steam_get_app_id steam_get_user_account_id steam_ugc_download steam_ugc_create_item steam_ugc_start_item_update steam_ugc_set_item_title steam_ugc_set_item_description steam_ugc_set_item_visibility steam_ugc_set_item_tags steam_ugc_set_item_content steam_ugc_set_item_preview steam_ugc_submit_item_update steam_ugc_get_item_update_progress steam_ugc_subscribe_item steam_ugc_unsubscribe_item steam_ugc_num_subscribed_items steam_ugc_get_subscribed_items steam_ugc_get_item_install_info steam_ugc_get_item_update_info steam_ugc_request_item_details steam_ugc_create_query_user steam_ugc_create_query_user_ex steam_ugc_create_query_all steam_ugc_create_query_all_ex steam_ugc_query_set_cloud_filename_filter steam_ugc_query_set_match_any_tag steam_ugc_query_set_search_text steam_ugc_query_set_ranked_by_trend_days steam_ugc_query_add_required_tag steam_ugc_query_add_excluded_tag steam_ugc_query_set_return_long_description steam_ugc_query_set_return_total_only steam_ugc_query_set_allow_cached_response steam_ugc_send_query shader_set shader_get_name shader_reset shader_current shader_is_compiled shader_get_sampler_index shader_get_uniform shader_set_uniform_i shader_set_uniform_i_array shader_set_uniform_f shader_set_uniform_f_array shader_set_uniform_matrix shader_set_uniform_matrix_array shader_enable_corner_id texture_set_stage texture_get_texel_width texture_get_texel_height shaders_are_supported vertex_format_begin vertex_format_end vertex_format_delete vertex_format_add_position vertex_format_add_position_3d vertex_format_add_colour vertex_format_add_color vertex_format_add_normal vertex_format_add_texcoord vertex_format_add_textcoord vertex_format_add_custom vertex_create_buffer vertex_create_buffer_ext vertex_delete_buffer vertex_begin vertex_end vertex_position vertex_position_3d vertex_colour vertex_color vertex_argb vertex_texcoord vertex_normal vertex_float1 vertex_float2 vertex_float3 vertex_float4 vertex_ubyte4 vertex_submit vertex_freeze vertex_get_number vertex_get_buffer_size vertex_create_buffer_from_buffer vertex_create_buffer_from_buffer_ext push_local_notification push_get_first_local_notification push_get_next_local_notification push_cancel_local_notification skeleton_animation_set skeleton_animation_get skeleton_animation_mix skeleton_animation_set_ext skeleton_animation_get_ext skeleton_animation_get_duration skeleton_animation_get_frames skeleton_animation_clear skeleton_skin_set skeleton_skin_get skeleton_attachment_set skeleton_attachment_get skeleton_attachment_create skeleton_collision_draw_set skeleton_bone_data_get skeleton_bone_data_set skeleton_bone_state_get skeleton_bone_state_set skeleton_get_minmax skeleton_get_num_bounds skeleton_get_bounds skeleton_animation_get_frame skeleton_animation_set_frame draw_skeleton draw_skeleton_time draw_skeleton_instance draw_skeleton_collision skeleton_animation_list skeleton_skin_list skeleton_slot_data layer_get_id layer_get_id_at_depth layer_get_depth layer_create layer_destroy layer_destroy_instances layer_add_instance layer_has_instance layer_set_visible layer_get_visible layer_exists layer_x layer_y layer_get_x layer_get_y layer_hspeed layer_vspeed layer_get_hspeed layer_get_vspeed layer_script_begin layer_script_end layer_shader layer_get_script_begin layer_get_script_end layer_get_shader layer_set_target_room layer_get_target_room layer_reset_target_room layer_get_all layer_get_all_elements layer_get_name layer_depth layer_get_element_layer layer_get_element_type layer_element_move layer_force_draw_depth layer_is_draw_depth_forced layer_get_forced_depth layer_background_get_id layer_background_exists layer_background_create layer_background_destroy layer_background_visible layer_background_change layer_background_sprite layer_background_htiled layer_background_vtiled layer_background_stretch layer_background_yscale layer_background_xscale layer_background_blend layer_background_alpha layer_background_index layer_background_speed layer_background_get_visible layer_background_get_sprite layer_background_get_htiled layer_background_get_vtiled layer_background_get_stretch layer_background_get_yscale layer_background_get_xscale layer_background_get_blend layer_background_get_alpha layer_background_get_index layer_background_get_speed layer_sprite_get_id layer_sprite_exists layer_sprite_create layer_sprite_destroy layer_sprite_change layer_sprite_index layer_sprite_speed layer_sprite_xscale layer_sprite_yscale layer_sprite_angle layer_sprite_blend layer_sprite_alpha layer_sprite_x layer_sprite_y layer_sprite_get_sprite layer_sprite_get_index layer_sprite_get_speed layer_sprite_get_xscale layer_sprite_get_yscale layer_sprite_get_angle layer_sprite_get_blend layer_sprite_get_alpha layer_sprite_get_x layer_sprite_get_y layer_tilemap_get_id layer_tilemap_exists layer_tilemap_create layer_tilemap_destroy tilemap_tileset tilemap_x tilemap_y tilemap_set tilemap_set_at_pixel tilemap_get_tileset tilemap_get_tile_width tilemap_get_tile_height tilemap_get_width tilemap_get_height tilemap_get_x tilemap_get_y tilemap_get tilemap_get_at_pixel tilemap_get_cell_x_at_pixel tilemap_get_cell_y_at_pixel tilemap_clear draw_tilemap draw_tile tilemap_set_global_mask tilemap_get_global_mask tilemap_set_mask tilemap_get_mask tilemap_get_frame tile_set_empty tile_set_index tile_set_flip tile_set_mirror tile_set_rotate tile_get_empty tile_get_index tile_get_flip tile_get_mirror tile_get_rotate layer_tile_exists layer_tile_create layer_tile_destroy layer_tile_change layer_tile_xscale layer_tile_yscale layer_tile_blend layer_tile_alpha layer_tile_x layer_tile_y layer_tile_region layer_tile_visible layer_tile_get_sprite layer_tile_get_xscale layer_tile_get_yscale layer_tile_get_blend layer_tile_get_alpha layer_tile_get_x layer_tile_get_y layer_tile_get_region layer_tile_get_visible layer_instance_get_instance instance_activate_layer instance_deactivate_layer camera_create camera_create_view camera_destroy camera_apply camera_get_active camera_get_default camera_set_default camera_set_view_mat camera_set_proj_mat camera_set_update_script camera_set_begin_script camera_set_end_script camera_set_view_pos camera_set_view_size camera_set_view_speed camera_set_view_border camera_set_view_angle camera_set_view_target camera_get_view_mat camera_get_proj_mat camera_get_update_script camera_get_begin_script camera_get_end_script camera_get_view_x camera_get_view_y camera_get_view_width camera_get_view_height camera_get_view_speed_x camera_get_view_speed_y camera_get_view_border_x camera_get_view_border_y camera_get_view_angle camera_get_view_target view_get_camera view_get_visible view_get_xport view_get_yport view_get_wport view_get_hport view_get_surface_id view_set_camera view_set_visible view_set_xport view_set_yport view_set_wport view_set_hport view_set_surface_id gesture_drag_time gesture_drag_distance gesture_flick_speed gesture_double_tap_time gesture_double_tap_distance gesture_pinch_distance gesture_pinch_angle_towards gesture_pinch_angle_away gesture_rotate_time gesture_rotate_angle gesture_tap_count gesture_get_drag_time gesture_get_drag_distance gesture_get_flick_speed gesture_get_double_tap_time gesture_get_double_tap_distance gesture_get_pinch_distance gesture_get_pinch_angle_towards gesture_get_pinch_angle_away gesture_get_rotate_time gesture_get_rotate_angle gesture_get_tap_count keyboard_virtual_show keyboard_virtual_hide keyboard_virtual_status keyboard_virtual_height",
+literal:"self other all noone global local undefined pointer_invalid pointer_null path_action_stop path_action_restart path_action_continue path_action_reverse true false pi GM_build_date GM_version GM_runtime_version timezone_local timezone_utc gamespeed_fps gamespeed_microseconds ev_create ev_destroy ev_step ev_alarm ev_keyboard ev_mouse ev_collision ev_other ev_draw ev_draw_begin ev_draw_end ev_draw_pre ev_draw_post ev_keypress ev_keyrelease ev_trigger ev_left_button ev_right_button ev_middle_button ev_no_button ev_left_press ev_right_press ev_middle_press ev_left_release ev_right_release ev_middle_release ev_mouse_enter ev_mouse_leave ev_mouse_wheel_up ev_mouse_wheel_down ev_global_left_button ev_global_right_button ev_global_middle_button ev_global_left_press ev_global_right_press ev_global_middle_press ev_global_left_release ev_global_right_release ev_global_middle_release ev_joystick1_left ev_joystick1_right ev_joystick1_up ev_joystick1_down ev_joystick1_button1 ev_joystick1_button2 ev_joystick1_button3 ev_joystick1_button4 ev_joystick1_button5 ev_joystick1_button6 ev_joystick1_button7 ev_joystick1_button8 ev_joystick2_left ev_joystick2_right ev_joystick2_up ev_joystick2_down ev_joystick2_button1 ev_joystick2_button2 ev_joystick2_button3 ev_joystick2_button4 ev_joystick2_button5 ev_joystick2_button6 ev_joystick2_button7 ev_joystick2_button8 ev_outside ev_boundary ev_game_start ev_game_end ev_room_start ev_room_end ev_no_more_lives ev_animation_end ev_end_of_path ev_no_more_health ev_close_button ev_user0 ev_user1 ev_user2 ev_user3 ev_user4 ev_user5 ev_user6 ev_user7 ev_user8 ev_user9 ev_user10 ev_user11 ev_user12 ev_user13 ev_user14 ev_user15 ev_step_normal ev_step_begin ev_step_end ev_gui ev_gui_begin ev_gui_end ev_cleanup ev_gesture ev_gesture_tap ev_gesture_double_tap ev_gesture_drag_start ev_gesture_dragging ev_gesture_drag_end ev_gesture_flick ev_gesture_pinch_start ev_gesture_pinch_in ev_gesture_pinch_out ev_gesture_pinch_end ev_gesture_rotate_start ev_gesture_rotating ev_gesture_rotate_end ev_global_gesture_tap ev_global_gesture_double_tap ev_global_gesture_drag_start ev_global_gesture_dragging ev_global_gesture_drag_end ev_global_gesture_flick ev_global_gesture_pinch_start ev_global_gesture_pinch_in ev_global_gesture_pinch_out ev_global_gesture_pinch_end ev_global_gesture_rotate_start ev_global_gesture_rotating ev_global_gesture_rotate_end vk_nokey vk_anykey vk_enter vk_return vk_shift vk_control vk_alt vk_escape vk_space vk_backspace vk_tab vk_pause vk_printscreen vk_left vk_right vk_up vk_down vk_home vk_end vk_delete vk_insert vk_pageup vk_pagedown vk_f1 vk_f2 vk_f3 vk_f4 vk_f5 vk_f6 vk_f7 vk_f8 vk_f9 vk_f10 vk_f11 vk_f12 vk_numpad0 vk_numpad1 vk_numpad2 vk_numpad3 vk_numpad4 vk_numpad5 vk_numpad6 vk_numpad7 vk_numpad8 vk_numpad9 vk_divide vk_multiply vk_subtract vk_add vk_decimal vk_lshift vk_lcontrol vk_lalt vk_rshift vk_rcontrol vk_ralt mb_any mb_none mb_left mb_right mb_middle c_aqua c_black c_blue c_dkgray c_fuchsia c_gray c_green c_lime c_ltgray c_maroon c_navy c_olive c_purple c_red c_silver c_teal c_white c_yellow c_orange fa_left fa_center fa_right fa_top fa_middle fa_bottom pr_pointlist pr_linelist pr_linestrip pr_trianglelist pr_trianglestrip pr_trianglefan bm_complex bm_normal bm_add bm_max bm_subtract bm_zero bm_one bm_src_colour bm_inv_src_colour bm_src_color bm_inv_src_color bm_src_alpha bm_inv_src_alpha bm_dest_alpha bm_inv_dest_alpha bm_dest_colour bm_inv_dest_colour bm_dest_color bm_inv_dest_color bm_src_alpha_sat tf_point tf_linear tf_anisotropic mip_off mip_on mip_markedonly audio_falloff_none audio_falloff_inverse_distance audio_falloff_inverse_distance_clamped audio_falloff_linear_distance audio_falloff_linear_distance_clamped audio_falloff_exponent_distance audio_falloff_exponent_distance_clamped audio_old_system audio_new_system audio_mono audio_stereo audio_3d cr_default cr_none cr_arrow cr_cross cr_beam cr_size_nesw cr_size_ns cr_size_nwse cr_size_we cr_uparrow cr_hourglass cr_drag cr_appstart cr_handpoint cr_size_all spritespeed_framespersecond spritespeed_framespergameframe asset_object asset_unknown asset_sprite asset_sound asset_room asset_path asset_script asset_font asset_timeline asset_tiles asset_shader fa_readonly fa_hidden fa_sysfile fa_volumeid fa_directory fa_archive ds_type_map ds_type_list ds_type_stack ds_type_queue ds_type_grid ds_type_priority ef_explosion ef_ring ef_ellipse ef_firework ef_smoke ef_smokeup ef_star ef_spark ef_flare ef_cloud ef_rain ef_snow pt_shape_pixel pt_shape_disk pt_shape_square pt_shape_line pt_shape_star pt_shape_circle pt_shape_ring pt_shape_sphere pt_shape_flare pt_shape_spark pt_shape_explosion pt_shape_cloud pt_shape_smoke pt_shape_snow ps_distr_linear ps_distr_gaussian ps_distr_invgaussian ps_shape_rectangle ps_shape_ellipse ps_shape_diamond ps_shape_line ty_real ty_string dll_cdecl dll_stdcall matrix_view matrix_projection matrix_world os_win32 os_windows os_macosx os_ios os_android os_symbian os_linux os_unknown os_winphone os_tizen os_win8native os_wiiu os_3ds os_psvita os_bb10 os_ps4 os_xboxone os_ps3 os_xbox360 os_uwp os_tvos os_switch browser_not_a_browser browser_unknown browser_ie browser_firefox browser_chrome browser_safari browser_safari_mobile browser_opera browser_tizen browser_edge browser_windows_store browser_ie_mobile device_ios_unknown device_ios_iphone device_ios_iphone_retina device_ios_ipad device_ios_ipad_retina device_ios_iphone5 device_ios_iphone6 device_ios_iphone6plus device_emulator device_tablet display_landscape display_landscape_flipped display_portrait display_portrait_flipped tm_sleep tm_countvsyncs of_challenge_win of_challen ge_lose of_challenge_tie leaderboard_type_number leaderboard_type_time_mins_secs cmpfunc_never cmpfunc_less cmpfunc_equal cmpfunc_lessequal cmpfunc_greater cmpfunc_notequal cmpfunc_greaterequal cmpfunc_always cull_noculling cull_clockwise cull_counterclockwise lighttype_dir lighttype_point iap_ev_storeload iap_ev_product iap_ev_purchase iap_ev_consume iap_ev_restore iap_storeload_ok iap_storeload_failed iap_status_uninitialised iap_status_unavailable iap_status_loading iap_status_available iap_status_processing iap_status_restoring iap_failed iap_unavailable iap_available iap_purchased iap_canceled iap_refunded fb_login_default fb_login_fallback_to_webview fb_login_no_fallback_to_webview fb_login_forcing_webview fb_login_use_system_account fb_login_forcing_safari phy_joint_anchor_1_x phy_joint_anchor_1_y phy_joint_anchor_2_x phy_joint_anchor_2_y phy_joint_reaction_force_x phy_joint_reaction_force_y phy_joint_reaction_torque phy_joint_motor_speed phy_joint_angle phy_joint_motor_torque phy_joint_max_motor_torque phy_joint_translation phy_joint_speed phy_joint_motor_force phy_joint_max_motor_force phy_joint_length_1 phy_joint_length_2 phy_joint_damping_ratio phy_joint_frequency phy_joint_lower_angle_limit phy_joint_upper_angle_limit phy_joint_angle_limits phy_joint_max_length phy_joint_max_torque phy_joint_max_force phy_debug_render_aabb phy_debug_render_collision_pairs phy_debug_render_coms phy_debug_render_core_shapes phy_debug_render_joints phy_debug_render_obb phy_debug_render_shapes phy_particle_flag_water phy_particle_flag_zombie phy_particle_flag_wall phy_particle_flag_spring phy_particle_flag_elastic phy_particle_flag_viscous phy_particle_flag_powder phy_particle_flag_tensile phy_particle_flag_colourmixing phy_particle_flag_colormixing phy_particle_group_flag_solid phy_particle_group_flag_rigid phy_particle_data_flag_typeflags phy_particle_data_flag_position phy_particle_data_flag_velocity phy_particle_data_flag_colour phy_particle_data_flag_color phy_particle_data_flag_category achievement_our_info achievement_friends_info achievement_leaderboard_info achievement_achievement_info achievement_filter_all_players achievement_filter_friends_only achievement_filter_favorites_only achievement_type_achievement_challenge achievement_type_score_challenge achievement_pic_loaded achievement_show_ui achievement_show_profile achievement_show_leaderboard achievement_show_achievement achievement_show_bank achievement_show_friend_picker achievement_show_purchase_prompt network_socket_tcp network_socket_udp network_socket_bluetooth network_type_connect network_type_disconnect network_type_data network_type_non_blocking_connect network_config_connect_timeout network_config_use_non_blocking_socket network_config_enable_reliable_udp network_config_disable_reliable_udp buffer_fixed buffer_grow buffer_wrap buffer_fast buffer_vbuffer buffer_network buffer_u8 buffer_s8 buffer_u16 buffer_s16 buffer_u32 buffer_s32 buffer_u64 buffer_f16 buffer_f32 buffer_f64 buffer_bool buffer_text buffer_string buffer_surface_copy buffer_seek_start buffer_seek_relative buffer_seek_end buffer_generalerror buffer_outofspace buffer_outofbounds buffer_invalidtype text_type button_type input_type ANSI_CHARSET DEFAULT_CHARSET EASTEUROPE_CHARSET RUSSIAN_CHARSET SYMBOL_CHARSET SHIFTJIS_CHARSET HANGEUL_CHARSET GB2312_CHARSET CHINESEBIG5_CHARSET JOHAB_CHARSET HEBREW_CHARSET ARABIC_CHARSET GREEK_CHARSET TURKISH_CHARSET VIETNAMESE_CHARSET THAI_CHARSET MAC_CHARSET BALTIC_CHARSET OEM_CHARSET gp_face1 gp_face2 gp_face3 gp_face4 gp_shoulderl gp_shoulderr gp_shoulderlb gp_shoulderrb gp_select gp_start gp_stickl gp_stickr gp_padu gp_padd gp_padl gp_padr gp_axislh gp_axislv gp_axisrh gp_axisrv ov_friends ov_community ov_players ov_settings ov_gamegroup ov_achievements lb_sort_none lb_sort_ascending lb_sort_descending lb_disp_none lb_disp_numeric lb_disp_time_sec lb_disp_time_ms ugc_result_success ugc_filetype_community ugc_filetype_microtrans ugc_visibility_public ugc_visibility_friends_only ugc_visibility_private ugc_query_RankedByVote ugc_query_RankedByPublicationDate ugc_query_AcceptedForGameRankedByAcceptanceDate ugc_query_RankedByTrend ugc_query_FavoritedByFriendsRankedByPublicationDate ugc_query_CreatedByFriendsRankedByPublicationDate ugc_query_RankedByNumTimesReported ugc_query_CreatedByFollowedUsersRankedByPublicationDate ugc_query_NotYetRated ugc_query_RankedByTotalVotesAsc ugc_query_RankedByVotesUp ugc_query_RankedByTextSearch ugc_sortorder_CreationOrderDesc ugc_sortorder_CreationOrderAsc ugc_sortorder_TitleAsc ugc_sortorder_LastUpdatedDesc ugc_sortorder_SubscriptionDateDesc ugc_sortorder_VoteScoreDesc ugc_sortorder_ForModeration ugc_list_Published ugc_list_VotedOn ugc_list_VotedUp ugc_list_VotedDown ugc_list_WillVoteLater ugc_list_Favorited ugc_list_Subscribed ugc_list_UsedOrPlayed ugc_list_Followed ugc_match_Items ugc_match_Items_Mtx ugc_match_Items_ReadyToUse ugc_match_Collections ugc_match_Artwork ugc_match_Videos ugc_match_Screenshots ugc_match_AllGuides ugc_match_WebGuides ugc_match_IntegratedGuides ugc_match_UsableInGame ugc_match_ControllerBindings vertex_usage_position vertex_usage_colour vertex_usage_color vertex_usage_normal vertex_usage_texcoord vertex_usage_textcoord vertex_usage_blendweight vertex_usage_blendindices vertex_usage_psize vertex_usage_tangent vertex_usage_binormal vertex_usage_fog vertex_usage_depth vertex_usage_sample vertex_type_float1 vertex_type_float2 vertex_type_float3 vertex_type_float4 vertex_type_colour vertex_type_color vertex_type_ubyte4 layerelementtype_undefined layerelementtype_background layerelementtype_instance layerelementtype_oldtilemap layerelementtype_sprite layerelementtype_tilemap layerelementtype_particlesystem layerelementtype_tile tile_rotate tile_flip tile_mirror tile_index_mask kbv_type_default kbv_type_ascii kbv_type_url kbv_type_email kbv_type_numbers kbv_type_phone kbv_type_phone_name kbv_returnkey_default kbv_returnkey_go kbv_returnkey_google kbv_returnkey_join kbv_returnkey_next kbv_returnkey_route kbv_returnkey_search kbv_returnkey_send kbv_returnkey_yahoo kbv_returnkey_done kbv_returnkey_continue kbv_returnkey_emergency kbv_autocapitalize_none kbv_autocapitalize_words kbv_autocapitalize_sentences kbv_autocapitalize_characters",
+symbol:"argument_relative argument argument0 argument1 argument2 argument3 argument4 argument5 argument6 argument7 argument8 argument9 argument10 argument11 argument12 argument13 argument14 argument15 argument_count x|0 y|0 xprevious yprevious xstart ystart hspeed vspeed direction speed friction gravity gravity_direction path_index path_position path_positionprevious path_speed path_scale path_orientation path_endaction object_index id solid persistent mask_index instance_count instance_id room_speed fps fps_real current_time current_year current_month current_day current_weekday current_hour current_minute current_second alarm timeline_index timeline_position timeline_speed timeline_running timeline_loop room room_first room_last room_width room_height room_caption room_persistent score lives health show_score show_lives show_health caption_score caption_lives caption_health event_type event_number event_object event_action application_surface gamemaker_pro gamemaker_registered gamemaker_version error_occurred error_last debug_mode keyboard_key keyboard_lastkey keyboard_lastchar keyboard_string mouse_x mouse_y mouse_button mouse_lastbutton cursor_sprite visible sprite_index sprite_width sprite_height sprite_xoffset sprite_yoffset image_number image_index image_speed depth image_xscale image_yscale image_angle image_alpha image_blend bbox_left bbox_right bbox_top bbox_bottom layer background_colour background_showcolour background_color background_showcolor view_enabled view_current view_visible view_xview view_yview view_wview view_hview view_xport view_yport view_wport view_hport view_angle view_hborder view_vborder view_hspeed view_vspeed view_object view_surface_id view_camera game_id game_display_name game_project_name game_save_id working_directory temp_directory program_directory browser_width browser_height os_type os_device os_browser os_version display_aa async_load delta_time webgl_enabled event_data iap_data phy_rotation phy_position_x phy_position_y phy_angular_velocity phy_linear_velocity_x phy_linear_velocity_y phy_speed_x phy_speed_y phy_speed phy_angular_damping phy_linear_damping phy_bullet phy_fixed_rotation phy_active phy_mass phy_inertia phy_com_x phy_com_y phy_dynamic phy_kinematic phy_sleeping phy_collision_points phy_collision_x phy_collision_y phy_col_normal_x phy_col_normal_y phy_position_xprevious phy_position_yprevious"
+},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE]
+})})());
+hljs.registerLanguage("go",(()=>{"use strict";return e=>{const n={
+keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",
+literal:"true false iota nil",
+built_in:"append cap close complex copy imag len make new panic print println real recover delete"
+};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",
+variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{
+className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1
+},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",
+end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",
+begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}})());
+hljs.registerLanguage("golo",(()=>{"use strict";return e=>({name:"Golo",
+keywords:{
+keyword:"println readln print import module function local return let var while for foreach times in case when match with break continue augment augmentation each find filter reduce if then else otherwise try catch finally raise throw orIfNull DynamicObject|10 DynamicVariable struct Observable map set vector list array",
+literal:"true false null"},
+contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"@[A-Za-z]+"}]})})());
+hljs.registerLanguage("gradle",(()=>{"use strict";return e=>({name:"Gradle",
+case_insensitive:!0,keywords:{
+keyword:"task project allprojects subprojects artifacts buildscript configurations dependencies repositories sourceSets description delete from into include exclude source classpath destinationDir includes options sourceCompatibility targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant def abstract break case catch continue default do else extends final finally for if implements instanceof native new private protected public return static switch synchronized throw throws transient try volatile while strictfp package import false null super this true antlrtask checkstyle codenarc copy boolean byte char class double float int interface long short void compile runTime file fileTree abs any append asList asWritable call collect compareTo count div dump each eachByte eachFile eachLine every find findAll flatten getAt getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter newReader newWriter next plus pop power previous print println push putAt read readBytes readLines reverse reverseEach round size sort splitEachLine step subMap times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader withStream withWriter withWriterAppend write writeLine"
+},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,e.REGEXP_MODE]
+})})());
+hljs.registerLanguage("groovy",(()=>{"use strict";function e(e,n={}){
+return n.variants=e,n}return n=>{
+const a="[A-Za-z0-9_$]+",t=e([n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.COMMENT("/\\*\\*","\\*/",{
+relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",
+begin:"@[A-Za-z]+"}]})]),s={className:"regexp",begin:/~?\/[^\/\n]+\//,
+contains:[n.BACKSLASH_ESCAPE]
+},i=e([n.BINARY_NUMBER_MODE,n.C_NUMBER_MODE]),r=e([{begin:/"""/,end:/"""/},{
+begin:/'''/,end:/'''/},{begin:"\\$/",end:"/\\$",relevance:10
+},n.APOS_STRING_MODE,n.QUOTE_STRING_MODE],{className:"string"});return{
+name:"Groovy",keywords:{built_in:"this super",literal:"true false null",
+keyword:"byte short char int long boolean float double void def as in assert trait abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"
+},contains:[n.SHEBANG({binary:"groovy",relevance:10}),t,r,s,i,{
+className:"class",beginKeywords:"class interface trait enum",end:/\{/,
+illegal:":",contains:[{beginKeywords:"extends implements"
+},n.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"@[A-Za-z]+",relevance:0},{
+className:"attr",begin:a+"[ \t]*:",relevance:0},{begin:/\?/,end:/:/,relevance:0,
+contains:[t,r,s,i,"self"]},{className:"symbol",
+begin:"^[ \t]*"+(l=a+":",((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(?=",l,")")),
+excludeBegin:!0,end:a+":",relevance:0}],illegal:/#|<\//};var l}})());
+hljs.registerLanguage("haml",(()=>{"use strict";return e=>({name:"HAML",
+case_insensitive:!0,contains:[{className:"meta",
+begin:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",
+relevance:10},e.COMMENT("^\\s*(!=#|=#|-#|/).*$",!1,{relevance:0}),{
+begin:"^\\s*(-|=|!=)(?!#)",starts:{end:"\\n",subLanguage:"ruby"}},{
+className:"tag",begin:"^\\s*%",contains:[{className:"selector-tag",begin:"\\w+"
+},{className:"selector-id",begin:"#[\\w-]+"},{className:"selector-class",
+begin:"\\.[\\w-]+"},{begin:/\{\s*/,end:/\s*\}/,contains:[{begin:":\\w+\\s*=>",
+end:",\\s+",returnBegin:!0,endsWithParent:!0,contains:[{className:"attr",
+begin:":\\w+"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{begin:"\\w+",relevance:0
+}]}]},{begin:"\\(\\s*",end:"\\s*\\)",excludeEnd:!0,contains:[{begin:"\\w+\\s*=",
+end:"\\s+",returnBegin:!0,endsWithParent:!0,contains:[{className:"attr",
+begin:"\\w+",relevance:0},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{begin:"\\w+",
+relevance:0}]}]}]},{begin:"^\\s*[=~]\\s*"},{begin:/#\{/,starts:{end:/\}/,
+subLanguage:"ruby"}}]})})());
+hljs.registerLanguage("handlebars",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}return a=>{const t={
+"builtin-name":["action","bindattr","collection","component","concat","debugger","each","each-in","get","hash","if","in","input","link-to","loc","log","lookup","mut","outlet","partial","query-params","render","template","textarea","unbound","unless","view","with","yield"]
+},s=/\[\]|\[[^\]]+\]/,i=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,r=((...n)=>"("+n.map((n=>e(n))).join("|")+")")(/""|"[^"]+"/,/''|'[^']+'/,s,i),l=n(n("(",/\.|\.\/|\//,")?"),r,(h=n(/(\.|\/)/,r),
+n("(",h,")*"))),c=n("(",s,"|",i,")(?==)"),o={begin:l,lexemes:/[\w.\/]+/
+},m=a.inherit(o,{keywords:{literal:["true","false","undefined","null"]}}),d={
+begin:/\(/,end:/\)/},g={className:"attr",begin:c,relevance:0,starts:{begin:/=/,
+end:/=/,starts:{
+contains:[a.NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,m,d]}}},b={
+contains:[a.NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{begin:/as\s+\|/,
+keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},g,m,d],returnEnd:!0
+},u=a.inherit(o,{className:"name",keywords:t,starts:a.inherit(b,{end:/\)/})})
+;var h;d.contains=[u];const N=a.inherit(o,{keywords:t,className:"name",
+starts:a.inherit(b,{end:/\}\}/})}),p=a.inherit(o,{keywords:t,className:"name"
+}),E=a.inherit(o,{className:"name",keywords:t,starts:a.inherit(b,{end:/\}\}/})})
+;return{name:"Handlebars",
+aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,
+subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,
+skip:!0},a.COMMENT(/\{\{!--/,/--\}\}/),a.COMMENT(/\{\{!/,/\}\}/),{
+className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[N],
+starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{
+className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[p]},{
+className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[N]},{
+className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{
+className:"template-tag",begin:/\{\{(?=else if)/,end:/\}\}/,keywords:"else if"
+},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[p]},{
+className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[E]},{
+className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[E]}]}}})());
+hljs.registerLanguage("haskell",(()=>{"use strict";return e=>{const n={
+variants:[e.COMMENT("--","$"),e.COMMENT(/\{-/,/-\}/,{contains:["self"]})]},i={
+className:"meta",begin:/\{-#/,end:/#-\}/},a={className:"meta",begin:"^#",end:"$"
+},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",
+end:"\\)",illegal:'"',contains:[i,a,{className:"type",
+begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{
+begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],
+keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",
+contains:[{beginKeywords:"module",end:"where",keywords:"module where",
+contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",
+keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{
+className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",
+keywords:"class family instance where",contains:[s,l,n]},{className:"class",
+begin:"\\b(data|(new)?type)\\b",end:"$",
+keywords:"data family type newtype deriving",contains:[i,s,l,{begin:/\{/,
+end:/\}/,contains:l.contains},n]},{beginKeywords:"default",end:"$",
+contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",
+contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",
+keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",
+contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",
+begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"
+},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{
+begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}})());
+hljs.registerLanguage("haxe",(()=>{"use strict";return e=>({name:"Haxe",
+aliases:["hx"],keywords:{
+keyword:"break case cast catch continue default do dynamic else enum extern for function here if import in inline never new override package private get set public return static super switch this throw trace try typedef untyped using var while Int Float String Bool Dynamic Void Array ",
+built_in:"trace this",literal:"true false null _"},contains:[{
+className:"string",begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE,{
+className:"subst",begin:"\\$\\{",end:"\\}"},{className:"subst",begin:"\\$",
+end:/\W\}/}]
+},e.QUOTE_STRING_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"@:",end:"$"},{className:"meta",begin:"#",end:"$",
+keywords:{"meta-keyword":"if else elseif end error"}},{className:"type",
+begin:":[ \t]*",end:"[^A-Za-z0-9_ \t\\->]",excludeBegin:!0,excludeEnd:!0,
+relevance:0},{className:"type",begin:":[ \t]*",end:"\\W",excludeBegin:!0,
+excludeEnd:!0},{className:"type",begin:"new *",end:"\\W",excludeBegin:!0,
+excludeEnd:!0},{className:"class",beginKeywords:"enum",end:"\\{",
+contains:[e.TITLE_MODE]},{className:"class",beginKeywords:"abstract",
+end:"[\\{$]",contains:[{className:"type",begin:"\\(",end:"\\)",excludeBegin:!0,
+excludeEnd:!0},{className:"type",begin:"from +",end:"\\W",excludeBegin:!0,
+excludeEnd:!0},{className:"type",begin:"to +",end:"\\W",excludeBegin:!0,
+excludeEnd:!0},e.TITLE_MODE],keywords:{keyword:"abstract from to"}},{
+className:"class",begin:"\\b(class|interface) +",end:"[\\{$]",excludeEnd:!0,
+keywords:"class interface",contains:[{className:"keyword",
+begin:"\\b(extends|implements) +",keywords:"extends implements",contains:[{
+className:"type",begin:e.IDENT_RE,relevance:0}]},e.TITLE_MODE]},{
+className:"function",beginKeywords:"function",end:"\\(",excludeEnd:!0,
+illegal:"\\S",contains:[e.TITLE_MODE]}],illegal:/<\//})})());
+hljs.registerLanguage("hsp",(()=>{"use strict";return e=>({name:"HSP",
+case_insensitive:!0,keywords:{$pattern:/[\w._]+/,
+keyword:"goto gosub return break repeat loop continue wait await dim sdim foreach dimtype dup dupptr end stop newmod delmod mref run exgoto on mcall assert logmes newlab resume yield onexit onerror onkey onclick oncmd exist delete mkdir chdir dirlist bload bsave bcopy memfile if else poke wpoke lpoke getstr chdpm memexpand memcpy memset notesel noteadd notedel noteload notesave randomize noteunsel noteget split strrep setease button chgdisp exec dialog mmload mmplay mmstop mci pset pget syscolor mes print title pos circle cls font sysfont objsize picload color palcolor palette redraw width gsel gcopy gzoom gmode bmpsave hsvcolor getkey listbox chkbox combox input mesbox buffer screen bgscr mouse objsel groll line clrobj boxf objprm objmode stick grect grotate gsquare gradf objimage objskip objenable celload celdiv celput newcom querycom delcom cnvstow comres axobj winobj sendmsg comevent comevarg sarrayconv callfunc cnvwtos comevdisp libptr system hspstat hspver stat cnt err strsize looplev sublev iparam wparam lparam refstr refdval int rnd strlen length length2 length3 length4 vartype gettime peek wpeek lpeek varptr varuse noteinfo instr abs limit getease str strmid strf getpath strtrim sin cos tan atan sqrt double absf expf logf limitf powf geteasef mousex mousey mousew hwnd hinstance hdc ginfo objinfo dirinfo sysinfo thismod __hspver__ __hsp30__ __date__ __time__ __line__ __file__ _debug __hspdef__ and or xor not screen_normal screen_palette screen_hide screen_fixedsize screen_tool screen_frame gmode_gdi gmode_mem gmode_rgb0 gmode_alpha gmode_rgb0alpha gmode_add gmode_sub gmode_pixela ginfo_mx ginfo_my ginfo_act ginfo_sel ginfo_wx1 ginfo_wy1 ginfo_wx2 ginfo_wy2 ginfo_vx ginfo_vy ginfo_sizex ginfo_sizey ginfo_winx ginfo_winy ginfo_mesx ginfo_mesy ginfo_r ginfo_g ginfo_b ginfo_paluse ginfo_dispx ginfo_dispy ginfo_cx ginfo_cy ginfo_intid ginfo_newid ginfo_sx ginfo_sy objinfo_mode objinfo_bmscr objinfo_hwnd notemax notesize dir_cur dir_exe dir_win dir_sys dir_cmdline dir_desktop dir_mydoc dir_tv font_normal font_bold font_italic font_underline font_strikeout font_antialias objmode_normal objmode_guifont objmode_usefont gsquare_grad msgothic msmincho do until while wend for next _break _continue switch case default swbreak swend ddim ldim alloc m_pi rad2deg deg2rad ease_linear ease_quad_in ease_quad_out ease_quad_inout ease_cubic_in ease_cubic_out ease_cubic_inout ease_quartic_in ease_quartic_out ease_quartic_inout ease_bounce_in ease_bounce_out ease_bounce_inout ease_shake_in ease_shake_out ease_shake_inout ease_loop"
+},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{
+className:"string",begin:/\{"/,end:/"\}/,contains:[e.BACKSLASH_ESCAPE]
+},e.COMMENT(";","$",{relevance:0}),{className:"meta",begin:"#",end:"$",
+keywords:{
+"meta-keyword":"addion cfunc cmd cmpopt comfunc const defcfunc deffunc define else endif enum epack func global if ifdef ifndef include modcfunc modfunc modinit modterm module pack packopt regcmd runtime undef usecom uselib"
+},contains:[e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"
+}),e.NUMBER_MODE,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]
+},{className:"symbol",begin:"^\\*(\\w+|@)"},e.NUMBER_MODE,e.C_NUMBER_MODE]})
+})());
+hljs.registerLanguage("htmlbars",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}return a=>{const t=function(a){const t={
+"builtin-name":["action","bindattr","collection","component","concat","debugger","each","each-in","get","hash","if","in","input","link-to","loc","log","lookup","mut","outlet","partial","query-params","render","template","textarea","unbound","unless","view","with","yield"]
+},s=/\[\]|\[[^\]]+\]/,i=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,r=((...n)=>"("+n.map((n=>e(n))).join("|")+")")(/""|"[^"]+"/,/''|'[^']+'/,s,i),l=n(n("(",/\.|\.\/|\//,")?"),r,(c=n(/(\.|\/)/,r),
+n("(",c,")*")));var c;const o=n("(",s,"|",i,")(?==)"),m={begin:l,
+lexemes:/[\w.\/]+/},d=a.inherit(m,{keywords:{
+literal:["true","false","undefined","null"]}}),g={begin:/\(/,end:/\)/},b={
+className:"attr",begin:o,relevance:0,starts:{begin:/=/,end:/=/,starts:{
+contains:[a.NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,d,g]}}},u={
+contains:[a.NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{begin:/as\s+\|/,
+keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},b,d,g],returnEnd:!0
+},h=a.inherit(m,{className:"name",keywords:t,starts:a.inherit(u,{end:/\)/})})
+;g.contains=[h];const N=a.inherit(m,{keywords:t,className:"name",
+starts:a.inherit(u,{end:/\}\}/})}),p=a.inherit(m,{keywords:t,className:"name"
+}),E=a.inherit(m,{className:"name",keywords:t,starts:a.inherit(u,{end:/\}\}/})})
+;return{name:"Handlebars",
+aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,
+subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,
+skip:!0},a.COMMENT(/\{\{!--/,/--\}\}/),a.COMMENT(/\{\{!/,/\}\}/),{
+className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[N],
+starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{
+className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[p]},{
+className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[N]},{
+className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{
+className:"template-tag",begin:/\{\{(?=else if)/,end:/\}\}/,keywords:"else if"
+},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[p]},{
+className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[E]},{
+className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[E]}]}}(a)
+;return t.name="HTMLbars",a.getLanguage("handlebars")&&(t.disableAutodetect=!0),
+t}})());
+hljs.registerLanguage("http",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const a="HTTP/(2|1\\.[01])",s=[{className:"attribute",
+begin:e("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{
+className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}
+},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{
+name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+a+" \\d{3})",
+end:/$/,contains:[{className:"meta",begin:a},{className:"number",
+begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:s}},{
+begin:"(?=^[A-Z]+ (.*?) "+a+"$)",end:/$/,contains:[{className:"string",
+begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:a},{
+className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:s}
+}]}}})());
+hljs.registerLanguage("hy",(()=>{"use strict";return e=>{
+var a="a-zA-Z_\\-!.?+*=<>&#'",t="["+a+"]["+a+"0-9/;:]*",i={$pattern:t,
+"builtin-name":"!= % %= & &= * ** **= *= *map + += , --build-class-- --import-- -= . / // //= /= < << <<= <= = > >= >> >>= @ @= ^ ^= abs accumulate all and any ap-compose ap-dotimes ap-each ap-each-while ap-filter ap-first ap-if ap-last ap-map ap-map-when ap-pipe ap-reduce ap-reject apply as-> ascii assert assoc bin break butlast callable calling-module-name car case cdr chain chr coll? combinations compile compress cond cons cons? continue count curry cut cycle dec def default-method defclass defmacro defmacro-alias defmacro/g! defmain defmethod defmulti defn defn-alias defnc defnr defreader defseq del delattr delete-route dict-comp dir disassemble dispatch-reader-macro distinct divmod do doto drop drop-last drop-while empty? end-sequence eval eval-and-compile eval-when-compile even? every? except exec filter first flatten float? fn fnc fnr for for* format fraction genexpr gensym get getattr global globals group-by hasattr hash hex id identity if if* if-not if-python2 import in inc input instance? integer integer-char? integer? interleave interpose is is-coll is-cons is-empty is-even is-every is-float is-instance is-integer is-integer-char is-iterable is-iterator is-keyword is-neg is-none is-not is-numeric is-odd is-pos is-string is-symbol is-zero isinstance islice issubclass iter iterable? iterate iterator? keyword keyword? lambda last len let lif lif-not list* list-comp locals loop macro-error macroexpand macroexpand-1 macroexpand-all map max merge-with method-decorator min multi-decorator multicombinations name neg? next none? nonlocal not not-in not? nth numeric? oct odd? open or ord partition permutations pos? post-route postwalk pow prewalk print product profile/calls profile/cpu put-route quasiquote quote raise range read read-str recursive-replace reduce remove repeat repeatedly repr require rest round route route-with-methods rwm second seq set-comp setattr setv some sorted string string? sum switch symbol? take take-nth take-while tee try unless unquote unquote-splicing vars walk when while with with* with-decorator with-gensyms xi xor yield yield-from zero? zip zip-longest | |= ~"
+},r={begin:t,relevance:0},n={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",
+relevance:0},s=e.inherit(e.QUOTE_STRING_MODE,{illegal:null
+}),o=e.COMMENT(";","$",{relevance:0}),l={className:"literal",
+begin:/\b([Tt]rue|[Ff]alse|nil|None)\b/},c={begin:"[\\[\\{]",end:"[\\]\\}]"},d={
+className:"comment",begin:"\\^"+t},m=e.COMMENT("\\^\\{","\\}"),p={
+className:"symbol",begin:"[:]{1,2}"+t},u={begin:"\\(",end:"\\)"},f={
+endsWithParent:!0,relevance:0},g={className:"name",relevance:0,keywords:i,
+begin:t,starts:f},h=[u,s,d,m,o,p,c,n,l,r]
+;return u.contains=[e.COMMENT("comment",""),g,f],f.contains=h,c.contains=h,{
+name:"Hy",aliases:["hylang"],illegal:/\S/,
+contains:[e.SHEBANG(),u,s,d,m,o,p,c,n,l]}}})());
+hljs.registerLanguage("inform7",(()=>{"use strict";return e=>({name:"Inform 7",
+aliases:["i7"],case_insensitive:!0,keywords:{
+keyword:"thing room person man woman animal container supporter backdrop door scenery open closed locked inside gender is are say understand kind of rule"
+},contains:[{className:"string",begin:'"',end:'"',relevance:0,contains:[{
+className:"subst",begin:"\\[",end:"\\]"}]},{className:"section",
+begin:/^(Volume|Book|Part|Chapter|Section|Table)\b/,end:"$"},{
+begin:/^(Check|Carry out|Report|Instead of|To|Rule|When|Before|After)\b/,
+end:":",contains:[{begin:"\\(This",end:"\\)"}]},{className:"comment",
+begin:"\\[",end:"\\]",contains:["self"]}]})})());
+hljs.registerLanguage("ini",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}return s=>{const a={className:"number",
+relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:s.NUMBER_RE}]
+},i=s.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const t={
+className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/
+}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={
+className:"string",contains:[s.BACKSLASH_ESCAPE],variants:[{begin:"'''",
+end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'
+},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,a,"self"],
+relevance:0
+},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map((n=>e(n))).join("|")+")"
+;return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,
+contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{
+begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",
+starts:{end:/$/,contains:[i,c,r,t,l,a]}}]}}})());
+hljs.registerLanguage("irpf90",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const t=/(_[a-z_\d]+)?/,i=/([de][+-]?\d+)?/,a={
+className:"number",variants:[{begin:e(/\b\d+/,/\.(\d*)/,i,t)},{
+begin:e(/\b\d+/,i,t)},{begin:e(/\.\d+/,i,t)}],relevance:0};return{name:"IRPF90",
+case_insensitive:!0,keywords:{literal:".False. .True.",
+keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data begin_provider &begin_provider end_provider begin_shell end_shell begin_template end_template subst assert touch soft_touch provide no_dep free irp_if irp_else irp_endif irp_write irp_read",
+built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image IRP_ALIGN irp_here"
+},illegal:/\/\*/,contains:[n.inherit(n.APOS_STRING_MODE,{className:"string",
+relevance:0}),n.inherit(n.QUOTE_STRING_MODE,{className:"string",relevance:0}),{
+className:"function",beginKeywords:"subroutine function program",
+illegal:"[${=\\n]",contains:[n.UNDERSCORE_TITLE_MODE,{className:"params",
+begin:"\\(",end:"\\)"}]},n.COMMENT("!","$",{relevance:0
+}),n.COMMENT("begin_doc","end_doc",{relevance:10}),a]}}})());
+hljs.registerLanguage("isbl",(()=>{"use strict";return S=>{
+const E="[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_!][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*",_={
+className:"number",begin:S.NUMBER_RE,relevance:0},T={className:"string",
+variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]},R={className:"doctag",
+begin:"\\b(?:TODO|DONE|BEGIN|END|STUB|CHG|FIXME|NOTE|BUG|XXX)\\b",relevance:0
+},O={variants:[{className:"comment",begin:"//",end:"$",relevance:0,
+contains:[S.PHRASAL_WORDS_MODE,R]},{className:"comment",begin:"/\\*",end:"\\*/",
+relevance:0,contains:[S.PHRASAL_WORDS_MODE,R]}]},C={$pattern:E,
+keyword:"and \u0438 else \u0438\u043d\u0430\u0447\u0435 endexcept endfinally endforeach \u043a\u043e\u043d\u0435\u0446\u0432\u0441\u0435 endif \u043a\u043e\u043d\u0435\u0446\u0435\u0441\u043b\u0438 endwhile \u043a\u043e\u043d\u0435\u0446\u043f\u043e\u043a\u0430 except exitfor finally foreach \u0432\u0441\u0435 if \u0435\u0441\u043b\u0438 in \u0432 not \u043d\u0435 or \u0438\u043b\u0438 try while \u043f\u043e\u043a\u0430 ",
+built_in:"SYSRES_CONST_ACCES_RIGHT_TYPE_EDIT SYSRES_CONST_ACCES_RIGHT_TYPE_FULL SYSRES_CONST_ACCES_RIGHT_TYPE_VIEW SYSRES_CONST_ACCESS_MODE_REQUISITE_CODE SYSRES_CONST_ACCESS_NO_ACCESS_VIEW SYSRES_CONST_ACCESS_NO_ACCESS_VIEW_CODE SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_RIGHTS_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW SYSRES_CONST_ACCESS_RIGHTS_VIEW_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_CODE SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_YES_CODE SYSRES_CONST_ACCESS_TYPE_CHANGE SYSRES_CONST_ACCESS_TYPE_CHANGE_CODE SYSRES_CONST_ACCESS_TYPE_EXISTS SYSRES_CONST_ACCESS_TYPE_EXISTS_CODE SYSRES_CONST_ACCESS_TYPE_FULL SYSRES_CONST_ACCESS_TYPE_FULL_CODE SYSRES_CONST_ACCESS_TYPE_VIEW SYSRES_CONST_ACCESS_TYPE_VIEW_CODE SYSRES_CONST_ACTION_TYPE_ABORT SYSRES_CONST_ACTION_TYPE_ACCEPT SYSRES_CONST_ACTION_TYPE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ADD_ATTACHMENT SYSRES_CONST_ACTION_TYPE_CHANGE_CARD SYSRES_CONST_ACTION_TYPE_CHANGE_KIND SYSRES_CONST_ACTION_TYPE_CHANGE_STORAGE SYSRES_CONST_ACTION_TYPE_CONTINUE SYSRES_CONST_ACTION_TYPE_COPY SYSRES_CONST_ACTION_TYPE_CREATE SYSRES_CONST_ACTION_TYPE_CREATE_VERSION SYSRES_CONST_ACTION_TYPE_DELETE SYSRES_CONST_ACTION_TYPE_DELETE_ATTACHMENT SYSRES_CONST_ACTION_TYPE_DELETE_VERSION SYSRES_CONST_ACTION_TYPE_DISABLE_DELEGATE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ENABLE_DELEGATE_ACCESS_RIGHTS SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE_AND_PASSWORD SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_PASSWORD SYSRES_CONST_ACTION_TYPE_EXPORT_WITH_LOCK SYSRES_CONST_ACTION_TYPE_EXPORT_WITHOUT_LOCK SYSRES_CONST_ACTION_TYPE_IMPORT_WITH_UNLOCK SYSRES_CONST_ACTION_TYPE_IMPORT_WITHOUT_UNLOCK SYSRES_CONST_ACTION_TYPE_LIFE_CYCLE_STAGE SYSRES_CONST_ACTION_TYPE_LOCK SYSRES_CONST_ACTION_TYPE_LOCK_FOR_SERVER SYSRES_CONST_ACTION_TYPE_LOCK_MODIFY SYSRES_CONST_ACTION_TYPE_MARK_AS_READED SYSRES_CONST_ACTION_TYPE_MARK_AS_UNREADED SYSRES_CONST_ACTION_TYPE_MODIFY SYSRES_CONST_ACTION_TYPE_MODIFY_CARD SYSRES_CONST_ACTION_TYPE_MOVE_TO_ARCHIVE SYSRES_CONST_ACTION_TYPE_OFF_ENCRYPTION SYSRES_CONST_ACTION_TYPE_PASSWORD_CHANGE SYSRES_CONST_ACTION_TYPE_PERFORM SYSRES_CONST_ACTION_TYPE_RECOVER_FROM_LOCAL_COPY SYSRES_CONST_ACTION_TYPE_RESTART SYSRES_CONST_ACTION_TYPE_RESTORE_FROM_ARCHIVE SYSRES_CONST_ACTION_TYPE_REVISION SYSRES_CONST_ACTION_TYPE_SEND_BY_MAIL SYSRES_CONST_ACTION_TYPE_SIGN SYSRES_CONST_ACTION_TYPE_START SYSRES_CONST_ACTION_TYPE_UNLOCK SYSRES_CONST_ACTION_TYPE_UNLOCK_FROM_SERVER SYSRES_CONST_ACTION_TYPE_VERSION_STATE SYSRES_CONST_ACTION_TYPE_VERSION_VISIBILITY SYSRES_CONST_ACTION_TYPE_VIEW SYSRES_CONST_ACTION_TYPE_VIEW_SHADOW_COPY SYSRES_CONST_ACTION_TYPE_WORKFLOW_DESCRIPTION_MODIFY SYSRES_CONST_ACTION_TYPE_WRITE_HISTORY SYSRES_CONST_ACTIVE_VERSION_STATE_PICK_VALUE SYSRES_CONST_ADD_REFERENCE_MODE_NAME SYSRES_CONST_ADDITION_REQUISITE_CODE SYSRES_CONST_ADDITIONAL_PARAMS_REQUISITE_CODE SYSRES_CONST_ADITIONAL_JOB_END_DATE_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_READ_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_START_DATE_REQUISITE_NAME SYSRES_CONST_ADITIONAL_JOB_STATE_REQUISITE_NAME SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION_CODE SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE_ACTION SYSRES_CONST_ALL_ACCEPT_CONDITION_RUS SYSRES_CONST_ALL_USERS_GROUP SYSRES_CONST_ALL_USERS_GROUP_NAME SYSRES_CONST_ALL_USERS_SERVER_GROUP_NAME SYSRES_CONST_ALLOWED_ACCESS_TYPE_CODE SYSRES_CONST_ALLOWED_ACCESS_TYPE_NAME SYSRES_CONST_APP_VIEWER_TYPE_REQUISITE_CODE SYSRES_CONST_APPROVING_SIGNATURE_NAME SYSRES_CONST_APPROVING_SIGNATURE_REQUISITE_CODE SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE_CODE SYSRES_CONST_ATTACH_TYPE_COMPONENT_TOKEN SYSRES_CONST_ATTACH_TYPE_DOC SYSRES_CONST_ATTACH_TYPE_EDOC SYSRES_CONST_ATTACH_TYPE_FOLDER SYSRES_CONST_ATTACH_TYPE_JOB SYSRES_CONST_ATTACH_TYPE_REFERENCE SYSRES_CONST_ATTACH_TYPE_TASK SYSRES_CONST_AUTH_ENCODED_PASSWORD SYSRES_CONST_AUTH_ENCODED_PASSWORD_CODE SYSRES_CONST_AUTH_NOVELL SYSRES_CONST_AUTH_PASSWORD SYSRES_CONST_AUTH_PASSWORD_CODE SYSRES_CONST_AUTH_WINDOWS SYSRES_CONST_AUTHENTICATING_SIGNATURE_NAME SYSRES_CONST_AUTHENTICATING_SIGNATURE_REQUISITE_CODE SYSRES_CONST_AUTO_ENUM_METHOD_FLAG SYSRES_CONST_AUTO_NUMERATION_CODE SYSRES_CONST_AUTO_STRONG_ENUM_METHOD_FLAG SYSRES_CONST_AUTOTEXT_NAME_REQUISITE_CODE SYSRES_CONST_AUTOTEXT_TEXT_REQUISITE_CODE SYSRES_CONST_AUTOTEXT_USAGE_ALL SYSRES_CONST_AUTOTEXT_USAGE_ALL_CODE SYSRES_CONST_AUTOTEXT_USAGE_SIGN SYSRES_CONST_AUTOTEXT_USAGE_SIGN_CODE SYSRES_CONST_AUTOTEXT_USAGE_WORK SYSRES_CONST_AUTOTEXT_USAGE_WORK_CODE SYSRES_CONST_AUTOTEXT_USE_ANYWHERE_CODE SYSRES_CONST_AUTOTEXT_USE_ON_SIGNING_CODE SYSRES_CONST_AUTOTEXT_USE_ON_WORK_CODE SYSRES_CONST_BEGIN_DATE_REQUISITE_CODE SYSRES_CONST_BLACK_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_BLUE_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_BTN_PART SYSRES_CONST_CALCULATED_ROLE_TYPE_CODE SYSRES_CONST_CALL_TYPE_VARIABLE_BUTTON_VALUE SYSRES_CONST_CALL_TYPE_VARIABLE_PROGRAM_VALUE SYSRES_CONST_CANCEL_MESSAGE_FUNCTION_RESULT SYSRES_CONST_CARD_PART SYSRES_CONST_CARD_REFERENCE_MODE_NAME SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_ENCRYPT_VALUE SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_AND_ENCRYPT_VALUE SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_VALUE SYSRES_CONST_CHECK_PARAM_VALUE_DATE_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_FLOAT_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_INTEGER_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_PICK_PARAM_TYPE SYSRES_CONST_CHECK_PARAM_VALUE_REEFRENCE_PARAM_TYPE SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_FEMININE SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_CODE_COMPONENT_TYPE_ADMIN SYSRES_CONST_CODE_COMPONENT_TYPE_DEVELOPER SYSRES_CONST_CODE_COMPONENT_TYPE_DOCS SYSRES_CONST_CODE_COMPONENT_TYPE_EDOC_CARDS SYSRES_CONST_CODE_COMPONENT_TYPE_EXTERNAL_EXECUTABLE SYSRES_CONST_CODE_COMPONENT_TYPE_OTHER SYSRES_CONST_CODE_COMPONENT_TYPE_REFERENCE SYSRES_CONST_CODE_COMPONENT_TYPE_REPORT SYSRES_CONST_CODE_COMPONENT_TYPE_SCRIPT SYSRES_CONST_CODE_COMPONENT_TYPE_URL SYSRES_CONST_CODE_REQUISITE_ACCESS SYSRES_CONST_CODE_REQUISITE_CODE SYSRES_CONST_CODE_REQUISITE_COMPONENT SYSRES_CONST_CODE_REQUISITE_DESCRIPTION SYSRES_CONST_CODE_REQUISITE_EXCLUDE_COMPONENT SYSRES_CONST_CODE_REQUISITE_RECORD SYSRES_CONST_COMMENT_REQ_CODE SYSRES_CONST_COMMON_SETTINGS_REQUISITE_CODE SYSRES_CONST_COMP_CODE_GRD SYSRES_CONST_COMPONENT_GROUP_TYPE_REQUISITE_CODE SYSRES_CONST_COMPONENT_TYPE_ADMIN_COMPONENTS SYSRES_CONST_COMPONENT_TYPE_DEVELOPER_COMPONENTS SYSRES_CONST_COMPONENT_TYPE_DOCS SYSRES_CONST_COMPONENT_TYPE_EDOC_CARDS SYSRES_CONST_COMPONENT_TYPE_EDOCS SYSRES_CONST_COMPONENT_TYPE_EXTERNAL_EXECUTABLE SYSRES_CONST_COMPONENT_TYPE_OTHER SYSRES_CONST_COMPONENT_TYPE_REFERENCE_TYPES SYSRES_CONST_COMPONENT_TYPE_REFERENCES SYSRES_CONST_COMPONENT_TYPE_REPORTS SYSRES_CONST_COMPONENT_TYPE_SCRIPTS SYSRES_CONST_COMPONENT_TYPE_URL SYSRES_CONST_COMPONENTS_REMOTE_SERVERS_VIEW_CODE SYSRES_CONST_CONDITION_BLOCK_DESCRIPTION SYSRES_CONST_CONST_FIRM_STATUS_COMMON SYSRES_CONST_CONST_FIRM_STATUS_INDIVIDUAL SYSRES_CONST_CONST_NEGATIVE_VALUE SYSRES_CONST_CONST_POSITIVE_VALUE SYSRES_CONST_CONST_SERVER_STATUS_DONT_REPLICATE SYSRES_CONST_CONST_SERVER_STATUS_REPLICATE SYSRES_CONST_CONTENTS_REQUISITE_CODE SYSRES_CONST_DATA_TYPE_BOOLEAN SYSRES_CONST_DATA_TYPE_DATE SYSRES_CONST_DATA_TYPE_FLOAT SYSRES_CONST_DATA_TYPE_INTEGER SYSRES_CONST_DATA_TYPE_PICK SYSRES_CONST_DATA_TYPE_REFERENCE SYSRES_CONST_DATA_TYPE_STRING SYSRES_CONST_DATA_TYPE_TEXT SYSRES_CONST_DATA_TYPE_VARIANT SYSRES_CONST_DATE_CLOSE_REQ_CODE SYSRES_CONST_DATE_FORMAT_DATE_ONLY_CHAR SYSRES_CONST_DATE_OPEN_REQ_CODE SYSRES_CONST_DATE_REQUISITE SYSRES_CONST_DATE_REQUISITE_CODE SYSRES_CONST_DATE_REQUISITE_NAME SYSRES_CONST_DATE_REQUISITE_TYPE SYSRES_CONST_DATE_TYPE_CHAR SYSRES_CONST_DATETIME_FORMAT_VALUE SYSRES_CONST_DEA_ACCESS_RIGHTS_ACTION_CODE SYSRES_CONST_DESCRIPTION_LOCALIZE_ID_REQUISITE_CODE SYSRES_CONST_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_DET1_PART SYSRES_CONST_DET2_PART SYSRES_CONST_DET3_PART SYSRES_CONST_DET4_PART SYSRES_CONST_DET5_PART SYSRES_CONST_DET6_PART SYSRES_CONST_DETAIL_DATASET_KEY_REQUISITE_CODE SYSRES_CONST_DETAIL_PICK_REQUISITE_CODE SYSRES_CONST_DETAIL_REQ_CODE SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_CODE SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_NAME SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_CODE SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_NAME SYSRES_CONST_DOCUMENT_STORAGES_CODE SYSRES_CONST_DOCUMENT_TEMPLATES_TYPE_NAME SYSRES_CONST_DOUBLE_REQUISITE_CODE SYSRES_CONST_EDITOR_CLOSE_FILE_OBSERV_TYPE_CODE SYSRES_CONST_EDITOR_CLOSE_PROCESS_OBSERV_TYPE_CODE SYSRES_CONST_EDITOR_TYPE_REQUISITE_CODE SYSRES_CONST_EDITORS_APPLICATION_NAME_REQUISITE_CODE SYSRES_CONST_EDITORS_CREATE_SEVERAL_PROCESSES_REQUISITE_CODE SYSRES_CONST_EDITORS_EXTENSION_REQUISITE_CODE SYSRES_CONST_EDITORS_OBSERVER_BY_PROCESS_TYPE SYSRES_CONST_EDITORS_REFERENCE_CODE SYSRES_CONST_EDITORS_REPLACE_SPEC_CHARS_REQUISITE_CODE SYSRES_CONST_EDITORS_USE_PLUGINS_REQUISITE_CODE SYSRES_CONST_EDITORS_VIEW_DOCUMENT_OPENED_TO_EDIT_CODE SYSRES_CONST_EDOC_CARD_TYPE_REQUISITE_CODE SYSRES_CONST_EDOC_CARD_TYPES_LINK_REQUISITE_CODE SYSRES_CONST_EDOC_CERTIFICATE_AND_PASSWORD_ENCODE_CODE SYSRES_CONST_EDOC_CERTIFICATE_ENCODE_CODE SYSRES_CONST_EDOC_DATE_REQUISITE_CODE SYSRES_CONST_EDOC_KIND_REFERENCE_CODE SYSRES_CONST_EDOC_KINDS_BY_TEMPLATE_ACTION_CODE SYSRES_CONST_EDOC_MANAGE_ACCESS_CODE SYSRES_CONST_EDOC_NONE_ENCODE_CODE SYSRES_CONST_EDOC_NUMBER_REQUISITE_CODE SYSRES_CONST_EDOC_PASSWORD_ENCODE_CODE SYSRES_CONST_EDOC_READONLY_ACCESS_CODE SYSRES_CONST_EDOC_SHELL_LIFE_TYPE_VIEW_VALUE SYSRES_CONST_EDOC_SIZE_RESTRICTION_PRIORITY_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_CHECK_ACCESS_RIGHTS_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_COMPUTER_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_DATABASE_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_EDIT_IN_STORAGE_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_LOCAL_PATH_REQUISITE_CODE SYSRES_CONST_EDOC_STORAGE_SHARED_SOURCE_NAME_REQUISITE_CODE SYSRES_CONST_EDOC_TEMPLATE_REQUISITE_CODE SYSRES_CONST_EDOC_TYPES_REFERENCE_CODE SYSRES_CONST_EDOC_VERSION_ACTIVE_STAGE_CODE SYSRES_CONST_EDOC_VERSION_DESIGN_STAGE_CODE SYSRES_CONST_EDOC_VERSION_OBSOLETE_STAGE_CODE SYSRES_CONST_EDOC_WRITE_ACCES_CODE SYSRES_CONST_EDOCUMENT_CARD_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE SYSRES_CONST_ENCODE_CERTIFICATE_TYPE_CODE SYSRES_CONST_END_DATE_REQUISITE_CODE SYSRES_CONST_ENUMERATION_TYPE_REQUISITE_CODE SYSRES_CONST_EXECUTE_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_EXECUTIVE_FILE_STORAGE_TYPE SYSRES_CONST_EXIST_CONST SYSRES_CONST_EXIST_VALUE SYSRES_CONST_EXPORT_LOCK_TYPE_ASK SYSRES_CONST_EXPORT_LOCK_TYPE_WITH_LOCK SYSRES_CONST_EXPORT_LOCK_TYPE_WITHOUT_LOCK SYSRES_CONST_EXPORT_VERSION_TYPE_ASK SYSRES_CONST_EXPORT_VERSION_TYPE_LAST SYSRES_CONST_EXPORT_VERSION_TYPE_LAST_ACTIVE SYSRES_CONST_EXTENSION_REQUISITE_CODE SYSRES_CONST_FILTER_NAME_REQUISITE_CODE SYSRES_CONST_FILTER_REQUISITE_CODE SYSRES_CONST_FILTER_TYPE_COMMON_CODE SYSRES_CONST_FILTER_TYPE_COMMON_NAME SYSRES_CONST_FILTER_TYPE_USER_CODE SYSRES_CONST_FILTER_TYPE_USER_NAME SYSRES_CONST_FILTER_VALUE_REQUISITE_NAME SYSRES_CONST_FLOAT_NUMBER_FORMAT_CHAR SYSRES_CONST_FLOAT_REQUISITE_TYPE SYSRES_CONST_FOLDER_AUTHOR_VALUE SYSRES_CONST_FOLDER_KIND_ANY_OBJECTS SYSRES_CONST_FOLDER_KIND_COMPONENTS SYSRES_CONST_FOLDER_KIND_EDOCS SYSRES_CONST_FOLDER_KIND_JOBS SYSRES_CONST_FOLDER_KIND_TASKS SYSRES_CONST_FOLDER_TYPE_COMMON SYSRES_CONST_FOLDER_TYPE_COMPONENT SYSRES_CONST_FOLDER_TYPE_FAVORITES SYSRES_CONST_FOLDER_TYPE_INBOX SYSRES_CONST_FOLDER_TYPE_OUTBOX SYSRES_CONST_FOLDER_TYPE_QUICK_LAUNCH SYSRES_CONST_FOLDER_TYPE_SEARCH SYSRES_CONST_FOLDER_TYPE_SHORTCUTS SYSRES_CONST_FOLDER_TYPE_USER SYSRES_CONST_FROM_DICTIONARY_ENUM_METHOD_FLAG SYSRES_CONST_FULL_SUBSTITUTE_TYPE SYSRES_CONST_FULL_SUBSTITUTE_TYPE_CODE SYSRES_CONST_FUNCTION_CANCEL_RESULT SYSRES_CONST_FUNCTION_CATEGORY_SYSTEM SYSRES_CONST_FUNCTION_CATEGORY_USER SYSRES_CONST_FUNCTION_FAILURE_RESULT SYSRES_CONST_FUNCTION_SAVE_RESULT SYSRES_CONST_GENERATED_REQUISITE SYSRES_CONST_GREEN_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_GROUP_ACCOUNT_TYPE_VALUE_CODE SYSRES_CONST_GROUP_CATEGORY_NORMAL_CODE SYSRES_CONST_GROUP_CATEGORY_NORMAL_NAME SYSRES_CONST_GROUP_CATEGORY_SERVICE_CODE SYSRES_CONST_GROUP_CATEGORY_SERVICE_NAME SYSRES_CONST_GROUP_COMMON_CATEGORY_FIELD_VALUE SYSRES_CONST_GROUP_FULL_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_RIGHTS_T_REQUISITE_CODE SYSRES_CONST_GROUP_SERVER_CODES_REQUISITE_CODE SYSRES_CONST_GROUP_SERVER_NAME_REQUISITE_CODE SYSRES_CONST_GROUP_SERVICE_CATEGORY_FIELD_VALUE SYSRES_CONST_GROUP_USER_REQUISITE_CODE SYSRES_CONST_GROUPS_REFERENCE_CODE SYSRES_CONST_GROUPS_REQUISITE_CODE SYSRES_CONST_HIDDEN_MODE_NAME SYSRES_CONST_HIGH_LVL_REQUISITE_CODE SYSRES_CONST_HISTORY_ACTION_CREATE_CODE SYSRES_CONST_HISTORY_ACTION_DELETE_CODE SYSRES_CONST_HISTORY_ACTION_EDIT_CODE SYSRES_CONST_HOUR_CHAR SYSRES_CONST_ID_REQUISITE_CODE SYSRES_CONST_IDSPS_REQUISITE_CODE SYSRES_CONST_IMAGE_MODE_COLOR SYSRES_CONST_IMAGE_MODE_GREYSCALE SYSRES_CONST_IMAGE_MODE_MONOCHROME SYSRES_CONST_IMPORTANCE_HIGH SYSRES_CONST_IMPORTANCE_LOW SYSRES_CONST_IMPORTANCE_NORMAL SYSRES_CONST_IN_DESIGN_VERSION_STATE_PICK_VALUE SYSRES_CONST_INCOMING_WORK_RULE_TYPE_CODE SYSRES_CONST_INT_REQUISITE SYSRES_CONST_INT_REQUISITE_TYPE SYSRES_CONST_INTEGER_NUMBER_FORMAT_CHAR SYSRES_CONST_INTEGER_TYPE_CHAR SYSRES_CONST_IS_GENERATED_REQUISITE_NEGATIVE_VALUE SYSRES_CONST_IS_PUBLIC_ROLE_REQUISITE_CODE SYSRES_CONST_IS_REMOTE_USER_NEGATIVE_VALUE SYSRES_CONST_IS_REMOTE_USER_POSITIVE_VALUE SYSRES_CONST_IS_STORED_REQUISITE_NEGATIVE_VALUE SYSRES_CONST_IS_STORED_REQUISITE_STORED_VALUE SYSRES_CONST_ITALIC_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_JOB_BLOCK_DESCRIPTION SYSRES_CONST_JOB_KIND_CONTROL_JOB SYSRES_CONST_JOB_KIND_JOB SYSRES_CONST_JOB_KIND_NOTICE SYSRES_CONST_JOB_STATE_ABORTED SYSRES_CONST_JOB_STATE_COMPLETE SYSRES_CONST_JOB_STATE_WORKING SYSRES_CONST_KIND_REQUISITE_CODE SYSRES_CONST_KIND_REQUISITE_NAME SYSRES_CONST_KINDS_CREATE_SHADOW_COPIES_REQUISITE_CODE SYSRES_CONST_KINDS_DEFAULT_EDOC_LIFE_STAGE_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALL_TEPLATES_ALLOWED_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALLOW_LIFE_CYCLE_STAGE_CHANGING_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_ALLOW_MULTIPLE_ACTIVE_VERSIONS_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_SHARE_ACCES_RIGHTS_BY_DEFAULT_CODE SYSRES_CONST_KINDS_EDOC_TEMPLATE_REQUISITE_CODE SYSRES_CONST_KINDS_EDOC_TYPE_REQUISITE_CODE SYSRES_CONST_KINDS_SIGNERS_REQUISITES_CODE SYSRES_CONST_KOD_INPUT_TYPE SYSRES_CONST_LAST_UPDATE_DATE_REQUISITE_CODE SYSRES_CONST_LIFE_CYCLE_START_STAGE_REQUISITE_CODE SYSRES_CONST_LILAC_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_LINK_OBJECT_KIND_COMPONENT SYSRES_CONST_LINK_OBJECT_KIND_DOCUMENT SYSRES_CONST_LINK_OBJECT_KIND_EDOC SYSRES_CONST_LINK_OBJECT_KIND_FOLDER SYSRES_CONST_LINK_OBJECT_KIND_JOB SYSRES_CONST_LINK_OBJECT_KIND_REFERENCE SYSRES_CONST_LINK_OBJECT_KIND_TASK SYSRES_CONST_LINK_REF_TYPE_REQUISITE_CODE SYSRES_CONST_LIST_REFERENCE_MODE_NAME SYSRES_CONST_LOCALIZATION_DICTIONARY_MAIN_VIEW_CODE SYSRES_CONST_MAIN_VIEW_CODE SYSRES_CONST_MANUAL_ENUM_METHOD_FLAG SYSRES_CONST_MASTER_COMP_TYPE_REQUISITE_CODE SYSRES_CONST_MASTER_TABLE_REC_ID_REQUISITE_CODE SYSRES_CONST_MAXIMIZED_MODE_NAME SYSRES_CONST_ME_VALUE SYSRES_CONST_MESSAGE_ATTENTION_CAPTION SYSRES_CONST_MESSAGE_CONFIRMATION_CAPTION SYSRES_CONST_MESSAGE_ERROR_CAPTION SYSRES_CONST_MESSAGE_INFORMATION_CAPTION SYSRES_CONST_MINIMIZED_MODE_NAME SYSRES_CONST_MINUTE_CHAR SYSRES_CONST_MODULE_REQUISITE_CODE SYSRES_CONST_MONITORING_BLOCK_DESCRIPTION SYSRES_CONST_MONTH_FORMAT_VALUE SYSRES_CONST_NAME_LOCALIZE_ID_REQUISITE_CODE SYSRES_CONST_NAME_REQUISITE_CODE SYSRES_CONST_NAME_SINGULAR_REQUISITE_CODE SYSRES_CONST_NAMEAN_INPUT_TYPE SYSRES_CONST_NEGATIVE_PICK_VALUE SYSRES_CONST_NEGATIVE_VALUE SYSRES_CONST_NO SYSRES_CONST_NO_PICK_VALUE SYSRES_CONST_NO_SIGNATURE_REQUISITE_CODE SYSRES_CONST_NO_VALUE SYSRES_CONST_NONE_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_NORMAL_ACCESS_RIGHTS_TYPE_CODE SYSRES_CONST_NORMAL_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_NORMAL_MODE_NAME SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_CODE SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_NAME SYSRES_CONST_NOTE_REQUISITE_CODE SYSRES_CONST_NOTICE_BLOCK_DESCRIPTION SYSRES_CONST_NUM_REQUISITE SYSRES_CONST_NUM_STR_REQUISITE_CODE SYSRES_CONST_NUMERATION_AUTO_NOT_STRONG SYSRES_CONST_NUMERATION_AUTO_STRONG SYSRES_CONST_NUMERATION_FROM_DICTONARY SYSRES_CONST_NUMERATION_MANUAL SYSRES_CONST_NUMERIC_TYPE_CHAR SYSRES_CONST_NUMREQ_REQUISITE_CODE SYSRES_CONST_OBSOLETE_VERSION_STATE_PICK_VALUE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_CODE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_FEMININE SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_MASCULINE SYSRES_CONST_OPTIONAL_FORM_COMP_REQCODE_PREFIX SYSRES_CONST_ORANGE_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_ORIGINALREF_REQUISITE_CODE SYSRES_CONST_OURFIRM_REF_CODE SYSRES_CONST_OURFIRM_REQUISITE_CODE SYSRES_CONST_OURFIRM_VAR SYSRES_CONST_OUTGOING_WORK_RULE_TYPE_CODE SYSRES_CONST_PICK_NEGATIVE_RESULT SYSRES_CONST_PICK_POSITIVE_RESULT SYSRES_CONST_PICK_REQUISITE SYSRES_CONST_PICK_REQUISITE_TYPE SYSRES_CONST_PICK_TYPE_CHAR SYSRES_CONST_PLAN_STATUS_REQUISITE_CODE SYSRES_CONST_PLATFORM_VERSION_COMMENT SYSRES_CONST_PLUGINS_SETTINGS_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_POSITIVE_PICK_VALUE SYSRES_CONST_POWER_TO_CREATE_ACTION_CODE SYSRES_CONST_POWER_TO_SIGN_ACTION_CODE SYSRES_CONST_PRIORITY_REQUISITE_CODE SYSRES_CONST_QUALIFIED_TASK_TYPE SYSRES_CONST_QUALIFIED_TASK_TYPE_CODE SYSRES_CONST_RECSTAT_REQUISITE_CODE SYSRES_CONST_RED_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_REF_ID_T_REF_TYPE_REQUISITE_CODE SYSRES_CONST_REF_REQUISITE SYSRES_CONST_REF_REQUISITE_TYPE SYSRES_CONST_REF_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE SYSRES_CONST_REFERENCE_RECORD_HISTORY_CREATE_ACTION_CODE SYSRES_CONST_REFERENCE_RECORD_HISTORY_DELETE_ACTION_CODE SYSRES_CONST_REFERENCE_RECORD_HISTORY_MODIFY_ACTION_CODE SYSRES_CONST_REFERENCE_TYPE_CHAR SYSRES_CONST_REFERENCE_TYPE_REQUISITE_NAME SYSRES_CONST_REFERENCES_ADD_PARAMS_REQUISITE_CODE SYSRES_CONST_REFERENCES_DISPLAY_REQUISITE_REQUISITE_CODE SYSRES_CONST_REMOTE_SERVER_STATUS_WORKING SYSRES_CONST_REMOTE_SERVER_TYPE_MAIN SYSRES_CONST_REMOTE_SERVER_TYPE_SECONDARY SYSRES_CONST_REMOTE_USER_FLAG_VALUE_CODE SYSRES_CONST_REPORT_APP_EDITOR_INTERNAL SYSRES_CONST_REPORT_BASE_REPORT_ID_REQUISITE_CODE SYSRES_CONST_REPORT_BASE_REPORT_REQUISITE_CODE SYSRES_CONST_REPORT_SCRIPT_REQUISITE_CODE SYSRES_CONST_REPORT_TEMPLATE_REQUISITE_CODE SYSRES_CONST_REPORT_VIEWER_CODE_REQUISITE_CODE SYSRES_CONST_REQ_ALLOW_COMPONENT_DEFAULT_VALUE SYSRES_CONST_REQ_ALLOW_RECORD_DEFAULT_VALUE SYSRES_CONST_REQ_ALLOW_SERVER_COMPONENT_DEFAULT_VALUE SYSRES_CONST_REQ_MODE_AVAILABLE_CODE SYSRES_CONST_REQ_MODE_EDIT_CODE SYSRES_CONST_REQ_MODE_HIDDEN_CODE SYSRES_CONST_REQ_MODE_NOT_AVAILABLE_CODE SYSRES_CONST_REQ_MODE_VIEW_CODE SYSRES_CONST_REQ_NUMBER_REQUISITE_CODE SYSRES_CONST_REQ_SECTION_VALUE SYSRES_CONST_REQ_TYPE_VALUE SYSRES_CONST_REQUISITE_FORMAT_BY_UNIT SYSRES_CONST_REQUISITE_FORMAT_DATE_FULL SYSRES_CONST_REQUISITE_FORMAT_DATE_TIME SYSRES_CONST_REQUISITE_FORMAT_LEFT SYSRES_CONST_REQUISITE_FORMAT_RIGHT SYSRES_CONST_REQUISITE_FORMAT_WITHOUT_UNIT SYSRES_CONST_REQUISITE_NUMBER_REQUISITE_CODE SYSRES_CONST_REQUISITE_SECTION_ACTIONS SYSRES_CONST_REQUISITE_SECTION_BUTTON SYSRES_CONST_REQUISITE_SECTION_BUTTONS SYSRES_CONST_REQUISITE_SECTION_CARD SYSRES_CONST_REQUISITE_SECTION_TABLE SYSRES_CONST_REQUISITE_SECTION_TABLE10 SYSRES_CONST_REQUISITE_SECTION_TABLE11 SYSRES_CONST_REQUISITE_SECTION_TABLE12 SYSRES_CONST_REQUISITE_SECTION_TABLE13 SYSRES_CONST_REQUISITE_SECTION_TABLE14 SYSRES_CONST_REQUISITE_SECTION_TABLE15 SYSRES_CONST_REQUISITE_SECTION_TABLE16 SYSRES_CONST_REQUISITE_SECTION_TABLE17 SYSRES_CONST_REQUISITE_SECTION_TABLE18 SYSRES_CONST_REQUISITE_SECTION_TABLE19 SYSRES_CONST_REQUISITE_SECTION_TABLE2 SYSRES_CONST_REQUISITE_SECTION_TABLE20 SYSRES_CONST_REQUISITE_SECTION_TABLE21 SYSRES_CONST_REQUISITE_SECTION_TABLE22 SYSRES_CONST_REQUISITE_SECTION_TABLE23 SYSRES_CONST_REQUISITE_SECTION_TABLE24 SYSRES_CONST_REQUISITE_SECTION_TABLE3 SYSRES_CONST_REQUISITE_SECTION_TABLE4 SYSRES_CONST_REQUISITE_SECTION_TABLE5 SYSRES_CONST_REQUISITE_SECTION_TABLE6 SYSRES_CONST_REQUISITE_SECTION_TABLE7 SYSRES_CONST_REQUISITE_SECTION_TABLE8 SYSRES_CONST_REQUISITE_SECTION_TABLE9 SYSRES_CONST_REQUISITES_PSEUDOREFERENCE_REQUISITE_NUMBER_REQUISITE_CODE SYSRES_CONST_RIGHT_ALIGNMENT_CODE SYSRES_CONST_ROLES_REFERENCE_CODE SYSRES_CONST_ROUTE_STEP_AFTER_RUS SYSRES_CONST_ROUTE_STEP_AND_CONDITION_RUS SYSRES_CONST_ROUTE_STEP_OR_CONDITION_RUS SYSRES_CONST_ROUTE_TYPE_COMPLEX SYSRES_CONST_ROUTE_TYPE_PARALLEL SYSRES_CONST_ROUTE_TYPE_SERIAL SYSRES_CONST_SBDATASETDESC_NEGATIVE_VALUE SYSRES_CONST_SBDATASETDESC_POSITIVE_VALUE SYSRES_CONST_SBVIEWSDESC_POSITIVE_VALUE SYSRES_CONST_SCRIPT_BLOCK_DESCRIPTION SYSRES_CONST_SEARCH_BY_TEXT_REQUISITE_CODE SYSRES_CONST_SEARCHES_COMPONENT_CONTENT SYSRES_CONST_SEARCHES_CRITERIA_ACTION_NAME SYSRES_CONST_SEARCHES_EDOC_CONTENT SYSRES_CONST_SEARCHES_FOLDER_CONTENT SYSRES_CONST_SEARCHES_JOB_CONTENT SYSRES_CONST_SEARCHES_REFERENCE_CODE SYSRES_CONST_SEARCHES_TASK_CONTENT SYSRES_CONST_SECOND_CHAR SYSRES_CONST_SECTION_REQUISITE_ACTIONS_VALUE SYSRES_CONST_SECTION_REQUISITE_CARD_VALUE SYSRES_CONST_SECTION_REQUISITE_CODE SYSRES_CONST_SECTION_REQUISITE_DETAIL_1_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_2_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_3_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_4_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_5_VALUE SYSRES_CONST_SECTION_REQUISITE_DETAIL_6_VALUE SYSRES_CONST_SELECT_REFERENCE_MODE_NAME SYSRES_CONST_SELECT_TYPE_SELECTABLE SYSRES_CONST_SELECT_TYPE_SELECTABLE_ONLY_CHILD SYSRES_CONST_SELECT_TYPE_SELECTABLE_WITH_CHILD SYSRES_CONST_SELECT_TYPE_UNSLECTABLE SYSRES_CONST_SERVER_TYPE_MAIN SYSRES_CONST_SERVICE_USER_CATEGORY_FIELD_VALUE SYSRES_CONST_SETTINGS_USER_REQUISITE_CODE SYSRES_CONST_SIGNATURE_AND_ENCODE_CERTIFICATE_TYPE_CODE SYSRES_CONST_SIGNATURE_CERTIFICATE_TYPE_CODE SYSRES_CONST_SINGULAR_TITLE_REQUISITE_CODE SYSRES_CONST_SQL_SERVER_AUTHENTIFICATION_FLAG_VALUE_CODE SYSRES_CONST_SQL_SERVER_ENCODE_AUTHENTIFICATION_FLAG_VALUE_CODE SYSRES_CONST_STANDART_ROUTE_REFERENCE_CODE SYSRES_CONST_STANDART_ROUTE_REFERENCE_COMMENT_REQUISITE_CODE SYSRES_CONST_STANDART_ROUTES_GROUPS_REFERENCE_CODE SYSRES_CONST_STATE_REQ_NAME SYSRES_CONST_STATE_REQUISITE_ACTIVE_VALUE SYSRES_CONST_STATE_REQUISITE_CLOSED_VALUE SYSRES_CONST_STATE_REQUISITE_CODE SYSRES_CONST_STATIC_ROLE_TYPE_CODE SYSRES_CONST_STATUS_PLAN_DEFAULT_VALUE SYSRES_CONST_STATUS_VALUE_AUTOCLEANING SYSRES_CONST_STATUS_VALUE_BLUE_SQUARE SYSRES_CONST_STATUS_VALUE_COMPLETE SYSRES_CONST_STATUS_VALUE_GREEN_SQUARE SYSRES_CONST_STATUS_VALUE_ORANGE_SQUARE SYSRES_CONST_STATUS_VALUE_PURPLE_SQUARE SYSRES_CONST_STATUS_VALUE_RED_SQUARE SYSRES_CONST_STATUS_VALUE_SUSPEND SYSRES_CONST_STATUS_VALUE_YELLOW_SQUARE SYSRES_CONST_STDROUTE_SHOW_TO_USERS_REQUISITE_CODE SYSRES_CONST_STORAGE_TYPE_FILE SYSRES_CONST_STORAGE_TYPE_SQL_SERVER SYSRES_CONST_STR_REQUISITE SYSRES_CONST_STRIKEOUT_LIFE_CYCLE_STAGE_DRAW_STYLE SYSRES_CONST_STRING_FORMAT_LEFT_ALIGN_CHAR SYSRES_CONST_STRING_FORMAT_RIGHT_ALIGN_CHAR SYSRES_CONST_STRING_REQUISITE_CODE SYSRES_CONST_STRING_REQUISITE_TYPE SYSRES_CONST_STRING_TYPE_CHAR SYSRES_CONST_SUBSTITUTES_PSEUDOREFERENCE_CODE SYSRES_CONST_SUBTASK_BLOCK_DESCRIPTION SYSRES_CONST_SYSTEM_SETTING_CURRENT_USER_PARAM_VALUE SYSRES_CONST_SYSTEM_SETTING_EMPTY_VALUE_PARAM_VALUE SYSRES_CONST_SYSTEM_VERSION_COMMENT SYSRES_CONST_TASK_ACCESS_TYPE_ALL SYSRES_CONST_TASK_ACCESS_TYPE_ALL_MEMBERS SYSRES_CONST_TASK_ACCESS_TYPE_MANUAL SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION_AND_PASSWORD SYSRES_CONST_TASK_ENCODE_TYPE_NONE SYSRES_CONST_TASK_ENCODE_TYPE_PASSWORD SYSRES_CONST_TASK_ROUTE_ALL_CONDITION SYSRES_CONST_TASK_ROUTE_AND_CONDITION SYSRES_CONST_TASK_ROUTE_OR_CONDITION SYSRES_CONST_TASK_STATE_ABORTED SYSRES_CONST_TASK_STATE_COMPLETE SYSRES_CONST_TASK_STATE_CONTINUED SYSRES_CONST_TASK_STATE_CONTROL SYSRES_CONST_TASK_STATE_INIT SYSRES_CONST_TASK_STATE_WORKING SYSRES_CONST_TASK_TITLE SYSRES_CONST_TASK_TYPES_GROUPS_REFERENCE_CODE SYSRES_CONST_TASK_TYPES_REFERENCE_CODE SYSRES_CONST_TEMPLATES_REFERENCE_CODE SYSRES_CONST_TEST_DATE_REQUISITE_NAME SYSRES_CONST_TEST_DEV_DATABASE_NAME SYSRES_CONST_TEST_DEV_SYSTEM_CODE SYSRES_CONST_TEST_EDMS_DATABASE_NAME SYSRES_CONST_TEST_EDMS_MAIN_CODE SYSRES_CONST_TEST_EDMS_MAIN_DB_NAME SYSRES_CONST_TEST_EDMS_SECOND_CODE SYSRES_CONST_TEST_EDMS_SECOND_DB_NAME SYSRES_CONST_TEST_EDMS_SYSTEM_CODE SYSRES_CONST_TEST_NUMERIC_REQUISITE_NAME SYSRES_CONST_TEXT_REQUISITE SYSRES_CONST_TEXT_REQUISITE_CODE SYSRES_CONST_TEXT_REQUISITE_TYPE SYSRES_CONST_TEXT_TYPE_CHAR SYSRES_CONST_TYPE_CODE_REQUISITE_CODE SYSRES_CONST_TYPE_REQUISITE_CODE SYSRES_CONST_UNDEFINED_LIFE_CYCLE_STAGE_FONT_COLOR SYSRES_CONST_UNITS_SECTION_ID_REQUISITE_CODE SYSRES_CONST_UNITS_SECTION_REQUISITE_CODE SYSRES_CONST_UNOPERATING_RECORD_FLAG_VALUE_CODE SYSRES_CONST_UNSTORED_DATA_REQUISITE_CODE SYSRES_CONST_UNSTORED_DATA_REQUISITE_NAME SYSRES_CONST_USE_ACCESS_TYPE_CODE SYSRES_CONST_USE_ACCESS_TYPE_NAME SYSRES_CONST_USER_ACCOUNT_TYPE_VALUE_CODE SYSRES_CONST_USER_ADDITIONAL_INFORMATION_REQUISITE_CODE SYSRES_CONST_USER_AND_GROUP_ID_FROM_PSEUDOREFERENCE_REQUISITE_CODE SYSRES_CONST_USER_CATEGORY_NORMAL SYSRES_CONST_USER_CERTIFICATE_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_STATE_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_SUBJECT_NAME_REQUISITE_CODE SYSRES_CONST_USER_CERTIFICATE_THUMBPRINT_REQUISITE_CODE SYSRES_CONST_USER_COMMON_CATEGORY SYSRES_CONST_USER_COMMON_CATEGORY_CODE SYSRES_CONST_USER_FULL_NAME_REQUISITE_CODE SYSRES_CONST_USER_GROUP_TYPE_REQUISITE_CODE SYSRES_CONST_USER_LOGIN_REQUISITE_CODE SYSRES_CONST_USER_REMOTE_CONTROLLER_REQUISITE_CODE SYSRES_CONST_USER_REMOTE_SYSTEM_REQUISITE_CODE SYSRES_CONST_USER_RIGHTS_T_REQUISITE_CODE SYSRES_CONST_USER_SERVER_NAME_REQUISITE_CODE SYSRES_CONST_USER_SERVICE_CATEGORY SYSRES_CONST_USER_SERVICE_CATEGORY_CODE SYSRES_CONST_USER_STATUS_ADMINISTRATOR_CODE SYSRES_CONST_USER_STATUS_ADMINISTRATOR_NAME SYSRES_CONST_USER_STATUS_DEVELOPER_CODE SYSRES_CONST_USER_STATUS_DEVELOPER_NAME SYSRES_CONST_USER_STATUS_DISABLED_CODE SYSRES_CONST_USER_STATUS_DISABLED_NAME SYSRES_CONST_USER_STATUS_SYSTEM_DEVELOPER_CODE SYSRES_CONST_USER_STATUS_USER_CODE SYSRES_CONST_USER_STATUS_USER_NAME SYSRES_CONST_USER_STATUS_USER_NAME_DEPRECATED SYSRES_CONST_USER_TYPE_FIELD_VALUE_USER SYSRES_CONST_USER_TYPE_REQUISITE_CODE SYSRES_CONST_USERS_CONTROLLER_REQUISITE_CODE SYSRES_CONST_USERS_IS_MAIN_SERVER_REQUISITE_CODE SYSRES_CONST_USERS_REFERENCE_CODE SYSRES_CONST_USERS_REGISTRATION_CERTIFICATES_ACTION_NAME SYSRES_CONST_USERS_REQUISITE_CODE SYSRES_CONST_USERS_SYSTEM_REQUISITE_CODE SYSRES_CONST_USERS_USER_ACCESS_RIGHTS_TYPR_REQUISITE_CODE SYSRES_CONST_USERS_USER_AUTHENTICATION_REQUISITE_CODE SYSRES_CONST_USERS_USER_COMPONENT_REQUISITE_CODE SYSRES_CONST_USERS_USER_GROUP_REQUISITE_CODE SYSRES_CONST_USERS_VIEW_CERTIFICATES_ACTION_NAME SYSRES_CONST_VIEW_DEFAULT_CODE SYSRES_CONST_VIEW_DEFAULT_NAME SYSRES_CONST_VIEWER_REQUISITE_CODE SYSRES_CONST_WAITING_BLOCK_DESCRIPTION SYSRES_CONST_WIZARD_FORM_LABEL_TEST_STRING SYSRES_CONST_WIZARD_QUERY_PARAM_HEIGHT_ETALON_STRING SYSRES_CONST_WIZARD_REFERENCE_COMMENT_REQUISITE_CODE SYSRES_CONST_WORK_RULES_DESCRIPTION_REQUISITE_CODE SYSRES_CONST_WORK_TIME_CALENDAR_REFERENCE_CODE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE_RUS SYSRES_CONST_WORK_WORKFLOW_SOFT_ROUTE_TYPE_VALUE_CODE_RUS SYSRES_CONST_WORKFLOW_ROUTE_TYPR_HARD SYSRES_CONST_WORKFLOW_ROUTE_TYPR_SOFT SYSRES_CONST_XML_ENCODING SYSRES_CONST_XREC_STAT_REQUISITE_CODE SYSRES_CONST_XRECID_FIELD_NAME SYSRES_CONST_YES SYSRES_CONST_YES_NO_2_REQUISITE_CODE SYSRES_CONST_YES_NO_REQUISITE_CODE SYSRES_CONST_YES_NO_T_REF_TYPE_REQUISITE_CODE SYSRES_CONST_YES_PICK_VALUE SYSRES_CONST_YES_VALUE CR FALSE nil NO_VALUE NULL TAB TRUE YES_VALUE ADMINISTRATORS_GROUP_NAME CUSTOMIZERS_GROUP_NAME DEVELOPERS_GROUP_NAME SERVICE_USERS_GROUP_NAME DECISION_BLOCK_FIRST_OPERAND_PROPERTY DECISION_BLOCK_NAME_PROPERTY DECISION_BLOCK_OPERATION_PROPERTY DECISION_BLOCK_RESULT_TYPE_PROPERTY DECISION_BLOCK_SECOND_OPERAND_PROPERTY ANY_FILE_EXTENTION COMPRESSED_DOCUMENT_EXTENSION EXTENDED_DOCUMENT_EXTENSION SHORT_COMPRESSED_DOCUMENT_EXTENSION SHORT_EXTENDED_DOCUMENT_EXTENSION JOB_BLOCK_ABORT_DEADLINE_PROPERTY JOB_BLOCK_AFTER_FINISH_EVENT JOB_BLOCK_AFTER_QUERY_PARAMETERS_EVENT JOB_BLOCK_ATTACHMENT_PROPERTY JOB_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY JOB_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY JOB_BLOCK_BEFORE_QUERY_PARAMETERS_EVENT JOB_BLOCK_BEFORE_START_EVENT JOB_BLOCK_CREATED_JOBS_PROPERTY JOB_BLOCK_DEADLINE_PROPERTY JOB_BLOCK_EXECUTION_RESULTS_PROPERTY JOB_BLOCK_IS_PARALLEL_PROPERTY JOB_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY JOB_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY JOB_BLOCK_JOB_TEXT_PROPERTY JOB_BLOCK_NAME_PROPERTY JOB_BLOCK_NEED_SIGN_ON_PERFORM_PROPERTY JOB_BLOCK_PERFORMER_PROPERTY JOB_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY JOB_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY JOB_BLOCK_SUBJECT_PROPERTY ENGLISH_LANGUAGE_CODE RUSSIAN_LANGUAGE_CODE smHidden smMaximized smMinimized smNormal wmNo wmYes COMPONENT_TOKEN_LINK_KIND DOCUMENT_LINK_KIND EDOCUMENT_LINK_KIND FOLDER_LINK_KIND JOB_LINK_KIND REFERENCE_LINK_KIND TASK_LINK_KIND COMPONENT_TOKEN_LOCK_TYPE EDOCUMENT_VERSION_LOCK_TYPE MONITOR_BLOCK_AFTER_FINISH_EVENT MONITOR_BLOCK_BEFORE_START_EVENT MONITOR_BLOCK_DEADLINE_PROPERTY MONITOR_BLOCK_INTERVAL_PROPERTY MONITOR_BLOCK_INTERVAL_TYPE_PROPERTY MONITOR_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY MONITOR_BLOCK_NAME_PROPERTY MONITOR_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY MONITOR_BLOCK_SEARCH_SCRIPT_PROPERTY NOTICE_BLOCK_AFTER_FINISH_EVENT NOTICE_BLOCK_ATTACHMENT_PROPERTY NOTICE_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY NOTICE_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY NOTICE_BLOCK_BEFORE_START_EVENT NOTICE_BLOCK_CREATED_NOTICES_PROPERTY NOTICE_BLOCK_DEADLINE_PROPERTY NOTICE_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY NOTICE_BLOCK_NAME_PROPERTY NOTICE_BLOCK_NOTICE_TEXT_PROPERTY NOTICE_BLOCK_PERFORMER_PROPERTY NOTICE_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY NOTICE_BLOCK_SUBJECT_PROPERTY dseAfterCancel dseAfterClose dseAfterDelete dseAfterDeleteOutOfTransaction dseAfterInsert dseAfterOpen dseAfterScroll dseAfterUpdate dseAfterUpdateOutOfTransaction dseBeforeCancel dseBeforeClose dseBeforeDelete dseBeforeDetailUpdate dseBeforeInsert dseBeforeOpen dseBeforeUpdate dseOnAnyRequisiteChange dseOnCloseRecord dseOnDeleteError dseOnOpenRecord dseOnPrepareUpdate dseOnUpdateError dseOnUpdateRatifiedRecord dseOnValidDelete dseOnValidUpdate reOnChange reOnChangeValues SELECTION_BEGIN_ROUTE_EVENT SELECTION_END_ROUTE_EVENT CURRENT_PERIOD_IS_REQUIRED PREVIOUS_CARD_TYPE_NAME SHOW_RECORD_PROPERTIES_FORM ACCESS_RIGHTS_SETTING_DIALOG_CODE ADMINISTRATOR_USER_CODE ANALYTIC_REPORT_TYPE asrtHideLocal asrtHideRemote CALCULATED_ROLE_TYPE_CODE COMPONENTS_REFERENCE_DEVELOPER_VIEW_CODE DCTS_TEST_PROTOCOLS_FOLDER_PATH E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED_BY_USER E_EDOC_VERSION_ALREDY_SIGNED E_EDOC_VERSION_ALREDY_SIGNED_BY_USER EDOC_TYPES_CODE_REQUISITE_FIELD_NAME EDOCUMENTS_ALIAS_NAME FILES_FOLDER_PATH FILTER_OPERANDS_DELIMITER FILTER_OPERATIONS_DELIMITER FORMCARD_NAME FORMLIST_NAME GET_EXTENDED_DOCUMENT_EXTENSION_CREATION_MODE GET_EXTENDED_DOCUMENT_EXTENSION_IMPORT_MODE INTEGRATED_REPORT_TYPE IS_BUILDER_APPLICATION_ROLE IS_BUILDER_APPLICATION_ROLE2 IS_BUILDER_USERS ISBSYSDEV LOG_FOLDER_PATH mbCancel mbNo mbNoToAll mbOK mbYes mbYesToAll MEMORY_DATASET_DESRIPTIONS_FILENAME mrNo mrNoToAll mrYes mrYesToAll MULTIPLE_SELECT_DIALOG_CODE NONOPERATING_RECORD_FLAG_FEMININE NONOPERATING_RECORD_FLAG_MASCULINE OPERATING_RECORD_FLAG_FEMININE OPERATING_RECORD_FLAG_MASCULINE PROFILING_SETTINGS_COMMON_SETTINGS_CODE_VALUE PROGRAM_INITIATED_LOOKUP_ACTION ratDelete ratEdit ratInsert REPORT_TYPE REQUIRED_PICK_VALUES_VARIABLE rmCard rmList SBRTE_PROGID_DEV SBRTE_PROGID_RELEASE STATIC_ROLE_TYPE_CODE SUPPRESS_EMPTY_TEMPLATE_CREATION SYSTEM_USER_CODE UPDATE_DIALOG_DATASET USED_IN_OBJECT_HINT_PARAM USER_INITIATED_LOOKUP_ACTION USER_NAME_FORMAT USER_SELECTION_RESTRICTIONS WORKFLOW_TEST_PROTOCOLS_FOLDER_PATH ELS_SUBTYPE_CONTROL_NAME ELS_FOLDER_KIND_CONTROL_NAME REPEAT_PROCESS_CURRENT_OBJECT_EXCEPTION_NAME PRIVILEGE_COMPONENT_FULL_ACCESS PRIVILEGE_DEVELOPMENT_EXPORT PRIVILEGE_DEVELOPMENT_IMPORT PRIVILEGE_DOCUMENT_DELETE PRIVILEGE_ESD PRIVILEGE_FOLDER_DELETE PRIVILEGE_MANAGE_ACCESS_RIGHTS PRIVILEGE_MANAGE_REPLICATION PRIVILEGE_MANAGE_SESSION_SERVER PRIVILEGE_OBJECT_FULL_ACCESS PRIVILEGE_OBJECT_VIEW PRIVILEGE_RESERVE_LICENSE PRIVILEGE_SYSTEM_CUSTOMIZE PRIVILEGE_SYSTEM_DEVELOP PRIVILEGE_SYSTEM_INSTALL PRIVILEGE_TASK_DELETE PRIVILEGE_USER_PLUGIN_SETTINGS_CUSTOMIZE PRIVILEGES_PSEUDOREFERENCE_CODE ACCESS_TYPES_PSEUDOREFERENCE_CODE ALL_AVAILABLE_COMPONENTS_PSEUDOREFERENCE_CODE ALL_AVAILABLE_PRIVILEGES_PSEUDOREFERENCE_CODE ALL_REPLICATE_COMPONENTS_PSEUDOREFERENCE_CODE AVAILABLE_DEVELOPERS_COMPONENTS_PSEUDOREFERENCE_CODE COMPONENTS_PSEUDOREFERENCE_CODE FILTRATER_SETTINGS_CONFLICTS_PSEUDOREFERENCE_CODE GROUPS_PSEUDOREFERENCE_CODE RECEIVE_PROTOCOL_PSEUDOREFERENCE_CODE REFERENCE_REQUISITE_PSEUDOREFERENCE_CODE REFERENCE_REQUISITES_PSEUDOREFERENCE_CODE REFTYPES_PSEUDOREFERENCE_CODE REPLICATION_SEANCES_DIARY_PSEUDOREFERENCE_CODE SEND_PROTOCOL_PSEUDOREFERENCE_CODE SUBSTITUTES_PSEUDOREFERENCE_CODE SYSTEM_SETTINGS_PSEUDOREFERENCE_CODE UNITS_PSEUDOREFERENCE_CODE USERS_PSEUDOREFERENCE_CODE VIEWERS_PSEUDOREFERENCE_CODE CERTIFICATE_TYPE_ENCRYPT CERTIFICATE_TYPE_SIGN CERTIFICATE_TYPE_SIGN_AND_ENCRYPT STORAGE_TYPE_FILE STORAGE_TYPE_NAS_CIFS STORAGE_TYPE_SAPERION STORAGE_TYPE_SQL_SERVER COMPTYPE2_REQUISITE_DOCUMENTS_VALUE COMPTYPE2_REQUISITE_TASKS_VALUE COMPTYPE2_REQUISITE_FOLDERS_VALUE COMPTYPE2_REQUISITE_REFERENCES_VALUE SYSREQ_CODE SYSREQ_COMPTYPE2 SYSREQ_CONST_AVAILABLE_FOR_WEB SYSREQ_CONST_COMMON_CODE SYSREQ_CONST_COMMON_VALUE SYSREQ_CONST_FIRM_CODE SYSREQ_CONST_FIRM_STATUS SYSREQ_CONST_FIRM_VALUE SYSREQ_CONST_SERVER_STATUS SYSREQ_CONTENTS SYSREQ_DATE_OPEN SYSREQ_DATE_CLOSE SYSREQ_DESCRIPTION SYSREQ_DESCRIPTION_LOCALIZE_ID SYSREQ_DOUBLE SYSREQ_EDOC_ACCESS_TYPE SYSREQ_EDOC_AUTHOR SYSREQ_EDOC_CREATED SYSREQ_EDOC_DELEGATE_RIGHTS_REQUISITE_CODE SYSREQ_EDOC_EDITOR SYSREQ_EDOC_ENCODE_TYPE SYSREQ_EDOC_ENCRYPTION_PLUGIN_NAME SYSREQ_EDOC_ENCRYPTION_PLUGIN_VERSION SYSREQ_EDOC_EXPORT_DATE SYSREQ_EDOC_EXPORTER SYSREQ_EDOC_KIND SYSREQ_EDOC_LIFE_STAGE_NAME SYSREQ_EDOC_LOCKED_FOR_SERVER_CODE SYSREQ_EDOC_MODIFIED SYSREQ_EDOC_NAME SYSREQ_EDOC_NOTE SYSREQ_EDOC_QUALIFIED_ID SYSREQ_EDOC_SESSION_KEY SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_NAME SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_VERSION SYSREQ_EDOC_SIGNATURE_TYPE SYSREQ_EDOC_SIGNED SYSREQ_EDOC_STORAGE SYSREQ_EDOC_STORAGES_ARCHIVE_STORAGE SYSREQ_EDOC_STORAGES_CHECK_RIGHTS SYSREQ_EDOC_STORAGES_COMPUTER_NAME SYSREQ_EDOC_STORAGES_EDIT_IN_STORAGE SYSREQ_EDOC_STORAGES_EXECUTIVE_STORAGE SYSREQ_EDOC_STORAGES_FUNCTION SYSREQ_EDOC_STORAGES_INITIALIZED SYSREQ_EDOC_STORAGES_LOCAL_PATH SYSREQ_EDOC_STORAGES_SAPERION_DATABASE_NAME SYSREQ_EDOC_STORAGES_SEARCH_BY_TEXT SYSREQ_EDOC_STORAGES_SERVER_NAME SYSREQ_EDOC_STORAGES_SHARED_SOURCE_NAME SYSREQ_EDOC_STORAGES_TYPE SYSREQ_EDOC_TEXT_MODIFIED SYSREQ_EDOC_TYPE_ACT_CODE SYSREQ_EDOC_TYPE_ACT_DESCRIPTION SYSREQ_EDOC_TYPE_ACT_DESCRIPTION_LOCALIZE_ID SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE_EXISTS SYSREQ_EDOC_TYPE_ACT_SECTION SYSREQ_EDOC_TYPE_ADD_PARAMS SYSREQ_EDOC_TYPE_COMMENT SYSREQ_EDOC_TYPE_EVENT_TEXT SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID SYSREQ_EDOC_TYPE_NAME_LOCALIZE_ID SYSREQ_EDOC_TYPE_NUMERATION_METHOD SYSREQ_EDOC_TYPE_PSEUDO_REQUISITE_CODE SYSREQ_EDOC_TYPE_REQ_CODE SYSREQ_EDOC_TYPE_REQ_DESCRIPTION SYSREQ_EDOC_TYPE_REQ_DESCRIPTION_LOCALIZE_ID SYSREQ_EDOC_TYPE_REQ_IS_LEADING SYSREQ_EDOC_TYPE_REQ_IS_REQUIRED SYSREQ_EDOC_TYPE_REQ_NUMBER SYSREQ_EDOC_TYPE_REQ_ON_CHANGE SYSREQ_EDOC_TYPE_REQ_ON_CHANGE_EXISTS SYSREQ_EDOC_TYPE_REQ_ON_SELECT SYSREQ_EDOC_TYPE_REQ_ON_SELECT_KIND SYSREQ_EDOC_TYPE_REQ_SECTION SYSREQ_EDOC_TYPE_VIEW_CARD SYSREQ_EDOC_TYPE_VIEW_CODE SYSREQ_EDOC_TYPE_VIEW_COMMENT SYSREQ_EDOC_TYPE_VIEW_IS_MAIN SYSREQ_EDOC_TYPE_VIEW_NAME SYSREQ_EDOC_TYPE_VIEW_NAME_LOCALIZE_ID SYSREQ_EDOC_VERSION_AUTHOR SYSREQ_EDOC_VERSION_CRC SYSREQ_EDOC_VERSION_DATA SYSREQ_EDOC_VERSION_EDITOR SYSREQ_EDOC_VERSION_EXPORT_DATE SYSREQ_EDOC_VERSION_EXPORTER SYSREQ_EDOC_VERSION_HIDDEN SYSREQ_EDOC_VERSION_LIFE_STAGE SYSREQ_EDOC_VERSION_MODIFIED SYSREQ_EDOC_VERSION_NOTE SYSREQ_EDOC_VERSION_SIGNATURE_TYPE SYSREQ_EDOC_VERSION_SIGNED SYSREQ_EDOC_VERSION_SIZE SYSREQ_EDOC_VERSION_SOURCE SYSREQ_EDOC_VERSION_TEXT_MODIFIED SYSREQ_EDOCKIND_DEFAULT_VERSION_STATE_CODE SYSREQ_FOLDER_KIND SYSREQ_FUNC_CATEGORY SYSREQ_FUNC_COMMENT SYSREQ_FUNC_GROUP SYSREQ_FUNC_GROUP_COMMENT SYSREQ_FUNC_GROUP_NUMBER SYSREQ_FUNC_HELP SYSREQ_FUNC_PARAM_DEF_VALUE SYSREQ_FUNC_PARAM_IDENT SYSREQ_FUNC_PARAM_NUMBER SYSREQ_FUNC_PARAM_TYPE SYSREQ_FUNC_TEXT SYSREQ_GROUP_CATEGORY SYSREQ_ID SYSREQ_LAST_UPDATE SYSREQ_LEADER_REFERENCE SYSREQ_LINE_NUMBER SYSREQ_MAIN_RECORD_ID SYSREQ_NAME SYSREQ_NAME_LOCALIZE_ID SYSREQ_NOTE SYSREQ_ORIGINAL_RECORD SYSREQ_OUR_FIRM SYSREQ_PROFILING_SETTINGS_BATCH_LOGING SYSREQ_PROFILING_SETTINGS_BATCH_SIZE SYSREQ_PROFILING_SETTINGS_PROFILING_ENABLED SYSREQ_PROFILING_SETTINGS_SQL_PROFILING_ENABLED SYSREQ_PROFILING_SETTINGS_START_LOGGED SYSREQ_RECORD_STATUS SYSREQ_REF_REQ_FIELD_NAME SYSREQ_REF_REQ_FORMAT SYSREQ_REF_REQ_GENERATED SYSREQ_REF_REQ_LENGTH SYSREQ_REF_REQ_PRECISION SYSREQ_REF_REQ_REFERENCE SYSREQ_REF_REQ_SECTION SYSREQ_REF_REQ_STORED SYSREQ_REF_REQ_TOKENS SYSREQ_REF_REQ_TYPE SYSREQ_REF_REQ_VIEW SYSREQ_REF_TYPE_ACT_CODE SYSREQ_REF_TYPE_ACT_DESCRIPTION SYSREQ_REF_TYPE_ACT_DESCRIPTION_LOCALIZE_ID SYSREQ_REF_TYPE_ACT_ON_EXECUTE SYSREQ_REF_TYPE_ACT_ON_EXECUTE_EXISTS SYSREQ_REF_TYPE_ACT_SECTION SYSREQ_REF_TYPE_ADD_PARAMS SYSREQ_REF_TYPE_COMMENT SYSREQ_REF_TYPE_COMMON_SETTINGS SYSREQ_REF_TYPE_DISPLAY_REQUISITE_NAME SYSREQ_REF_TYPE_EVENT_TEXT SYSREQ_REF_TYPE_MAIN_LEADING_REF SYSREQ_REF_TYPE_NAME_IN_SINGULAR SYSREQ_REF_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID SYSREQ_REF_TYPE_NAME_LOCALIZE_ID SYSREQ_REF_TYPE_NUMERATION_METHOD SYSREQ_REF_TYPE_REQ_CODE SYSREQ_REF_TYPE_REQ_DESCRIPTION SYSREQ_REF_TYPE_REQ_DESCRIPTION_LOCALIZE_ID SYSREQ_REF_TYPE_REQ_IS_CONTROL SYSREQ_REF_TYPE_REQ_IS_FILTER SYSREQ_REF_TYPE_REQ_IS_LEADING SYSREQ_REF_TYPE_REQ_IS_REQUIRED SYSREQ_REF_TYPE_REQ_NUMBER SYSREQ_REF_TYPE_REQ_ON_CHANGE SYSREQ_REF_TYPE_REQ_ON_CHANGE_EXISTS SYSREQ_REF_TYPE_REQ_ON_SELECT SYSREQ_REF_TYPE_REQ_ON_SELECT_KIND SYSREQ_REF_TYPE_REQ_SECTION SYSREQ_REF_TYPE_VIEW_CARD SYSREQ_REF_TYPE_VIEW_CODE SYSREQ_REF_TYPE_VIEW_COMMENT SYSREQ_REF_TYPE_VIEW_IS_MAIN SYSREQ_REF_TYPE_VIEW_NAME SYSREQ_REF_TYPE_VIEW_NAME_LOCALIZE_ID SYSREQ_REFERENCE_TYPE_ID SYSREQ_STATE SYSREQ_STAT\u0415 SYSREQ_SYSTEM_SETTINGS_VALUE SYSREQ_TYPE SYSREQ_UNIT SYSREQ_UNIT_ID SYSREQ_USER_GROUPS_GROUP_FULL_NAME SYSREQ_USER_GROUPS_GROUP_NAME SYSREQ_USER_GROUPS_GROUP_SERVER_NAME SYSREQ_USERS_ACCESS_RIGHTS SYSREQ_USERS_AUTHENTICATION SYSREQ_USERS_CATEGORY SYSREQ_USERS_COMPONENT SYSREQ_USERS_COMPONENT_USER_IS_PUBLIC SYSREQ_USERS_DOMAIN SYSREQ_USERS_FULL_USER_NAME SYSREQ_USERS_GROUP SYSREQ_USERS_IS_MAIN_SERVER SYSREQ_USERS_LOGIN SYSREQ_USERS_REFERENCE_USER_IS_PUBLIC SYSREQ_USERS_STATUS SYSREQ_USERS_USER_CERTIFICATE SYSREQ_USERS_USER_CERTIFICATE_INFO SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_NAME SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_VERSION SYSREQ_USERS_USER_CERTIFICATE_STATE SYSREQ_USERS_USER_CERTIFICATE_SUBJECT_NAME SYSREQ_USERS_USER_CERTIFICATE_THUMBPRINT SYSREQ_USERS_USER_DEFAULT_CERTIFICATE SYSREQ_USERS_USER_DESCRIPTION SYSREQ_USERS_USER_GLOBAL_NAME SYSREQ_USERS_USER_LOGIN SYSREQ_USERS_USER_MAIN_SERVER SYSREQ_USERS_USER_TYPE SYSREQ_WORK_RULES_FOLDER_ID RESULT_VAR_NAME RESULT_VAR_NAME_ENG AUTO_NUMERATION_RULE_ID CANT_CHANGE_ID_REQUISITE_RULE_ID CANT_CHANGE_OURFIRM_REQUISITE_RULE_ID CHECK_CHANGING_REFERENCE_RECORD_USE_RULE_ID CHECK_CODE_REQUISITE_RULE_ID CHECK_DELETING_REFERENCE_RECORD_USE_RULE_ID CHECK_FILTRATER_CHANGES_RULE_ID CHECK_RECORD_INTERVAL_RULE_ID CHECK_REFERENCE_INTERVAL_RULE_ID CHECK_REQUIRED_DATA_FULLNESS_RULE_ID CHECK_REQUIRED_REQUISITES_FULLNESS_RULE_ID MAKE_RECORD_UNRATIFIED_RULE_ID RESTORE_AUTO_NUMERATION_RULE_ID SET_FIRM_CONTEXT_FROM_RECORD_RULE_ID SET_FIRST_RECORD_IN_LIST_FORM_RULE_ID SET_IDSPS_VALUE_RULE_ID SET_NEXT_CODE_VALUE_RULE_ID SET_OURFIRM_BOUNDS_RULE_ID SET_OURFIRM_REQUISITE_RULE_ID SCRIPT_BLOCK_AFTER_FINISH_EVENT SCRIPT_BLOCK_BEFORE_START_EVENT SCRIPT_BLOCK_EXECUTION_RESULTS_PROPERTY SCRIPT_BLOCK_NAME_PROPERTY SCRIPT_BLOCK_SCRIPT_PROPERTY SUBTASK_BLOCK_ABORT_DEADLINE_PROPERTY SUBTASK_BLOCK_AFTER_FINISH_EVENT SUBTASK_BLOCK_ASSIGN_PARAMS_EVENT SUBTASK_BLOCK_ATTACHMENTS_PROPERTY SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY SUBTASK_BLOCK_BEFORE_START_EVENT SUBTASK_BLOCK_CREATED_TASK_PROPERTY SUBTASK_BLOCK_CREATION_EVENT SUBTASK_BLOCK_DEADLINE_PROPERTY SUBTASK_BLOCK_IMPORTANCE_PROPERTY SUBTASK_BLOCK_INITIATOR_PROPERTY SUBTASK_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY SUBTASK_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY SUBTASK_BLOCK_JOBS_TYPE_PROPERTY SUBTASK_BLOCK_NAME_PROPERTY SUBTASK_BLOCK_PARALLEL_ROUTE_PROPERTY SUBTASK_BLOCK_PERFORMERS_PROPERTY SUBTASK_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY SUBTASK_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY SUBTASK_BLOCK_REQUIRE_SIGN_PROPERTY SUBTASK_BLOCK_STANDARD_ROUTE_PROPERTY SUBTASK_BLOCK_START_EVENT SUBTASK_BLOCK_STEP_CONTROL_PROPERTY SUBTASK_BLOCK_SUBJECT_PROPERTY SUBTASK_BLOCK_TASK_CONTROL_PROPERTY SUBTASK_BLOCK_TEXT_PROPERTY SUBTASK_BLOCK_UNLOCK_ATTACHMENTS_ON_STOP_PROPERTY SUBTASK_BLOCK_USE_STANDARD_ROUTE_PROPERTY SUBTASK_BLOCK_WAIT_FOR_TASK_COMPLETE_PROPERTY SYSCOMP_CONTROL_JOBS SYSCOMP_FOLDERS SYSCOMP_JOBS SYSCOMP_NOTICES SYSCOMP_TASKS SYSDLG_CREATE_EDOCUMENT SYSDLG_CREATE_EDOCUMENT_VERSION SYSDLG_CURRENT_PERIOD SYSDLG_EDIT_FUNCTION_HELP SYSDLG_EDOCUMENT_KINDS_FOR_TEMPLATE SYSDLG_EXPORT_MULTIPLE_EDOCUMENTS SYSDLG_EXPORT_SINGLE_EDOCUMENT SYSDLG_IMPORT_EDOCUMENT SYSDLG_MULTIPLE_SELECT SYSDLG_SETUP_ACCESS_RIGHTS SYSDLG_SETUP_DEFAULT_RIGHTS SYSDLG_SETUP_FILTER_CONDITION SYSDLG_SETUP_SIGN_RIGHTS SYSDLG_SETUP_TASK_OBSERVERS SYSDLG_SETUP_TASK_ROUTE SYSDLG_SETUP_USERS_LIST SYSDLG_SIGN_EDOCUMENT SYSDLG_SIGN_MULTIPLE_EDOCUMENTS SYSREF_ACCESS_RIGHTS_TYPES SYSREF_ADMINISTRATION_HISTORY SYSREF_ALL_AVAILABLE_COMPONENTS SYSREF_ALL_AVAILABLE_PRIVILEGES SYSREF_ALL_REPLICATING_COMPONENTS SYSREF_AVAILABLE_DEVELOPERS_COMPONENTS SYSREF_CALENDAR_EVENTS SYSREF_COMPONENT_TOKEN_HISTORY SYSREF_COMPONENT_TOKENS SYSREF_COMPONENTS SYSREF_CONSTANTS SYSREF_DATA_RECEIVE_PROTOCOL SYSREF_DATA_SEND_PROTOCOL SYSREF_DIALOGS SYSREF_DIALOGS_REQUISITES SYSREF_EDITORS SYSREF_EDOC_CARDS SYSREF_EDOC_TYPES SYSREF_EDOCUMENT_CARD_REQUISITES SYSREF_EDOCUMENT_CARD_TYPES SYSREF_EDOCUMENT_CARD_TYPES_REFERENCE SYSREF_EDOCUMENT_CARDS SYSREF_EDOCUMENT_HISTORY SYSREF_EDOCUMENT_KINDS SYSREF_EDOCUMENT_REQUISITES SYSREF_EDOCUMENT_SIGNATURES SYSREF_EDOCUMENT_TEMPLATES SYSREF_EDOCUMENT_TEXT_STORAGES SYSREF_EDOCUMENT_VIEWS SYSREF_FILTERER_SETUP_CONFLICTS SYSREF_FILTRATER_SETTING_CONFLICTS SYSREF_FOLDER_HISTORY SYSREF_FOLDERS SYSREF_FUNCTION_GROUPS SYSREF_FUNCTION_PARAMS SYSREF_FUNCTIONS SYSREF_JOB_HISTORY SYSREF_LINKS SYSREF_LOCALIZATION_DICTIONARY SYSREF_LOCALIZATION_LANGUAGES SYSREF_MODULES SYSREF_PRIVILEGES SYSREF_RECORD_HISTORY SYSREF_REFERENCE_REQUISITES SYSREF_REFERENCE_TYPE_VIEWS SYSREF_REFERENCE_TYPES SYSREF_REFERENCES SYSREF_REFERENCES_REQUISITES SYSREF_REMOTE_SERVERS SYSREF_REPLICATION_SESSIONS_LOG SYSREF_REPLICATION_SESSIONS_PROTOCOL SYSREF_REPORTS SYSREF_ROLES SYSREF_ROUTE_BLOCK_GROUPS SYSREF_ROUTE_BLOCKS SYSREF_SCRIPTS SYSREF_SEARCHES SYSREF_SERVER_EVENTS SYSREF_SERVER_EVENTS_HISTORY SYSREF_STANDARD_ROUTE_GROUPS SYSREF_STANDARD_ROUTES SYSREF_STATUSES SYSREF_SYSTEM_SETTINGS SYSREF_TASK_HISTORY SYSREF_TASK_KIND_GROUPS SYSREF_TASK_KINDS SYSREF_TASK_RIGHTS SYSREF_TASK_SIGNATURES SYSREF_TASKS SYSREF_UNITS SYSREF_USER_GROUPS SYSREF_USER_GROUPS_REFERENCE SYSREF_USER_SUBSTITUTION SYSREF_USERS SYSREF_USERS_REFERENCE SYSREF_VIEWERS SYSREF_WORKING_TIME_CALENDARS ACCESS_RIGHTS_TABLE_NAME EDMS_ACCESS_TABLE_NAME EDOC_TYPES_TABLE_NAME TEST_DEV_DB_NAME TEST_DEV_SYSTEM_CODE TEST_EDMS_DB_NAME TEST_EDMS_MAIN_CODE TEST_EDMS_MAIN_DB_NAME TEST_EDMS_SECOND_CODE TEST_EDMS_SECOND_DB_NAME TEST_EDMS_SYSTEM_CODE TEST_ISB5_MAIN_CODE TEST_ISB5_SECOND_CODE TEST_SQL_SERVER_2005_NAME TEST_SQL_SERVER_NAME ATTENTION_CAPTION cbsCommandLinks cbsDefault CONFIRMATION_CAPTION ERROR_CAPTION INFORMATION_CAPTION mrCancel mrOk EDOC_VERSION_ACTIVE_STAGE_CODE EDOC_VERSION_DESIGN_STAGE_CODE EDOC_VERSION_OBSOLETE_STAGE_CODE cpDataEnciphermentEnabled cpDigitalSignatureEnabled cpID cpIssuer cpPluginVersion cpSerial cpSubjectName cpSubjSimpleName cpValidFromDate cpValidToDate ISBL_SYNTAX NO_SYNTAX XML_SYNTAX WAIT_BLOCK_AFTER_FINISH_EVENT WAIT_BLOCK_BEFORE_START_EVENT WAIT_BLOCK_DEADLINE_PROPERTY WAIT_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY WAIT_BLOCK_NAME_PROPERTY WAIT_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY SYSRES_COMMON SYSRES_CONST SYSRES_MBFUNC SYSRES_SBDATA SYSRES_SBGUI SYSRES_SBINTF SYSRES_SBREFDSC SYSRES_SQLERRORS SYSRES_SYSCOMP atUser atGroup atRole aemEnabledAlways aemDisabledAlways aemEnabledOnBrowse aemEnabledOnEdit aemDisabledOnBrowseEmpty apBegin apEnd alLeft alRight asmNever asmNoButCustomize asmAsLastTime asmYesButCustomize asmAlways cirCommon cirRevoked ctSignature ctEncode ctSignatureEncode clbUnchecked clbChecked clbGrayed ceISB ceAlways ceNever ctDocument ctReference ctScript ctUnknown ctReport ctDialog ctFunction ctFolder ctEDocument ctTask ctJob ctNotice ctControlJob cfInternal cfDisplay ciUnspecified ciWrite ciRead ckFolder ckEDocument ckTask ckJob ckComponentToken ckAny ckReference ckScript ckReport ckDialog ctISBLEditor ctBevel ctButton ctCheckListBox ctComboBox ctComboEdit ctGrid ctDBCheckBox ctDBComboBox ctDBEdit ctDBEllipsis ctDBMemo ctDBNavigator ctDBRadioGroup ctDBStatusLabel ctEdit ctGroupBox ctInplaceHint ctMemo ctPanel ctListBox ctRadioButton ctRichEdit ctTabSheet ctWebBrowser ctImage ctHyperLink ctLabel ctDBMultiEllipsis ctRibbon ctRichView ctInnerPanel ctPanelGroup ctBitButton cctDate cctInteger cctNumeric cctPick cctReference cctString cctText cltInternal cltPrimary cltGUI dseBeforeOpen dseAfterOpen dseBeforeClose dseAfterClose dseOnValidDelete dseBeforeDelete dseAfterDelete dseAfterDeleteOutOfTransaction dseOnDeleteError dseBeforeInsert dseAfterInsert dseOnValidUpdate dseBeforeUpdate dseOnUpdateRatifiedRecord dseAfterUpdate dseAfterUpdateOutOfTransaction dseOnUpdateError dseAfterScroll dseOnOpenRecord dseOnCloseRecord dseBeforeCancel dseAfterCancel dseOnUpdateDeadlockError dseBeforeDetailUpdate dseOnPrepareUpdate dseOnAnyRequisiteChange dssEdit dssInsert dssBrowse dssInActive dftDate dftShortDate dftDateTime dftTimeStamp dotDays dotHours dotMinutes dotSeconds dtkndLocal dtkndUTC arNone arView arEdit arFull ddaView ddaEdit emLock emEdit emSign emExportWithLock emImportWithUnlock emChangeVersionNote emOpenForModify emChangeLifeStage emDelete emCreateVersion emImport emUnlockExportedWithLock emStart emAbort emReInit emMarkAsReaded emMarkAsUnreaded emPerform emAccept emResume emChangeRights emEditRoute emEditObserver emRecoveryFromLocalCopy emChangeWorkAccessType emChangeEncodeTypeToCertificate emChangeEncodeTypeToPassword emChangeEncodeTypeToNone emChangeEncodeTypeToCertificatePassword emChangeStandardRoute emGetText emOpenForView emMoveToStorage emCreateObject emChangeVersionHidden emDeleteVersion emChangeLifeCycleStage emApprovingSign emExport emContinue emLockFromEdit emUnLockForEdit emLockForServer emUnlockFromServer emDelegateAccessRights emReEncode ecotFile ecotProcess eaGet eaCopy eaCreate eaCreateStandardRoute edltAll edltNothing edltQuery essmText essmCard esvtLast esvtLastActive esvtSpecified edsfExecutive edsfArchive edstSQLServer edstFile edvstNone edvstEDocumentVersionCopy edvstFile edvstTemplate edvstScannedFile vsDefault vsDesign vsActive vsObsolete etNone etCertificate etPassword etCertificatePassword ecException ecWarning ecInformation estAll estApprovingOnly evtLast evtLastActive evtQuery fdtString fdtNumeric fdtInteger fdtDate fdtText fdtUnknown fdtWideString fdtLargeInteger ftInbox ftOutbox ftFavorites ftCommonFolder ftUserFolder ftComponents ftQuickLaunch ftShortcuts ftSearch grhAuto grhX1 grhX2 grhX3 hltText hltRTF hltHTML iffBMP iffJPEG iffMultiPageTIFF iffSinglePageTIFF iffTIFF iffPNG im8bGrayscale im24bRGB im1bMonochrome itBMP itJPEG itWMF itPNG ikhInformation ikhWarning ikhError ikhNoIcon icUnknown icScript icFunction icIntegratedReport icAnalyticReport icDataSetEventHandler icActionHandler icFormEventHandler icLookUpEventHandler icRequisiteChangeEventHandler icBeforeSearchEventHandler icRoleCalculation icSelectRouteEventHandler icBlockPropertyCalculation icBlockQueryParamsEventHandler icChangeSearchResultEventHandler icBlockEventHandler icSubTaskInitEventHandler icEDocDataSetEventHandler icEDocLookUpEventHandler icEDocActionHandler icEDocFormEventHandler icEDocRequisiteChangeEventHandler icStructuredConversionRule icStructuredConversionEventBefore icStructuredConversionEventAfter icWizardEventHandler icWizardFinishEventHandler icWizardStepEventHandler icWizardStepFinishEventHandler icWizardActionEnableEventHandler icWizardActionExecuteEventHandler icCreateJobsHandler icCreateNoticesHandler icBeforeLookUpEventHandler icAfterLookUpEventHandler icTaskAbortEventHandler icWorkflowBlockActionHandler icDialogDataSetEventHandler icDialogActionHandler icDialogLookUpEventHandler icDialogRequisiteChangeEventHandler icDialogFormEventHandler icDialogValidCloseEventHandler icBlockFormEventHandler icTaskFormEventHandler icReferenceMethod icEDocMethod icDialogMethod icProcessMessageHandler isShow isHide isByUserSettings jkJob jkNotice jkControlJob jtInner jtLeft jtRight jtFull jtCross lbpAbove lbpBelow lbpLeft lbpRight eltPerConnection eltPerUser sfcUndefined sfcBlack sfcGreen sfcRed sfcBlue sfcOrange sfcLilac sfsItalic sfsStrikeout sfsNormal ldctStandardRoute ldctWizard ldctScript ldctFunction ldctRouteBlock ldctIntegratedReport ldctAnalyticReport ldctReferenceType ldctEDocumentType ldctDialog ldctServerEvents mrcrtNone mrcrtUser mrcrtMaximal mrcrtCustom vtEqual vtGreaterOrEqual vtLessOrEqual vtRange rdYesterday rdToday rdTomorrow rdThisWeek rdThisMonth rdThisYear rdNextMonth rdNextWeek rdLastWeek rdLastMonth rdWindow rdFile rdPrinter rdtString rdtNumeric rdtInteger rdtDate rdtReference rdtAccount rdtText rdtPick rdtUnknown rdtLargeInteger rdtDocument reOnChange reOnChangeValues ttGlobal ttLocal ttUser ttSystem ssmBrowse ssmSelect ssmMultiSelect ssmBrowseModal smSelect smLike smCard stNone stAuthenticating stApproving sctString sctStream sstAnsiSort sstNaturalSort svtEqual svtContain soatString soatNumeric soatInteger soatDatetime soatReferenceRecord soatText soatPick soatBoolean soatEDocument soatAccount soatIntegerCollection soatNumericCollection soatStringCollection soatPickCollection soatDatetimeCollection soatBooleanCollection soatReferenceRecordCollection soatEDocumentCollection soatAccountCollection soatContents soatUnknown tarAbortByUser tarAbortByWorkflowException tvtAllWords tvtExactPhrase tvtAnyWord usNone usCompleted usRedSquare usBlueSquare usYellowSquare usGreenSquare usOrangeSquare usPurpleSquare usFollowUp utUnknown utUser utDeveloper utAdministrator utSystemDeveloper utDisconnected btAnd btDetailAnd btOr btNotOr btOnly vmView vmSelect vmNavigation vsmSingle vsmMultiple vsmMultipleCheck vsmNoSelection wfatPrevious wfatNext wfatCancel wfatFinish wfepUndefined wfepText3 wfepText6 wfepText9 wfepSpinEdit wfepDropDown wfepRadioGroup wfepFlag wfepText12 wfepText15 wfepText18 wfepText21 wfepText24 wfepText27 wfepText30 wfepRadioGroupColumn1 wfepRadioGroupColumn2 wfepRadioGroupColumn3 wfetQueryParameter wfetText wfetDelimiter wfetLabel wptString wptInteger wptNumeric wptBoolean wptDateTime wptPick wptText wptUser wptUserList wptEDocumentInfo wptEDocumentInfoList wptReferenceRecordInfo wptReferenceRecordInfoList wptFolderInfo wptTaskInfo wptContents wptFileName wptDate wsrComplete wsrGoNext wsrGoPrevious wsrCustom wsrCancel wsrGoFinal wstForm wstEDocument wstTaskCard wstReferenceRecordCard wstFinal waAll waPerformers waManual wsbStart wsbFinish wsbNotice wsbStep wsbDecision wsbWait wsbMonitor wsbScript wsbConnector wsbSubTask wsbLifeCycleStage wsbPause wdtInteger wdtFloat wdtString wdtPick wdtDateTime wdtBoolean wdtTask wdtJob wdtFolder wdtEDocument wdtReferenceRecord wdtUser wdtGroup wdtRole wdtIntegerCollection wdtFloatCollection wdtStringCollection wdtPickCollection wdtDateTimeCollection wdtBooleanCollection wdtTaskCollection wdtJobCollection wdtFolderCollection wdtEDocumentCollection wdtReferenceRecordCollection wdtUserCollection wdtGroupCollection wdtRoleCollection wdtContents wdtUserList wdtSearchDescription wdtDeadLine wdtPickSet wdtAccountCollection wiLow wiNormal wiHigh wrtSoft wrtHard wsInit wsRunning wsDone wsControlled wsAborted wsContinued wtmFull wtmFromCurrent wtmOnlyCurrent ",
class:"AltState Application CallType ComponentTokens CreatedJobs CreatedNotices ControlState DialogResult Dialogs EDocuments EDocumentVersionSource Folders GlobalIDs Job Jobs InputValue LookUpReference LookUpRequisiteNames LookUpSearch Object ParentComponent Processes References Requisite ReportName Reports Result Scripts Searches SelectedAttachments SelectedItems SelectMode Sender ServerEvents ServiceFactory ShiftState SubTask SystemDialogs Tasks Wizard Wizards Work \u0412\u044b\u0437\u043e\u0432\u0421\u043f\u043e\u0441\u043e\u0431 \u0418\u043c\u044f\u041e\u0442\u0447\u0435\u0442\u0430 \u0420\u0435\u043a\u0432\u0417\u043d\u0430\u0447 ",
-literal:"null true false nil "};a={begin:"\\.\\s*"+a.UNDERSCORE_IDENT_RE,keywords:e,relevance:0};var f={className:"type",begin:":[ \\t]*("+"IApplication IAccessRights IAccountRepository IAccountSelectionRestrictions IAction IActionList IAdministrationHistoryDescription IAnchors IApplication IArchiveInfo IAttachment IAttachmentList ICheckListBox ICheckPointedList IColumn IComponent IComponentDescription IComponentToken IComponentTokenFactory IComponentTokenInfo ICompRecordInfo IConnection IContents IControl IControlJob IControlJobInfo IControlList ICrypto ICrypto2 ICustomJob ICustomJobInfo ICustomListBox ICustomObjectWizardStep ICustomWork ICustomWorkInfo IDataSet IDataSetAccessInfo IDataSigner IDateCriterion IDateRequisite IDateRequisiteDescription IDateValue IDeaAccessRights IDeaObjectInfo IDevelopmentComponentLock IDialog IDialogFactory IDialogPickRequisiteItems IDialogsFactory IDICSFactory IDocRequisite IDocumentInfo IDualListDialog IECertificate IECertificateInfo IECertificates IEditControl IEditorForm IEdmsExplorer IEdmsObject IEdmsObjectDescription IEdmsObjectFactory IEdmsObjectInfo IEDocument IEDocumentAccessRights IEDocumentDescription IEDocumentEditor IEDocumentFactory IEDocumentInfo IEDocumentStorage IEDocumentVersion IEDocumentVersionListDialog IEDocumentVersionSource IEDocumentWizardStep IEDocVerSignature IEDocVersionState IEnabledMode IEncodeProvider IEncrypter IEvent IEventList IException IExternalEvents IExternalHandler IFactory IField IFileDialog IFolder IFolderDescription IFolderDialog IFolderFactory IFolderInfo IForEach IForm IFormTitle IFormWizardStep IGlobalIDFactory IGlobalIDInfo IGrid IHasher IHistoryDescription IHyperLinkControl IImageButton IImageControl IInnerPanel IInplaceHint IIntegerCriterion IIntegerList IIntegerRequisite IIntegerValue IISBLEditorForm IJob IJobDescription IJobFactory IJobForm IJobInfo ILabelControl ILargeIntegerCriterion ILargeIntegerRequisite ILargeIntegerValue ILicenseInfo ILifeCycleStage IList IListBox ILocalIDInfo ILocalization ILock IMemoryDataSet IMessagingFactory IMetadataRepository INotice INoticeInfo INumericCriterion INumericRequisite INumericValue IObject IObjectDescription IObjectImporter IObjectInfo IObserver IPanelGroup IPickCriterion IPickProperty IPickRequisite IPickRequisiteDescription IPickRequisiteItem IPickRequisiteItems IPickValue IPrivilege IPrivilegeList IProcess IProcessFactory IProcessMessage IProgress IProperty IPropertyChangeEvent IQuery IReference IReferenceCriterion IReferenceEnabledMode IReferenceFactory IReferenceHistoryDescription IReferenceInfo IReferenceRecordCardWizardStep IReferenceRequisiteDescription IReferencesFactory IReferenceValue IRefRequisite IReport IReportFactory IRequisite IRequisiteDescription IRequisiteDescriptionList IRequisiteFactory IRichEdit IRouteStep IRule IRuleList ISchemeBlock IScript IScriptFactory ISearchCriteria ISearchCriterion ISearchDescription ISearchFactory ISearchFolderInfo ISearchForObjectDescription ISearchResultRestrictions ISecuredContext ISelectDialog IServerEvent IServerEventFactory IServiceDialog IServiceFactory ISignature ISignProvider ISignProvider2 ISignProvider3 ISimpleCriterion IStringCriterion IStringList IStringRequisite IStringRequisiteDescription IStringValue ISystemDialogsFactory ISystemInfo ITabSheet ITask ITaskAbortReasonInfo ITaskCardWizardStep ITaskDescription ITaskFactory ITaskInfo ITaskRoute ITextCriterion ITextRequisite ITextValue ITreeListSelectDialog IUser IUserList IValue IView IWebBrowserControl IWizard IWizardAction IWizardFactory IWizardFormElement IWizardParam IWizardPickParam IWizardReferenceParam IWizardStep IWorkAccessRights IWorkDescription IWorkflowAskableParam IWorkflowAskableParams IWorkflowBlock IWorkflowBlockResult IWorkflowEnabledMode IWorkflowParam IWorkflowPickParam IWorkflowReferenceParam IWorkState IWorkTreeCustomNode IWorkTreeJobNode IWorkTreeTaskNode IXMLEditorForm SBCrypto".replace(/\s/g,
-"|")+")",end:"[ \\t]*=",excludeEnd:!0},h={className:"variable",keywords:e,begin:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_!][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*",relevance:0,contains:[f,a]};return{name:"ISBL",aliases:["isbl"],case_insensitive:!0,keywords:e,illegal:"\\$|\\?|%|,|;$|~|#|@|</",contains:[{className:"function",begin:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*\\(",end:"\\)$",returnBegin:!0,keywords:e,illegal:"[\\[\\]\\|\\$\\?%,~#@]",
-contains:[{className:"title",keywords:{$pattern:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_!][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*",built_in:"AddSubString AdjustLineBreaks AmountInWords Analysis ArrayDimCount ArrayHighBound ArrayLowBound ArrayOf ArrayReDim Assert Assigned BeginOfMonth BeginOfPeriod BuildProfilingOperationAnalysis CallProcedure CanReadFile CArrayElement CDataSetRequisite ChangeDate ChangeReferenceDataset Char CharPos CheckParam CheckParamValue CompareStrings ConstantExists ControlState ConvertDateStr Copy CopyFile CreateArray CreateCachedReference CreateConnection CreateDialog CreateDualListDialog CreateEditor CreateException CreateFile CreateFolderDialog CreateInputDialog CreateLinkFile CreateList CreateLock CreateMemoryDataSet CreateObject CreateOpenDialog CreateProgress CreateQuery CreateReference CreateReport CreateSaveDialog CreateScript CreateSQLPivotFunction CreateStringList CreateTreeListSelectDialog CSelectSQL CSQL CSubString CurrentUserID CurrentUserName CurrentVersion DataSetLocateEx DateDiff DateTimeDiff DateToStr DayOfWeek DeleteFile DirectoryExists DisableCheckAccessRights DisableCheckFullShowingRestriction DisableMassTaskSendingRestrictions DropTable DupeString EditText EnableCheckAccessRights EnableCheckFullShowingRestriction EnableMassTaskSendingRestrictions EndOfMonth EndOfPeriod ExceptionExists ExceptionsOff ExceptionsOn Execute ExecuteProcess Exit ExpandEnvironmentVariables ExtractFileDrive ExtractFileExt ExtractFileName ExtractFilePath ExtractParams FileExists FileSize FindFile FindSubString FirmContext ForceDirectories Format FormatDate FormatNumeric FormatSQLDate FormatString FreeException GetComponent GetComponentLaunchParam GetConstant GetLastException GetReferenceRecord GetRefTypeByRefID GetTableID GetTempFolder IfThen In IndexOf InputDialog InputDialogEx InteractiveMode IsFileLocked IsGraphicFile IsNumeric Length LoadString LoadStringFmt LocalTimeToUTC LowerCase Max MessageBox MessageBoxEx MimeDecodeBinary MimeDecodeString MimeEncodeBinary MimeEncodeString Min MoneyInWords MoveFile NewID Now OpenFile Ord Precision Raise ReadCertificateFromFile ReadFile ReferenceCodeByID ReferenceNumber ReferenceRequisiteMode ReferenceRequisiteValue RegionDateSettings RegionNumberSettings RegionTimeSettings RegRead RegWrite RenameFile Replace Round SelectServerCode SelectSQL ServerDateTime SetConstant SetManagedFolderFieldsState ShowConstantsInputDialog ShowMessage Sleep Split SQL SQL2XLSTAB SQLProfilingSendReport StrToDate SubString SubStringCount SystemSetting Time TimeDiff Today Transliterate Trim UpperCase UserStatus UTCToLocalTime ValidateXML VarIsClear VarIsEmpty VarIsNull WorkTimeDiff WriteFile WriteFileEx WriteObjectHistory \u0410\u043d\u0430\u043b\u0438\u0437 \u0411\u0430\u0437\u0430\u0414\u0430\u043d\u043d\u044b\u0445 \u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c \u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c\u0420\u0430\u0441\u0448 \u0411\u043b\u043e\u043a\u0418\u043d\u0444\u043e \u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c \u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c\u0420\u0430\u0441\u0448 \u0411\u043b\u043e\u043a\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0412\u0432\u043e\u0434 \u0412\u0432\u043e\u0434\u041c\u0435\u043d\u044e \u0412\u0435\u0434\u0421 \u0412\u0435\u0434\u0421\u043f\u0440 \u0412\u0435\u0440\u0445\u043d\u044f\u044f\u0413\u0440\u0430\u043d\u0438\u0446\u0430\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0412\u043d\u0435\u0448\u041f\u0440\u043e\u0433\u0440 \u0412\u043e\u0441\u0441\u0442 \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f\u041f\u0430\u043f\u043a\u0430 \u0412\u0440\u0435\u043c\u044f \u0412\u044b\u0431\u043e\u0440SQL \u0412\u044b\u0431\u0440\u0430\u0442\u044c\u0417\u0430\u043f\u0438\u0441\u044c \u0412\u044b\u0434\u0435\u043b\u0438\u0442\u044c\u0421\u0442\u0440 \u0412\u044b\u0437\u0432\u0430\u0442\u044c \u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0412\u044b\u043f\u041f\u0440\u043e\u0433\u0440 \u0413\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0439\u0424\u0430\u0439\u043b \u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0414\u0430\u0442\u0430\u0412\u0440\u0435\u043c\u044f\u0421\u0435\u0440\u0432 \u0414\u0435\u043d\u044c\u041d\u0435\u0434\u0435\u043b\u0438 \u0414\u0438\u0430\u043b\u043e\u0433\u0414\u0430\u041d\u0435\u0442 \u0414\u043b\u0438\u043d\u0430\u0421\u0442\u0440 \u0414\u043e\u0431\u041f\u043e\u0434\u0441\u0442\u0440 \u0415\u041f\u0443\u0441\u0442\u043e \u0415\u0441\u043b\u0438\u0422\u043e \u0415\u0427\u0438\u0441\u043b\u043e \u0417\u0430\u043c\u041f\u043e\u0434\u0441\u0442\u0440 \u0417\u0430\u043f\u0438\u0441\u044c\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u0417\u043d\u0430\u0447\u041f\u043e\u043b\u044f\u0421\u043f\u0440 \u0418\u0414\u0422\u0438\u043f\u0421\u043f\u0440 \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0414\u0438\u0441\u043a \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0418\u043c\u044f\u0424\u0430\u0439\u043b\u0430 \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u041f\u0443\u0442\u044c \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0418\u0437\u043c\u0414\u0430\u0442 \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0420\u0430\u0437\u043c\u0435\u0440\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0418\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0418\u043c\u044f\u041e\u0440\u0433 \u0418\u043c\u044f\u041f\u043e\u043b\u044f\u0421\u043f\u0440 \u0418\u043d\u0434\u0435\u043a\u0441 \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0428\u0430\u0433 \u0418\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439\u0420\u0435\u0436\u0438\u043c \u0418\u0442\u043e\u0433\u0422\u0431\u043b\u0421\u043f\u0440 \u041a\u043e\u0434\u0412\u0438\u0434\u0412\u0435\u0434\u0421\u043f\u0440 \u041a\u043e\u0434\u0412\u0438\u0434\u0421\u043f\u0440\u041f\u043e\u0418\u0414 \u041a\u043e\u0434\u041f\u043eAnalit \u041a\u043e\u0434\u0421\u0438\u043c\u0432\u043e\u043b\u0430 \u041a\u043e\u0434\u0421\u043f\u0440 \u041a\u043e\u043b\u041f\u043e\u0434\u0441\u0442\u0440 \u041a\u043e\u043b\u041f\u0440\u043e\u043f \u041a\u043e\u043d\u041c\u0435\u0441 \u041a\u043e\u043d\u0441\u0442 \u041a\u043e\u043d\u0441\u0442\u0415\u0441\u0442\u044c \u041a\u043e\u043d\u0441\u0442\u0417\u043d\u0430\u0447 \u041a\u043e\u043d\u0422\u0440\u0430\u043d \u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0424\u0430\u0439\u043b \u041a\u043e\u043f\u0438\u044f\u0421\u0442\u0440 \u041a\u041f\u0435\u0440\u0438\u043e\u0434 \u041a\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u041c\u0430\u043a\u0441 \u041c\u0430\u043a\u0441\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u041c\u0430\u0441\u0441\u0438\u0432 \u041c\u0435\u043d\u044e \u041c\u0435\u043d\u044e\u0420\u0430\u0441\u0448 \u041c\u0438\u043d \u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445\u041d\u0430\u0439\u0442\u0438\u0420\u0430\u0441\u0448 \u041d\u0430\u0438\u043c\u0412\u0438\u0434\u0421\u043f\u0440 \u041d\u0430\u0438\u043c\u041f\u043eAnalit \u041d\u0430\u0438\u043c\u0421\u043f\u0440 \u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u044b\u0421\u0442\u0440\u043e\u043a \u041d\u0430\u0447\u041c\u0435\u0441 \u041d\u0430\u0447\u0422\u0440\u0430\u043d \u041d\u0438\u0436\u043d\u044f\u044f\u0413\u0440\u0430\u043d\u0438\u0446\u0430\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u041d\u043e\u043c\u0435\u0440\u0421\u043f\u0440 \u041d\u041f\u0435\u0440\u0438\u043e\u0434 \u041e\u043a\u043d\u043e \u041e\u043a\u0440 \u041e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u041e\u0442\u043b\u0418\u043d\u0444\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u041e\u0442\u043b\u0418\u043d\u0444\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u041e\u0442\u0447\u0435\u0442 \u041e\u0442\u0447\u0435\u0442\u0410\u043d\u0430\u043b \u041e\u0442\u0447\u0435\u0442\u0418\u043d\u0442 \u041f\u0430\u043f\u043a\u0430\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u041f\u0430\u0443\u0437\u0430 \u041f\u0412\u044b\u0431\u043e\u0440SQL \u041f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c\u0424\u0430\u0439\u043b \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0424\u0430\u0439\u043b \u041f\u043e\u0434\u0441\u0442\u0440 \u041f\u043e\u0438\u0441\u043a\u041f\u043e\u0434\u0441\u0442\u0440 \u041f\u043e\u0438\u0441\u043a\u0421\u0442\u0440 \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0418\u0414\u0422\u0430\u0431\u043b\u0438\u0446\u044b \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0418\u0414 \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0418\u043c\u044f \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0421\u0442\u0430\u0442\u0443\u0441 \u041f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0417\u043d\u0430\u0447 \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u0423\u0441\u043b\u043e\u0432\u0438\u0435 \u0420\u0430\u0437\u0431\u0421\u0442\u0440 \u0420\u0430\u0437\u043d\u0412\u0440\u0435\u043c\u044f \u0420\u0430\u0437\u043d\u0414\u0430\u0442 \u0420\u0430\u0437\u043d\u0414\u0430\u0442\u0430\u0412\u0440\u0435\u043c\u044f \u0420\u0430\u0437\u043d\u0420\u0430\u0431\u0412\u0440\u0435\u043c\u044f \u0420\u0435\u0433\u0423\u0441\u0442\u0412\u0440\u0435\u043c \u0420\u0435\u0433\u0423\u0441\u0442\u0414\u0430\u0442 \u0420\u0435\u0433\u0423\u0441\u0442\u0427\u0441\u043b \u0420\u0435\u0434\u0422\u0435\u043a\u0441\u0442 \u0420\u0435\u0435\u0441\u0442\u0440\u0417\u0430\u043f\u0438\u0441\u044c \u0420\u0435\u0435\u0441\u0442\u0440\u0421\u043f\u0438\u0441\u043e\u043a\u0418\u043c\u0435\u043d\u041f\u0430\u0440\u0430\u043c \u0420\u0435\u0435\u0441\u0442\u0440\u0427\u0442\u0435\u043d\u0438\u0435 \u0420\u0435\u043a\u0432\u0421\u043f\u0440 \u0420\u0435\u043a\u0432\u0421\u043f\u0440\u041f\u0440 \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0421\u0435\u0439\u0447\u0430\u0441 \u0421\u0435\u0440\u0432\u0435\u0440 \u0421\u0435\u0440\u0432\u0435\u0440\u041f\u0440\u043e\u0446\u0435\u0441\u0441\u0418\u0414 \u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0424\u0430\u0439\u043b\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0421\u0436\u041f\u0440\u043e\u0431 \u0421\u0438\u043c\u0432\u043e\u043b \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u0414\u0438\u0440\u0435\u043a\u0442\u0443\u043c\u041a\u043e\u0434 \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u041a\u043e\u0434 \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0412\u044b\u0431\u043e\u0440\u0430\u0418\u0437\u0414\u0432\u0443\u0445\u0421\u043f\u0438\u0441\u043a\u043e\u0432 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0412\u044b\u0431\u043e\u0440\u0430\u041f\u0430\u043f\u043a\u0438 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u041e\u0442\u043a\u0440\u044b\u0442\u0438\u044f\u0424\u0430\u0439\u043b\u0430 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f\u0424\u0430\u0439\u043b\u0430 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0417\u0430\u043f\u0440\u043e\u0441 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041c\u0430\u0441\u0441\u0438\u0432 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041e\u0431\u044a\u0435\u043a\u0442 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041e\u0442\u0447\u0435\u0442 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041f\u0430\u043f\u043a\u0443 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0438\u0441\u043e\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0438\u0441\u043e\u043a\u0421\u0442\u0440\u043e\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0421\u043e\u0437\u0434\u0421\u043f\u0440 \u0421\u043e\u0441\u0442\u0421\u043f\u0440 \u0421\u043e\u0445\u0440 \u0421\u043e\u0445\u0440\u0421\u043f\u0440 \u0421\u043f\u0438\u0441\u043e\u043a\u0421\u0438\u0441\u0442\u0435\u043c \u0421\u043f\u0440 \u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c\u0420\u0430\u0441\u0448 \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0421\u043f\u0440\u0418\u0437\u043c\u041d\u0430\u0431\u0414\u0430\u043d \u0421\u043f\u0440\u041a\u043e\u0434 \u0421\u043f\u0440\u041d\u043e\u043c\u0435\u0440 \u0421\u043f\u0440\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0421\u043f\u0440\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0421\u043f\u0440\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0421\u043f\u0440\u041f\u0430\u0440\u0430\u043c \u0421\u043f\u0440\u041f\u043e\u043b\u0435\u0417\u043d\u0430\u0447 \u0421\u043f\u0440\u041f\u043e\u043b\u0435\u0418\u043c\u044f \u0421\u043f\u0440\u0420\u0435\u043a\u0432 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0412\u0432\u0435\u0434\u0417\u043d \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041d\u043e\u0432\u044b\u0435 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041f\u0440 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041f\u0440\u0435\u0434\u0417\u043d \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0420\u0435\u0436\u0438\u043c \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0422\u0438\u043f\u0422\u0435\u043a\u0441\u0442 \u0421\u043f\u0440\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0421\u043f\u0440\u0421\u043e\u0441\u0442 \u0421\u043f\u0440\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0421\u043f\u0440\u0422\u0431\u043b\u0418\u0442\u043e\u0433 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041a\u043e\u043b \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041c\u0430\u043a\u0441 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041c\u0438\u043d \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041f\u0440\u0435\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0421\u043b\u0435\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0421\u043e\u0437\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0423\u0434 \u0421\u043f\u0440\u0422\u0435\u043a\u041f\u0440\u0435\u0434\u0441\u0442 \u0421\u043f\u0440\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0421\u0440\u0430\u0432\u043d\u0438\u0442\u044c\u0421\u0442\u0440 \u0421\u0442\u0440\u0412\u0435\u0440\u0445\u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0421\u0442\u0440\u041d\u0438\u0436\u043d\u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u0421\u0443\u043c\u041f\u0440\u043e\u043f \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439\u041f\u0430\u0440\u0430\u043c \u0422\u0435\u043a\u0412\u0435\u0440\u0441\u0438\u044f \u0422\u0435\u043a\u041e\u0440\u0433 \u0422\u043e\u0447\u043d \u0422\u0440\u0430\u043d \u0422\u0440\u0430\u043d\u0441\u043b\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0422\u0430\u0431\u043b\u0438\u0446\u0443 \u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0424\u0430\u0439\u043b \u0423\u0434\u0421\u043f\u0440 \u0423\u0434\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u0423\u0441\u0442 \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442 \u0424\u0430\u0439\u043b\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u0412\u0440\u0435\u043c\u044f \u0424\u0430\u0439\u043b\u0412\u0440\u0435\u043c\u044f\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0417\u0430\u043d\u044f\u0442 \u0424\u0430\u0439\u043b\u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0418\u0441\u043a\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041c\u043e\u0436\u043d\u043e\u0427\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0424\u0430\u0439\u043b\u0420\u0430\u0437\u043c\u0435\u0440 \u0424\u0430\u0439\u043b\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0421\u0441\u044b\u043b\u043a\u0430\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0424\u0430\u0439\u043b\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0424\u043c\u0442SQL\u0414\u0430\u0442 \u0424\u043c\u0442\u0414\u0430\u0442 \u0424\u043c\u0442\u0421\u0442\u0440 \u0424\u043c\u0442\u0427\u0441\u043b \u0424\u043e\u0440\u043c\u0430\u0442 \u0426\u041c\u0430\u0441\u0441\u0438\u0432\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0426\u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442 \u0426\u041f\u043e\u0434\u0441\u0442\u0440 "},
-begin:"[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*\\(",end:"\\(",returnBegin:!0,excludeEnd:!0},a,h,c,b,d]},f,a,h,c,b,d]}}}());
-hljs.registerLanguage("java",function(){return function(a){var b={className:"meta",begin:"@[\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};return{name:"Java",aliases:["jsp"],keywords:"false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",illegal:/<\/|#/,
-contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*(<[\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+
-a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:"false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
-contains:[a.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:"false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",relevance:0,contains:[b,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,
-a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},b]}}}());
-hljs.registerLanguage("javascript",function(){function a(a){for(var b=[],c=0;c<arguments.length;++c)b[c-0]=arguments[c];return b.map(function(a){return a&&a.source||a}).join("")}var b="as in of if for while finally var new function do return void else break catch instanceof with throw case default try switch continue typeof delete let yield const class debugger async await static import from export extends".split(" "),c="true false null undefined NaN Infinity".split(" "),d=[].concat("setInterval setTimeout clearInterval clearTimeout require exports eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape".split(" "),
-"arguments this super console window document localStorage module global".split(" "),"Intl DataView Number Math Date String RegExp Object Function Boolean Error Symbol Set Map WeakSet WeakMap Proxy Reflect JSON Promise Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Float32Array Array Uint8Array Uint8ClampedArray ArrayBuffer".split(" "),"EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError".split(" "));return function(e){var f=/<[A-Za-z0-9\\._:-]+/,
-h=/\/[A-Za-z0-9\\._:-]+>|\/>/,g={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:b.join(" "),literal:c.join(" "),built_in:d.join(" ")},k={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},l={className:"subst",begin:"\\$\\{",end:"\\}",keywords:g,contains:[]},m={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,l],subLanguage:"xml"}},p={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,
-l],subLanguage:"css"}},q={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,l]};l.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,m,p,q,k,e.REGEXP_MODE];l=l.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(l.contains,[e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE])},e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]);var r={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,
-contains:[e.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,m,p,q,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:"[A-Za-z$_][0-9A-Za-z$_]*(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,k,{begin:a(/[{,\n]\s*/,
-a("(?=",a(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,"[A-Za-z$_][0-9A-Za-z$_]*\\s*:"),")")),relevance:0,contains:[{className:"attr",begin:"[A-Za-z$_][0-9A-Za-z$_]*"+a("(?=","\\s*:",")"),relevance:0}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+e.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",
-variants:[{begin:e.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:g,contains:l}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:"</>"},{begin:f,end:h}],subLanguage:"xml",contains:[{begin:f,end:h,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),
-r],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?=[A-Za-z$_][0-9A-Za-z$_]*\\()",end:/{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),{begin:/\(\)/},r]}],illegal:/#(?!!)/}}}());
-hljs.registerLanguage("jboss-cli",function(){return function(a){return{name:"JBoss CLI",aliases:["wildfly-cli"],keywords:{$pattern:"[a-z-]+",keyword:"alias batch cd clear command connect connection-factory connection-info data-source deploy deployment-info deployment-overlay echo echo-dmr help history if jdbc-driver-info jms-queue|20 jms-topic|20 ls patch pwd quit read-attribute read-operation reload rollout-plan run-batch set shutdown try unalias undeploy unset version xa-data-source",literal:"true false"},
-contains:[a.HASH_COMMENT_MODE,a.QUOTE_STRING_MODE,{className:"params",begin:/--[\w\-=\/]+/},{className:"function",begin:/:[\w\-.]+/,relevance:0},{className:"string",begin:/\B(([\/.])[\w\-.\/=]+)+/},{className:"params",begin:/\(/,end:/\)/,contains:[{begin:/[\w-]+ *=/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/[\w-]+/}]}],relevance:0}]}}}());
-hljs.registerLanguage("json",function(){return function(a){var b={literal:"true false null"},c=[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE],d=[a.QUOTE_STRING_MODE,a.C_NUMBER_MODE],e={end:",",endsWithParent:!0,excludeEnd:!0,contains:d,keywords:b},f={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE],illegal:"\\n"},a.inherit(e,{begin:/:/})].concat(c),illegal:"\\S"};a={begin:"\\[",end:"\\]",contains:[a.inherit(e)],illegal:"\\S"};d.push(f,a);c.forEach(function(a){d.push(a)});
-return{name:"JSON",contains:d,keywords:b,illegal:"\\S"}}}());
-hljs.registerLanguage("julia",function(){return function(a){var b={$pattern:"[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi \u03b3 \u03c0 \u03c6 ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},
-c={keywords:b,illegal:/<\//};b={className:"subst",begin:/\$\(/,end:/\)/,keywords:b};var d={className:"variable",begin:"\\$[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*"},e={className:"string",contains:[a.BACKSLASH_ESCAPE,b,d],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]};d={className:"string",contains:[a.BACKSLASH_ESCAPE,b,d],begin:"`",end:"`"};c.name="Julia";c.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,
-relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},e,d,{className:"meta",begin:"@[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*"},{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},a.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}];b.contains=c.contains;return c}}());
-hljs.registerLanguage("julia-repl",function(){return function(a){return{name:"Julia REPL",contains:[{className:"meta",begin:/^julia>/,relevance:10,starts:{end:/^(?![ ]{6})/,subLanguage:"julia"},aliases:["jldoctest"]}]}}}());
-hljs.registerLanguage("kotlin",function(){return function(a){var b={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",
-literal:"true false null"},c={className:"symbol",begin:a.UNDERSCORE_IDENT_RE+"@"},d={className:"subst",begin:"\\${",end:"}",contains:[a.C_NUMBER_MODE]},e={className:"variable",begin:"\\$"+a.UNDERSCORE_IDENT_RE};e={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[e,d]},{begin:"'",end:"'",illegal:/\n/,contains:[a.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[a.BACKSLASH_ESCAPE,e,d]}]};d.contains.push(e);d={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+
-a.UNDERSCORE_IDENT_RE+")?"};var f={className:"meta",begin:"@"+a.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[a.inherit(e,{className:"meta-string"})]}]},h=a.COMMENT("/\\*","\\*/",{contains:[a.C_BLOCK_COMMENT_MODE]}),g={variants:[{className:"type",begin:a.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]};g.variants[1].contains=[g];g.variants[1].contains=[g];return{name:"Kotlin",aliases:["kt"],keywords:b,contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",
-begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,h,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},c,d,f,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:b,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},
-{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:b,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[g,a.C_LINE_COMMENT_MODE,h],relevance:0},a.C_LINE_COMMENT_MODE,h,d,f,e,a.C_NUMBER_MODE]},h]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},a.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,
-relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},d,f]},e,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());
-hljs.registerLanguage("lasso",function(){return function(a){var b={$pattern:"[a-zA-Z_][\\w.]*|&[lg]t;",literal:"true false none minimal full all void and or not bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft",built_in:"array date decimal duration integer map pair string tag xml null boolean bytes keyword list locale queue set stack staticarray local var variable global data self inherited currentcapture givenblock",keyword:"cache database_names database_schemanames database_tablenames define_tag define_type email_batch encode_set html_comment handle handle_error header if inline iterate ljax_target link link_currentaction link_currentgroup link_currentrecord link_detail link_firstgroup link_firstrecord link_lastgroup link_lastrecord link_nextgroup link_nextrecord link_prevgroup link_prevrecord log loop namespace_using output_none portal private protect records referer referrer repeating resultset rows search_args search_arguments select sort_args sort_arguments thread_atomic value_list while abort case else fail_if fail_ifnot fail if_empty if_false if_null if_true loop_abort loop_continue loop_count params params_up return return_value run_children soap_definetag soap_lastrequest soap_lastresponse tag_name ascending average by define descending do equals frozen group handle_failure import in into join let match max min on order parent protected provide public require returnhome skip split_thread sum take thread to trait type where with yield yieldhome"},
-c=a.COMMENT("\x3c!--","--\x3e",{relevance:0}),d={className:"meta",begin:"\\[noprocess\\]",starts:{end:"\\[/noprocess\\]",returnEnd:!0,contains:[c]}},e={className:"meta",begin:"\\[/noprocess|<\\?(lasso(script)?|=)"};a=[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.inherit(a.C_NUMBER_MODE,{begin:a.C_NUMBER_RE+"|(-?infinity|NaN)\\b"}),a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),{className:"string",begin:"`",end:"`"},{variants:[{begin:"[#$][a-zA-Z_][\\w.]*"},
-{begin:"#",end:"\\d+",illegal:"\\W"}]},{className:"type",begin:"::\\s*",end:"[a-zA-Z_][\\w.]*",illegal:"\\W"},{className:"params",variants:[{begin:"-(?!infinity)[a-zA-Z_][\\w.]*",relevance:0},{begin:"(\\.\\.\\.)"}]},{begin:/(->|\.)\s*/,relevance:0,contains:[{className:"symbol",begin:"'[a-zA-Z_][\\w.]*'"}]},{className:"class",beginKeywords:"define",returnEnd:!0,end:"\\(|=>",contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_][\\w.]*(=(?!>))?|[-+*/%](?!>)"})]}];return{name:"Lasso",aliases:["ls","lassoscript"],
-case_insensitive:!0,keywords:b,contains:[{className:"meta",begin:"\\]|\\?>",relevance:0,starts:{end:"\\[|<\\?(lasso(script)?|=)",returnEnd:!0,relevance:0,contains:[c]}},d,e,{className:"meta",begin:"\\[no_square_brackets",starts:{end:"\\[/no_square_brackets\\]",keywords:b,contains:[{className:"meta",begin:"\\]|\\?>",relevance:0,starts:{end:"\\[noprocess\\]|<\\?(lasso(script)?|=)",returnEnd:!0,contains:[c]}},d,e].concat(a)}},{className:"meta",begin:"\\[",relevance:0},{className:"meta",begin:"^#!",end:"lasso9$",
-relevance:10}].concat(a)}}}());
-hljs.registerLanguage("latex",function(){return function(a){var b={className:"tag",begin:/\\/,relevance:0,contains:[{className:"name",variants:[{begin:/[a-zA-Z\u0430-\u044f\u0410-\u042f]+[*]?/},{begin:/[^a-zA-Z\u0430-\u044f\u0410-\u042f0-9]/}],starts:{endsWithParent:!0,relevance:0,contains:[{className:"string",variants:[{begin:/\[/,end:/\]/},{begin:/\{/,end:/\}/}]},{begin:/\s*=\s*/,endsWithParent:!0,relevance:0,contains:[{className:"number",begin:/-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/}]}]}}]};return{name:"LaTeX",
-aliases:["tex"],contains:[b,{className:"formula",contains:[b],relevance:0,variants:[{begin:/\$\$/,end:/\$\$/},{begin:/\$/,end:/\$/}]},a.COMMENT("%","$",{relevance:0})]}}}());hljs.registerLanguage("ldif",function(){return function(a){return{name:"LDIF",contains:[{className:"attribute",begin:"^dn",end:": ",excludeEnd:!0,starts:{end:"$",relevance:0},relevance:10},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,starts:{end:"$",relevance:0}},{className:"literal",begin:"^-",end:"$"},a.HASH_COMMENT_MODE]}}}());
-hljs.registerLanguage("leaf",function(){return function(a){return{name:"Leaf",contains:[{className:"function",begin:"#+[A-Za-z_0-9]*\\(",end:" {",returnBegin:!0,excludeEnd:!0,contains:[{className:"keyword",begin:"#+"},{className:"title",begin:"[A-Za-z_][A-Za-z_0-9]*"},{className:"params",begin:"\\(",end:"\\)",endsParent:!0,contains:[{className:"string",begin:'"',end:'"'},{className:"variable",begin:"[A-Za-z_][A-Za-z_0-9]*"}]}]}]}}}());
-hljs.registerLanguage("less",function(){return function(a){var b=[],c=[],d=function(a){return{className:"string",begin:"~?"+a+".*?"+a}},e=function(a,b,c){return{className:a,begin:b,relevance:c}},f={begin:"\\(",end:"\\)",contains:c,relevance:0};c.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,d("'"),d('"'),a.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},e("number","#[0-9A-Fa-f]+\\b"),f,e("variable","@@?[\\w-]+",10),e("variable","@{[\\w-]+}"),
-e("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});f=c.concat({begin:"{",end:"}",contains:b});var h={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(c)};d={begin:"([\\w-]+|@{[\\w-]+})\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:"([\\w-]+|@{[\\w-]+})",end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:c}}]};
-c={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:c,relevance:0}};var g={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:f}};e={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:"([\\w-]+|@{[\\w-]+})",end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,h,e("keyword","all\\b"),e("variable","@{[\\w-]+}"),e("selector-tag","([\\w-]+|@{[\\w-]+})%?",0),e("selector-id","#([\\w-]+|@{[\\w-]+})"),e("selector-class","\\.([\\w-]+|@{[\\w-]+})",0),e("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:f},{begin:"!important"}]};b.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,g,d,e);return{name:"Less",case_insensitive:!0,
-illegal:"[=>'/<($\"]",contains:b}}}());
-hljs.registerLanguage("lisp",function(){return function(a){var b={className:"literal",begin:"\\b(t{1}|nil)\\b"},c={className:"number",variants:[{begin:"(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",relevance:0},{begin:"#(b|B)[0-1]+(/[0-1]+)?"},{begin:"#(o|O)[0-7]+(/[0-7]+)?"},{begin:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{begin:"#(c|C)\\((\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)? +(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",end:"\\)"}]},
-d=a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),e=a.COMMENT(";","$",{relevance:0}),f={begin:"\\*",end:"\\*"},h={className:"symbol",begin:"[:&][a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},g={begin:"[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",relevance:0},k={contains:[c,d,f,h,{begin:"\\(",end:"\\)",contains:["self",b,d,c,g]},g],variants:[{begin:"['`]\\(",end:"\\)"},{begin:"\\(quote ",end:"\\)",keywords:{name:"quote"}},{begin:"'\\|[^]*?\\|"}]},
-l={variants:[{begin:"'[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},{begin:"#'[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*(::[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*)*"}]},m={begin:"\\(\\s*",end:"\\)"},p={endsWithParent:!0,relevance:0};m.contains=[{className:"name",variants:[{begin:"[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},{begin:"\\|[^]*?\\|"}]},p];p.contains=
-[k,l,m,b,c,d,e,f,h,{begin:"\\|[^]*?\\|"},g];return{name:"Lisp",illegal:/\S/,contains:[c,a.SHEBANG(),b,d,e,k,l,m,g]}}}());
-hljs.registerLanguage("livecodeserver",function(){return function(a){var b={className:"variable",variants:[{begin:"\\b([gtps][A-Z]{1}[a-zA-Z0-9]*)(\\[.+\\])?(?:\\s*?)"},{begin:"\\$_[A-Z]+"}],relevance:0},c=[a.C_BLOCK_COMMENT_MODE,a.HASH_COMMENT_MODE,a.COMMENT("--","$"),a.COMMENT("[^:]//","$")],d=a.inherit(a.TITLE_MODE,{variants:[{begin:"\\b_*rig[A-Z]+[A-Za-z0-9_\\-]*"},{begin:"\\b_[a-z0-9\\-]+"}]}),e=a.inherit(a.TITLE_MODE,{begin:"\\b([A-Za-z0-9_\\-]+)\\b"});return{name:"LiveCode",case_insensitive:!1,
-keywords:{keyword:"$_COOKIE $_FILES $_GET $_GET_BINARY $_GET_RAW $_POST $_POST_BINARY $_POST_RAW $_SESSION $_SERVER codepoint codepoints segment segments codeunit codeunits sentence sentences trueWord trueWords paragraph after byte bytes english the until http forever descending using line real8 with seventh for stdout finally element word words fourth before black ninth sixth characters chars stderr uInt1 uInt1s uInt2 uInt2s stdin string lines relative rel any fifth items from middle mid at else of catch then third it file milliseconds seconds second secs sec int1 int1s int4 int4s internet int2 int2s normal text item last long detailed effective uInt4 uInt4s repeat end repeat URL in try into switch to words https token binfile each tenth as ticks tick system real4 by dateItems without char character ascending eighth whole dateTime numeric short first ftp integer abbreviated abbr abbrev private case while if div mod wrap and or bitAnd bitNot bitOr bitXor among not in a an within contains ends with begins the keys of keys",
+literal:"null true false nil "},I={begin:"\\.\\s*"+S.UNDERSCORE_IDENT_RE,
+keywords:C,relevance:0},N={className:"type",
+begin:":[ \\t]*(IApplication|IAccessRights|IAccountRepository|IAccountSelectionRestrictions|IAction|IActionList|IAdministrationHistoryDescription|IAnchors|IApplication|IArchiveInfo|IAttachment|IAttachmentList|ICheckListBox|ICheckPointedList|IColumn|IComponent|IComponentDescription|IComponentToken|IComponentTokenFactory|IComponentTokenInfo|ICompRecordInfo|IConnection|IContents|IControl|IControlJob|IControlJobInfo|IControlList|ICrypto|ICrypto2|ICustomJob|ICustomJobInfo|ICustomListBox|ICustomObjectWizardStep|ICustomWork|ICustomWorkInfo|IDataSet|IDataSetAccessInfo|IDataSigner|IDateCriterion|IDateRequisite|IDateRequisiteDescription|IDateValue|IDeaAccessRights|IDeaObjectInfo|IDevelopmentComponentLock|IDialog|IDialogFactory|IDialogPickRequisiteItems|IDialogsFactory|IDICSFactory|IDocRequisite|IDocumentInfo|IDualListDialog|IECertificate|IECertificateInfo|IECertificates|IEditControl|IEditorForm|IEdmsExplorer|IEdmsObject|IEdmsObjectDescription|IEdmsObjectFactory|IEdmsObjectInfo|IEDocument|IEDocumentAccessRights|IEDocumentDescription|IEDocumentEditor|IEDocumentFactory|IEDocumentInfo|IEDocumentStorage|IEDocumentVersion|IEDocumentVersionListDialog|IEDocumentVersionSource|IEDocumentWizardStep|IEDocVerSignature|IEDocVersionState|IEnabledMode|IEncodeProvider|IEncrypter|IEvent|IEventList|IException|IExternalEvents|IExternalHandler|IFactory|IField|IFileDialog|IFolder|IFolderDescription|IFolderDialog|IFolderFactory|IFolderInfo|IForEach|IForm|IFormTitle|IFormWizardStep|IGlobalIDFactory|IGlobalIDInfo|IGrid|IHasher|IHistoryDescription|IHyperLinkControl|IImageButton|IImageControl|IInnerPanel|IInplaceHint|IIntegerCriterion|IIntegerList|IIntegerRequisite|IIntegerValue|IISBLEditorForm|IJob|IJobDescription|IJobFactory|IJobForm|IJobInfo|ILabelControl|ILargeIntegerCriterion|ILargeIntegerRequisite|ILargeIntegerValue|ILicenseInfo|ILifeCycleStage|IList|IListBox|ILocalIDInfo|ILocalization|ILock|IMemoryDataSet|IMessagingFactory|IMetadataRepository|INotice|INoticeInfo|INumericCriterion|INumericRequisite|INumericValue|IObject|IObjectDescription|IObjectImporter|IObjectInfo|IObserver|IPanelGroup|IPickCriterion|IPickProperty|IPickRequisite|IPickRequisiteDescription|IPickRequisiteItem|IPickRequisiteItems|IPickValue|IPrivilege|IPrivilegeList|IProcess|IProcessFactory|IProcessMessage|IProgress|IProperty|IPropertyChangeEvent|IQuery|IReference|IReferenceCriterion|IReferenceEnabledMode|IReferenceFactory|IReferenceHistoryDescription|IReferenceInfo|IReferenceRecordCardWizardStep|IReferenceRequisiteDescription|IReferencesFactory|IReferenceValue|IRefRequisite|IReport|IReportFactory|IRequisite|IRequisiteDescription|IRequisiteDescriptionList|IRequisiteFactory|IRichEdit|IRouteStep|IRule|IRuleList|ISchemeBlock|IScript|IScriptFactory|ISearchCriteria|ISearchCriterion|ISearchDescription|ISearchFactory|ISearchFolderInfo|ISearchForObjectDescription|ISearchResultRestrictions|ISecuredContext|ISelectDialog|IServerEvent|IServerEventFactory|IServiceDialog|IServiceFactory|ISignature|ISignProvider|ISignProvider2|ISignProvider3|ISimpleCriterion|IStringCriterion|IStringList|IStringRequisite|IStringRequisiteDescription|IStringValue|ISystemDialogsFactory|ISystemInfo|ITabSheet|ITask|ITaskAbortReasonInfo|ITaskCardWizardStep|ITaskDescription|ITaskFactory|ITaskInfo|ITaskRoute|ITextCriterion|ITextRequisite|ITextValue|ITreeListSelectDialog|IUser|IUserList|IValue|IView|IWebBrowserControl|IWizard|IWizardAction|IWizardFactory|IWizardFormElement|IWizardParam|IWizardPickParam|IWizardReferenceParam|IWizardStep|IWorkAccessRights|IWorkDescription|IWorkflowAskableParam|IWorkflowAskableParams|IWorkflowBlock|IWorkflowBlockResult|IWorkflowEnabledMode|IWorkflowParam|IWorkflowPickParam|IWorkflowReferenceParam|IWorkState|IWorkTreeCustomNode|IWorkTreeJobNode|IWorkTreeTaskNode|IXMLEditorForm|SBCrypto)",
+end:"[ \\t]*=",excludeEnd:!0},A={className:"variable",keywords:C,begin:E,
+relevance:0,contains:[N,I]
+},e="[A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_][A-Za-z\u0410-\u042f\u0430-\u044f\u0451\u0401_0-9]*\\("
+;return{name:"ISBL",case_insensitive:!0,keywords:C,
+illegal:"\\$|\\?|%|,|;$|~|#|@|</",contains:[{className:"function",begin:e,
+end:"\\)$",returnBegin:!0,keywords:C,illegal:"[\\[\\]\\|\\$\\?%,~#@]",
+contains:[{className:"title",keywords:{$pattern:E,
+built_in:"AddSubString AdjustLineBreaks AmountInWords Analysis ArrayDimCount ArrayHighBound ArrayLowBound ArrayOf ArrayReDim Assert Assigned BeginOfMonth BeginOfPeriod BuildProfilingOperationAnalysis CallProcedure CanReadFile CArrayElement CDataSetRequisite ChangeDate ChangeReferenceDataset Char CharPos CheckParam CheckParamValue CompareStrings ConstantExists ControlState ConvertDateStr Copy CopyFile CreateArray CreateCachedReference CreateConnection CreateDialog CreateDualListDialog CreateEditor CreateException CreateFile CreateFolderDialog CreateInputDialog CreateLinkFile CreateList CreateLock CreateMemoryDataSet CreateObject CreateOpenDialog CreateProgress CreateQuery CreateReference CreateReport CreateSaveDialog CreateScript CreateSQLPivotFunction CreateStringList CreateTreeListSelectDialog CSelectSQL CSQL CSubString CurrentUserID CurrentUserName CurrentVersion DataSetLocateEx DateDiff DateTimeDiff DateToStr DayOfWeek DeleteFile DirectoryExists DisableCheckAccessRights DisableCheckFullShowingRestriction DisableMassTaskSendingRestrictions DropTable DupeString EditText EnableCheckAccessRights EnableCheckFullShowingRestriction EnableMassTaskSendingRestrictions EndOfMonth EndOfPeriod ExceptionExists ExceptionsOff ExceptionsOn Execute ExecuteProcess Exit ExpandEnvironmentVariables ExtractFileDrive ExtractFileExt ExtractFileName ExtractFilePath ExtractParams FileExists FileSize FindFile FindSubString FirmContext ForceDirectories Format FormatDate FormatNumeric FormatSQLDate FormatString FreeException GetComponent GetComponentLaunchParam GetConstant GetLastException GetReferenceRecord GetRefTypeByRefID GetTableID GetTempFolder IfThen In IndexOf InputDialog InputDialogEx InteractiveMode IsFileLocked IsGraphicFile IsNumeric Length LoadString LoadStringFmt LocalTimeToUTC LowerCase Max MessageBox MessageBoxEx MimeDecodeBinary MimeDecodeString MimeEncodeBinary MimeEncodeString Min MoneyInWords MoveFile NewID Now OpenFile Ord Precision Raise ReadCertificateFromFile ReadFile ReferenceCodeByID ReferenceNumber ReferenceRequisiteMode ReferenceRequisiteValue RegionDateSettings RegionNumberSettings RegionTimeSettings RegRead RegWrite RenameFile Replace Round SelectServerCode SelectSQL ServerDateTime SetConstant SetManagedFolderFieldsState ShowConstantsInputDialog ShowMessage Sleep Split SQL SQL2XLSTAB SQLProfilingSendReport StrToDate SubString SubStringCount SystemSetting Time TimeDiff Today Transliterate Trim UpperCase UserStatus UTCToLocalTime ValidateXML VarIsClear VarIsEmpty VarIsNull WorkTimeDiff WriteFile WriteFileEx WriteObjectHistory \u0410\u043d\u0430\u043b\u0438\u0437 \u0411\u0430\u0437\u0430\u0414\u0430\u043d\u043d\u044b\u0445 \u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c \u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c\u0420\u0430\u0441\u0448 \u0411\u043b\u043e\u043a\u0418\u043d\u0444\u043e \u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c \u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c\u0420\u0430\u0441\u0448 \u0411\u043b\u043e\u043a\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0412\u0432\u043e\u0434 \u0412\u0432\u043e\u0434\u041c\u0435\u043d\u044e \u0412\u0435\u0434\u0421 \u0412\u0435\u0434\u0421\u043f\u0440 \u0412\u0435\u0440\u0445\u043d\u044f\u044f\u0413\u0440\u0430\u043d\u0438\u0446\u0430\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0412\u043d\u0435\u0448\u041f\u0440\u043e\u0433\u0440 \u0412\u043e\u0441\u0441\u0442 \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f\u041f\u0430\u043f\u043a\u0430 \u0412\u0440\u0435\u043c\u044f \u0412\u044b\u0431\u043e\u0440SQL \u0412\u044b\u0431\u0440\u0430\u0442\u044c\u0417\u0430\u043f\u0438\u0441\u044c \u0412\u044b\u0434\u0435\u043b\u0438\u0442\u044c\u0421\u0442\u0440 \u0412\u044b\u0437\u0432\u0430\u0442\u044c \u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0412\u044b\u043f\u041f\u0440\u043e\u0433\u0440 \u0413\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0439\u0424\u0430\u0439\u043b \u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0414\u0430\u0442\u0430\u0412\u0440\u0435\u043c\u044f\u0421\u0435\u0440\u0432 \u0414\u0435\u043d\u044c\u041d\u0435\u0434\u0435\u043b\u0438 \u0414\u0438\u0430\u043b\u043e\u0433\u0414\u0430\u041d\u0435\u0442 \u0414\u043b\u0438\u043d\u0430\u0421\u0442\u0440 \u0414\u043e\u0431\u041f\u043e\u0434\u0441\u0442\u0440 \u0415\u041f\u0443\u0441\u0442\u043e \u0415\u0441\u043b\u0438\u0422\u043e \u0415\u0427\u0438\u0441\u043b\u043e \u0417\u0430\u043c\u041f\u043e\u0434\u0441\u0442\u0440 \u0417\u0430\u043f\u0438\u0441\u044c\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u0417\u043d\u0430\u0447\u041f\u043e\u043b\u044f\u0421\u043f\u0440 \u0418\u0414\u0422\u0438\u043f\u0421\u043f\u0440 \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0414\u0438\u0441\u043a \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0418\u043c\u044f\u0424\u0430\u0439\u043b\u0430 \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u041f\u0443\u0442\u044c \u0418\u0437\u0432\u043b\u0435\u0447\u044c\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0418\u0437\u043c\u0414\u0430\u0442 \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0420\u0430\u0437\u043c\u0435\u0440\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0418\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u0418\u043c\u044f\u041e\u0440\u0433 \u0418\u043c\u044f\u041f\u043e\u043b\u044f\u0421\u043f\u0440 \u0418\u043d\u0434\u0435\u043a\u0441 \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0428\u0430\u0433 \u0418\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439\u0420\u0435\u0436\u0438\u043c \u0418\u0442\u043e\u0433\u0422\u0431\u043b\u0421\u043f\u0440 \u041a\u043e\u0434\u0412\u0438\u0434\u0412\u0435\u0434\u0421\u043f\u0440 \u041a\u043e\u0434\u0412\u0438\u0434\u0421\u043f\u0440\u041f\u043e\u0418\u0414 \u041a\u043e\u0434\u041f\u043eAnalit \u041a\u043e\u0434\u0421\u0438\u043c\u0432\u043e\u043b\u0430 \u041a\u043e\u0434\u0421\u043f\u0440 \u041a\u043e\u043b\u041f\u043e\u0434\u0441\u0442\u0440 \u041a\u043e\u043b\u041f\u0440\u043e\u043f \u041a\u043e\u043d\u041c\u0435\u0441 \u041a\u043e\u043d\u0441\u0442 \u041a\u043e\u043d\u0441\u0442\u0415\u0441\u0442\u044c \u041a\u043e\u043d\u0441\u0442\u0417\u043d\u0430\u0447 \u041a\u043e\u043d\u0422\u0440\u0430\u043d \u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0424\u0430\u0439\u043b \u041a\u043e\u043f\u0438\u044f\u0421\u0442\u0440 \u041a\u041f\u0435\u0440\u0438\u043e\u0434 \u041a\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u041c\u0430\u043a\u0441 \u041c\u0430\u043a\u0441\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u041c\u0430\u0441\u0441\u0438\u0432 \u041c\u0435\u043d\u044e \u041c\u0435\u043d\u044e\u0420\u0430\u0441\u0448 \u041c\u0438\u043d \u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445\u041d\u0430\u0439\u0442\u0438\u0420\u0430\u0441\u0448 \u041d\u0430\u0438\u043c\u0412\u0438\u0434\u0421\u043f\u0440 \u041d\u0430\u0438\u043c\u041f\u043eAnalit \u041d\u0430\u0438\u043c\u0421\u043f\u0440 \u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u044b\u0421\u0442\u0440\u043e\u043a \u041d\u0430\u0447\u041c\u0435\u0441 \u041d\u0430\u0447\u0422\u0440\u0430\u043d \u041d\u0438\u0436\u043d\u044f\u044f\u0413\u0440\u0430\u043d\u0438\u0446\u0430\u041c\u0430\u0441\u0441\u0438\u0432\u0430 \u041d\u043e\u043c\u0435\u0440\u0421\u043f\u0440 \u041d\u041f\u0435\u0440\u0438\u043e\u0434 \u041e\u043a\u043d\u043e \u041e\u043a\u0440 \u041e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u041e\u0442\u043b\u0418\u043d\u0444\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u041e\u0442\u043b\u0418\u043d\u0444\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u041e\u0442\u0447\u0435\u0442 \u041e\u0442\u0447\u0435\u0442\u0410\u043d\u0430\u043b \u041e\u0442\u0447\u0435\u0442\u0418\u043d\u0442 \u041f\u0430\u043f\u043a\u0430\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u041f\u0430\u0443\u0437\u0430 \u041f\u0412\u044b\u0431\u043e\u0440SQL \u041f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c\u0424\u0430\u0439\u043b \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c\u0424\u0430\u0439\u043b \u041f\u043e\u0434\u0441\u0442\u0440 \u041f\u043e\u0438\u0441\u043a\u041f\u043e\u0434\u0441\u0442\u0440 \u041f\u043e\u0438\u0441\u043a\u0421\u0442\u0440 \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u0418\u0414\u0422\u0430\u0431\u043b\u0438\u0446\u044b \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0418\u0414 \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0418\u043c\u044f \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0421\u0442\u0430\u0442\u0443\u0441 \u041f\u0440\u0435\u0440\u0432\u0430\u0442\u044c \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0417\u043d\u0430\u0447 \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c\u0423\u0441\u043b\u043e\u0432\u0438\u0435 \u0420\u0430\u0437\u0431\u0421\u0442\u0440 \u0420\u0430\u0437\u043d\u0412\u0440\u0435\u043c\u044f \u0420\u0430\u0437\u043d\u0414\u0430\u0442 \u0420\u0430\u0437\u043d\u0414\u0430\u0442\u0430\u0412\u0440\u0435\u043c\u044f \u0420\u0430\u0437\u043d\u0420\u0430\u0431\u0412\u0440\u0435\u043c\u044f \u0420\u0435\u0433\u0423\u0441\u0442\u0412\u0440\u0435\u043c \u0420\u0435\u0433\u0423\u0441\u0442\u0414\u0430\u0442 \u0420\u0435\u0433\u0423\u0441\u0442\u0427\u0441\u043b \u0420\u0435\u0434\u0422\u0435\u043a\u0441\u0442 \u0420\u0435\u0435\u0441\u0442\u0440\u0417\u0430\u043f\u0438\u0441\u044c \u0420\u0435\u0435\u0441\u0442\u0440\u0421\u043f\u0438\u0441\u043e\u043a\u0418\u043c\u0435\u043d\u041f\u0430\u0440\u0430\u043c \u0420\u0435\u0435\u0441\u0442\u0440\u0427\u0442\u0435\u043d\u0438\u0435 \u0420\u0435\u043a\u0432\u0421\u043f\u0440 \u0420\u0435\u043a\u0432\u0421\u043f\u0440\u041f\u0440 \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0421\u0435\u0439\u0447\u0430\u0441 \u0421\u0435\u0440\u0432\u0435\u0440 \u0421\u0435\u0440\u0432\u0435\u0440\u041f\u0440\u043e\u0446\u0435\u0441\u0441\u0418\u0414 \u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0424\u0430\u0439\u043b\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0421\u0436\u041f\u0440\u043e\u0431 \u0421\u0438\u043c\u0432\u043e\u043b \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u0414\u0438\u0440\u0435\u043a\u0442\u0443\u043c\u041a\u043e\u0434 \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0421\u0438\u0441\u0442\u0435\u043c\u0430\u041a\u043e\u0434 \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0412\u044b\u0431\u043e\u0440\u0430\u0418\u0437\u0414\u0432\u0443\u0445\u0421\u043f\u0438\u0441\u043a\u043e\u0432 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0412\u044b\u0431\u043e\u0440\u0430\u041f\u0430\u043f\u043a\u0438 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u041e\u0442\u043a\u0440\u044b\u0442\u0438\u044f\u0424\u0430\u0439\u043b\u0430 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0414\u0438\u0430\u043b\u043e\u0433\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f\u0424\u0430\u0439\u043b\u0430 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0417\u0430\u043f\u0440\u043e\u0441 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041c\u0430\u0441\u0441\u0438\u0432 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041e\u0431\u044a\u0435\u043a\u0442 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041e\u0442\u0447\u0435\u0442 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u041f\u0430\u043f\u043a\u0443 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0438\u0441\u043e\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0438\u0441\u043e\u043a\u0421\u0442\u0440\u043e\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0421\u043e\u0437\u0434\u0421\u043f\u0440 \u0421\u043e\u0441\u0442\u0421\u043f\u0440 \u0421\u043e\u0445\u0440 \u0421\u043e\u0445\u0440\u0421\u043f\u0440 \u0421\u043f\u0438\u0441\u043e\u043a\u0421\u0438\u0441\u0442\u0435\u043c \u0421\u043f\u0440 \u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0415\u0441\u0442\u044c \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0421\u043d\u044f\u0442\u044c\u0420\u0430\u0441\u0448 \u0421\u043f\u0440\u0411\u043b\u043e\u043a\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0421\u043f\u0440\u0418\u0437\u043c\u041d\u0430\u0431\u0414\u0430\u043d \u0421\u043f\u0440\u041a\u043e\u0434 \u0421\u043f\u0440\u041d\u043e\u043c\u0435\u0440 \u0421\u043f\u0440\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0421\u043f\u0440\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0421\u043f\u0440\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0421\u043f\u0440\u041f\u0430\u0440\u0430\u043c \u0421\u043f\u0440\u041f\u043e\u043b\u0435\u0417\u043d\u0430\u0447 \u0421\u043f\u0440\u041f\u043e\u043b\u0435\u0418\u043c\u044f \u0421\u043f\u0440\u0420\u0435\u043a\u0432 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0412\u0432\u0435\u0434\u0417\u043d \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041d\u043e\u0432\u044b\u0435 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041f\u0440 \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u041f\u0440\u0435\u0434\u0417\u043d \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0420\u0435\u0436\u0438\u043c \u0421\u043f\u0440\u0420\u0435\u043a\u0432\u0422\u0438\u043f\u0422\u0435\u043a\u0441\u0442 \u0421\u043f\u0440\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0421\u043f\u0440\u0421\u043e\u0441\u0442 \u0421\u043f\u0440\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0421\u043f\u0440\u0422\u0431\u043b\u0418\u0442\u043e\u0433 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041a\u043e\u043b \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041c\u0430\u043a\u0441 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041c\u0438\u043d \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u041f\u0440\u0435\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0421\u043b\u0435\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0421\u043e\u0437\u0434 \u0421\u043f\u0440\u0422\u0431\u043b\u0421\u0442\u0440\u0423\u0434 \u0421\u043f\u0440\u0422\u0435\u043a\u041f\u0440\u0435\u0434\u0441\u0442 \u0421\u043f\u0440\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0421\u0440\u0430\u0432\u043d\u0438\u0442\u044c\u0421\u0442\u0440 \u0421\u0442\u0440\u0412\u0435\u0440\u0445\u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0421\u0442\u0440\u041d\u0438\u0436\u043d\u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u0421\u0443\u043c\u041f\u0440\u043e\u043f \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439\u041f\u0430\u0440\u0430\u043c \u0422\u0435\u043a\u0412\u0435\u0440\u0441\u0438\u044f \u0422\u0435\u043a\u041e\u0440\u0433 \u0422\u043e\u0447\u043d \u0422\u0440\u0430\u043d \u0422\u0440\u0430\u043d\u0441\u043b\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044f \u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0422\u0430\u0431\u043b\u0438\u0446\u0443 \u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0424\u0430\u0439\u043b \u0423\u0434\u0421\u043f\u0440 \u0423\u0434\u0421\u0442\u0440\u0422\u0431\u043b\u0421\u043f\u0440 \u0423\u0441\u0442 \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442 \u0424\u0430\u0439\u043b\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u0412\u0440\u0435\u043c\u044f \u0424\u0430\u0439\u043b\u0412\u0440\u0435\u043c\u044f\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0417\u0430\u043d\u044f\u0442 \u0424\u0430\u0439\u043b\u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0418\u0441\u043a\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041c\u043e\u0436\u043d\u043e\u0427\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0424\u0430\u0439\u043b\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0424\u0430\u0439\u043b\u0420\u0430\u0437\u043c\u0435\u0440 \u0424\u0430\u0439\u043b\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0421\u0441\u044b\u043b\u043a\u0430\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0424\u0430\u0439\u043b\u0421\u0447\u0438\u0442\u0430\u0442\u044c \u0424\u0430\u0439\u043b\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0424\u043c\u0442SQL\u0414\u0430\u0442 \u0424\u043c\u0442\u0414\u0430\u0442 \u0424\u043c\u0442\u0421\u0442\u0440 \u0424\u043c\u0442\u0427\u0441\u043b \u0424\u043e\u0440\u043c\u0430\u0442 \u0426\u041c\u0430\u0441\u0441\u0438\u0432\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0426\u041d\u0430\u0431\u043e\u0440\u0414\u0430\u043d\u043d\u044b\u0445\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442 \u0426\u041f\u043e\u0434\u0441\u0442\u0440 "
+},begin:e,end:"\\(",returnBegin:!0,excludeEnd:!0},I,A,T,_,O]},N,I,A,T,_,O]}}
+})());
+hljs.registerLanguage("java",(()=>{"use strict"
+;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={
+className:"number",variants:[{
+begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
+},{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{
+begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{
+begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
+},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{
+begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],
+relevance:0};return e=>{
+var n="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s={
+className:"meta",begin:"@[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",
+contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};const r=a;return{
+name:"Java",aliases:["jsp"],keywords:n,illegal:/<\/|#/,
+contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,
+relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{
+begin:/import java\.[a-z]+\./,keywords:"import",relevance:2
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{
+className:"class",beginKeywords:"class interface enum",end:/[{;=]/,
+excludeEnd:!0,relevance:1,keywords:"class interface enum",illegal:/[:"\[\]]/,
+contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{
+beginKeywords:"new throw return else",relevance:0},{className:"class",
+begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0,
+end:/[{;=]/,keywords:n,contains:[{beginKeywords:"record"},{
+begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,
+keywords:n,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function",
+begin:"([\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(<[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",
+returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:n,contains:[{
+begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,
+keywords:n,relevance:0,
+contains:[s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,e.C_BLOCK_COMMENT_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},r,s]}}})());
+hljs.registerLanguage("javascript",(()=>{"use strict"
+;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"])
+;function r(e){return t("(?=",e,")")}function t(...e){return e.map((e=>{
+return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return i=>{
+const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,
+isTrulyOpeningTag:(e,n)=>{const a=e[0].length+e.index,s=e.input[a]
+;"<"!==s?">"===s&&(((e,{after:n})=>{const a="</"+e[0].slice(1)
+;return-1!==e.input.indexOf(a,n)})(e,{after:a
+})||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n,literal:a,
+built_in:s},g="\\.([0-9](_?[0-9])*)",b="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",d={
+className:"number",variants:[{
+begin:`(\\b(${b})((${g})|\\.)?|(${g}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{
+begin:`\\b(${b})\\b((${g})\\b|\\.)?|(${g})\\b`},{
+begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
+begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
+begin:"\\b0[0-7]+n?\\b"}],relevance:0},E={className:"subst",begin:"\\$\\{",
+end:"\\}",keywords:l,contains:[]},u={begin:"html`",end:"",starts:{end:"`",
+returnEnd:!1,contains:[i.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},_={
+begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
+contains:[i.BACKSLASH_ESCAPE,E],subLanguage:"css"}},m={className:"string",
+begin:"`",end:"`",contains:[i.BACKSLASH_ESCAPE,E]},y={className:"comment",
+variants:[i.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
+className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",
+end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)",
+endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
+}),i.C_BLOCK_COMMENT_MODE,i.C_LINE_COMMENT_MODE]
+},N=[i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,u,_,m,d,i.REGEXP_MODE]
+;E.contains=N.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(N)
+});const A=[].concat(y,E.contains),f=A.concat([{begin:/\(/,end:/\)/,keywords:l,
+contains:["self"].concat(A)}]),p={className:"params",begin:/\(/,end:/\)/,
+excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f};return{name:"Javascript",
+aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:f},
+illegal:/#(?![$_A-z])/,contains:[i.SHEBANG({label:"shebang",binary:"node",
+relevance:5}),{label:"use_strict",className:"meta",relevance:10,
+begin:/^\s*['"]use (strict|asm)['"]/
+},i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,u,_,m,y,d,{
+begin:t(/[{,\n]\s*/,r(t(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))),
+relevance:0,contains:[{className:"attr",begin:c+r("\\s*:"),relevance:0}]},{
+begin:"("+i.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",contains:[y,i.REGEXP_MODE,{className:"function",
+begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+i.UNDERSCORE_IDENT_RE+")\\s*=>",
+returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{
+begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0
+},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f}]}]
+},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{
+variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,
+end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0,
+contains:["self"]}]}],relevance:0},{className:"function",
+beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l,
+contains:["self",i.inherit(i.TITLE_MODE,{begin:c}),p],illegal:/%/},{
+beginKeywords:"while if switch catch for"},{className:"function",
+begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
+returnBegin:!0,contains:[p,i.inherit(i.TITLE_MODE,{begin:c})]},{variants:[{
+begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class",
+beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{
+beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,
+end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:c}),"self",p]
+},{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set",
+contains:[i.inherit(i.TITLE_MODE,{begin:c}),{begin:/\(\)/},p]},{begin:/\$[(.]/}]
+}}})());
+hljs.registerLanguage("jboss-cli",(()=>{"use strict";return e=>({
+name:"JBoss CLI",aliases:["wildfly-cli"],keywords:{$pattern:"[a-z-]+",
+keyword:"alias batch cd clear command connect connection-factory connection-info data-source deploy deployment-info deployment-overlay echo echo-dmr help history if jdbc-driver-info jms-queue|20 jms-topic|20 ls patch pwd quit read-attribute read-operation reload rollout-plan run-batch set shutdown try unalias undeploy unset version xa-data-source",
+literal:"true false"},contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,{
+className:"params",begin:/--[\w\-=\/]+/},{className:"function",
+begin:/:[\w\-.]+/,relevance:0},{className:"string",begin:/\B([\/.])[\w\-.\/=]+/
+},{className:"params",begin:/\(/,end:/\)/,contains:[{begin:/[\w-]+ *=/,
+returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/[\w-]+/}]}],
+relevance:0}]})})());
+hljs.registerLanguage("json",(()=>{"use strict";return n=>{const e={
+literal:"true false null"
+},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],a=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],l={
+end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:e},t={begin:/\{/,
+end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,
+contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(l,{begin:/:/
+})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(l)],
+illegal:"\\S"};return a.push(t,s),i.forEach((n=>{a.push(n)})),{name:"JSON",
+contains:a,keywords:e,illegal:"\\S"}}})());
+hljs.registerLanguage("julia",(()=>{"use strict";return e=>{
+var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,
+keyword:["baremodule","begin","break","catch","ccall","const","continue","do","else","elseif","end","export","false","finally","for","function","global","if","import","in","isa","let","local","macro","module","quote","return","true","try","using","where","while"],
+literal:["ARGS","C_NULL","DEPOT_PATH","ENDIAN_BOM","ENV","Inf","Inf16","Inf32","Inf64","InsertionSort","LOAD_PATH","MergeSort","NaN","NaN16","NaN32","NaN64","PROGRAM_FILE","QuickSort","RoundDown","RoundFromZero","RoundNearest","RoundNearestTiesAway","RoundNearestTiesUp","RoundToZero","RoundUp","VERSION|0","devnull","false","im","missing","nothing","pi","stderr","stdin","stdout","true","undef","\u03c0","\u212f"],
+built_in:["AbstractArray","AbstractChannel","AbstractChar","AbstractDict","AbstractDisplay","AbstractFloat","AbstractIrrational","AbstractMatrix","AbstractRange","AbstractSet","AbstractString","AbstractUnitRange","AbstractVecOrMat","AbstractVector","Any","ArgumentError","Array","AssertionError","BigFloat","BigInt","BitArray","BitMatrix","BitSet","BitVector","Bool","BoundsError","CapturedException","CartesianIndex","CartesianIndices","Cchar","Cdouble","Cfloat","Channel","Char","Cint","Cintmax_t","Clong","Clonglong","Cmd","Colon","Complex","ComplexF16","ComplexF32","ComplexF64","CompositeException","Condition","Cptrdiff_t","Cshort","Csize_t","Cssize_t","Cstring","Cuchar","Cuint","Cuintmax_t","Culong","Culonglong","Cushort","Cvoid","Cwchar_t","Cwstring","DataType","DenseArray","DenseMatrix","DenseVecOrMat","DenseVector","Dict","DimensionMismatch","Dims","DivideError","DomainError","EOFError","Enum","ErrorException","Exception","ExponentialBackOff","Expr","Float16","Float32","Float64","Function","GlobalRef","HTML","IO","IOBuffer","IOContext","IOStream","IdDict","IndexCartesian","IndexLinear","IndexStyle","InexactError","InitError","Int","Int128","Int16","Int32","Int64","Int8","Integer","InterruptException","InvalidStateException","Irrational","KeyError","LinRange","LineNumberNode","LinearIndices","LoadError","MIME","Matrix","Method","MethodError","Missing","MissingException","Module","NTuple","NamedTuple","Nothing","Number","OrdinalRange","OutOfMemoryError","OverflowError","Pair","PartialQuickSort","PermutedDimsArray","Pipe","ProcessFailedException","Ptr","QuoteNode","Rational","RawFD","ReadOnlyMemoryError","Real","ReentrantLock","Ref","Regex","RegexMatch","RoundingMode","SegmentationFault","Set","Signed","Some","StackOverflowError","StepRange","StepRangeLen","StridedArray","StridedMatrix","StridedVecOrMat","StridedVector","String","StringIndexError","SubArray","SubString","SubstitutionString","Symbol","SystemError","Task","TaskFailedException","Text","TextDisplay","Timer","Tuple","Type","TypeError","TypeVar","UInt","UInt128","UInt16","UInt32","UInt64","UInt8","UndefInitializer","UndefKeywordError","UndefRefError","UndefVarError","Union","UnionAll","UnitRange","Unsigned","Val","Vararg","VecElement","VecOrMat","Vector","VersionNumber","WeakKeyDict","WeakRef"]
+},n={keywords:t,illegal:/<\//},a={className:"subst",begin:/\$\(/,end:/\)/,
+keywords:t},i={className:"variable",begin:"\\$"+r},o={className:"string",
+contains:[e.BACKSLASH_ESCAPE,a,i],variants:[{begin:/\w*"""/,end:/"""\w*/,
+relevance:10},{begin:/\w*"/,end:/"\w*/}]},s={className:"string",
+contains:[e.BACKSLASH_ESCAPE,a,i],begin:"`",end:"`"},l={className:"meta",
+begin:"@"+r};return n.name="Julia",n.contains=[{className:"number",
+begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,
+relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},o,s,l,{
+className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",
+end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",
+begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/
+}],a.contains=n.contains,n}})());
+hljs.registerLanguage("julia-repl",(()=>{"use strict";return a=>({
+name:"Julia REPL",contains:[{className:"meta",begin:/^julia>/,relevance:10,
+starts:{end:/^(?![ ]{6})/,subLanguage:"julia"},aliases:["jldoctest"]}]})})());
+hljs.registerLanguage("kotlin",(()=>{"use strict"
+;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={
+className:"number",variants:[{
+begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
+},{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{
+begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{
+begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
+},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{
+begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],
+relevance:0};return e=>{const n={
+keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",
+built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",
+literal:"true false null"},i={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"
+},s={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},t={
+className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string",
+variants:[{begin:'"""',end:'"""(?=[^"])',contains:[t,s]},{begin:"'",end:"'",
+illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,
+contains:[e.BACKSLASH_ESCAPE,t,s]}]};s.contains.push(r);const l={
+className:"meta",
+begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"
+},c={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,
+end:/\)/,contains:[e.inherit(r,{className:"meta-string"})]}]
+},o=a,b=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),E={
+variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,
+contains:[]}]},d=E;return d.variants[1].contains=[E],E.variants[1].contains=[d],
+{name:"Kotlin",aliases:["kt","kts"],keywords:n,
+contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",
+begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,b,{className:"keyword",
+begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",
+begin:/@\w+/}]}},i,l,c,{className:"function",beginKeywords:"fun",end:"[(]|$",
+returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{
+begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,
+keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,
+endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,
+endsWithParent:!0,contains:[E,e.C_LINE_COMMENT_MODE,b],relevance:0
+},e.C_LINE_COMMENT_MODE,b,l,c,r,e.C_NUMBER_MODE]},b]},{className:"class",
+beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,
+illegal:"extends implements",contains:[{
+beginKeywords:"public protected internal private constructor"
+},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,
+excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,
+excludeBegin:!0,returnEnd:!0},l,c]},r,{className:"meta",begin:"^#!/usr/bin/env",
+end:"$",illegal:"\n"},o]}}})());
+hljs.registerLanguage("lasso",(()=>{"use strict";return e=>{
+const a="<\\?(lasso(script)?|=)",n="\\]|\\?>",r={
+$pattern:"[a-zA-Z_][\\w.]*|&[lg]t;",
+literal:"true false none minimal full all void and or not bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft",
+built_in:"array date decimal duration integer map pair string tag xml null boolean bytes keyword list locale queue set stack staticarray local var variable global data self inherited currentcapture givenblock",
+keyword:"cache database_names database_schemanames database_tablenames define_tag define_type email_batch encode_set html_comment handle handle_error header if inline iterate ljax_target link link_currentaction link_currentgroup link_currentrecord link_detail link_firstgroup link_firstrecord link_lastgroup link_lastrecord link_nextgroup link_nextrecord link_prevgroup link_prevrecord log loop namespace_using output_none portal private protect records referer referrer repeating resultset rows search_args search_arguments select sort_args sort_arguments thread_atomic value_list while abort case else fail_if fail_ifnot fail if_empty if_false if_null if_true loop_abort loop_continue loop_count params params_up return return_value run_children soap_definetag soap_lastrequest soap_lastresponse tag_name ascending average by define descending do equals frozen group handle_failure import in into join let match max min on order parent protected provide public require returnhome skip split_thread sum take thread to trait type where with yield yieldhome"
+},t=e.COMMENT("\x3c!--","--\x3e",{relevance:0}),s={className:"meta",
+begin:"\\[noprocess\\]",starts:{end:"\\[/noprocess\\]",returnEnd:!0,contains:[t]
+}},i={className:"meta",begin:"\\[/noprocess|"+a
+},l=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.inherit(e.C_NUMBER_MODE,{
+begin:e.C_NUMBER_RE+"|(-?infinity|NaN)\\b"}),e.inherit(e.APOS_STRING_MODE,{
+illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{
+className:"string",begin:"`",end:"`"},{variants:[{begin:"[#$][a-zA-Z_][\\w.]*"
+},{begin:"#",end:"\\d+",illegal:"\\W"}]},{className:"type",begin:"::\\s*",
+end:"[a-zA-Z_][\\w.]*",illegal:"\\W"},{className:"params",variants:[{
+begin:"-(?!infinity)[a-zA-Z_][\\w.]*",relevance:0},{begin:"(\\.\\.\\.)"}]},{
+begin:/(->|\.)\s*/,relevance:0,contains:[{className:"symbol",
+begin:"'[a-zA-Z_][\\w.]*'"}]},{className:"class",beginKeywords:"define",
+returnEnd:!0,end:"\\(|=>",contains:[e.inherit(e.TITLE_MODE,{
+begin:"[a-zA-Z_][\\w.]*(=(?!>))?|[-+*/%](?!>)"})]}];return{name:"Lasso",
+aliases:["ls","lassoscript"],case_insensitive:!0,keywords:r,contains:[{
+className:"meta",begin:n,relevance:0,starts:{end:"\\[|"+a,returnEnd:!0,
+relevance:0,contains:[t]}},s,i,{className:"meta",begin:"\\[no_square_brackets",
+starts:{end:"\\[/no_square_brackets\\]",keywords:r,contains:[{className:"meta",
+begin:n,relevance:0,starts:{end:"\\[noprocess\\]|"+a,returnEnd:!0,contains:[t]}
+},s,i].concat(l)}},{className:"meta",begin:"\\[",relevance:0},{className:"meta",
+begin:"^#!",end:"lasso9$",relevance:10}].concat(l)}}})());
+hljs.registerLanguage("latex",(()=>{"use strict";return e=>{const n=[{
+begin:/\^{6}[0-9a-f]{6}/},{begin:/\^{5}[0-9a-f]{5}/},{begin:/\^{4}[0-9a-f]{4}/
+},{begin:/\^{3}[0-9a-f]{3}/},{begin:/\^{2}[0-9a-f]{2}/},{
+begin:/\^{2}[\u0000-\u007f]/}],a=[{className:"keyword",begin:/\\/,relevance:0,
+contains:[{endsParent:!0,begin:((...e)=>"("+e.map((e=>{
+return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("|")+")")(...["(?:NeedsTeXFormat|RequirePackage|GetIdInfo)","Provides(?:Expl)?(?:Package|Class|File)","(?:DeclareOption|ProcessOptions)","(?:documentclass|usepackage|input|include)","makeat(?:letter|other)","ExplSyntax(?:On|Off)","(?:new|renew|provide)?command","(?:re)newenvironment","(?:New|Renew|Provide|Declare)(?:Expandable)?DocumentCommand","(?:New|Renew|Provide|Declare)DocumentEnvironment","(?:(?:e|g|x)?def|let)","(?:begin|end)","(?:part|chapter|(?:sub){0,2}section|(?:sub)?paragraph)","caption","(?:label|(?:eq|page|name)?ref|(?:paren|foot|super)?cite)","(?:alpha|beta|[Gg]amma|[Dd]elta|(?:var)?epsilon|zeta|eta|[Tt]heta|vartheta)","(?:iota|(?:var)?kappa|[Ll]ambda|mu|nu|[Xx]i|[Pp]i|varpi|(?:var)rho)","(?:[Ss]igma|varsigma|tau|[Uu]psilon|[Pp]hi|varphi|chi|[Pp]si|[Oo]mega)","(?:frac|sum|prod|lim|infty|times|sqrt|leq|geq|left|right|middle|[bB]igg?)","(?:[lr]angle|q?quad|[lcvdi]?dots|d?dot|hat|tilde|bar)"].map((e=>e+"(?![a-zA-Z@:_])")))
+},{endsParent:!0,
+begin:RegExp(["(?:__)?[a-zA-Z]{2,}_[a-zA-Z](?:_?[a-zA-Z])+:[a-zA-Z]*","[lgc]__?[a-zA-Z](?:_?[a-zA-Z])*_[a-zA-Z]{2,}","[qs]__?[a-zA-Z](?:_?[a-zA-Z])+","use(?:_i)?:[a-zA-Z]*","(?:else|fi|or):","(?:if|cs|exp):w","(?:hbox|vbox):n","::[a-zA-Z]_unbraced","::[a-zA-Z:]"].map((e=>e+"(?![a-zA-Z:_])")).join("|"))
+},{endsParent:!0,variants:n},{endsParent:!0,relevance:0,variants:[{
+begin:/[a-zA-Z@]+/},{begin:/[^a-zA-Z@]?/}]}]},{className:"params",relevance:0,
+begin:/#+\d?/},{variants:n},{className:"built_in",relevance:0,begin:/[$&^_]/},{
+className:"meta",begin:"% !TeX",end:"$",relevance:10},e.COMMENT("%","$",{
+relevance:0})],i={begin:/\{/,end:/\}/,relevance:0,contains:["self",...a]
+},t=e.inherit(i,{relevance:0,endsParent:!0,contains:[i,...a]}),r={begin:/\[/,
+end:/\]/,endsParent:!0,relevance:0,contains:[i,...a]},s={begin:/\s+/,relevance:0
+},c=[t],l=[r],o=(e,n)=>({contains:[s],starts:{relevance:0,contains:e,starts:n}
+}),d=(e,n)=>({begin:"\\\\"+e+"(?![a-zA-Z@:_])",keywords:{$pattern:/\\[a-zA-Z]+/,
+keyword:"\\"+e},relevance:0,contains:[s],starts:n}),g=(n,a)=>e.inherit({
+begin:"\\\\begin(?=[ \t]*(\\r?\\n[ \t]*)?\\{"+n+"\\})",keywords:{
+$pattern:/\\[a-zA-Z]+/,keyword:"\\begin"},relevance:0
+},o(c,a)),m=(n="string")=>e.END_SAME_AS_BEGIN({className:n,begin:/(.|\r?\n)/,
+end:/(.|\r?\n)/,excludeBegin:!0,excludeEnd:!0,endsParent:!0}),b=e=>({
+className:"string",end:"(?=\\\\end\\{"+e+"\\})"}),p=(e="string")=>({relevance:0,
+begin:/\{/,starts:{endsParent:!0,contains:[{className:e,end:/(?=\})/,
+endsParent:!0,contains:[{begin:/\{/,end:/\}/,relevance:0,contains:["self"]}]}]}
+});return{name:"LaTeX",aliases:["TeX"],
+contains:[...["verb","lstinline"].map((e=>d(e,{contains:[m()]}))),d("mint",o(c,{
+contains:[m()]})),d("mintinline",o(c,{contains:[p(),m()]})),d("url",{
+contains:[p("link"),p("link")]}),d("hyperref",{contains:[p("link")]
+}),d("href",o(l,{contains:[p("link")]
+})),...[].concat(...["","\\*"].map((e=>[g("verbatim"+e,b("verbatim"+e)),g("filecontents"+e,o(c,b("filecontents"+e))),...["","B","L"].map((n=>g(n+"Verbatim"+e,o(l,b(n+"Verbatim"+e)))))]))),g("minted",o(l,o(c,b("minted")))),...a]
+}}})());
+hljs.registerLanguage("ldif",(()=>{"use strict";return e=>({name:"LDIF",
+contains:[{className:"attribute",begin:"^dn",end:": ",excludeEnd:!0,starts:{
+end:"$",relevance:0},relevance:10},{className:"attribute",begin:"^\\w",end:": ",
+excludeEnd:!0,starts:{end:"$",relevance:0}},{className:"literal",begin:"^-",
+end:"$"},e.HASH_COMMENT_MODE]})})());
+hljs.registerLanguage("leaf",(()=>{"use strict";return e=>({name:"Leaf",
+contains:[{className:"function",begin:"#+[A-Za-z_0-9]*\\(",end:/ \{/,
+returnBegin:!0,excludeEnd:!0,contains:[{className:"keyword",begin:"#+"},{
+className:"title",begin:"[A-Za-z_][A-Za-z_0-9]*"},{className:"params",
+begin:"\\(",end:"\\)",endsParent:!0,contains:[{className:"string",begin:'"',
+end:'"'},{className:"variable",begin:"[A-Za-z_][A-Za-z_0-9]*"}]}]}]})})());
+hljs.registerLanguage("less",(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],n=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse(),r=i.concat(o)
+;return a=>{const s=(e=>({IMPORTANT:{className:"meta",begin:"!important"},
+HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"},
+ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/,
+illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}
+}))(a),l=r,d="([\\w-]+|@\\{[\\w-]+\\})",c=[],g=[],b=e=>({className:"string",
+begin:"~?"+e+".*?"+e}),m=(e,t,i)=>({className:e,begin:t,relevance:i}),u={
+$pattern:/[a-z-]+/,keyword:"and or not only",attribute:t.join(" ")},p={
+begin:"\\(",end:"\\)",contains:g,keywords:u,relevance:0}
+;g.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,b("'"),b('"'),a.CSS_NUMBER_MODE,{
+begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",
+excludeEnd:!0}
+},s.HEXCOLOR,p,m("variable","@@?[\\w-]+",10),m("variable","@\\{[\\w-]+\\}"),m("built_in","~?`[^`]*?`"),{
+className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0
+},s.IMPORTANT);const f=g.concat({begin:/\{/,end:/\}/,contains:c}),h={
+beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"
+}].concat(g)},w={begin:d+"\\s*:",returnBegin:!0,end:/[;}]/,relevance:0,
+contains:[{begin:/-(webkit|moz|ms|o)-/},{className:"attribute",
+begin:"\\b("+n.join("|")+")\\b",end:/(?=:)/,starts:{endsWithParent:!0,
+illegal:"[<=$]",relevance:0,contains:g}}]},v={className:"keyword",
+begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",
+starts:{end:"[;{}]",keywords:u,returnEnd:!0,contains:g,relevance:0}},y={
+className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{
+begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:f}},k={variants:[{
+begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:d,end:/\{/}],returnBegin:!0,
+returnEnd:!0,illegal:"[<='$\"]",relevance:0,
+contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,h,m("keyword","all\\b"),m("variable","@\\{[\\w-]+\\}"),{
+begin:"\\b("+e.join("|")+")\\b",className:"selector-tag"
+},m("selector-tag",d+"%?",0),m("selector-id","#"+d),m("selector-class","\\."+d,0),m("selector-tag","&",0),s.ATTRIBUTE_SELECTOR_MODE,{
+className:"selector-pseudo",begin:":("+i.join("|")+")"},{
+className:"selector-pseudo",begin:"::("+o.join("|")+")"},{begin:"\\(",end:"\\)",
+contains:f},{begin:"!important"}]},E={begin:`[\\w-]+:(:)?(${l.join("|")})`,
+returnBegin:!0,contains:[k]}
+;return c.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,v,y,E,w,k),{
+name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:c}}})());
+hljs.registerLanguage("lisp",(()=>{"use strict";return e=>{
+var n="[a-zA-Z_\\-+\\*\\/<=>&#][a-zA-Z0-9_\\-+*\\/<=>&#!]*",a="\\|[^]*?\\|",i="(-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|-)?\\d+)?",s={
+className:"literal",begin:"\\b(t{1}|nil)\\b"},l={className:"number",variants:[{
+begin:i,relevance:0},{begin:"#(b|B)[0-1]+(/[0-1]+)?"},{
+begin:"#(o|O)[0-7]+(/[0-7]+)?"},{begin:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{
+begin:"#(c|C)\\("+i+" +"+i,end:"\\)"}]},b=e.inherit(e.QUOTE_STRING_MODE,{
+illegal:null}),g=e.COMMENT(";","$",{relevance:0}),r={begin:"\\*",end:"\\*"},t={
+className:"symbol",begin:"[:&]"+n},c={begin:n,relevance:0},d={begin:a},o={
+contains:[l,b,r,t,{begin:"\\(",end:"\\)",contains:["self",s,b,l,c]},c],
+variants:[{begin:"['`]\\(",end:"\\)"},{begin:"\\(quote ",end:"\\)",keywords:{
+name:"quote"}},{begin:"'"+a}]},v={variants:[{begin:"'"+n},{
+begin:"#'"+n+"(::"+n+")*"}]},m={begin:"\\(\\s*",end:"\\)"},u={endsWithParent:!0,
+relevance:0};return m.contains=[{className:"name",variants:[{begin:n,relevance:0
+},{begin:a}]},u],u.contains=[o,v,m,s,l,b,g,r,t,d,c],{name:"Lisp",illegal:/\S/,
+contains:[l,e.SHEBANG(),s,b,g,o,v,m,c]}}})());
+hljs.registerLanguage("livecodeserver",(()=>{"use strict";return e=>{const r={
+className:"variable",variants:[{
+begin:"\\b([gtps][A-Z]{1}[a-zA-Z0-9]*)(\\[.+\\])?(?:\\s*?)"},{begin:"\\$_[A-Z]+"
+}],relevance:0
+},t=[e.C_BLOCK_COMMENT_MODE,e.HASH_COMMENT_MODE,e.COMMENT("--","$"),e.COMMENT("[^:]//","$")],a=e.inherit(e.TITLE_MODE,{
+variants:[{begin:"\\b_*rig[A-Z][A-Za-z0-9_\\-]*"},{begin:"\\b_[a-z0-9\\-]+"}]
+}),o=e.inherit(e.TITLE_MODE,{begin:"\\b([A-Za-z0-9_\\-]+)\\b"});return{
+name:"LiveCode",case_insensitive:!1,keywords:{
+keyword:"$_COOKIE $_FILES $_GET $_GET_BINARY $_GET_RAW $_POST $_POST_BINARY $_POST_RAW $_SESSION $_SERVER codepoint codepoints segment segments codeunit codeunits sentence sentences trueWord trueWords paragraph after byte bytes english the until http forever descending using line real8 with seventh for stdout finally element word words fourth before black ninth sixth characters chars stderr uInt1 uInt1s uInt2 uInt2s stdin string lines relative rel any fifth items from middle mid at else of catch then third it file milliseconds seconds second secs sec int1 int1s int4 int4s internet int2 int2s normal text item last long detailed effective uInt4 uInt4s repeat end repeat URL in try into switch to words https token binfile each tenth as ticks tick system real4 by dateItems without char character ascending eighth whole dateTime numeric short first ftp integer abbreviated abbr abbrev private case while if div mod wrap and or bitAnd bitNot bitOr bitXor among not in a an within contains ends with begins the keys of keys",
literal:"SIX TEN FORMFEED NINE ZERO NONE SPACE FOUR FALSE COLON CRLF PI COMMA ENDOFFILE EOF EIGHT FIVE QUOTE EMPTY ONE TRUE RETURN CR LINEFEED RIGHT BACKSLASH NULL SEVEN TAB THREE TWO six ten formfeed nine zero none space four false colon crlf pi comma endoffile eof eight five quote empty one true return cr linefeed right backslash null seven tab three two RIVERSION RISTATE FILE_READ_MODE FILE_WRITE_MODE FILE_WRITE_MODE DIR_WRITE_MODE FILE_READ_UMASK FILE_WRITE_UMASK DIR_READ_UMASK DIR_WRITE_UMASK",
-built_in:"put abs acos aliasReference annuity arrayDecode arrayEncode asin atan atan2 average avg avgDev base64Decode base64Encode baseConvert binaryDecode binaryEncode byteOffset byteToNum cachedURL cachedURLs charToNum cipherNames codepointOffset codepointProperty codepointToNum codeunitOffset commandNames compound compress constantNames cos date dateFormat decompress difference directories diskSpace DNSServers exp exp1 exp2 exp10 extents files flushEvents folders format functionNames geometricMean global globals hasMemory harmonicMean hostAddress hostAddressToName hostName hostNameToAddress isNumber ISOToMac itemOffset keys len length libURLErrorData libUrlFormData libURLftpCommand libURLLastHTTPHeaders libURLLastRHHeaders libUrlMultipartFormAddPart libUrlMultipartFormData libURLVersion lineOffset ln ln1 localNames log log2 log10 longFilePath lower macToISO matchChunk matchText matrixMultiply max md5Digest median merge messageAuthenticationCode messageDigest millisec millisecs millisecond milliseconds min monthNames nativeCharToNum normalizeText num number numToByte numToChar numToCodepoint numToNativeChar offset open openfiles openProcesses openProcessIDs openSockets paragraphOffset paramCount param params peerAddress pendingMessages platform popStdDev populationStandardDeviation populationVariance popVariance processID random randomBytes replaceText result revCreateXMLTree revCreateXMLTreeFromFile revCurrentRecord revCurrentRecordIsFirst revCurrentRecordIsLast revDatabaseColumnCount revDatabaseColumnIsNull revDatabaseColumnLengths revDatabaseColumnNames revDatabaseColumnNamed revDatabaseColumnNumbered revDatabaseColumnTypes revDatabaseConnectResult revDatabaseCursors revDatabaseID revDatabaseTableNames revDatabaseType revDataFromQuery revdb_closeCursor revdb_columnbynumber revdb_columncount revdb_columnisnull revdb_columnlengths revdb_columnnames revdb_columntypes revdb_commit revdb_connect revdb_connections revdb_connectionerr revdb_currentrecord revdb_cursorconnection revdb_cursorerr revdb_cursors revdb_dbtype revdb_disconnect revdb_execute revdb_iseof revdb_isbof revdb_movefirst revdb_movelast revdb_movenext revdb_moveprev revdb_query revdb_querylist revdb_recordcount revdb_rollback revdb_tablenames revGetDatabaseDriverPath revNumberOfRecords revOpenDatabase revOpenDatabases revQueryDatabase revQueryDatabaseBlob revQueryResult revQueryIsAtStart revQueryIsAtEnd revUnixFromMacPath revXMLAttribute revXMLAttributes revXMLAttributeValues revXMLChildContents revXMLChildNames revXMLCreateTreeFromFileWithNamespaces revXMLCreateTreeWithNamespaces revXMLDataFromXPathQuery revXMLEvaluateXPath revXMLFirstChild revXMLMatchingNode revXMLNextSibling revXMLNodeContents revXMLNumberOfChildren revXMLParent revXMLPreviousSibling revXMLRootNode revXMLRPC_CreateRequest revXMLRPC_Documents revXMLRPC_Error revXMLRPC_GetHost revXMLRPC_GetMethod revXMLRPC_GetParam revXMLText revXMLRPC_Execute revXMLRPC_GetParamCount revXMLRPC_GetParamNode revXMLRPC_GetParamType revXMLRPC_GetPath revXMLRPC_GetPort revXMLRPC_GetProtocol revXMLRPC_GetRequest revXMLRPC_GetResponse revXMLRPC_GetSocket revXMLTree revXMLTrees revXMLValidateDTD revZipDescribeItem revZipEnumerateItems revZipOpenArchives round sampVariance sec secs seconds sentenceOffset sha1Digest shell shortFilePath sin specialFolderPath sqrt standardDeviation statRound stdDev sum sysError systemVersion tan tempName textDecode textEncode tick ticks time to tokenOffset toLower toUpper transpose truewordOffset trunc uniDecode uniEncode upper URLDecode URLEncode URLStatus uuid value variableNames variance version waitDepth weekdayNames wordOffset xsltApplyStylesheet xsltApplyStylesheetFromFile xsltLoadStylesheet xsltLoadStylesheetFromFile add breakpoint cancel clear local variable file word line folder directory URL close socket process combine constant convert create new alias folder directory decrypt delete variable word line folder directory URL dispatch divide do encrypt filter get include intersect kill libURLDownloadToFile libURLFollowHttpRedirects libURLftpUpload libURLftpUploadFile libURLresetAll libUrlSetAuthCallback libURLSetDriver libURLSetCustomHTTPHeaders libUrlSetExpect100 libURLSetFTPListCommand libURLSetFTPMode libURLSetFTPStopTime libURLSetStatusCallback load extension loadedExtensions multiply socket prepare process post seek rel relative read from process rename replace require resetAll resolve revAddXMLNode revAppendXML revCloseCursor revCloseDatabase revCommitDatabase revCopyFile revCopyFolder revCopyXMLNode revDeleteFolder revDeleteXMLNode revDeleteAllXMLTrees revDeleteXMLTree revExecuteSQL revGoURL revInsertXMLNode revMoveFolder revMoveToFirstRecord revMoveToLastRecord revMoveToNextRecord revMoveToPreviousRecord revMoveToRecord revMoveXMLNode revPutIntoXMLNode revRollBackDatabase revSetDatabaseDriverPath revSetXMLAttribute revXMLRPC_AddParam revXMLRPC_DeleteAllDocuments revXMLAddDTD revXMLRPC_Free revXMLRPC_FreeAll revXMLRPC_DeleteDocument revXMLRPC_DeleteParam revXMLRPC_SetHost revXMLRPC_SetMethod revXMLRPC_SetPort revXMLRPC_SetProtocol revXMLRPC_SetSocket revZipAddItemWithData revZipAddItemWithFile revZipAddUncompressedItemWithData revZipAddUncompressedItemWithFile revZipCancel revZipCloseArchive revZipDeleteItem revZipExtractItemToFile revZipExtractItemToVariable revZipSetProgressCallback revZipRenameItem revZipReplaceItemWithData revZipReplaceItemWithFile revZipOpenArchive send set sort split start stop subtract symmetric union unload vectorDotProduct wait write"},
-contains:[b,{className:"keyword",begin:"\\bend\\sif\\b"},{className:"function",beginKeywords:"function",end:"$",contains:[b,e,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE,d]},{className:"function",begin:"\\bend\\s+",end:"$",keywords:"end",contains:[e,d],relevance:0},{beginKeywords:"command on",end:"$",contains:[b,e,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE,d]},{className:"meta",variants:[{begin:"<\\?(rev|lc|livecode)",relevance:10},
-{begin:"<\\?"},{begin:"\\?>"}]},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE,d].concat(c),illegal:";$|^\\[|^=|&|{"}}}());
-hljs.registerLanguage("livescript",function(){var a="as in of if for while finally var new function do return void else break catch instanceof with throw case default try switch continue typeof delete let yield const class debugger async await static import from export extends".split(" "),b="true false null undefined NaN Infinity".split(" "),c=[].concat("setInterval setTimeout clearInterval clearTimeout require exports eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape".split(" "),"arguments this super console window document localStorage module global".split(" "),
-"Intl DataView Number Math Date String RegExp Object Function Boolean Error Symbol Set Map WeakSet WeakMap Proxy Reflect JSON Promise Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Float32Array Array Uint8Array Uint8ClampedArray ArrayBuffer".split(" "),"EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError".split(" "));return function(d){var e={keyword:a.concat("then unless until loop of by when and or is isnt not it that otherwise from to til fallthrough case enum native list map __hasProp __extends __slice __bind __indexOf".split(" ")).join(" "),
-literal:b.concat("yes no on off it that void".split(" ")).join(" "),built_in:c.concat(["npm","print"]).join(" ")},f=d.inherit(d.TITLE_MODE,{begin:"[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*"}),h={className:"subst",begin:/#\{/,end:/}/,keywords:e},g={className:"subst",begin:/#[A-Za-z$_]/,end:/(?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*/,keywords:e};g=[d.BINARY_NUMBER_MODE,{className:"number",begin:"(\\b0[xX][a-fA-F0-9_]+)|(\\b\\d(\\d|_\\d)*(\\.(\\d(\\d|_\\d)*)?)?(_*[eE]([-+]\\d(_\\d|\\d)*)?)?[_a-z]*)",relevance:0,
-starts:{end:"(\\s*/)?",relevance:0}},{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[d.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[d.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[d.BACKSLASH_ESCAPE,h,g]},{begin:/"/,end:/"/,contains:[d.BACKSLASH_ESCAPE,h,g]},{begin:/\\/,end:/(\s|$)/,excludeEnd:!0}]},{className:"regexp",variants:[{begin:"//",end:"//[gim]*",contains:[h,d.HASH_COMMENT_MODE]},{begin:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W)/}]},{begin:"@[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*"},
-{begin:"``",end:"``",excludeBegin:!0,excludeEnd:!0,subLanguage:"javascript"}];h.contains=g;h={className:"params",begin:"\\(",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:e,contains:["self"].concat(g)}]};return{name:"LiveScript",aliases:["ls"],keywords:e,illegal:/\/\*/,contains:g.concat([d.COMMENT("\\/\\*","\\*\\/"),d.HASH_COMMENT_MODE,{begin:"(#=>|=>|\\|>>|-?->|\\!->)"},{className:"function",contains:[f,h],returnBegin:!0,variants:[{begin:"([A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B\\->\\*?",
-end:"\\->\\*?"},{begin:"([A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*\\s*(?:=|:=)\\s*)?!?(\\(.*\\))?\\s*\\B[-~]{1,2}>\\*?",end:"[-~]{1,2}>\\*?"},{begin:"([A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B!?[-~]{1,2}>\\*?",end:"!?[-~]{1,2}>\\*?"}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[f]},f]},{begin:"[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*:",end:":",returnBegin:!0,
-returnEnd:!0,relevance:0}])}}}());
-hljs.registerLanguage("llvm",function(){return function(a){return{name:"LLVM IR",keywords:"begin end true false declare define global constant private linker_private internal available_externally linkonce linkonce_odr weak weak_odr appending dllimport dllexport common default hidden protected extern_weak external thread_local zeroinitializer undef null to tail target triple datalayout volatile nuw nsw nnan ninf nsz arcp fast exact inbounds align addrspace section alias module asm sideeffect gc dbg linker_private_weak attributes blockaddress initialexec localdynamic localexec prefix unnamed_addr ccc fastcc coldcc x86_stdcallcc x86_fastcallcc arm_apcscc arm_aapcscc arm_aapcs_vfpcc ptx_device ptx_kernel intel_ocl_bicc msp430_intrcc spir_func spir_kernel x86_64_sysvcc x86_64_win64cc x86_thiscallcc cc c signext zeroext inreg sret nounwind noreturn noalias nocapture byval nest readnone readonly inlinehint noinline alwaysinline optsize ssp sspreq noredzone noimplicitfloat naked builtin cold nobuiltin noduplicate nonlazybind optnone returns_twice sanitize_address sanitize_memory sanitize_thread sspstrong uwtable returned type opaque eq ne slt sgt sle sge ult ugt ule uge oeq one olt ogt ole oge ord uno ueq une x acq_rel acquire alignstack atomic catch cleanup filter inteldialect max min monotonic nand personality release seq_cst singlethread umax umin unordered xchg add fadd sub fsub mul fmul udiv sdiv fdiv urem srem frem shl lshr ashr and or xor icmp fcmp phi call trunc zext sext fptrunc fpext uitofp sitofp fptoui fptosi inttoptr ptrtoint bitcast addrspacecast select va_arg ret br switch invoke unwind unreachable indirectbr landingpad resume malloc alloca free load store getelementptr extractelement insertelement shufflevector getresult extractvalue insertvalue atomicrmw cmpxchg fence argmemonly double",contains:[{className:"keyword",
-begin:"i\\d+"},a.COMMENT(";","\\n",{relevance:0}),a.QUOTE_STRING_MODE,{className:"string",variants:[{begin:'"',end:'[^\\\\]"'}],relevance:0},{className:"title",variants:[{begin:"@([-a-zA-Z$._][\\w\\-$.]*)"},{begin:"@\\d+"},{begin:"!([-a-zA-Z$._][\\w\\-$.]*)"},{begin:"!\\d+([-a-zA-Z$._][\\w\\-$.]*)"}]},{className:"symbol",variants:[{begin:"%([-a-zA-Z$._][\\w\\-$.]*)"},{begin:"%\\d+"},{begin:"#\\d+"}]},{className:"number",variants:[{begin:"0[xX][a-fA-F0-9]+"},{begin:"-?\\d+(?:[.]\\d+)?(?:[eE][-+]?\\d+(?:[.]\\d+)?)?"}],
-relevance:0}]}}}());
-hljs.registerLanguage("lsl",function(){return function(a){var b={className:"number",begin:a.C_NUMBER_RE};return{name:"LSL (Linden Scripting Language)",illegal:":",contains:[{className:"string",begin:'"',end:'"',contains:[{className:"subst",begin:/\\[tn"\\]/}]},{className:"comment",variants:[a.COMMENT("//","$"),a.COMMENT("/\\*","\\*/")],relevance:0},b,{className:"section",variants:[{begin:"\\b(?:state|default)\\b"},{begin:"\\b(?:state_(?:entry|exit)|touch(?:_(?:start|end))?|(?:land_)?collision(?:_(?:start|end))?|timer|listen|(?:no_)?sensor|control|(?:not_)?at_(?:rot_)?target|money|email|experience_permissions(?:_denied)?|run_time_permissions|changed|attach|dataserver|moving_(?:start|end)|link_message|(?:on|object)_rez|remote_data|http_re(?:sponse|quest)|path_update|transaction_result)\\b"}]},{className:"built_in",
-begin:"\\b(?:ll(?:AgentInExperience|(?:Create|DataSize|Delete|KeyCount|Keys|Read|Update)KeyValue|GetExperience(?:Details|ErrorMessage)|ReturnObjectsBy(?:ID|Owner)|Json(?:2List|[GS]etValue|ValueType)|Sin|Cos|Tan|Atan2|Sqrt|Pow|Abs|Fabs|Frand|Floor|Ceil|Round|Vec(?:Mag|Norm|Dist)|Rot(?:Between|2(?:Euler|Fwd|Left|Up))|(?:Euler|Axes)2Rot|Whisper|(?:Region|Owner)?Say|Shout|Listen(?:Control|Remove)?|Sensor(?:Repeat|Remove)?|Detected(?:Name|Key|Owner|Type|Pos|Vel|Grab|Rot|Group|LinkNumber)|Die|Ground|Wind|(?:[GS]et)(?:AnimationOverride|MemoryLimit|PrimMediaParams|ParcelMusicURL|Object(?:Desc|Name)|PhysicsMaterial|Status|Scale|Color|Alpha|Texture|Pos|Rot|Force|Torque)|ResetAnimationOverride|(?:Scale|Offset|Rotate)Texture|(?:Rot)?Target(?:Remove)?|(?:Stop)?MoveToTarget|Apply(?:Rotational)?Impulse|Set(?:KeyframedMotion|ContentType|RegionPos|(?:Angular)?Velocity|Buoyancy|HoverHeight|ForceAndTorque|TimerEvent|ScriptState|Damage|TextureAnim|Sound(?:Queueing|Radius)|Vehicle(?:Type|(?:Float|Vector|Rotation)Param)|(?:Touch|Sit)?Text|Camera(?:Eye|At)Offset|PrimitiveParams|ClickAction|Link(?:Alpha|Color|PrimitiveParams(?:Fast)?|Texture(?:Anim)?|Camera|Media)|RemoteScriptAccessPin|PayPrice|LocalRot)|ScaleByFactor|Get(?:(?:Max|Min)ScaleFactor|ClosestNavPoint|StaticPath|SimStats|Env|PrimitiveParams|Link(?:PrimitiveParams|Number(?:OfSides)?|Key|Name|Media)|HTTPHeader|FreeURLs|Object(?:Details|PermMask|PrimCount)|Parcel(?:MaxPrims|Details|Prim(?:Count|Owners))|Attached(?:List)?|(?:SPMax|Free|Used)Memory|Region(?:Name|TimeDilation|FPS|Corner|AgentCount)|Root(?:Position|Rotation)|UnixTime|(?:Parcel|Region)Flags|(?:Wall|GMT)clock|SimulatorHostname|BoundingBox|GeometricCenter|Creator|NumberOf(?:Prims|NotecardLines|Sides)|Animation(?:List)?|(?:Camera|Local)(?:Pos|Rot)|Vel|Accel|Omega|Time(?:stamp|OfDay)|(?:Object|CenterOf)?Mass|MassMKS|Energy|Owner|(?:Owner)?Key|SunDirection|Texture(?:Offset|Scale|Rot)|Inventory(?:Number|Name|Key|Type|Creator|PermMask)|Permissions(?:Key)?|StartParameter|List(?:Length|EntryType)|Date|Agent(?:Size|Info|Language|List)|LandOwnerAt|NotecardLine|Script(?:Name|State))|(?:Get|Reset|GetAndReset)Time|PlaySound(?:Slave)?|LoopSound(?:Master|Slave)?|(?:Trigger|Stop|Preload)Sound|(?:(?:Get|Delete)Sub|Insert)String|To(?:Upper|Lower)|Give(?:InventoryList|Money)|RezObject|(?:Stop)?LookAt|Sleep|CollisionFilter|(?:Take|Release)Controls|DetachFromAvatar|AttachToAvatar(?:Temp)?|InstantMessage|(?:GetNext)?Email|StopHover|MinEventDelay|RotLookAt|String(?:Length|Trim)|(?:Start|Stop)Animation|TargetOmega|Request(?:Experience)?Permissions|(?:Create|Break)Link|BreakAllLinks|(?:Give|Remove)Inventory|Water|PassTouches|Request(?:Agent|Inventory)Data|TeleportAgent(?:Home|GlobalCoords)?|ModifyLand|CollisionSound|ResetScript|MessageLinked|PushObject|PassCollisions|AxisAngle2Rot|Rot2(?:Axis|Angle)|A(?:cos|sin)|AngleBetween|AllowInventoryDrop|SubStringIndex|List2(?:CSV|Integer|Json|Float|String|Key|Vector|Rot|List(?:Strided)?)|DeleteSubList|List(?:Statistics|Sort|Randomize|(?:Insert|Find|Replace)List)|EdgeOfWorld|AdjustSoundVolume|Key2Name|TriggerSoundLimited|EjectFromLand|(?:CSV|ParseString)2List|OverMyLand|SameGroup|UnSit|Ground(?:Slope|Normal|Contour)|GroundRepel|(?:Set|Remove)VehicleFlags|SitOnLink|(?:AvatarOn)?(?:Link)?SitTarget|Script(?:Danger|Profiler)|Dialog|VolumeDetect|ResetOtherScript|RemoteLoadScriptPin|(?:Open|Close)RemoteDataChannel|SendRemoteData|RemoteDataReply|(?:Integer|String)ToBase64|XorBase64|Log(?:10)?|Base64To(?:String|Integer)|ParseStringKeepNulls|RezAtRoot|RequestSimulatorData|ForceMouselook|(?:Load|Release|(?:E|Une)scape)URL|ParcelMedia(?:CommandList|Query)|ModPow|MapDestination|(?:RemoveFrom|AddTo|Reset)Land(?:Pass|Ban)List|(?:Set|Clear)CameraParams|HTTP(?:Request|Response)|TextBox|DetectedTouch(?:UV|Face|Pos|(?:N|Bin)ormal|ST)|(?:MD5|SHA1|DumpList2)String|Request(?:Secure)?URL|Clear(?:Prim|Link)Media|(?:Link)?ParticleSystem|(?:Get|Request)(?:Username|DisplayName)|RegionSayTo|CastRay|GenerateKey|TransferLindenDollars|ManageEstateAccess|(?:Create|Delete)Character|ExecCharacterCmd|Evade|FleeFrom|NavigateTo|PatrolPoints|Pursue|UpdateCharacter|WanderWithin))\\b"},
-{className:"literal",variants:[{begin:"\\b(?:PI|TWO_PI|PI_BY_TWO|DEG_TO_RAD|RAD_TO_DEG|SQRT2)\\b"},{begin:"\\b(?:XP_ERROR_(?:EXPERIENCES_DISABLED|EXPERIENCE_(?:DISABLED|SUSPENDED)|INVALID_(?:EXPERIENCE|PARAMETERS)|KEY_NOT_FOUND|MATURITY_EXCEEDED|NONE|NOT_(?:FOUND|PERMITTED(?:_LAND)?)|NO_EXPERIENCE|QUOTA_EXCEEDED|RETRY_UPDATE|STORAGE_EXCEPTION|STORE_DISABLED|THROTTLED|UNKNOWN_ERROR)|JSON_APPEND|STATUS_(?:PHYSICS|ROTATE_[XYZ]|PHANTOM|SANDBOX|BLOCK_GRAB(?:_OBJECT)?|(?:DIE|RETURN)_AT_EDGE|CAST_SHADOWS|OK|MALFORMED_PARAMS|TYPE_MISMATCH|BOUNDS_ERROR|NOT_(?:FOUND|SUPPORTED)|INTERNAL_ERROR|WHITELIST_FAILED)|AGENT(?:_(?:BY_(?:LEGACY_|USER)NAME|FLYING|ATTACHMENTS|SCRIPTED|MOUSELOOK|SITTING|ON_OBJECT|AWAY|WALKING|IN_AIR|TYPING|CROUCHING|BUSY|ALWAYS_RUN|AUTOPILOT|LIST_(?:PARCEL(?:_OWNER)?|REGION)))?|CAMERA_(?:PITCH|DISTANCE|BEHINDNESS_(?:ANGLE|LAG)|(?:FOCUS|POSITION)(?:_(?:THRESHOLD|LOCKED|LAG))?|FOCUS_OFFSET|ACTIVE)|ANIM_ON|LOOP|REVERSE|PING_PONG|SMOOTH|ROTATE|SCALE|ALL_SIDES|LINK_(?:ROOT|SET|ALL_(?:OTHERS|CHILDREN)|THIS)|ACTIVE|PASS(?:IVE|_(?:ALWAYS|IF_NOT_HANDLED|NEVER))|SCRIPTED|CONTROL_(?:FWD|BACK|(?:ROT_)?(?:LEFT|RIGHT)|UP|DOWN|(?:ML_)?LBUTTON)|PERMISSION_(?:RETURN_OBJECTS|DEBIT|OVERRIDE_ANIMATIONS|SILENT_ESTATE_MANAGEMENT|TAKE_CONTROLS|TRIGGER_ANIMATION|ATTACH|CHANGE_LINKS|(?:CONTROL|TRACK)_CAMERA|TELEPORT)|INVENTORY_(?:TEXTURE|SOUND|OBJECT|SCRIPT|LANDMARK|CLOTHING|NOTECARD|BODYPART|ANIMATION|GESTURE|ALL|NONE)|CHANGED_(?:INVENTORY|COLOR|SHAPE|SCALE|TEXTURE|LINK|ALLOWED_DROP|OWNER|REGION(?:_START)?|TELEPORT|MEDIA)|OBJECT_(?:CLICK_ACTION|HOVER_HEIGHT|LAST_OWNER_ID|(?:PHYSICS|SERVER|STREAMING)_COST|UNKNOWN_DETAIL|CHARACTER_TIME|PHANTOM|PHYSICS|TEMP_(?:ATTACHED|ON_REZ)|NAME|DESC|POS|PRIM_(?:COUNT|EQUIVALENCE)|RETURN_(?:PARCEL(?:_OWNER)?|REGION)|REZZER_KEY|ROO?T|VELOCITY|OMEGA|OWNER|GROUP(?:_TAG)?|CREATOR|ATTACHED_(?:POINT|SLOTS_AVAILABLE)|RENDER_WEIGHT|(?:BODY_SHAPE|PATHFINDING)_TYPE|(?:RUNNING|TOTAL)_SCRIPT_COUNT|TOTAL_INVENTORY_COUNT|SCRIPT_(?:MEMORY|TIME))|TYPE_(?:INTEGER|FLOAT|STRING|KEY|VECTOR|ROTATION|INVALID)|(?:DEBUG|PUBLIC)_CHANNEL|ATTACH_(?:AVATAR_CENTER|CHEST|HEAD|BACK|PELVIS|MOUTH|CHIN|NECK|NOSE|BELLY|[LR](?:SHOULDER|HAND|FOOT|EAR|EYE|[UL](?:ARM|LEG)|HIP)|(?:LEFT|RIGHT)_PEC|HUD_(?:CENTER_[12]|TOP_(?:RIGHT|CENTER|LEFT)|BOTTOM(?:_(?:RIGHT|LEFT))?)|[LR]HAND_RING1|TAIL_(?:BASE|TIP)|[LR]WING|FACE_(?:JAW|[LR]EAR|[LR]EYE|TOUNGE)|GROIN|HIND_[LR]FOOT)|LAND_(?:LEVEL|RAISE|LOWER|SMOOTH|NOISE|REVERT)|DATA_(?:ONLINE|NAME|BORN|SIM_(?:POS|STATUS|RATING)|PAYINFO)|PAYMENT_INFO_(?:ON_FILE|USED)|REMOTE_DATA_(?:CHANNEL|REQUEST|REPLY)|PSYS_(?:PART_(?:BF_(?:ZERO|ONE(?:_MINUS_(?:DEST_COLOR|SOURCE_(ALPHA|COLOR)))?|DEST_COLOR|SOURCE_(ALPHA|COLOR))|BLEND_FUNC_(DEST|SOURCE)|FLAGS|(?:START|END)_(?:COLOR|ALPHA|SCALE|GLOW)|MAX_AGE|(?:RIBBON|WIND|INTERP_(?:COLOR|SCALE)|BOUNCE|FOLLOW_(?:SRC|VELOCITY)|TARGET_(?:POS|LINEAR)|EMISSIVE)_MASK)|SRC_(?:MAX_AGE|PATTERN|ANGLE_(?:BEGIN|END)|BURST_(?:RATE|PART_COUNT|RADIUS|SPEED_(?:MIN|MAX))|ACCEL|TEXTURE|TARGET_KEY|OMEGA|PATTERN_(?:DROP|EXPLODE|ANGLE(?:_CONE(?:_EMPTY)?)?)))|VEHICLE_(?:REFERENCE_FRAME|TYPE_(?:NONE|SLED|CAR|BOAT|AIRPLANE|BALLOON)|(?:LINEAR|ANGULAR)_(?:FRICTION_TIMESCALE|MOTOR_DIRECTION)|LINEAR_MOTOR_OFFSET|HOVER_(?:HEIGHT|EFFICIENCY|TIMESCALE)|BUOYANCY|(?:LINEAR|ANGULAR)_(?:DEFLECTION_(?:EFFICIENCY|TIMESCALE)|MOTOR_(?:DECAY_)?TIMESCALE)|VERTICAL_ATTRACTION_(?:EFFICIENCY|TIMESCALE)|BANKING_(?:EFFICIENCY|MIX|TIMESCALE)|FLAG_(?:NO_DEFLECTION_UP|LIMIT_(?:ROLL_ONLY|MOTOR_UP)|HOVER_(?:(?:WATER|TERRAIN|UP)_ONLY|GLOBAL_HEIGHT)|MOUSELOOK_(?:STEER|BANK)|CAMERA_DECOUPLED))|PRIM_(?:ALLOW_UNSIT|ALPHA_MODE(?:_(?:BLEND|EMISSIVE|MASK|NONE))?|NORMAL|SPECULAR|TYPE(?:_(?:BOX|CYLINDER|PRISM|SPHERE|TORUS|TUBE|RING|SCULPT))?|HOLE_(?:DEFAULT|CIRCLE|SQUARE|TRIANGLE)|MATERIAL(?:_(?:STONE|METAL|GLASS|WOOD|FLESH|PLASTIC|RUBBER))?|SHINY_(?:NONE|LOW|MEDIUM|HIGH)|BUMP_(?:NONE|BRIGHT|DARK|WOOD|BARK|BRICKS|CHECKER|CONCRETE|TILE|STONE|DISKS|GRAVEL|BLOBS|SIDING|LARGETILE|STUCCO|SUCTION|WEAVE)|TEXGEN_(?:DEFAULT|PLANAR)|SCRIPTED_SIT_ONLY|SCULPT_(?:TYPE_(?:SPHERE|TORUS|PLANE|CYLINDER|MASK)|FLAG_(?:MIRROR|INVERT))|PHYSICS(?:_(?:SHAPE_(?:CONVEX|NONE|PRIM|TYPE)))?|(?:POS|ROT)_LOCAL|SLICE|TEXT|FLEXIBLE|POINT_LIGHT|TEMP_ON_REZ|PHANTOM|POSITION|SIT_TARGET|SIZE|ROTATION|TEXTURE|NAME|OMEGA|DESC|LINK_TARGET|COLOR|BUMP_SHINY|FULLBRIGHT|TEXGEN|GLOW|MEDIA_(?:ALT_IMAGE_ENABLE|CONTROLS|(?:CURRENT|HOME)_URL|AUTO_(?:LOOP|PLAY|SCALE|ZOOM)|FIRST_CLICK_INTERACT|(?:WIDTH|HEIGHT)_PIXELS|WHITELIST(?:_ENABLE)?|PERMS_(?:INTERACT|CONTROL)|PARAM_MAX|CONTROLS_(?:STANDARD|MINI)|PERM_(?:NONE|OWNER|GROUP|ANYONE)|MAX_(?:URL_LENGTH|WHITELIST_(?:SIZE|COUNT)|(?:WIDTH|HEIGHT)_PIXELS)))|MASK_(?:BASE|OWNER|GROUP|EVERYONE|NEXT)|PERM_(?:TRANSFER|MODIFY|COPY|MOVE|ALL)|PARCEL_(?:MEDIA_COMMAND_(?:STOP|PAUSE|PLAY|LOOP|TEXTURE|URL|TIME|AGENT|UNLOAD|AUTO_ALIGN|TYPE|SIZE|DESC|LOOP_SET)|FLAG_(?:ALLOW_(?:FLY|(?:GROUP_)?SCRIPTS|LANDMARK|TERRAFORM|DAMAGE|CREATE_(?:GROUP_)?OBJECTS)|USE_(?:ACCESS_(?:GROUP|LIST)|BAN_LIST|LAND_PASS_LIST)|LOCAL_SOUND_ONLY|RESTRICT_PUSHOBJECT|ALLOW_(?:GROUP|ALL)_OBJECT_ENTRY)|COUNT_(?:TOTAL|OWNER|GROUP|OTHER|SELECTED|TEMP)|DETAILS_(?:NAME|DESC|OWNER|GROUP|AREA|ID|SEE_AVATARS))|LIST_STAT_(?:MAX|MIN|MEAN|MEDIAN|STD_DEV|SUM(?:_SQUARES)?|NUM_COUNT|GEOMETRIC_MEAN|RANGE)|PAY_(?:HIDE|DEFAULT)|REGION_FLAG_(?:ALLOW_DAMAGE|FIXED_SUN|BLOCK_TERRAFORM|SANDBOX|DISABLE_(?:COLLISIONS|PHYSICS)|BLOCK_FLY|ALLOW_DIRECT_TELEPORT|RESTRICT_PUSHOBJECT)|HTTP_(?:METHOD|MIMETYPE|BODY_(?:MAXLENGTH|TRUNCATED)|CUSTOM_HEADER|PRAGMA_NO_CACHE|VERBOSE_THROTTLE|VERIFY_CERT)|SIT_(?:INVALID_(?:AGENT|LINK_OBJECT)|NO(?:T_EXPERIENCE|_(?:ACCESS|EXPERIENCE_PERMISSION|SIT_TARGET)))|STRING_(?:TRIM(?:_(?:HEAD|TAIL))?)|CLICK_ACTION_(?:NONE|TOUCH|SIT|BUY|PAY|OPEN(?:_MEDIA)?|PLAY|ZOOM)|TOUCH_INVALID_FACE|PROFILE_(?:NONE|SCRIPT_MEMORY)|RC_(?:DATA_FLAGS|DETECT_PHANTOM|GET_(?:LINK_NUM|NORMAL|ROOT_KEY)|MAX_HITS|REJECT_(?:TYPES|AGENTS|(?:NON)?PHYSICAL|LAND))|RCERR_(?:CAST_TIME_EXCEEDED|SIM_PERF_LOW|UNKNOWN)|ESTATE_ACCESS_(?:ALLOWED_(?:AGENT|GROUP)_(?:ADD|REMOVE)|BANNED_AGENT_(?:ADD|REMOVE))|DENSITY|FRICTION|RESTITUTION|GRAVITY_MULTIPLIER|KFM_(?:COMMAND|CMD_(?:PLAY|STOP|PAUSE)|MODE|FORWARD|LOOP|PING_PONG|REVERSE|DATA|ROTATION|TRANSLATION)|ERR_(?:GENERIC|PARCEL_PERMISSIONS|MALFORMED_PARAMS|RUNTIME_PERMISSIONS|THROTTLED)|CHARACTER_(?:CMD_(?:(?:SMOOTH_)?STOP|JUMP)|DESIRED_(?:TURN_)?SPEED|RADIUS|STAY_WITHIN_PARCEL|LENGTH|ORIENTATION|ACCOUNT_FOR_SKIPPED_FRAMES|AVOIDANCE_MODE|TYPE(?:_(?:[ABCD]|NONE))?|MAX_(?:DECEL|TURN_RADIUS|(?:ACCEL|SPEED)))|PURSUIT_(?:OFFSET|FUZZ_FACTOR|GOAL_TOLERANCE|INTERCEPT)|REQUIRE_LINE_OF_SIGHT|FORCE_DIRECT_PATH|VERTICAL|HORIZONTAL|AVOID_(?:CHARACTERS|DYNAMIC_OBSTACLES|NONE)|PU_(?:EVADE_(?:HIDDEN|SPOTTED)|FAILURE_(?:DYNAMIC_PATHFINDING_DISABLED|INVALID_(?:GOAL|START)|NO_(?:NAVMESH|VALID_DESTINATION)|OTHER|TARGET_GONE|(?:PARCEL_)?UNREACHABLE)|(?:GOAL|SLOWDOWN_DISTANCE)_REACHED)|TRAVERSAL_TYPE(?:_(?:FAST|NONE|SLOW))?|CONTENT_TYPE_(?:ATOM|FORM|HTML|JSON|LLSD|RSS|TEXT|XHTML|XML)|GCNP_(?:RADIUS|STATIC)|(?:PATROL|WANDER)_PAUSE_AT_WAYPOINTS|OPT_(?:AVATAR|CHARACTER|EXCLUSION_VOLUME|LEGACY_LINKSET|MATERIAL_VOLUME|OTHER|STATIC_OBSTACLE|WALKABLE)|SIM_STAT_PCT_CHARS_STEPPED)\\b"},
-{begin:"\\b(?:FALSE|TRUE)\\b"},{begin:"\\b(?:ZERO_ROTATION)\\b"},{begin:"\\b(?:EOF|JSON_(?:ARRAY|DELETE|FALSE|INVALID|NULL|NUMBER|OBJECT|STRING|TRUE)|NULL_KEY|TEXTURE_(?:BLANK|DEFAULT|MEDIA|PLYWOOD|TRANSPARENT)|URL_REQUEST_(?:GRANTED|DENIED))\\b"},{begin:"\\b(?:ZERO_VECTOR|TOUCH_INVALID_(?:TEXCOORD|VECTOR))\\b"}]},{className:"type",begin:"\\b(?:integer|float|string|key|vector|quaternion|rotation|list)\\b"}]}}}());
-hljs.registerLanguage("lua",function(){return function(a){var b={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},c=[a.COMMENT("--(?!\\[=*\\[)","$"),a.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[b],relevance:10})];return{name:"Lua",keywords:{$pattern:a.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},
-contains:c.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[a.inherit(a.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:c}].concat(c)},a.C_NUMBER_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[b],relevance:5}])}}}());
-hljs.registerLanguage("makefile",function(){return function(a){var b={className:"variable",variants:[{begin:"\\$\\("+a.UNDERSCORE_IDENT_RE+"\\)",contains:[a.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]};return{name:"Makefile",aliases:["mk","mak"],keywords:{$pattern:/[\w-]+/,keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"},contains:[a.HASH_COMMENT_MODE,b,{className:"string",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,
-b]},{className:"variable",begin:/\$\([\w-]+\s/,end:/\)/,keywords:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},contains:[b]},{begin:"^"+a.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},{className:"meta",begin:/^\.PHONY:/,end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},{className:"section",
-begin:/^[^\s]+:/,end:/$/,contains:[b]}]}}}());
-hljs.registerLanguage("mathematica",function(){return function(a){return{name:"Mathematica",aliases:["mma","wl"],keywords:{$pattern:"(\\$|\\b)"+a.IDENT_RE+"\\b",keyword:"AASTriangle AbelianGroup Abort AbortKernels AbortProtect AbortScheduledTask Above Abs AbsArg AbsArgPlot Absolute AbsoluteCorrelation AbsoluteCorrelationFunction AbsoluteCurrentValue AbsoluteDashing AbsoluteFileName AbsoluteOptions AbsolutePointSize AbsoluteThickness AbsoluteTime AbsoluteTiming AcceptanceThreshold AccountingForm Accumulate Accuracy AccuracyGoal ActionDelay ActionMenu ActionMenuBox ActionMenuBoxOptions Activate Active ActiveClassification ActiveClassificationObject ActiveItem ActivePrediction ActivePredictionObject ActiveStyle AcyclicGraphQ AddOnHelpPath AddSides AddTo AddToSearchIndex AddUsers AdjacencyGraph AdjacencyList AdjacencyMatrix AdjustmentBox AdjustmentBoxOptions AdjustTimeSeriesForecast AdministrativeDivisionData AffineHalfSpace AffineSpace AffineStateSpaceModel AffineTransform After AggregatedEntityClass AggregationLayer AircraftData AirportData AirPressureData AirTemperatureData AiryAi AiryAiPrime AiryAiZero AiryBi AiryBiPrime AiryBiZero AlgebraicIntegerQ AlgebraicNumber AlgebraicNumberDenominator AlgebraicNumberNorm AlgebraicNumberPolynomial AlgebraicNumberTrace AlgebraicRules AlgebraicRulesData Algebraics AlgebraicUnitQ Alignment AlignmentMarker AlignmentPoint All AllowAdultContent AllowedCloudExtraParameters AllowedCloudParameterExtensions AllowedDimensions AllowedFrequencyRange AllowedHeads AllowGroupClose AllowIncomplete AllowInlineCells AllowKernelInitialization AllowLooseGrammar AllowReverseGroupClose AllowScriptLevelChange AllTrue Alphabet AlphabeticOrder AlphabeticSort AlphaChannel AlternateImage AlternatingFactorial AlternatingGroup AlternativeHypothesis Alternatives AltitudeMethod AmbientLight AmbiguityFunction AmbiguityList Analytic AnatomyData AnatomyForm AnatomyPlot3D AnatomySkinStyle AnatomyStyling AnchoredSearch And AndersonDarlingTest AngerJ AngleBisector AngleBracket AnglePath AnglePath3D AngleVector AngularGauge Animate AnimationCycleOffset AnimationCycleRepetitions AnimationDirection AnimationDisplayTime AnimationRate AnimationRepetitions AnimationRunning AnimationRunTime AnimationTimeIndex Animator AnimatorBox AnimatorBoxOptions AnimatorElements Annotate Annotation AnnotationDelete AnnotationNames AnnotationRules AnnotationValue Annuity AnnuityDue Annulus AnomalyDetection AnomalyDetectorFunction Anonymous Antialiasing AntihermitianMatrixQ Antisymmetric AntisymmetricMatrixQ Antonyms AnyOrder AnySubset AnyTrue Apart ApartSquareFree APIFunction Appearance AppearanceElements AppearanceRules AppellF1 Append AppendCheck AppendLayer AppendTo ApplicationIdentificationKey Apply ApplySides ArcCos ArcCosh ArcCot ArcCoth ArcCsc ArcCsch ArcCurvature ARCHProcess ArcLength ArcSec ArcSech ArcSin ArcSinDistribution ArcSinh ArcTan ArcTanh Area Arg ArgMax ArgMin ArgumentCountQ ARIMAProcess ArithmeticGeometricMean ARMAProcess Around AroundReplace ARProcess Array ArrayComponents ArrayDepth ArrayFilter ArrayFlatten ArrayMesh ArrayPad ArrayPlot ArrayQ ArrayResample ArrayReshape ArrayRules Arrays Arrow Arrow3DBox ArrowBox Arrowheads ASATriangle Ask AskAppend AskConfirm AskDisplay AskedQ AskedValue AskFunction AskState AskTemplateDisplay AspectRatio AspectRatioFixed Assert AssociateTo Association AssociationFormat AssociationMap AssociationQ AssociationThread AssumeDeterministic Assuming Assumptions AstronomicalData AsymptoticDSolveValue AsymptoticEqual AsymptoticEquivalent AsymptoticGreater AsymptoticGreaterEqual AsymptoticIntegrate AsymptoticLess AsymptoticLessEqual AsymptoticOutputTracker AsymptoticRSolveValue AsymptoticSolve AsymptoticSum Asynchronous AsynchronousTaskObject AsynchronousTasks Atom AtomCoordinates AtomCount AtomDiagramCoordinates AtomList AtomQ AttentionLayer Attributes Audio AudioAmplify AudioAnnotate AudioAnnotationLookup AudioBlockMap AudioCapture AudioChannelAssignment AudioChannelCombine AudioChannelMix AudioChannels AudioChannelSeparate AudioData AudioDelay AudioDelete AudioDevice AudioDistance AudioFade AudioFrequencyShift AudioGenerator AudioIdentify AudioInputDevice AudioInsert AudioIntervals AudioJoin AudioLabel AudioLength AudioLocalMeasurements AudioLooping AudioLoudness AudioMeasurements AudioNormalize AudioOutputDevice AudioOverlay AudioPad AudioPan AudioPartition AudioPause AudioPitchShift AudioPlay AudioPlot AudioQ AudioRecord AudioReplace AudioResample AudioReverb AudioSampleRate AudioSpectralMap AudioSpectralTransformation AudioSplit AudioStop AudioStream AudioStreams AudioTimeStretch AudioTrim AudioType AugmentedPolyhedron AugmentedSymmetricPolynomial Authenticate Authentication AuthenticationDialog AutoAction Autocomplete AutocompletionFunction AutoCopy AutocorrelationTest AutoDelete AutoEvaluateEvents AutoGeneratedPackage AutoIndent AutoIndentSpacings AutoItalicWords AutoloadPath AutoMatch Automatic AutomaticImageSize AutoMultiplicationSymbol AutoNumberFormatting AutoOpenNotebooks AutoOpenPalettes AutoQuoteCharacters AutoRefreshed AutoRemove AutorunSequencing AutoScaling AutoScroll AutoSpacing AutoStyleOptions AutoStyleWords AutoSubmitting Axes AxesEdge AxesLabel AxesOrigin AxesStyle AxiomaticTheory Axis BabyMonsterGroupB Back Background BackgroundAppearance BackgroundTasksSettings Backslash Backsubstitution Backward Ball Band BandpassFilter BandstopFilter BarabasiAlbertGraphDistribution BarChart BarChart3D BarcodeImage BarcodeRecognize BaringhausHenzeTest BarLegend BarlowProschanImportance BarnesG BarOrigin BarSpacing BartlettHannWindow BartlettWindow BaseDecode BaseEncode BaseForm Baseline BaselinePosition BaseStyle BasicRecurrentLayer BatchNormalizationLayer BatchSize BatesDistribution BattleLemarieWavelet BayesianMaximization BayesianMaximizationObject BayesianMinimization BayesianMinimizationObject Because BeckmannDistribution Beep Before Begin BeginDialogPacket BeginFrontEndInteractionPacket BeginPackage BellB BellY Below BenfordDistribution BeniniDistribution BenktanderGibratDistribution BenktanderWeibullDistribution BernoulliB BernoulliDistribution BernoulliGraphDistribution BernoulliProcess BernsteinBasis BesselFilterModel BesselI BesselJ BesselJZero BesselK BesselY BesselYZero Beta BetaBinomialDistribution BetaDistribution BetaNegativeBinomialDistribution BetaPrimeDistribution BetaRegularized Between BetweennessCentrality BeveledPolyhedron BezierCurve BezierCurve3DBox BezierCurve3DBoxOptions BezierCurveBox BezierCurveBoxOptions BezierFunction BilateralFilter Binarize BinaryDeserialize BinaryDistance BinaryFormat BinaryImageQ BinaryRead BinaryReadList BinarySerialize BinaryWrite BinCounts BinLists Binomial BinomialDistribution BinomialProcess BinormalDistribution BiorthogonalSplineWavelet BipartiteGraphQ BiquadraticFilterModel BirnbaumImportance BirnbaumSaundersDistribution BitAnd BitClear BitGet BitLength BitNot BitOr BitSet BitShiftLeft BitShiftRight BitXor BiweightLocation BiweightMidvariance Black BlackmanHarrisWindow BlackmanNuttallWindow BlackmanWindow Blank BlankForm BlankNullSequence BlankSequence Blend Block BlockchainAddressData BlockchainBase BlockchainBlockData BlockchainContractValue BlockchainData BlockchainGet BlockchainKeyEncode BlockchainPut BlockchainTokenData BlockchainTransaction BlockchainTransactionData BlockchainTransactionSign BlockchainTransactionSubmit BlockMap BlockRandom BlomqvistBeta BlomqvistBetaTest Blue Blur BodePlot BohmanWindow Bold Bond BondCount BondList BondQ Bookmarks Boole BooleanConsecutiveFunction BooleanConvert BooleanCountingFunction BooleanFunction BooleanGraph BooleanMaxterms BooleanMinimize BooleanMinterms BooleanQ BooleanRegion Booleans BooleanStrings BooleanTable BooleanVariables BorderDimensions BorelTannerDistribution Bottom BottomHatTransform BoundaryDiscretizeGraphics BoundaryDiscretizeRegion BoundaryMesh BoundaryMeshRegion BoundaryMeshRegionQ BoundaryStyle BoundedRegionQ BoundingRegion Bounds Box BoxBaselineShift BoxData BoxDimensions Boxed Boxes BoxForm BoxFormFormatTypes BoxFrame BoxID BoxMargins BoxMatrix BoxObject BoxRatios BoxRotation BoxRotationPoint BoxStyle BoxWhiskerChart Bra BracketingBar BraKet BrayCurtisDistance BreadthFirstScan Break BridgeData BrightnessEqualize BroadcastStationData Brown BrownForsytheTest BrownianBridgeProcess BrowserCategory BSplineBasis BSplineCurve BSplineCurve3DBox BSplineCurve3DBoxOptions BSplineCurveBox BSplineCurveBoxOptions BSplineFunction BSplineSurface BSplineSurface3DBox BSplineSurface3DBoxOptions BubbleChart BubbleChart3D BubbleScale BubbleSizes BuildingData BulletGauge BusinessDayQ ButterflyGraph ButterworthFilterModel Button ButtonBar ButtonBox ButtonBoxOptions ButtonCell ButtonContents ButtonData ButtonEvaluator ButtonExpandable ButtonFrame ButtonFunction ButtonMargins ButtonMinHeight ButtonNote ButtonNotebook ButtonSource ButtonStyle ButtonStyleMenuListing Byte ByteArray ByteArrayFormat ByteArrayQ ByteArrayToString ByteCount ByteOrdering C CachedValue CacheGraphics CachePersistence CalendarConvert CalendarData CalendarType Callout CalloutMarker CalloutStyle CallPacket CanberraDistance Cancel CancelButton CandlestickChart CanonicalGraph CanonicalizePolygon CanonicalizePolyhedron CanonicalName CanonicalWarpingCorrespondence CanonicalWarpingDistance CantorMesh CantorStaircase Cap CapForm CapitalDifferentialD Capitalize CapsuleShape CaptureRunning CardinalBSplineBasis CarlemanLinearize CarmichaelLambda CaseOrdering Cases CaseSensitive Cashflow Casoratian Catalan CatalanNumber Catch Catenate CatenateLayer CauchyDistribution CauchyWindow CayleyGraph CDF CDFDeploy CDFInformation CDFWavelet Ceiling CelestialSystem Cell CellAutoOverwrite CellBaseline CellBoundingBox CellBracketOptions CellChangeTimes CellContents CellContext CellDingbat CellDynamicExpression CellEditDuplicate CellElementsBoundingBox CellElementSpacings CellEpilog CellEvaluationDuplicate CellEvaluationFunction CellEvaluationLanguage CellEventActions CellFrame CellFrameColor CellFrameLabelMargins CellFrameLabels CellFrameMargins CellGroup CellGroupData CellGrouping CellGroupingRules CellHorizontalScrolling CellID CellLabel CellLabelAutoDelete CellLabelMargins CellLabelPositioning CellLabelStyle CellLabelTemplate CellMargins CellObject CellOpen CellPrint CellProlog Cells CellSize CellStyle CellTags CellularAutomaton CensoredDistribution Censoring Center CenterArray CenterDot CentralFeature CentralMoment CentralMomentGeneratingFunction Cepstrogram CepstrogramArray CepstrumArray CForm ChampernowneNumber ChangeOptions ChannelBase ChannelBrokerAction ChannelDatabin ChannelHistoryLength ChannelListen ChannelListener ChannelListeners ChannelListenerWait ChannelObject ChannelPreSendFunction ChannelReceiverFunction ChannelSend ChannelSubscribers ChanVeseBinarize Character CharacterCounts CharacterEncoding CharacterEncodingsPath CharacteristicFunction CharacteristicPolynomial CharacterName CharacterRange Characters ChartBaseStyle ChartElementData ChartElementDataFunction ChartElementFunction ChartElements ChartLabels ChartLayout ChartLegends ChartStyle Chebyshev1FilterModel Chebyshev2FilterModel ChebyshevDistance ChebyshevT ChebyshevU Check CheckAbort CheckAll Checkbox CheckboxBar CheckboxBox CheckboxBoxOptions ChemicalData ChessboardDistance ChiDistribution ChineseRemainder ChiSquareDistribution ChoiceButtons ChoiceDialog CholeskyDecomposition Chop ChromaticityPlot ChromaticityPlot3D ChromaticPolynomial Circle CircleBox CircleDot CircleMinus CirclePlus CirclePoints CircleThrough CircleTimes CirculantGraph CircularOrthogonalMatrixDistribution CircularQuaternionMatrixDistribution CircularRealMatrixDistribution CircularSymplecticMatrixDistribution CircularUnitaryMatrixDistribution Circumsphere CityData ClassifierFunction ClassifierInformation ClassifierMeasurements ClassifierMeasurementsObject Classify ClassPriors Clear ClearAll ClearAttributes ClearCookies ClearPermissions ClearSystemCache ClebschGordan ClickPane Clip ClipboardNotebook ClipFill ClippingStyle ClipPlanes ClipPlanesStyle ClipRange Clock ClockGauge ClockwiseContourIntegral Close Closed CloseKernels ClosenessCentrality Closing ClosingAutoSave ClosingEvent CloudAccountData CloudBase CloudConnect CloudDeploy CloudDirectory CloudDisconnect CloudEvaluate CloudExport CloudExpression CloudExpressions CloudFunction CloudGet CloudImport CloudLoggingData CloudObject CloudObjectInformation CloudObjectInformationData CloudObjectNameFormat CloudObjects CloudObjectURLType CloudPublish CloudPut CloudRenderingMethod CloudSave CloudShare CloudSubmit CloudSymbol CloudUnshare ClusterClassify ClusterDissimilarityFunction ClusteringComponents ClusteringTree CMYKColor Coarse CodeAssistOptions Coefficient CoefficientArrays CoefficientDomain CoefficientList CoefficientRules CoifletWavelet Collect Colon ColonForm ColorBalance ColorCombine ColorConvert ColorCoverage ColorData ColorDataFunction ColorDetect ColorDistance ColorFunction ColorFunctionScaling Colorize ColorNegate ColorOutput ColorProfileData ColorQ ColorQuantize ColorReplace ColorRules ColorSelectorSettings ColorSeparate ColorSetter ColorSetterBox ColorSetterBoxOptions ColorSlider ColorsNear ColorSpace ColorToneMapping Column ColumnAlignments ColumnBackgrounds ColumnForm ColumnLines ColumnsEqual ColumnSpacings ColumnWidths CombinedEntityClass CombinerFunction CometData CommonDefaultFormatTypes Commonest CommonestFilter CommonName CommonUnits CommunityBoundaryStyle CommunityGraphPlot CommunityLabels CommunityRegionStyle CompanyData CompatibleUnitQ CompilationOptions CompilationTarget Compile Compiled CompiledCodeFunction CompiledFunction CompilerOptions Complement CompleteGraph CompleteGraphQ CompleteKaryTree CompletionsListPacket Complex Complexes ComplexExpand ComplexInfinity ComplexityFunction ComplexListPlot ComplexPlot ComplexPlot3D ComponentMeasurements ComponentwiseContextMenu Compose ComposeList ComposeSeries CompositeQ Composition CompoundElement CompoundExpression CompoundPoissonDistribution CompoundPoissonProcess CompoundRenewalProcess Compress CompressedData ComputeUncertainty Condition ConditionalExpression Conditioned Cone ConeBox ConfidenceLevel ConfidenceRange ConfidenceTransform ConfigurationPath ConformAudio ConformImages Congruent ConicHullRegion ConicHullRegion3DBox ConicHullRegionBox ConicOptimization Conjugate ConjugateTranspose Conjunction Connect ConnectedComponents ConnectedGraphComponents ConnectedGraphQ ConnectedMeshComponents ConnectedMoleculeComponents ConnectedMoleculeQ ConnectionSettings ConnectLibraryCallbackFunction ConnectSystemModelComponents ConnesWindow ConoverTest ConsoleMessage ConsoleMessagePacket ConsolePrint Constant ConstantArray ConstantArrayLayer ConstantImage ConstantPlusLayer ConstantRegionQ Constants ConstantTimesLayer ConstellationData ConstrainedMax ConstrainedMin Construct Containing ContainsAll ContainsAny ContainsExactly ContainsNone ContainsOnly ContentFieldOptions ContentLocationFunction ContentObject ContentPadding ContentsBoundingBox ContentSelectable ContentSize Context ContextMenu Contexts ContextToFileName Continuation Continue ContinuedFraction ContinuedFractionK ContinuousAction ContinuousMarkovProcess ContinuousTask ContinuousTimeModelQ ContinuousWaveletData ContinuousWaveletTransform ContourDetect ContourGraphics ContourIntegral ContourLabels ContourLines ContourPlot ContourPlot3D Contours ContourShading ContourSmoothing ContourStyle ContraharmonicMean ContrastiveLossLayer Control ControlActive ControlAlignment ControlGroupContentsBox ControllabilityGramian ControllabilityMatrix ControllableDecomposition ControllableModelQ ControllerDuration ControllerInformation ControllerInformationData ControllerLinking ControllerManipulate ControllerMethod ControllerPath ControllerState ControlPlacement ControlsRendering ControlType Convergents ConversionOptions ConversionRules ConvertToBitmapPacket ConvertToPostScript ConvertToPostScriptPacket ConvexHullMesh ConvexPolygonQ ConvexPolyhedronQ ConvolutionLayer Convolve ConwayGroupCo1 ConwayGroupCo2 ConwayGroupCo3 CookieFunction Cookies CoordinateBoundingBox CoordinateBoundingBoxArray CoordinateBounds CoordinateBoundsArray CoordinateChartData CoordinatesToolOptions CoordinateTransform CoordinateTransformData CoprimeQ Coproduct CopulaDistribution Copyable CopyDatabin CopyDirectory CopyFile CopyTag CopyToClipboard CornerFilter CornerNeighbors Correlation CorrelationDistance CorrelationFunction CorrelationTest Cos Cosh CoshIntegral CosineDistance CosineWindow CosIntegral Cot Coth Count CountDistinct CountDistinctBy CounterAssignments CounterBox CounterBoxOptions CounterClockwiseContourIntegral CounterEvaluator CounterFunction CounterIncrements CounterStyle CounterStyleMenuListing CountRoots CountryData Counts CountsBy Covariance CovarianceEstimatorFunction CovarianceFunction CoxianDistribution CoxIngersollRossProcess CoxModel CoxModelFit CramerVonMisesTest CreateArchive CreateCellID CreateChannel CreateCloudExpression CreateDatabin CreateDataSystemModel CreateDialog CreateDirectory CreateDocument CreateFile CreateIntermediateDirectories CreateManagedLibraryExpression CreateNotebook CreatePalette CreatePalettePacket CreatePermissionsGroup CreateScheduledTask CreateSearchIndex CreateSystemModel CreateTemporary CreateUUID CreateWindow CriterionFunction CriticalityFailureImportance CriticalitySuccessImportance CriticalSection Cross CrossEntropyLossLayer CrossingCount CrossingDetect CrossingPolygon CrossMatrix Csc Csch CTCLossLayer Cube CubeRoot Cubics Cuboid CuboidBox Cumulant CumulantGeneratingFunction Cup CupCap Curl CurlyDoubleQuote CurlyQuote CurrencyConvert CurrentDate CurrentImage CurrentlySpeakingPacket CurrentNotebookImage CurrentScreenImage CurrentValue Curry CurvatureFlowFilter CurveClosed Cyan CycleGraph CycleIndexPolynomial Cycles CyclicGroup Cyclotomic Cylinder CylinderBox CylindricalDecomposition D DagumDistribution DamData DamerauLevenshteinDistance DampingFactor Darker Dashed Dashing DatabaseConnect DatabaseDisconnect DatabaseReference Databin DatabinAdd DatabinRemove Databins DatabinUpload DataCompression DataDistribution DataRange DataReversed Dataset Date DateBounds Dated DateDelimiters DateDifference DatedUnit DateFormat DateFunction DateHistogram DateList DateListLogPlot DateListPlot DateListStepPlot DateObject DateObjectQ DateOverlapsQ DatePattern DatePlus DateRange DateReduction DateString DateTicksFormat DateValue DateWithinQ DaubechiesWavelet DavisDistribution DawsonF DayCount DayCountConvention DayHemisphere DaylightQ DayMatchQ DayName DayNightTerminator DayPlus DayRange DayRound DeBruijnGraph DeBruijnSequence Debug DebugTag Decapitalize Decimal DecimalForm DeclareKnownSymbols DeclarePackage Decompose DeconvolutionLayer Decrement Decrypt DecryptFile DedekindEta DeepSpaceProbeData Default DefaultAxesStyle DefaultBaseStyle DefaultBoxStyle DefaultButton DefaultColor DefaultControlPlacement DefaultDuplicateCellStyle DefaultDuration DefaultElement DefaultFaceGridsStyle DefaultFieldHintStyle DefaultFont DefaultFontProperties DefaultFormatType DefaultFormatTypeForStyle DefaultFrameStyle DefaultFrameTicksStyle DefaultGridLinesStyle DefaultInlineFormatType DefaultInputFormatType DefaultLabelStyle DefaultMenuStyle DefaultNaturalLanguage DefaultNewCellStyle DefaultNewInlineCellStyle DefaultNotebook DefaultOptions DefaultOutputFormatType DefaultPrintPrecision DefaultStyle DefaultStyleDefinitions DefaultTextFormatType DefaultTextInlineFormatType DefaultTicksStyle DefaultTooltipStyle DefaultValue DefaultValues Defer DefineExternal DefineInputStreamMethod DefineOutputStreamMethod DefineResourceFunction Definition Degree DegreeCentrality DegreeGraphDistribution DegreeLexicographic DegreeReverseLexicographic DEigensystem DEigenvalues Deinitialization Del DelaunayMesh Delayed Deletable Delete DeleteAnomalies DeleteBorderComponents DeleteCases DeleteChannel DeleteCloudExpression DeleteContents DeleteDirectory DeleteDuplicates DeleteDuplicatesBy DeleteFile DeleteMissing DeleteObject DeletePermissionsKey DeleteSearchIndex DeleteSmallComponents DeleteStopwords DeleteWithContents DeletionWarning DelimitedArray DelimitedSequence Delimiter DelimiterFlashTime DelimiterMatching Delimiters DeliveryFunction Dendrogram Denominator DensityGraphics DensityHistogram DensityPlot DensityPlot3D DependentVariables Deploy Deployed Depth DepthFirstScan Derivative DerivativeFilter DerivedKey DescriptorStateSpace DesignMatrix DestroyAfterEvaluation Det DeviceClose DeviceConfigure DeviceExecute DeviceExecuteAsynchronous DeviceObject DeviceOpen DeviceOpenQ DeviceRead DeviceReadBuffer DeviceReadLatest DeviceReadList DeviceReadTimeSeries Devices DeviceStreams DeviceWrite DeviceWriteBuffer DGaussianWavelet DiacriticalPositioning Diagonal DiagonalizableMatrixQ DiagonalMatrix DiagonalMatrixQ Dialog DialogIndent DialogInput DialogLevel DialogNotebook DialogProlog DialogReturn DialogSymbols Diamond DiamondMatrix DiceDissimilarity DictionaryLookup DictionaryWordQ DifferenceDelta DifferenceOrder DifferenceQuotient DifferenceRoot DifferenceRootReduce Differences DifferentialD DifferentialRoot DifferentialRootReduce DifferentiatorFilter DigitalSignature DigitBlock DigitBlockMinimum DigitCharacter DigitCount DigitQ DihedralAngle DihedralGroup Dilation DimensionalCombinations DimensionalMeshComponents DimensionReduce DimensionReducerFunction DimensionReduction Dimensions DiracComb DiracDelta DirectedEdge DirectedEdges DirectedGraph DirectedGraphQ DirectedInfinity Direction Directive Directory DirectoryName DirectoryQ DirectoryStack DirichletBeta DirichletCharacter DirichletCondition DirichletConvolve DirichletDistribution DirichletEta DirichletL DirichletLambda DirichletTransform DirichletWindow DisableConsolePrintPacket DisableFormatting DiscreteChirpZTransform DiscreteConvolve DiscreteDelta DiscreteHadamardTransform DiscreteIndicator DiscreteLimit DiscreteLQEstimatorGains DiscreteLQRegulatorGains DiscreteLyapunovSolve DiscreteMarkovProcess DiscreteMaxLimit DiscreteMinLimit DiscretePlot DiscretePlot3D DiscreteRatio DiscreteRiccatiSolve DiscreteShift DiscreteTimeModelQ DiscreteUniformDistribution DiscreteVariables DiscreteWaveletData DiscreteWaveletPacketTransform DiscreteWaveletTransform DiscretizeGraphics DiscretizeRegion Discriminant DisjointQ Disjunction Disk DiskBox DiskMatrix DiskSegment Dispatch DispatchQ DispersionEstimatorFunction Display DisplayAllSteps DisplayEndPacket DisplayFlushImagePacket DisplayForm DisplayFunction DisplayPacket DisplayRules DisplaySetSizePacket DisplayString DisplayTemporary DisplayWith DisplayWithRef DisplayWithVariable DistanceFunction DistanceMatrix DistanceTransform Distribute Distributed DistributedContexts DistributeDefinitions DistributionChart DistributionDomain DistributionFitTest DistributionParameterAssumptions DistributionParameterQ Dithering Div Divergence Divide DivideBy Dividers DivideSides Divisible Divisors DivisorSigma DivisorSum DMSList DMSString Do DockedCells DocumentGenerator DocumentGeneratorInformation DocumentGeneratorInformationData DocumentGenerators DocumentNotebook DocumentWeightingRules Dodecahedron DomainRegistrationInformation DominantColors DOSTextFormat Dot DotDashed DotEqual DotLayer DotPlusLayer Dotted DoubleBracketingBar DoubleContourIntegral DoubleDownArrow DoubleLeftArrow DoubleLeftRightArrow DoubleLeftTee DoubleLongLeftArrow DoubleLongLeftRightArrow DoubleLongRightArrow DoubleRightArrow DoubleRightTee DoubleUpArrow DoubleUpDownArrow DoubleVerticalBar DoublyInfinite Down DownArrow DownArrowBar DownArrowUpArrow DownLeftRightVector DownLeftTeeVector DownLeftVector DownLeftVectorBar DownRightTeeVector DownRightVector DownRightVectorBar Downsample DownTee DownTeeArrow DownValues DragAndDrop DrawEdges DrawFrontFaces DrawHighlighted Drop DropoutLayer DSolve DSolveValue Dt DualLinearProgramming DualPolyhedron DualSystemsModel DumpGet DumpSave DuplicateFreeQ Duration Dynamic DynamicBox DynamicBoxOptions DynamicEvaluationTimeout DynamicGeoGraphics DynamicImage DynamicLocation DynamicModule DynamicModuleBox DynamicModuleBoxOptions DynamicModuleParent DynamicModuleValues DynamicName DynamicNamespace DynamicReference DynamicSetting DynamicUpdating DynamicWrapper DynamicWrapperBox DynamicWrapperBoxOptions E EarthImpactData EarthquakeData EccentricityCentrality Echo EchoFunction EclipseType EdgeAdd EdgeBetweennessCentrality EdgeCapacity EdgeCapForm EdgeColor EdgeConnectivity EdgeContract EdgeCost EdgeCount EdgeCoverQ EdgeCycleMatrix EdgeDashing EdgeDelete EdgeDetect EdgeForm EdgeIndex EdgeJoinForm EdgeLabeling EdgeLabels EdgeLabelStyle EdgeList EdgeOpacity EdgeQ EdgeRenderingFunction EdgeRules EdgeShapeFunction EdgeStyle EdgeThickness EdgeWeight EdgeWeightedGraphQ Editable EditButtonSettings EditCellTagsSettings EditDistance EffectiveInterest Eigensystem Eigenvalues EigenvectorCentrality Eigenvectors Element ElementData ElementwiseLayer ElidedForms Eliminate EliminationOrder Ellipsoid EllipticE EllipticExp EllipticExpPrime EllipticF EllipticFilterModel EllipticK EllipticLog EllipticNomeQ EllipticPi EllipticReducedHalfPeriods EllipticTheta EllipticThetaPrime EmbedCode EmbeddedHTML EmbeddedService EmbeddingLayer EmbeddingObject EmitSound EmphasizeSyntaxErrors EmpiricalDistribution Empty EmptyGraphQ EmptyRegion EnableConsolePrintPacket Enabled Encode Encrypt EncryptedObject EncryptFile End EndAdd EndDialogPacket EndFrontEndInteractionPacket EndOfBuffer EndOfFile EndOfLine EndOfString EndPackage EngineEnvironment EngineeringForm Enter EnterExpressionPacket EnterTextPacket Entity EntityClass EntityClassList EntityCopies EntityFunction EntityGroup EntityInstance EntityList EntityPrefetch EntityProperties EntityProperty EntityPropertyClass EntityRegister EntityStore EntityStores EntityTypeName EntityUnregister EntityValue Entropy EntropyFilter Environment Epilog EpilogFunction Equal EqualColumns EqualRows EqualTilde EqualTo EquatedTo Equilibrium EquirippleFilterKernel Equivalent Erf Erfc Erfi ErlangB ErlangC ErlangDistribution Erosion ErrorBox ErrorBoxOptions ErrorNorm ErrorPacket ErrorsDialogSettings EscapeRadius EstimatedBackground EstimatedDistribution EstimatedProcess EstimatorGains EstimatorRegulator EuclideanDistance EulerAngles EulerCharacteristic EulerE EulerGamma EulerianGraphQ EulerMatrix EulerPhi Evaluatable Evaluate Evaluated EvaluatePacket EvaluateScheduledTask EvaluationBox EvaluationCell EvaluationCompletionAction EvaluationData EvaluationElements EvaluationEnvironment EvaluationMode EvaluationMonitor EvaluationNotebook EvaluationObject EvaluationOrder Evaluator EvaluatorNames EvenQ EventData EventEvaluator EventHandler EventHandlerTag EventLabels EventSeries ExactBlackmanWindow ExactNumberQ ExactRootIsolation ExampleData Except ExcludedForms ExcludedLines ExcludedPhysicalQuantities ExcludePods Exclusions ExclusionsStyle Exists Exit ExitDialog ExoplanetData Exp Expand ExpandAll ExpandDenominator ExpandFileName ExpandNumerator Expectation ExpectationE ExpectedValue ExpGammaDistribution ExpIntegralE ExpIntegralEi ExpirationDate Exponent ExponentFunction ExponentialDistribution ExponentialFamily ExponentialGeneratingFunction ExponentialMovingAverage ExponentialPowerDistribution ExponentPosition ExponentStep Export ExportAutoReplacements ExportByteArray ExportForm ExportPacket ExportString Expression ExpressionCell ExpressionPacket ExpressionUUID ExpToTrig ExtendedEntityClass ExtendedGCD Extension ExtentElementFunction ExtentMarkers ExtentSize ExternalBundle ExternalCall ExternalDataCharacterEncoding ExternalEvaluate ExternalFunction ExternalFunctionName ExternalObject ExternalOptions ExternalSessionObject ExternalSessions ExternalTypeSignature ExternalValue Extract ExtractArchive ExtractLayer ExtremeValueDistribution FaceForm FaceGrids FaceGridsStyle FacialFeatures Factor FactorComplete Factorial Factorial2 FactorialMoment FactorialMomentGeneratingFunction FactorialPower FactorInteger FactorList FactorSquareFree FactorSquareFreeList FactorTerms FactorTermsList Fail Failure FailureAction FailureDistribution FailureQ False FareySequence FARIMAProcess FeatureDistance FeatureExtract FeatureExtraction FeatureExtractor FeatureExtractorFunction FeatureNames FeatureNearest FeatureSpacePlot FeatureSpacePlot3D FeatureTypes FEDisableConsolePrintPacket FeedbackLinearize FeedbackSector FeedbackSectorStyle FeedbackType FEEnableConsolePrintPacket FetalGrowthData Fibonacci Fibonorial FieldCompletionFunction FieldHint FieldHintStyle FieldMasked FieldSize File FileBaseName FileByteCount FileConvert FileDate FileExistsQ FileExtension FileFormat FileHandler FileHash FileInformation FileName FileNameDepth FileNameDialogSettings FileNameDrop FileNameForms FileNameJoin FileNames FileNameSetter FileNameSplit FileNameTake FilePrint FileSize FileSystemMap FileSystemScan FileTemplate FileTemplateApply FileType FilledCurve FilledCurveBox FilledCurveBoxOptions Filling FillingStyle FillingTransform FilteredEntityClass FilterRules FinancialBond FinancialData FinancialDerivative FinancialIndicator Find FindAnomalies FindArgMax FindArgMin FindChannels FindClique FindClusters FindCookies FindCurvePath FindCycle FindDevices FindDistribution FindDistributionParameters FindDivisions FindEdgeCover FindEdgeCut FindEdgeIndependentPaths FindEquationalProof FindEulerianCycle FindExternalEvaluators FindFaces FindFile FindFit FindFormula FindFundamentalCycles FindGeneratingFunction FindGeoLocation FindGeometricConjectures FindGeometricTransform FindGraphCommunities FindGraphIsomorphism FindGraphPartition FindHamiltonianCycle FindHamiltonianPath FindHiddenMarkovStates FindIndependentEdgeSet FindIndependentVertexSet FindInstance FindIntegerNullVector FindKClan FindKClique FindKClub FindKPlex FindLibrary FindLinearRecurrence FindList FindMatchingColor FindMaximum FindMaximumFlow FindMaxValue FindMeshDefects FindMinimum FindMinimumCostFlow FindMinimumCut FindMinValue FindMoleculeSubstructure FindPath FindPeaks FindPermutation FindPostmanTour FindProcessParameters FindRepeat FindRoot FindSequenceFunction FindSettings FindShortestPath FindShortestTour FindSpanningTree FindSystemModelEquilibrium FindTextualAnswer FindThreshold FindTransientRepeat FindVertexCover FindVertexCut FindVertexIndependentPaths Fine FinishDynamic FiniteAbelianGroupCount FiniteGroupCount FiniteGroupData First FirstCase FirstPassageTimeDistribution FirstPosition FischerGroupFi22 FischerGroupFi23 FischerGroupFi24Prime FisherHypergeometricDistribution FisherRatioTest FisherZDistribution Fit FitAll FitRegularization FittedModel FixedOrder FixedPoint FixedPointList FlashSelection Flat Flatten FlattenAt FlattenLayer FlatTopWindow FlipView Floor FlowPolynomial FlushPrintOutputPacket Fold FoldList FoldPair FoldPairList FollowRedirects Font FontColor FontFamily FontForm FontName FontOpacity FontPostScriptName FontProperties FontReencoding FontSize FontSlant FontSubstitutions FontTracking FontVariations FontWeight For ForAll Format FormatRules FormatType FormatTypeAutoConvert FormatValues FormBox FormBoxOptions FormControl FormFunction FormLayoutFunction FormObject FormPage FormTheme FormulaData FormulaLookup FortranForm Forward ForwardBackward Fourier FourierCoefficient FourierCosCoefficient FourierCosSeries FourierCosTransform FourierDCT FourierDCTFilter FourierDCTMatrix FourierDST FourierDSTMatrix FourierMatrix FourierParameters FourierSequenceTransform FourierSeries FourierSinCoefficient FourierSinSeries FourierSinTransform FourierTransform FourierTrigSeries FractionalBrownianMotionProcess FractionalGaussianNoiseProcess FractionalPart FractionBox FractionBoxOptions FractionLine Frame FrameBox FrameBoxOptions Framed FrameInset FrameLabel Frameless FrameMargins FrameRate FrameStyle FrameTicks FrameTicksStyle FRatioDistribution FrechetDistribution FreeQ FrenetSerretSystem FrequencySamplingFilterKernel FresnelC FresnelF FresnelG FresnelS Friday FrobeniusNumber FrobeniusSolve FromAbsoluteTime FromCharacterCode FromCoefficientRules FromContinuedFraction FromDate FromDigits FromDMS FromEntity FromJulianDate FromLetterNumber FromPolarCoordinates FromRomanNumeral FromSphericalCoordinates FromUnixTime Front FrontEndDynamicExpression FrontEndEventActions FrontEndExecute FrontEndObject FrontEndResource FrontEndResourceString FrontEndStackSize FrontEndToken FrontEndTokenExecute FrontEndValueCache FrontEndVersion FrontFaceColor FrontFaceOpacity Full FullAxes FullDefinition FullForm FullGraphics FullInformationOutputRegulator FullOptions FullRegion FullSimplify Function FunctionCompile FunctionCompileExport FunctionCompileExportByteArray FunctionCompileExportLibrary FunctionCompileExportString FunctionDomain FunctionExpand FunctionInterpolation FunctionPeriod FunctionRange FunctionSpace FussellVeselyImportanceGaborFilter GaborMatrix GaborWavelet GainMargins GainPhaseMargins GalaxyData GalleryView Gamma GammaDistribution GammaRegularized GapPenalty GARCHProcess GatedRecurrentLayer Gather GatherBy GaugeFaceElementFunction GaugeFaceStyle GaugeFrameElementFunction GaugeFrameSize GaugeFrameStyle GaugeLabels GaugeMarkers GaugeStyle GaussianFilter GaussianIntegers GaussianMatrix GaussianOrthogonalMatrixDistribution GaussianSymplecticMatrixDistribution GaussianUnitaryMatrixDistribution GaussianWindow GCD GegenbauerC General GeneralizedLinearModelFit GenerateAsymmetricKeyPair GenerateConditions GeneratedCell GeneratedDocumentBinding GenerateDerivedKey GenerateDigitalSignature GenerateDocument GeneratedParameters GeneratedQuantityMagnitudes GenerateHTTPResponse GenerateSecuredAuthenticationKey GenerateSymmetricKey GeneratingFunction GeneratorDescription GeneratorHistoryLength GeneratorOutputType Generic GenericCylindricalDecomposition GenomeData GenomeLookup GeoAntipode GeoArea GeoArraySize GeoBackground GeoBoundingBox GeoBounds GeoBoundsRegion GeoBubbleChart GeoCenter GeoCircle GeodesicClosing GeodesicDilation GeodesicErosion GeodesicOpening GeoDestination GeodesyData GeoDirection GeoDisk GeoDisplacement GeoDistance GeoDistanceList GeoElevationData GeoEntities GeoGraphics GeogravityModelData GeoGridDirectionDifference GeoGridLines GeoGridLinesStyle GeoGridPosition GeoGridRange GeoGridRangePadding GeoGridUnitArea GeoGridUnitDistance GeoGridVector GeoGroup GeoHemisphere GeoHemisphereBoundary GeoHistogram GeoIdentify GeoImage GeoLabels GeoLength GeoListPlot GeoLocation GeologicalPeriodData GeomagneticModelData GeoMarker GeometricAssertion GeometricBrownianMotionProcess GeometricDistribution GeometricMean GeometricMeanFilter GeometricScene GeometricTransformation GeometricTransformation3DBox GeometricTransformation3DBoxOptions GeometricTransformationBox GeometricTransformationBoxOptions GeoModel GeoNearest GeoPath GeoPosition GeoPositionENU GeoPositionXYZ GeoProjection GeoProjectionData GeoRange GeoRangePadding GeoRegionValuePlot GeoResolution GeoScaleBar GeoServer GeoSmoothHistogram GeoStreamPlot GeoStyling GeoStylingImageFunction GeoVariant GeoVector GeoVectorENU GeoVectorPlot GeoVectorXYZ GeoVisibleRegion GeoVisibleRegionBoundary GeoWithinQ GeoZoomLevel GestureHandler GestureHandlerTag Get GetBoundingBoxSizePacket GetContext GetEnvironment GetFileName GetFrontEndOptionsDataPacket GetLinebreakInformationPacket GetMenusPacket GetPageBreakInformationPacket Glaisher GlobalClusteringCoefficient GlobalPreferences GlobalSession Glow GoldenAngle GoldenRatio GompertzMakehamDistribution GoodmanKruskalGamma GoodmanKruskalGammaTest Goto Grad Gradient GradientFilter GradientOrientationFilter GrammarApply GrammarRules GrammarToken Graph Graph3D GraphAssortativity GraphAutomorphismGroup GraphCenter GraphComplement GraphData GraphDensity GraphDiameter GraphDifference GraphDisjointUnion GraphDistance GraphDistanceMatrix GraphElementData GraphEmbedding GraphHighlight GraphHighlightStyle GraphHub Graphics Graphics3D Graphics3DBox Graphics3DBoxOptions GraphicsArray GraphicsBaseline GraphicsBox GraphicsBoxOptions GraphicsColor GraphicsColumn GraphicsComplex GraphicsComplex3DBox GraphicsComplex3DBoxOptions GraphicsComplexBox GraphicsComplexBoxOptions GraphicsContents GraphicsData GraphicsGrid GraphicsGridBox GraphicsGroup GraphicsGroup3DBox GraphicsGroup3DBoxOptions GraphicsGroupBox GraphicsGroupBoxOptions GraphicsGrouping GraphicsHighlightColor GraphicsRow GraphicsSpacing GraphicsStyle GraphIntersection GraphLayout GraphLinkEfficiency GraphPeriphery GraphPlot GraphPlot3D GraphPower GraphPropertyDistribution GraphQ GraphRadius GraphReciprocity GraphRoot GraphStyle GraphUnion Gray GrayLevel Greater GreaterEqual GreaterEqualLess GreaterEqualThan GreaterFullEqual GreaterGreater GreaterLess GreaterSlantEqual GreaterThan GreaterTilde Green GreenFunction Grid GridBaseline GridBox GridBoxAlignment GridBoxBackground GridBoxDividers GridBoxFrame GridBoxItemSize GridBoxItemStyle GridBoxOptions GridBoxSpacings GridCreationSettings GridDefaultElement GridElementStyleOptions GridFrame GridFrameMargins GridGraph GridLines GridLinesStyle GroebnerBasis GroupActionBase GroupBy GroupCentralizer GroupElementFromWord GroupElementPosition GroupElementQ GroupElements GroupElementToWord GroupGenerators Groupings GroupMultiplicationTable GroupOrbits GroupOrder GroupPageBreakWithin GroupSetwiseStabilizer GroupStabilizer GroupStabilizerChain GroupTogetherGrouping GroupTogetherNestedGrouping GrowCutComponents Gudermannian GuidedFilter GumbelDistribution HaarWavelet HadamardMatrix HalfLine HalfNormalDistribution HalfPlane HalfSpace HamiltonianGraphQ HammingDistance HammingWindow HandlerFunctions HandlerFunctionsKeys HankelH1 HankelH2 HankelMatrix HankelTransform HannPoissonWindow HannWindow HaradaNortonGroupHN HararyGraph HarmonicMean HarmonicMeanFilter HarmonicNumber Hash Haversine HazardFunction Head HeadCompose HeaderLines Heads HeavisideLambda HeavisidePi HeavisideTheta HeldGroupHe HeldPart HelpBrowserLookup HelpBrowserNotebook HelpBrowserSettings Here HermiteDecomposition HermiteH HermitianMatrixQ HessenbergDecomposition Hessian HexadecimalCharacter Hexahedron HexahedronBox HexahedronBoxOptions HiddenMarkovProcess HiddenSurface Highlighted HighlightGraph HighlightImage HighlightMesh HighpassFilter HigmanSimsGroupHS HilbertCurve HilbertFilter HilbertMatrix Histogram Histogram3D HistogramDistribution HistogramList HistogramTransform HistogramTransformInterpolation HistoricalPeriodData HitMissTransform HITSCentrality HjorthDistribution HodgeDual HoeffdingD HoeffdingDTest Hold HoldAll HoldAllComplete HoldComplete HoldFirst HoldForm HoldPattern HoldRest HolidayCalendar HomeDirectory HomePage Horizontal HorizontalForm HorizontalGauge HorizontalScrollPosition HornerForm HostLookup HotellingTSquareDistribution HoytDistribution HTMLSave HTTPErrorResponse HTTPRedirect HTTPRequest HTTPRequestData HTTPResponse Hue HumanGrowthData HumpDownHump HumpEqual HurwitzLerchPhi HurwitzZeta HyperbolicDistribution HypercubeGraph HyperexponentialDistribution Hyperfactorial Hypergeometric0F1 Hypergeometric0F1Regularized Hypergeometric1F1 Hypergeometric1F1Regularized Hypergeometric2F1 Hypergeometric2F1Regularized HypergeometricDistribution HypergeometricPFQ HypergeometricPFQRegularized HypergeometricU Hyperlink HyperlinkCreationSettings Hyperplane Hyphenation HyphenationOptions HypoexponentialDistribution HypothesisTestData I IconData Iconize IconizedObject IconRules Icosahedron Identity IdentityMatrix If IgnoreCase IgnoreDiacritics IgnorePunctuation IgnoreSpellCheck IgnoringInactive Im Image Image3D Image3DProjection Image3DSlices ImageAccumulate ImageAdd ImageAdjust ImageAlign ImageApply ImageApplyIndexed ImageAspectRatio ImageAssemble ImageAugmentationLayer ImageBoundingBoxes ImageCache ImageCacheValid ImageCapture ImageCaptureFunction ImageCases ImageChannels ImageClip ImageCollage ImageColorSpace ImageCompose ImageContainsQ ImageContents ImageConvolve ImageCooccurrence ImageCorners ImageCorrelate ImageCorrespondingPoints ImageCrop ImageData ImageDeconvolve ImageDemosaic ImageDifference ImageDimensions ImageDisplacements ImageDistance ImageEffect ImageExposureCombine ImageFeatureTrack ImageFileApply ImageFileFilter ImageFileScan ImageFilter ImageFocusCombine ImageForestingComponents ImageFormattingWidth ImageForwardTransformation ImageGraphics ImageHistogram ImageIdentify ImageInstanceQ ImageKeypoints ImageLevels ImageLines ImageMargins ImageMarker ImageMarkers ImageMeasurements ImageMesh ImageMultiply ImageOffset ImagePad ImagePadding ImagePartition ImagePeriodogram ImagePerspectiveTransformation ImagePosition ImagePreviewFunction ImagePyramid ImagePyramidApply ImageQ ImageRangeCache ImageRecolor ImageReflect ImageRegion ImageResize ImageResolution ImageRestyle ImageRotate ImageRotated ImageSaliencyFilter ImageScaled ImageScan ImageSize ImageSizeAction ImageSizeCache ImageSizeMultipliers ImageSizeRaw ImageSubtract ImageTake ImageTransformation ImageTrim ImageType ImageValue ImageValuePositions ImagingDevice ImplicitRegion Implies Import ImportAutoReplacements ImportByteArray ImportOptions ImportString ImprovementImportance In Inactivate Inactive IncidenceGraph IncidenceList IncidenceMatrix IncludeAromaticBonds IncludeConstantBasis IncludeDefinitions IncludeDirectories IncludeFileExtension IncludeGeneratorTasks IncludeHydrogens IncludeInflections IncludeMetaInformation IncludePods IncludeQuantities IncludeRelatedTables IncludeSingularTerm IncludeWindowTimes Increment IndefiniteMatrixQ Indent IndentingNewlineSpacings IndentMaxFraction IndependenceTest IndependentEdgeSetQ IndependentPhysicalQuantity IndependentUnit IndependentUnitDimension IndependentVertexSetQ Indeterminate IndeterminateThreshold IndexCreationOptions Indexed IndexGraph IndexTag Inequality InexactNumberQ InexactNumbers InfiniteLine InfinitePlane Infinity Infix InflationAdjust InflationMethod Information InformationData InformationDataGrid Inherited InheritScope InhomogeneousPoissonProcess InitialEvaluationHistory Initialization InitializationCell InitializationCellEvaluation InitializationCellWarning InitializationObjects InitializationValue Initialize InitialSeeding InlineCounterAssignments InlineCounterIncrements InlineRules Inner InnerPolygon InnerPolyhedron Inpaint Input InputAliases InputAssumptions InputAutoReplacements InputField InputFieldBox InputFieldBoxOptions InputForm InputGrouping InputNamePacket InputNotebook InputPacket InputSettings InputStream InputString InputStringPacket InputToBoxFormPacket Insert InsertionFunction InsertionPointObject InsertLinebreaks InsertResults Inset Inset3DBox Inset3DBoxOptions InsetBox InsetBoxOptions Insphere Install InstallService InstanceNormalizationLayer InString Integer IntegerDigits IntegerExponent IntegerLength IntegerName IntegerPart IntegerPartitions IntegerQ IntegerReverse Integers IntegerString Integral Integrate Interactive InteractiveTradingChart Interlaced Interleaving InternallyBalancedDecomposition InterpolatingFunction InterpolatingPolynomial Interpolation InterpolationOrder InterpolationPoints InterpolationPrecision Interpretation InterpretationBox InterpretationBoxOptions InterpretationFunction Interpreter InterpretTemplate InterquartileRange Interrupt InterruptSettings IntersectingQ Intersection Interval IntervalIntersection IntervalMarkers IntervalMarkersStyle IntervalMemberQ IntervalSlider IntervalUnion Into Inverse InverseBetaRegularized InverseCDF InverseChiSquareDistribution InverseContinuousWaveletTransform InverseDistanceTransform InverseEllipticNomeQ InverseErf InverseErfc InverseFourier InverseFourierCosTransform InverseFourierSequenceTransform InverseFourierSinTransform InverseFourierTransform InverseFunction InverseFunctions InverseGammaDistribution InverseGammaRegularized InverseGaussianDistribution InverseGudermannian InverseHankelTransform InverseHaversine InverseImagePyramid InverseJacobiCD InverseJacobiCN InverseJacobiCS InverseJacobiDC InverseJacobiDN InverseJacobiDS InverseJacobiNC InverseJacobiND InverseJacobiNS InverseJacobiSC InverseJacobiSD InverseJacobiSN InverseLaplaceTransform InverseMellinTransform InversePermutation InverseRadon InverseRadonTransform InverseSeries InverseShortTimeFourier InverseSpectrogram InverseSurvivalFunction InverseTransformedRegion InverseWaveletTransform InverseWeierstrassP InverseWishartMatrixDistribution InverseZTransform Invisible InvisibleApplication InvisibleTimes IPAddress IrreduciblePolynomialQ IslandData IsolatingInterval IsomorphicGraphQ IsotopeData Italic Item ItemAspectRatio ItemBox ItemBoxOptions ItemSize ItemStyle ItoProcess JaccardDissimilarity JacobiAmplitude Jacobian JacobiCD JacobiCN JacobiCS JacobiDC JacobiDN JacobiDS JacobiNC JacobiND JacobiNS JacobiP JacobiSC JacobiSD JacobiSN JacobiSymbol JacobiZeta JankoGroupJ1 JankoGroupJ2 JankoGroupJ3 JankoGroupJ4 JarqueBeraALMTest JohnsonDistribution Join JoinAcross Joined JoinedCurve JoinedCurveBox JoinedCurveBoxOptions JoinForm JordanDecomposition JordanModelDecomposition JulianDate JuliaSetBoettcher JuliaSetIterationCount JuliaSetPlot JuliaSetPoints K KagiChart KaiserBesselWindow KaiserWindow KalmanEstimator KalmanFilter KarhunenLoeveDecomposition KaryTree KatzCentrality KCoreComponents KDistribution KEdgeConnectedComponents KEdgeConnectedGraphQ KelvinBei KelvinBer KelvinKei KelvinKer KendallTau KendallTauTest KernelExecute KernelFunction KernelMixtureDistribution Kernels Ket Key KeyCollisionFunction KeyComplement KeyDrop KeyDropFrom KeyExistsQ KeyFreeQ KeyIntersection KeyMap KeyMemberQ KeypointStrength Keys KeySelect KeySort KeySortBy KeyTake KeyUnion KeyValueMap KeyValuePattern Khinchin KillProcess KirchhoffGraph KirchhoffMatrix KleinInvariantJ KnapsackSolve KnightTourGraph KnotData KnownUnitQ KochCurve KolmogorovSmirnovTest KroneckerDelta KroneckerModelDecomposition KroneckerProduct KroneckerSymbol KuiperTest KumaraswamyDistribution Kurtosis KuwaharaFilter KVertexConnectedComponents KVertexConnectedGraphQ LABColor Label Labeled LabeledSlider LabelingFunction LabelingSize LabelStyle LabelVisibility LaguerreL LakeData LambdaComponents LambertW LaminaData LanczosWindow LandauDistribution Language LanguageCategory LanguageData LanguageIdentify LanguageOptions LaplaceDistribution LaplaceTransform Laplacian LaplacianFilter LaplacianGaussianFilter Large Larger Last Latitude LatitudeLongitude LatticeData LatticeReduce Launch LaunchKernels LayeredGraphPlot LayerSizeFunction LayoutInformation LCHColor LCM LeaderSize LeafCount LeapYearQ LearnDistribution LearnedDistribution LearningRate LearningRateMultipliers LeastSquares LeastSquaresFilterKernel Left LeftArrow LeftArrowBar LeftArrowRightArrow LeftDownTeeVector LeftDownVector LeftDownVectorBar LeftRightArrow LeftRightVector LeftTee LeftTeeArrow LeftTeeVector LeftTriangle LeftTriangleBar LeftTriangleEqual LeftUpDownVector LeftUpTeeVector LeftUpVector LeftUpVectorBar LeftVector LeftVectorBar LegendAppearance Legended LegendFunction LegendLabel LegendLayout LegendMargins LegendMarkers LegendMarkerSize LegendreP LegendreQ LegendreType Length LengthWhile LerchPhi Less LessEqual LessEqualGreater LessEqualThan LessFullEqual LessGreater LessLess LessSlantEqual LessThan LessTilde LetterCharacter LetterCounts LetterNumber LetterQ Level LeveneTest LeviCivitaTensor LevyDistribution Lexicographic LibraryDataType LibraryFunction LibraryFunctionError LibraryFunctionInformation LibraryFunctionLoad LibraryFunctionUnload LibraryLoad LibraryUnload LicenseID LiftingFilterData LiftingWaveletTransform LightBlue LightBrown LightCyan Lighter LightGray LightGreen Lighting LightingAngle LightMagenta LightOrange LightPink LightPurple LightRed LightSources LightYellow Likelihood Limit LimitsPositioning LimitsPositioningTokens LindleyDistribution Line Line3DBox Line3DBoxOptions LinearFilter LinearFractionalOptimization LinearFractionalTransform LinearGradientImage LinearizingTransformationData LinearLayer LinearModelFit LinearOffsetFunction LinearOptimization LinearProgramming LinearRecurrence LinearSolve LinearSolveFunction LineBox LineBoxOptions LineBreak LinebreakAdjustments LineBreakChart LinebreakSemicolonWeighting LineBreakWithin LineColor LineGraph LineIndent LineIndentMaxFraction LineIntegralConvolutionPlot LineIntegralConvolutionScale LineLegend LineOpacity LineSpacing LineWrapParts LinkActivate LinkClose LinkConnect LinkConnectedQ LinkCreate LinkError LinkFlush LinkFunction LinkHost LinkInterrupt LinkLaunch LinkMode LinkObject LinkOpen LinkOptions LinkPatterns LinkProtocol LinkRankCentrality LinkRead LinkReadHeld LinkReadyQ Links LinkService LinkWrite LinkWriteHeld LiouvilleLambda List Listable ListAnimate ListContourPlot ListContourPlot3D ListConvolve ListCorrelate ListCurvePathPlot ListDeconvolve ListDensityPlot ListDensityPlot3D Listen ListFormat ListFourierSequenceTransform ListInterpolation ListLineIntegralConvolutionPlot ListLinePlot ListLogLinearPlot ListLogLogPlot ListLogPlot ListPicker ListPickerBox ListPickerBoxBackground ListPickerBoxOptions ListPlay ListPlot ListPlot3D ListPointPlot3D ListPolarPlot ListQ ListSliceContourPlot3D ListSliceDensityPlot3D ListSliceVectorPlot3D ListStepPlot ListStreamDensityPlot ListStreamPlot ListSurfacePlot3D ListVectorDensityPlot ListVectorPlot ListVectorPlot3D ListZTransform Literal LiteralSearch LocalAdaptiveBinarize LocalCache LocalClusteringCoefficient LocalizeDefinitions LocalizeVariables LocalObject LocalObjects LocalResponseNormalizationLayer LocalSubmit LocalSymbol LocalTime LocalTimeZone LocationEquivalenceTest LocationTest Locator LocatorAutoCreate LocatorBox LocatorBoxOptions LocatorCentering LocatorPane LocatorPaneBox LocatorPaneBoxOptions LocatorRegion Locked Log Log10 Log2 LogBarnesG LogGamma LogGammaDistribution LogicalExpand LogIntegral LogisticDistribution LogisticSigmoid LogitModelFit LogLikelihood LogLinearPlot LogLogisticDistribution LogLogPlot LogMultinormalDistribution LogNormalDistribution LogPlot LogRankTest LogSeriesDistribution LongEqual Longest LongestCommonSequence LongestCommonSequencePositions LongestCommonSubsequence LongestCommonSubsequencePositions LongestMatch LongestOrderedSequence LongForm Longitude LongLeftArrow LongLeftRightArrow LongRightArrow LongShortTermMemoryLayer Lookup Loopback LoopFreeGraphQ LossFunction LowerCaseQ LowerLeftArrow LowerRightArrow LowerTriangularize LowerTriangularMatrixQ LowpassFilter LQEstimatorGains LQGRegulator LQOutputRegulatorGains LQRegulatorGains LUBackSubstitution LucasL LuccioSamiComponents LUDecomposition LunarEclipse LUVColor LyapunovSolve LyonsGroupLy MachineID MachineName MachineNumberQ MachinePrecision MacintoshSystemPageSetup Magenta Magnification Magnify MailAddressValidation MailExecute MailFolder MailItem MailReceiverFunction MailResponseFunction MailSearch MailServerConnect MailServerConnection MailSettings MainSolve MaintainDynamicCaches Majority MakeBoxes MakeExpression MakeRules ManagedLibraryExpressionID ManagedLibraryExpressionQ MandelbrotSetBoettcher MandelbrotSetDistance MandelbrotSetIterationCount MandelbrotSetMemberQ MandelbrotSetPlot MangoldtLambda ManhattanDistance Manipulate Manipulator MannedSpaceMissionData MannWhitneyTest MantissaExponent Manual Map MapAll MapAt MapIndexed MAProcess MapThread MarchenkoPasturDistribution MarcumQ MardiaCombinedTest MardiaKurtosisTest MardiaSkewnessTest MarginalDistribution MarkovProcessProperties Masking MatchingDissimilarity MatchLocalNameQ MatchLocalNames MatchQ Material MathematicalFunctionData MathematicaNotation MathieuC MathieuCharacteristicA MathieuCharacteristicB MathieuCharacteristicExponent MathieuCPrime MathieuGroupM11 MathieuGroupM12 MathieuGroupM22 MathieuGroupM23 MathieuGroupM24 MathieuS MathieuSPrime MathMLForm MathMLText Matrices MatrixExp MatrixForm MatrixFunction MatrixLog MatrixNormalDistribution MatrixPlot MatrixPower MatrixPropertyDistribution MatrixQ MatrixRank MatrixTDistribution Max MaxBend MaxCellMeasure MaxColorDistance MaxDetect MaxDuration MaxExtraBandwidths MaxExtraConditions MaxFeatureDisplacement MaxFeatures MaxFilter MaximalBy Maximize MaxItems MaxIterations MaxLimit MaxMemoryUsed MaxMixtureKernels MaxOverlapFraction MaxPlotPoints MaxPoints MaxRecursion MaxStableDistribution MaxStepFraction MaxSteps MaxStepSize MaxTrainingRounds MaxValue MaxwellDistribution MaxWordGap McLaughlinGroupMcL Mean MeanAbsoluteLossLayer MeanAround MeanClusteringCoefficient MeanDegreeConnectivity MeanDeviation MeanFilter MeanGraphDistance MeanNeighborDegree MeanShift MeanShiftFilter MeanSquaredLossLayer Median MedianDeviation MedianFilter MedicalTestData Medium MeijerG MeijerGReduce MeixnerDistribution MellinConvolve MellinTransform MemberQ MemoryAvailable MemoryConstrained MemoryConstraint MemoryInUse MengerMesh Menu MenuAppearance MenuCommandKey MenuEvaluator MenuItem MenuList MenuPacket MenuSortingValue MenuStyle MenuView Merge MergeDifferences MergingFunction MersennePrimeExponent MersennePrimeExponentQ Mesh MeshCellCentroid MeshCellCount MeshCellHighlight MeshCellIndex MeshCellLabel MeshCellMarker MeshCellMeasure MeshCellQuality MeshCells MeshCellShapeFunction MeshCellStyle MeshCoordinates MeshFunctions MeshPrimitives MeshQualityGoal MeshRange MeshRefinementFunction MeshRegion MeshRegionQ MeshShading MeshStyle Message MessageDialog MessageList MessageName MessageObject MessageOptions MessagePacket Messages MessagesNotebook MetaCharacters MetaInformation MeteorShowerData Method MethodOptions MexicanHatWavelet MeyerWavelet Midpoint Min MinColorDistance MinDetect MineralData MinFilter MinimalBy MinimalPolynomial MinimalStateSpaceModel Minimize MinimumTimeIncrement MinIntervalSize MinkowskiQuestionMark MinLimit MinMax MinorPlanetData Minors MinRecursion MinSize MinStableDistribution Minus MinusPlus MinValue Missing MissingBehavior MissingDataMethod MissingDataRules MissingQ MissingString MissingStyle MissingValuePattern MittagLefflerE MixedFractionParts MixedGraphQ MixedMagnitude MixedRadix MixedRadixQuantity MixedUnit MixtureDistribution Mod Modal Mode Modular ModularInverse ModularLambda Module Modulus MoebiusMu Molecule MoleculeContainsQ MoleculeEquivalentQ MoleculeGraph MoleculeModify MoleculePattern MoleculePlot MoleculePlot3D MoleculeProperty MoleculeQ MoleculeValue Moment Momentary MomentConvert MomentEvaluate MomentGeneratingFunction MomentOfInertia Monday Monitor MonomialList MonomialOrder MonsterGroupM MoonPhase MoonPosition MorletWavelet MorphologicalBinarize MorphologicalBranchPoints MorphologicalComponents MorphologicalEulerNumber MorphologicalGraph MorphologicalPerimeter MorphologicalTransform MortalityData Most MountainData MouseAnnotation MouseAppearance MouseAppearanceTag MouseButtons Mouseover MousePointerNote MousePosition MovieData MovingAverage MovingMap MovingMedian MoyalDistribution Multicolumn MultiedgeStyle MultigraphQ MultilaunchWarning MultiLetterItalics MultiLetterStyle MultilineFunction Multinomial MultinomialDistribution MultinormalDistribution MultiplicativeOrder Multiplicity MultiplySides Multiselection MultivariateHypergeometricDistribution MultivariatePoissonDistribution MultivariateTDistribution N NakagamiDistribution NameQ Names NamespaceBox NamespaceBoxOptions Nand NArgMax NArgMin NBernoulliB NBodySimulation NBodySimulationData NCache NDEigensystem NDEigenvalues NDSolve NDSolveValue Nearest NearestFunction NearestNeighborGraph NearestTo NebulaData NeedCurrentFrontEndPackagePacket NeedCurrentFrontEndSymbolsPacket NeedlemanWunschSimilarity Needs Negative NegativeBinomialDistribution NegativeDefiniteMatrixQ NegativeIntegers NegativeMultinomialDistribution NegativeRationals NegativeReals NegativeSemidefiniteMatrixQ NeighborhoodData NeighborhoodGraph Nest NestedGreaterGreater NestedLessLess NestedScriptRules NestGraph NestList NestWhile NestWhileList NetAppend NetBidirectionalOperator NetChain NetDecoder NetDelete NetDrop NetEncoder NetEvaluationMode NetExtract NetFlatten NetFoldOperator NetGraph NetInformation NetInitialize NetInsert NetInsertSharedArrays NetJoin NetMapOperator NetMapThreadOperator NetMeasurements NetModel NetNestOperator NetPairEmbeddingOperator NetPort NetPortGradient NetPrepend NetRename NetReplace NetReplacePart NetSharedArray NetStateObject NetTake NetTrain NetTrainResultsObject NetworkPacketCapture NetworkPacketRecording NetworkPacketRecordingDuring NetworkPacketTrace NeumannValue NevilleThetaC NevilleThetaD NevilleThetaN NevilleThetaS NewPrimitiveStyle NExpectation Next NextCell NextDate NextPrime NextScheduledTaskTime NHoldAll NHoldFirst NHoldRest NicholsGridLines NicholsPlot NightHemisphere NIntegrate NMaximize NMaxValue NMinimize NMinValue NominalVariables NonAssociative NoncentralBetaDistribution NoncentralChiSquareDistribution NoncentralFRatioDistribution NoncentralStudentTDistribution NonCommutativeMultiply NonConstants NondimensionalizationTransform None NoneTrue NonlinearModelFit NonlinearStateSpaceModel NonlocalMeansFilter NonNegative NonNegativeIntegers NonNegativeRationals NonNegativeReals NonPositive NonPositiveIntegers NonPositiveRationals NonPositiveReals Nor NorlundB Norm Normal NormalDistribution NormalGrouping NormalizationLayer Normalize Normalized NormalizedSquaredEuclideanDistance NormalMatrixQ NormalsFunction NormFunction Not NotCongruent NotCupCap NotDoubleVerticalBar Notebook NotebookApply NotebookAutoSave NotebookClose NotebookConvertSettings NotebookCreate NotebookCreateReturnObject NotebookDefault NotebookDelete NotebookDirectory NotebookDynamicExpression NotebookEvaluate NotebookEventActions NotebookFileName NotebookFind NotebookFindReturnObject NotebookGet NotebookGetLayoutInformationPacket NotebookGetMisspellingsPacket NotebookImport NotebookInformation NotebookInterfaceObject NotebookLocate NotebookObject NotebookOpen NotebookOpenReturnObject NotebookPath NotebookPrint NotebookPut NotebookPutReturnObject NotebookRead NotebookResetGeneratedCells Notebooks NotebookSave NotebookSaveAs NotebookSelection NotebookSetupLayoutInformationPacket NotebooksMenu NotebookTemplate NotebookWrite NotElement NotEqualTilde NotExists NotGreater NotGreaterEqual NotGreaterFullEqual NotGreaterGreater NotGreaterLess NotGreaterSlantEqual NotGreaterTilde Nothing NotHumpDownHump NotHumpEqual NotificationFunction NotLeftTriangle NotLeftTriangleBar NotLeftTriangleEqual NotLess NotLessEqual NotLessFullEqual NotLessGreater NotLessLess NotLessSlantEqual NotLessTilde NotNestedGreaterGreater NotNestedLessLess NotPrecedes NotPrecedesEqual NotPrecedesSlantEqual NotPrecedesTilde NotReverseElement NotRightTriangle NotRightTriangleBar NotRightTriangleEqual NotSquareSubset NotSquareSubsetEqual NotSquareSuperset NotSquareSupersetEqual NotSubset NotSubsetEqual NotSucceeds NotSucceedsEqual NotSucceedsSlantEqual NotSucceedsTilde NotSuperset NotSupersetEqual NotTilde NotTildeEqual NotTildeFullEqual NotTildeTilde NotVerticalBar Now NoWhitespace NProbability NProduct NProductFactors NRoots NSolve NSum NSumTerms NuclearExplosionData NuclearReactorData Null NullRecords NullSpace NullWords Number NumberCompose NumberDecompose NumberExpand NumberFieldClassNumber NumberFieldDiscriminant NumberFieldFundamentalUnits NumberFieldIntegralBasis NumberFieldNormRepresentatives NumberFieldRegulator NumberFieldRootsOfUnity NumberFieldSignature NumberForm NumberFormat NumberLinePlot NumberMarks NumberMultiplier NumberPadding NumberPoint NumberQ NumberSeparator NumberSigns NumberString Numerator NumeratorDenominator NumericalOrder NumericalSort NumericArray NumericArrayQ NumericArrayType NumericFunction NumericQ NuttallWindow NValues NyquistGridLines NyquistPlot O ObservabilityGramian ObservabilityMatrix ObservableDecomposition ObservableModelQ OceanData Octahedron OddQ Off Offset OLEData On ONanGroupON Once OneIdentity Opacity OpacityFunction OpacityFunctionScaling Open OpenAppend Opener OpenerBox OpenerBoxOptions OpenerView OpenFunctionInspectorPacket Opening OpenRead OpenSpecialOptions OpenTemporary OpenWrite Operate OperatingSystem OptimumFlowData Optional OptionalElement OptionInspectorSettings OptionQ Options OptionsPacket OptionsPattern OptionValue OptionValueBox OptionValueBoxOptions Or Orange Order OrderDistribution OrderedQ Ordering OrderingBy OrderingLayer Orderless OrderlessPatternSequence OrnsteinUhlenbeckProcess Orthogonalize OrthogonalMatrixQ Out Outer OuterPolygon OuterPolyhedron OutputAutoOverwrite OutputControllabilityMatrix OutputControllableModelQ OutputForm OutputFormData OutputGrouping OutputMathEditExpression OutputNamePacket OutputResponse OutputSizeLimit OutputStream Over OverBar OverDot Overflow OverHat Overlaps Overlay OverlayBox OverlayBoxOptions Overscript OverscriptBox OverscriptBoxOptions OverTilde OverVector OverwriteTarget OwenT OwnValues Package PackingMethod PaddedForm Padding PaddingLayer PaddingSize PadeApproximant PadLeft PadRight PageBreakAbove PageBreakBelow PageBreakWithin PageFooterLines PageFooters PageHeaderLines PageHeaders PageHeight PageRankCentrality PageTheme PageWidth Pagination PairedBarChart PairedHistogram PairedSmoothHistogram PairedTTest PairedZTest PaletteNotebook PalettePath PalindromeQ Pane PaneBox PaneBoxOptions Panel PanelBox PanelBoxOptions Paneled PaneSelector PaneSelectorBox PaneSelectorBoxOptions PaperWidth ParabolicCylinderD ParagraphIndent ParagraphSpacing ParallelArray ParallelCombine ParallelDo Parallelepiped ParallelEvaluate Parallelization Parallelize ParallelMap ParallelNeeds Parallelogram ParallelProduct ParallelSubmit ParallelSum ParallelTable ParallelTry Parameter ParameterEstimator ParameterMixtureDistribution ParameterVariables ParametricFunction ParametricNDSolve ParametricNDSolveValue ParametricPlot ParametricPlot3D ParametricRegion ParentBox ParentCell ParentConnect ParentDirectory ParentForm Parenthesize ParentList ParentNotebook ParetoDistribution ParetoPickandsDistribution ParkData Part PartBehavior PartialCorrelationFunction PartialD ParticleAcceleratorData ParticleData Partition PartitionGranularity PartitionsP PartitionsQ PartLayer PartOfSpeech PartProtection ParzenWindow PascalDistribution PassEventsDown PassEventsUp Paste PasteAutoQuoteCharacters PasteBoxFormInlineCells PasteButton Path PathGraph PathGraphQ Pattern PatternSequence PatternTest PauliMatrix PaulWavelet Pause PausedTime PDF PeakDetect PeanoCurve PearsonChiSquareTest PearsonCorrelationTest PearsonDistribution PercentForm PerfectNumber PerfectNumberQ PerformanceGoal Perimeter PeriodicBoundaryCondition PeriodicInterpolation Periodogram PeriodogramArray Permanent Permissions PermissionsGroup PermissionsGroupMemberQ PermissionsGroups PermissionsKey PermissionsKeys PermutationCycles PermutationCyclesQ PermutationGroup PermutationLength PermutationList PermutationListQ PermutationMax PermutationMin PermutationOrder PermutationPower PermutationProduct PermutationReplace Permutations PermutationSupport Permute PeronaMalikFilter Perpendicular PerpendicularBisector PersistenceLocation PersistenceTime PersistentObject PersistentObjects PersistentValue PersonData PERTDistribution PetersenGraph PhaseMargins PhaseRange PhysicalSystemData Pi Pick PIDData PIDDerivativeFilter PIDFeedforward PIDTune Piecewise PiecewiseExpand PieChart PieChart3D PillaiTrace PillaiTraceTest PingTime Pink PitchRecognize Pivoting PixelConstrained PixelValue PixelValuePositions Placed Placeholder PlaceholderReplace Plain PlanarAngle PlanarGraph PlanarGraphQ PlanckRadiationLaw PlaneCurveData PlanetaryMoonData PlanetData PlantData Play PlayRange Plot Plot3D Plot3Matrix PlotDivision PlotJoined PlotLabel PlotLabels PlotLayout PlotLegends PlotMarkers PlotPoints PlotRange PlotRangeClipping PlotRangeClipPlanesStyle PlotRangePadding PlotRegion PlotStyle PlotTheme Pluralize Plus PlusMinus Pochhammer PodStates PodWidth Point Point3DBox Point3DBoxOptions PointBox PointBoxOptions PointFigureChart PointLegend PointSize PoissonConsulDistribution PoissonDistribution PoissonProcess PoissonWindow PolarAxes PolarAxesOrigin PolarGridLines PolarPlot PolarTicks PoleZeroMarkers PolyaAeppliDistribution PolyGamma Polygon Polygon3DBox Polygon3DBoxOptions PolygonalNumber PolygonAngle PolygonBox PolygonBoxOptions PolygonCoordinates PolygonDecomposition PolygonHoleScale PolygonIntersections PolygonScale Polyhedron PolyhedronAngle PolyhedronCoordinates PolyhedronData PolyhedronDecomposition PolyhedronGenus PolyLog PolynomialExtendedGCD PolynomialForm PolynomialGCD PolynomialLCM PolynomialMod PolynomialQ PolynomialQuotient PolynomialQuotientRemainder PolynomialReduce PolynomialRemainder Polynomials PoolingLayer PopupMenu PopupMenuBox PopupMenuBoxOptions PopupView PopupWindow Position PositionIndex Positive PositiveDefiniteMatrixQ PositiveIntegers PositiveRationals PositiveReals PositiveSemidefiniteMatrixQ PossibleZeroQ Postfix PostScript Power PowerDistribution PowerExpand PowerMod PowerModList PowerRange PowerSpectralDensity PowersRepresentations PowerSymmetricPolynomial Precedence PrecedenceForm Precedes PrecedesEqual PrecedesSlantEqual PrecedesTilde Precision PrecisionGoal PreDecrement Predict PredictionRoot PredictorFunction PredictorInformation PredictorMeasurements PredictorMeasurementsObject PreemptProtect PreferencesPath Prefix PreIncrement Prepend PrependLayer PrependTo PreprocessingRules PreserveColor PreserveImageOptions Previous PreviousCell PreviousDate PriceGraphDistribution PrimaryPlaceholder Prime PrimeNu PrimeOmega PrimePi PrimePowerQ PrimeQ Primes PrimeZetaP PrimitivePolynomialQ PrimitiveRoot PrimitiveRootList PrincipalComponents PrincipalValue Print PrintableASCIIQ PrintAction PrintForm PrintingCopies PrintingOptions PrintingPageRange PrintingStartingPageNumber PrintingStyleEnvironment Printout3D Printout3DPreviewer PrintPrecision PrintTemporary Prism PrismBox PrismBoxOptions PrivateCellOptions PrivateEvaluationOptions PrivateFontOptions PrivateFrontEndOptions PrivateKey PrivateNotebookOptions PrivatePaths Probability ProbabilityDistribution ProbabilityPlot ProbabilityPr ProbabilityScalePlot ProbitModelFit ProcessConnection ProcessDirectory ProcessEnvironment Processes ProcessEstimator ProcessInformation ProcessObject ProcessParameterAssumptions ProcessParameterQ ProcessStateDomain ProcessStatus ProcessTimeDomain Product ProductDistribution ProductLog ProgressIndicator ProgressIndicatorBox ProgressIndicatorBoxOptions Projection Prolog PromptForm ProofObject Properties Property PropertyList PropertyValue Proportion Proportional Protect Protected ProteinData Pruning PseudoInverse PsychrometricPropertyData PublicKey PublisherID PulsarData PunctuationCharacter Purple Put PutAppend Pyramid PyramidBox PyramidBoxOptions QBinomial QFactorial QGamma QHypergeometricPFQ QnDispersion QPochhammer QPolyGamma QRDecomposition QuadraticIrrationalQ QuadraticOptimization Quantile QuantilePlot Quantity QuantityArray QuantityDistribution QuantityForm QuantityMagnitude QuantityQ QuantityUnit QuantityVariable QuantityVariableCanonicalUnit QuantityVariableDimensions QuantityVariableIdentifier QuantityVariablePhysicalQuantity Quartics QuartileDeviation Quartiles QuartileSkewness Query QueueingNetworkProcess QueueingProcess QueueProperties Quiet Quit Quotient QuotientRemainder RadialGradientImage RadialityCentrality RadicalBox RadicalBoxOptions RadioButton RadioButtonBar RadioButtonBox RadioButtonBoxOptions Radon RadonTransform RamanujanTau RamanujanTauL RamanujanTauTheta RamanujanTauZ Ramp Random RandomChoice RandomColor RandomComplex RandomEntity RandomFunction RandomGeoPosition RandomGraph RandomImage RandomInstance RandomInteger RandomPermutation RandomPoint RandomPolygon RandomPolyhedron RandomPrime RandomReal RandomSample RandomSeed RandomSeeding RandomVariate RandomWalkProcess RandomWord Range RangeFilter RangeSpecification RankedMax RankedMin RarerProbability Raster Raster3D Raster3DBox Raster3DBoxOptions RasterArray RasterBox RasterBoxOptions Rasterize RasterSize Rational RationalFunctions Rationalize Rationals Ratios RawArray RawBoxes RawData RawMedium RayleighDistribution Re Read ReadByteArray ReadLine ReadList ReadProtected ReadString Real RealAbs RealBlockDiagonalForm RealDigits RealExponent Reals RealSign Reap RecognitionPrior RecognitionThreshold Record RecordLists RecordSeparators Rectangle RectangleBox RectangleBoxOptions RectangleChart RectangleChart3D RectangularRepeatingElement RecurrenceFilter RecurrenceTable RecurringDigitsForm Red Reduce RefBox ReferenceLineStyle ReferenceMarkers ReferenceMarkerStyle Refine ReflectionMatrix ReflectionTransform Refresh RefreshRate Region RegionBinarize RegionBoundary RegionBounds RegionCentroid RegionDifference RegionDimension RegionDisjoint RegionDistance RegionDistanceFunction RegionEmbeddingDimension RegionEqual RegionFunction RegionImage RegionIntersection RegionMeasure RegionMember RegionMemberFunction RegionMoment RegionNearest RegionNearestFunction RegionPlot RegionPlot3D RegionProduct RegionQ RegionResize RegionSize RegionSymmetricDifference RegionUnion RegionWithin RegisterExternalEvaluator RegularExpression Regularization RegularlySampledQ RegularPolygon ReIm ReImLabels ReImPlot ReImStyle Reinstall RelationalDatabase RelationGraph Release ReleaseHold ReliabilityDistribution ReliefImage ReliefPlot RemoteAuthorizationCaching RemoteConnect RemoteConnectionObject RemoteFile RemoteRun RemoteRunProcess Remove RemoveAlphaChannel RemoveAsynchronousTask RemoveAudioStream RemoveBackground RemoveChannelListener RemoveChannelSubscribers Removed RemoveDiacritics RemoveInputStreamMethod RemoveOutputStreamMethod RemoveProperty RemoveScheduledTask RemoveUsers RenameDirectory RenameFile RenderAll RenderingOptions RenewalProcess RenkoChart RepairMesh Repeated RepeatedNull RepeatedString RepeatedTiming RepeatingElement Replace ReplaceAll ReplaceHeldPart ReplaceImageValue ReplaceList ReplacePart ReplacePixelValue ReplaceRepeated ReplicateLayer RequiredPhysicalQuantities Resampling ResamplingAlgorithmData ResamplingMethod Rescale RescalingTransform ResetDirectory ResetMenusPacket ResetScheduledTask ReshapeLayer Residue ResizeLayer Resolve ResourceAcquire ResourceData ResourceFunction ResourceObject ResourceRegister ResourceRemove ResourceSearch ResourceSubmissionObject ResourceSubmit ResourceSystemBase ResourceUpdate ResponseForm Rest RestartInterval Restricted Resultant ResumePacket Return ReturnEntersInput ReturnExpressionPacket ReturnInputFormPacket ReturnPacket ReturnReceiptFunction ReturnTextPacket Reverse ReverseBiorthogonalSplineWavelet ReverseElement ReverseEquilibrium ReverseGraph ReverseSort ReverseSortBy ReverseUpEquilibrium RevolutionAxis RevolutionPlot3D RGBColor RiccatiSolve RiceDistribution RidgeFilter RiemannR RiemannSiegelTheta RiemannSiegelZ RiemannXi Riffle Right RightArrow RightArrowBar RightArrowLeftArrow RightComposition RightCosetRepresentative RightDownTeeVector RightDownVector RightDownVectorBar RightTee RightTeeArrow RightTeeVector RightTriangle RightTriangleBar RightTriangleEqual RightUpDownVector RightUpTeeVector RightUpVector RightUpVectorBar RightVector RightVectorBar RiskAchievementImportance RiskReductionImportance RogersTanimotoDissimilarity RollPitchYawAngles RollPitchYawMatrix RomanNumeral Root RootApproximant RootIntervals RootLocusPlot RootMeanSquare RootOfUnityQ RootReduce Roots RootSum Rotate RotateLabel RotateLeft RotateRight RotationAction RotationBox RotationBoxOptions RotationMatrix RotationTransform Round RoundImplies RoundingRadius Row RowAlignments RowBackgrounds RowBox RowHeights RowLines RowMinHeight RowReduce RowsEqual RowSpacings RSolve RSolveValue RudinShapiro RudvalisGroupRu Rule RuleCondition RuleDelayed RuleForm RulePlot RulerUnits Run RunProcess RunScheduledTask RunThrough RuntimeAttributes RuntimeOptions RussellRaoDissimilarity SameQ SameTest SampledEntityClass SampleDepth SampledSoundFunction SampledSoundList SampleRate SamplingPeriod SARIMAProcess SARMAProcess SASTriangle SatelliteData SatisfiabilityCount SatisfiabilityInstances SatisfiableQ Saturday Save Saveable SaveAutoDelete SaveConnection SaveDefinitions SavitzkyGolayMatrix SawtoothWave Scale Scaled ScaleDivisions ScaledMousePosition ScaleOrigin ScalePadding ScaleRanges ScaleRangeStyle ScalingFunctions ScalingMatrix ScalingTransform Scan ScheduledTask ScheduledTaskActiveQ ScheduledTaskInformation ScheduledTaskInformationData ScheduledTaskObject ScheduledTasks SchurDecomposition ScientificForm ScientificNotationThreshold ScorerGi ScorerGiPrime ScorerHi ScorerHiPrime ScreenRectangle ScreenStyleEnvironment ScriptBaselineShifts ScriptForm ScriptLevel ScriptMinSize ScriptRules ScriptSizeMultipliers Scrollbars ScrollingOptions ScrollPosition SearchAdjustment SearchIndexObject SearchIndices SearchQueryString SearchResultObject Sec Sech SechDistribution SecondOrderConeOptimization SectionGrouping SectorChart SectorChart3D SectorOrigin SectorSpacing SecuredAuthenticationKey SecuredAuthenticationKeys SeedRandom Select Selectable SelectComponents SelectedCells SelectedNotebook SelectFirst Selection SelectionAnimate SelectionCell SelectionCellCreateCell SelectionCellDefaultStyle SelectionCellParentStyle SelectionCreateCell SelectionDebuggerTag SelectionDuplicateCell SelectionEvaluate SelectionEvaluateCreateCell SelectionMove SelectionPlaceholder SelectionSetStyle SelectWithContents SelfLoops SelfLoopStyle SemanticImport SemanticImportString SemanticInterpretation SemialgebraicComponentInstances SemidefiniteOptimization SendMail SendMessage Sequence SequenceAlignment SequenceAttentionLayer SequenceCases SequenceCount SequenceFold SequenceFoldList SequenceForm SequenceHold SequenceLastLayer SequenceMostLayer SequencePosition SequencePredict SequencePredictorFunction SequenceReplace SequenceRestLayer SequenceReverseLayer SequenceSplit Series SeriesCoefficient SeriesData ServiceConnect ServiceDisconnect ServiceExecute ServiceObject ServiceRequest ServiceResponse ServiceSubmit SessionSubmit SessionTime Set SetAccuracy SetAlphaChannel SetAttributes Setbacks SetBoxFormNamesPacket SetCloudDirectory SetCookies SetDelayed SetDirectory SetEnvironment SetEvaluationNotebook SetFileDate SetFileLoadingContext SetNotebookStatusLine SetOptions SetOptionsPacket SetPermissions SetPrecision SetProperty SetSecuredAuthenticationKey SetSelectedNotebook SetSharedFunction SetSharedVariable SetSpeechParametersPacket SetStreamPosition SetSystemModel SetSystemOptions Setter SetterBar SetterBox SetterBoxOptions Setting SetUsers SetValue Shading Shallow ShannonWavelet ShapiroWilkTest Share SharingList Sharpen ShearingMatrix ShearingTransform ShellRegion ShenCastanMatrix ShiftedGompertzDistribution ShiftRegisterSequence Short ShortDownArrow Shortest ShortestMatch ShortestPathFunction ShortLeftArrow ShortRightArrow ShortTimeFourier ShortTimeFourierData ShortUpArrow Show ShowAutoConvert ShowAutoSpellCheck ShowAutoStyles ShowCellBracket ShowCellLabel ShowCellTags ShowClosedCellArea ShowCodeAssist ShowContents ShowControls ShowCursorTracker ShowGroupOpenCloseIcon ShowGroupOpener ShowInvisibleCharacters ShowPageBreaks ShowPredictiveInterface ShowSelection ShowShortBoxForm ShowSpecialCharacters ShowStringCharacters ShowSyntaxStyles ShrinkingDelay ShrinkWrapBoundingBox SiderealTime SiegelTheta SiegelTukeyTest SierpinskiCurve SierpinskiMesh Sign Signature SignedRankTest SignedRegionDistance SignificanceLevel SignPadding SignTest SimilarityRules SimpleGraph SimpleGraphQ SimplePolygonQ SimplePolyhedronQ Simplex Simplify Sin Sinc SinghMaddalaDistribution SingleEvaluation SingleLetterItalics SingleLetterStyle SingularValueDecomposition SingularValueList SingularValuePlot SingularValues Sinh SinhIntegral SinIntegral SixJSymbol Skeleton SkeletonTransform SkellamDistribution Skewness SkewNormalDistribution SkinStyle Skip SliceContourPlot3D SliceDensityPlot3D SliceDistribution SliceVectorPlot3D Slider Slider2D Slider2DBox Slider2DBoxOptions SliderBox SliderBoxOptions SlideView Slot SlotSequence Small SmallCircle Smaller SmithDecomposition SmithDelayCompensator SmithWatermanSimilarity SmoothDensityHistogram SmoothHistogram SmoothHistogram3D SmoothKernelDistribution SnDispersion Snippet SnubPolyhedron SocialMediaData Socket SocketConnect SocketListen SocketListener SocketObject SocketOpen SocketReadMessage SocketReadyQ Sockets SocketWaitAll SocketWaitNext SoftmaxLayer SokalSneathDissimilarity SolarEclipse SolarSystemFeatureData SolidAngle SolidData SolidRegionQ Solve SolveAlways SolveDelayed Sort SortBy SortedBy SortedEntityClass Sound SoundAndGraphics SoundNote SoundVolume SourceLink Sow Space SpaceCurveData SpaceForm Spacer Spacings Span SpanAdjustments SpanCharacterRounding SpanFromAbove SpanFromBoth SpanFromLeft SpanLineThickness SpanMaxSize SpanMinSize SpanningCharacters SpanSymmetric SparseArray SpatialGraphDistribution SpatialMedian SpatialTransformationLayer Speak SpeakTextPacket SpearmanRankTest SpearmanRho SpeciesData SpecificityGoal SpectralLineData Spectrogram SpectrogramArray Specularity SpeechRecognize SpeechSynthesize SpellingCorrection SpellingCorrectionList SpellingDictionaries SpellingDictionariesPath SpellingOptions SpellingSuggestionsPacket Sphere SphereBox SpherePoints SphericalBesselJ SphericalBesselY SphericalHankelH1 SphericalHankelH2 SphericalHarmonicY SphericalPlot3D SphericalRegion SphericalShell SpheroidalEigenvalue SpheroidalJoiningFactor SpheroidalPS SpheroidalPSPrime SpheroidalQS SpheroidalQSPrime SpheroidalRadialFactor SpheroidalS1 SpheroidalS1Prime SpheroidalS2 SpheroidalS2Prime Splice SplicedDistribution SplineClosed SplineDegree SplineKnots SplineWeights Split SplitBy SpokenString Sqrt SqrtBox SqrtBoxOptions Square SquaredEuclideanDistance SquareFreeQ SquareIntersection SquareMatrixQ SquareRepeatingElement SquaresR SquareSubset SquareSubsetEqual SquareSuperset SquareSupersetEqual SquareUnion SquareWave SSSTriangle StabilityMargins StabilityMarginsStyle StableDistribution Stack StackBegin StackComplete StackedDateListPlot StackedListPlot StackInhibit StadiumShape StandardAtmosphereData StandardDeviation StandardDeviationFilter StandardForm Standardize Standardized StandardOceanData StandbyDistribution Star StarClusterData StarData StarGraph StartAsynchronousTask StartExternalSession StartingStepSize StartOfLine StartOfString StartProcess StartScheduledTask StartupSound StartWebSession StateDimensions StateFeedbackGains StateOutputEstimator StateResponse StateSpaceModel StateSpaceRealization StateSpaceTransform StateTransformationLinearize StationaryDistribution StationaryWaveletPacketTransform StationaryWaveletTransform StatusArea StatusCentrality StepMonitor StereochemistryElements StieltjesGamma StirlingS1 StirlingS2 StopAsynchronousTask StoppingPowerData StopScheduledTask StrataVariables StratonovichProcess StreamColorFunction StreamColorFunctionScaling StreamDensityPlot StreamMarkers StreamPlot StreamPoints StreamPosition Streams StreamScale StreamStyle String StringBreak StringByteCount StringCases StringContainsQ StringCount StringDelete StringDrop StringEndsQ StringExpression StringExtract StringForm StringFormat StringFreeQ StringInsert StringJoin StringLength StringMatchQ StringPadLeft StringPadRight StringPart StringPartition StringPosition StringQ StringRepeat StringReplace StringReplaceList StringReplacePart StringReverse StringRiffle StringRotateLeft StringRotateRight StringSkeleton StringSplit StringStartsQ StringTake StringTemplate StringToByteArray StringToStream StringTrim StripBoxes StripOnInput StripWrapperBoxes StrokeForm StructuralImportance StructuredArray StructuredSelection StruveH StruveL Stub StudentTDistribution Style StyleBox StyleBoxAutoDelete StyleData StyleDefinitions StyleForm StyleHints StyleKeyMapping StyleMenuListing StyleNameDialogSettings StyleNames StylePrint StyleSheetPath Subdivide Subfactorial Subgraph SubMinus SubPlus SubresultantPolynomialRemainders SubresultantPolynomials Subresultants Subscript SubscriptBox SubscriptBoxOptions Subscripted Subsequences Subset SubsetEqual SubsetMap SubsetQ Subsets SubStar SubstitutionSystem Subsuperscript SubsuperscriptBox SubsuperscriptBoxOptions Subtract SubtractFrom SubtractSides SubValues Succeeds SucceedsEqual SucceedsSlantEqual SucceedsTilde Success SuchThat Sum SumConvergence SummationLayer Sunday SunPosition Sunrise Sunset SuperDagger SuperMinus SupernovaData SuperPlus Superscript SuperscriptBox SuperscriptBoxOptions Superset SupersetEqual SuperStar Surd SurdForm SurfaceArea SurfaceColor SurfaceData SurfaceGraphics SurvivalDistribution SurvivalFunction SurvivalModel SurvivalModelFit SuspendPacket SuzukiDistribution SuzukiGroupSuz SwatchLegend Switch Symbol SymbolName SymletWavelet Symmetric SymmetricGroup SymmetricKey SymmetricMatrixQ SymmetricPolynomial SymmetricReduction Symmetrize SymmetrizedArray SymmetrizedArrayRules SymmetrizedDependentComponents SymmetrizedIndependentComponents SymmetrizedReplacePart SynchronousInitialization SynchronousUpdating Synonyms Syntax SyntaxForm SyntaxInformation SyntaxLength SyntaxPacket SyntaxQ SynthesizeMissingValues SystemDialogInput SystemException SystemGet SystemHelpPath SystemInformation SystemInformationData SystemInstall SystemModel SystemModeler SystemModelExamples SystemModelLinearize SystemModelParametricSimulate SystemModelPlot SystemModelProgressReporting SystemModelReliability SystemModels SystemModelSimulate SystemModelSimulateSensitivity SystemModelSimulationData SystemOpen SystemOptions SystemProcessData SystemProcesses SystemsConnectionsModel SystemsModelDelay SystemsModelDelayApproximate SystemsModelDelete SystemsModelDimensions SystemsModelExtract SystemsModelFeedbackConnect SystemsModelLabels SystemsModelLinearity SystemsModelMerge SystemsModelOrder SystemsModelParallelConnect SystemsModelSeriesConnect SystemsModelStateFeedbackConnect SystemsModelVectorRelativeOrders SystemStub SystemTest Tab TabFilling Table TableAlignments TableDepth TableDirections TableForm TableHeadings TableSpacing TableView TableViewBox TableViewBoxBackground TableViewBoxOptions TabSpacings TabView TabViewBox TabViewBoxOptions TagBox TagBoxNote TagBoxOptions TaggingRules TagSet TagSetDelayed TagStyle TagUnset Take TakeDrop TakeLargest TakeLargestBy TakeList TakeSmallest TakeSmallestBy TakeWhile Tally Tan Tanh TargetDevice TargetFunctions TargetSystem TargetUnits TaskAbort TaskExecute TaskObject TaskRemove TaskResume Tasks TaskSuspend TaskWait TautologyQ TelegraphProcess TemplateApply TemplateArgBox TemplateBox TemplateBoxOptions TemplateEvaluate TemplateExpression TemplateIf TemplateObject TemplateSequence TemplateSlot TemplateSlotSequence TemplateUnevaluated TemplateVerbatim TemplateWith TemporalData TemporalRegularity Temporary TemporaryVariable TensorContract TensorDimensions TensorExpand TensorProduct TensorQ TensorRank TensorReduce TensorSymmetry TensorTranspose TensorWedge TestID TestReport TestReportObject TestResultObject Tetrahedron TetrahedronBox TetrahedronBoxOptions TeXForm TeXSave Text Text3DBox Text3DBoxOptions TextAlignment TextBand TextBoundingBox TextBox TextCases TextCell TextClipboardType TextContents TextData TextElement TextForm TextGrid TextJustification TextLine TextPacket TextParagraph TextPosition TextRecognize TextSearch TextSearchReport TextSentences TextString TextStructure TextStyle TextTranslation Texture TextureCoordinateFunction TextureCoordinateScaling TextWords Therefore ThermodynamicData ThermometerGauge Thick Thickness Thin Thinning ThisLink ThompsonGroupTh Thread ThreadingLayer ThreeJSymbol Threshold Through Throw ThueMorse Thumbnail Thursday Ticks TicksStyle TideData Tilde TildeEqual TildeFullEqual TildeTilde TimeConstrained TimeConstraint TimeDirection TimeFormat TimeGoal TimelinePlot TimeObject TimeObjectQ Times TimesBy TimeSeries TimeSeriesAggregate TimeSeriesForecast TimeSeriesInsert TimeSeriesInvertibility TimeSeriesMap TimeSeriesMapThread TimeSeriesModel TimeSeriesModelFit TimeSeriesResample TimeSeriesRescale TimeSeriesShift TimeSeriesThread TimeSeriesWindow TimeUsed TimeValue TimeWarpingCorrespondence TimeWarpingDistance TimeZone TimeZoneConvert TimeZoneOffset Timing Tiny TitleGrouping TitsGroupT ToBoxes ToCharacterCode ToColor ToContinuousTimeModel ToDate Today ToDiscreteTimeModel ToEntity ToeplitzMatrix ToExpression ToFileName Together Toggle ToggleFalse Toggler TogglerBar TogglerBox TogglerBoxOptions ToHeldExpression ToInvertibleTimeSeries TokenWords Tolerance ToLowerCase Tomorrow ToNumberField TooBig Tooltip TooltipBox TooltipBoxOptions TooltipDelay TooltipStyle Top TopHatTransform ToPolarCoordinates TopologicalSort ToRadicals ToRules ToSphericalCoordinates ToString Total TotalHeight TotalLayer TotalVariationFilter TotalWidth TouchPosition TouchscreenAutoZoom TouchscreenControlPlacement ToUpperCase Tr Trace TraceAbove TraceAction TraceBackward TraceDepth TraceDialog TraceForward TraceInternal TraceLevel TraceOff TraceOn TraceOriginal TracePrint TraceScan TrackedSymbols TrackingFunction TracyWidomDistribution TradingChart TraditionalForm TraditionalFunctionNotation TraditionalNotation TraditionalOrder TrainingProgressCheckpointing TrainingProgressFunction TrainingProgressMeasurements TrainingProgressReporting TrainingStoppingCriterion TransferFunctionCancel TransferFunctionExpand TransferFunctionFactor TransferFunctionModel TransferFunctionPoles TransferFunctionTransform TransferFunctionZeros TransformationClass TransformationFunction TransformationFunctions TransformationMatrix TransformedDistribution TransformedField TransformedProcess TransformedRegion TransitionDirection TransitionDuration TransitionEffect TransitiveClosureGraph TransitiveReductionGraph Translate TranslationOptions TranslationTransform Transliterate Transparent TransparentColor Transpose TransposeLayer TrapSelection TravelDirections TravelDirectionsData TravelDistance TravelDistanceList TravelMethod TravelTime TreeForm TreeGraph TreeGraphQ TreePlot TrendStyle Triangle TriangleCenter TriangleConstruct TriangleMeasurement TriangleWave TriangularDistribution TriangulateMesh Trig TrigExpand TrigFactor TrigFactorList Trigger TrigReduce TrigToExp TrimmedMean TrimmedVariance TropicalStormData True TrueQ TruncatedDistribution TruncatedPolyhedron TsallisQExponentialDistribution TsallisQGaussianDistribution TTest Tube TubeBezierCurveBox TubeBezierCurveBoxOptions TubeBox TubeBoxOptions TubeBSplineCurveBox TubeBSplineCurveBoxOptions Tuesday TukeyLambdaDistribution TukeyWindow TunnelData Tuples TuranGraph TuringMachine TuttePolynomial TwoWayRule Typed TypeSpecifier UnateQ Uncompress UnconstrainedParameters Undefined UnderBar Underflow Underlined Underoverscript UnderoverscriptBox UnderoverscriptBoxOptions Underscript UnderscriptBox UnderscriptBoxOptions UnderseaFeatureData UndirectedEdge UndirectedGraph UndirectedGraphQ UndoOptions UndoTrackedVariables Unequal UnequalTo Unevaluated UniformDistribution UniformGraphDistribution UniformPolyhedron UniformSumDistribution Uninstall Union UnionPlus Unique UnitaryMatrixQ UnitBox UnitConvert UnitDimensions Unitize UnitRootTest UnitSimplify UnitStep UnitSystem UnitTriangle UnitVector UnitVectorLayer UnityDimensions UniverseModelData UniversityData UnixTime Unprotect UnregisterExternalEvaluator UnsameQ UnsavedVariables Unset UnsetShared UntrackedVariables Up UpArrow UpArrowBar UpArrowDownArrow Update UpdateDynamicObjects UpdateDynamicObjectsSynchronous UpdateInterval UpdateSearchIndex UpDownArrow UpEquilibrium UpperCaseQ UpperLeftArrow UpperRightArrow UpperTriangularize UpperTriangularMatrixQ Upsample UpSet UpSetDelayed UpTee UpTeeArrow UpTo UpValues URL URLBuild URLDecode URLDispatcher URLDownload URLDownloadSubmit URLEncode URLExecute URLExpand URLFetch URLFetchAsynchronous URLParse URLQueryDecode URLQueryEncode URLRead URLResponseTime URLSave URLSaveAsynchronous URLShorten URLSubmit UseGraphicsRange UserDefinedWavelet Using UsingFrontEnd UtilityFunction V2Get ValenceErrorHandling ValidationLength ValidationSet Value ValueBox ValueBoxOptions ValueDimensions ValueForm ValuePreprocessingFunction ValueQ Values ValuesData Variables Variance VarianceEquivalenceTest VarianceEstimatorFunction VarianceGammaDistribution VarianceTest VectorAngle VectorAround VectorColorFunction VectorColorFunctionScaling VectorDensityPlot VectorGlyphData VectorGreater VectorGreaterEqual VectorLess VectorLessEqual VectorMarkers VectorPlot VectorPlot3D VectorPoints VectorQ Vectors VectorScale VectorStyle Vee Verbatim Verbose VerboseConvertToPostScriptPacket VerificationTest VerifyConvergence VerifyDerivedKey VerifyDigitalSignature VerifyInterpretation VerifySecurityCertificates VerifySolutions VerifyTestAssumptions Version VersionNumber VertexAdd VertexCapacity VertexColors VertexComponent VertexConnectivity VertexContract VertexCoordinateRules VertexCoordinates VertexCorrelationSimilarity VertexCosineSimilarity VertexCount VertexCoverQ VertexDataCoordinates VertexDegree VertexDelete VertexDiceSimilarity VertexEccentricity VertexInComponent VertexInDegree VertexIndex VertexJaccardSimilarity VertexLabeling VertexLabels VertexLabelStyle VertexList VertexNormals VertexOutComponent VertexOutDegree VertexQ VertexRenderingFunction VertexReplace VertexShape VertexShapeFunction VertexSize VertexStyle VertexTextureCoordinates VertexWeight VertexWeightedGraphQ Vertical VerticalBar VerticalForm VerticalGauge VerticalSeparator VerticalSlider VerticalTilde ViewAngle ViewCenter ViewMatrix ViewPoint ViewPointSelectorSettings ViewPort ViewProjection ViewRange ViewVector ViewVertical VirtualGroupData Visible VisibleCell VoiceStyleData VoigtDistribution VolcanoData Volume VonMisesDistribution VoronoiMesh WaitAll WaitAsynchronousTask WaitNext WaitUntil WakebyDistribution WalleniusHypergeometricDistribution WaringYuleDistribution WarpingCorrespondence WarpingDistance WatershedComponents WatsonUSquareTest WattsStrogatzGraphDistribution WaveletBestBasis WaveletFilterCoefficients WaveletImagePlot WaveletListPlot WaveletMapIndexed WaveletMatrixPlot WaveletPhi WaveletPsi WaveletScale WaveletScalogram WaveletThreshold WeaklyConnectedComponents WeaklyConnectedGraphComponents WeaklyConnectedGraphQ WeakStationarity WeatherData WeatherForecastData WebAudioSearch WebElementObject WeberE WebExecute WebImage WebImageSearch WebSearch WebSessionObject WebSessions WebWindowObject Wedge Wednesday WeibullDistribution WeierstrassE1 WeierstrassE2 WeierstrassE3 WeierstrassEta1 WeierstrassEta2 WeierstrassEta3 WeierstrassHalfPeriods WeierstrassHalfPeriodW1 WeierstrassHalfPeriodW2 WeierstrassHalfPeriodW3 WeierstrassInvariantG2 WeierstrassInvariantG3 WeierstrassInvariants WeierstrassP WeierstrassPPrime WeierstrassSigma WeierstrassZeta WeightedAdjacencyGraph WeightedAdjacencyMatrix WeightedData WeightedGraphQ Weights WelchWindow WheelGraph WhenEvent Which While White WhiteNoiseProcess WhitePoint Whitespace WhitespaceCharacter WhittakerM WhittakerW WienerFilter WienerProcess WignerD WignerSemicircleDistribution WikipediaData WikipediaSearch WilksW WilksWTest WindDirectionData WindingCount WindingPolygon WindowClickSelect WindowElements WindowFloating WindowFrame WindowFrameElements WindowMargins WindowMovable WindowOpacity WindowPersistentStyles WindowSelected WindowSize WindowStatusArea WindowTitle WindowToolbars WindowWidth WindSpeedData WindVectorData WinsorizedMean WinsorizedVariance WishartMatrixDistribution With WolframAlpha WolframAlphaDate WolframAlphaQuantity WolframAlphaResult WolframLanguageData Word WordBoundary WordCharacter WordCloud WordCount WordCounts WordData WordDefinition WordFrequency WordFrequencyData WordList WordOrientation WordSearch WordSelectionFunction WordSeparators WordSpacings WordStem WordTranslation WorkingPrecision WrapAround Write WriteLine WriteString Wronskian XMLElement XMLObject XMLTemplate Xnor Xor XYZColor Yellow Yesterday YuleDissimilarity ZernikeR ZeroSymmetric ZeroTest ZeroWidthTimes Zeta ZetaZero ZIPCodeData ZipfDistribution ZoomCenter ZoomFactor ZTest ZTransform $Aborted $ActivationGroupID $ActivationKey $ActivationUserRegistered $AddOnsDirectory $AllowExternalChannelFunctions $AssertFunction $Assumptions $AsynchronousTask $AudioInputDevices $AudioOutputDevices $BaseDirectory $BatchInput $BatchOutput $BlockchainBase $BoxForms $ByteOrdering $CacheBaseDirectory $Canceled $ChannelBase $CharacterEncoding $CharacterEncodings $CloudBase $CloudConnected $CloudCreditsAvailable $CloudEvaluation $CloudExpressionBase $CloudObjectNameFormat $CloudObjectURLType $CloudRootDirectory $CloudSymbolBase $CloudUserID $CloudUserUUID $CloudVersion $CloudVersionNumber $CloudWolframEngineVersionNumber $CommandLine $CompilationTarget $ConditionHold $ConfiguredKernels $Context $ContextPath $ControlActiveSetting $Cookies $CookieStore $CreationDate $CurrentLink $CurrentTask $CurrentWebSession $DateStringFormat $DefaultAudioInputDevice $DefaultAudioOutputDevice $DefaultFont $DefaultFrontEnd $DefaultImagingDevice $DefaultLocalBase $DefaultMailbox $DefaultNetworkInterface $DefaultPath $Display $DisplayFunction $DistributedContexts $DynamicEvaluation $Echo $EmbedCodeEnvironments $EmbeddableServices $EntityStores $Epilog $EvaluationCloudBase $EvaluationCloudObject $EvaluationEnvironment $ExportFormats $Failed $FinancialDataSource $FontFamilies $FormatType $FrontEnd $FrontEndSession $GeoEntityTypes $GeoLocation $GeoLocationCity $GeoLocationCountry $GeoLocationPrecision $GeoLocationSource $HistoryLength $HomeDirectory $HTMLExportRules $HTTPCookies $HTTPRequest $IgnoreEOF $ImageFormattingWidth $ImagingDevice $ImagingDevices $ImportFormats $IncomingMailSettings $InitialDirectory $Initialization $InitializationContexts $Input $InputFileName $InputStreamMethods $Inspector $InstallationDate $InstallationDirectory $InterfaceEnvironment $InterpreterTypes $IterationLimit $KernelCount $KernelID $Language $LaunchDirectory $LibraryPath $LicenseExpirationDate $LicenseID $LicenseProcesses $LicenseServer $LicenseSubprocesses $LicenseType $Line $Linked $LinkSupported $LoadedFiles $LocalBase $LocalSymbolBase $MachineAddresses $MachineDomain $MachineDomains $MachineEpsilon $MachineID $MachineName $MachinePrecision $MachineType $MaxExtraPrecision $MaxLicenseProcesses $MaxLicenseSubprocesses $MaxMachineNumber $MaxNumber $MaxPiecewiseCases $MaxPrecision $MaxRootDegree $MessageGroups $MessageList $MessagePrePrint $Messages $MinMachineNumber $MinNumber $MinorReleaseNumber $MinPrecision $MobilePhone $ModuleNumber $NetworkConnected $NetworkInterfaces $NetworkLicense $NewMessage $NewSymbol $Notebooks $NoValue $NumberMarks $Off $OperatingSystem $Output $OutputForms $OutputSizeLimit $OutputStreamMethods $Packages $ParentLink $ParentProcessID $PasswordFile $PatchLevelID $Path $PathnameSeparator $PerformanceGoal $Permissions $PermissionsGroupBase $PersistenceBase $PersistencePath $PipeSupported $PlotTheme $Post $Pre $PreferencesDirectory $PreInitialization $PrePrint $PreRead $PrintForms $PrintLiteral $Printout3DPreviewer $ProcessID $ProcessorCount $ProcessorType $ProductInformation $ProgramName $PublisherID $RandomState $RecursionLimit $RegisteredDeviceClasses $RegisteredUserName $ReleaseNumber $RequesterAddress $RequesterWolframID $RequesterWolframUUID $ResourceSystemBase $RootDirectory $ScheduledTask $ScriptCommandLine $ScriptInputString $SecuredAuthenticationKeyTokens $ServiceCreditsAvailable $Services $SessionID $SetParentLink $SharedFunctions $SharedVariables $SoundDisplay $SoundDisplayFunction $SourceLink $SSHAuthentication $SummaryBoxDataSizeLimit $SuppressInputFormHeads $SynchronousEvaluation $SyntaxHandler $System $SystemCharacterEncoding $SystemID $SystemMemory $SystemShell $SystemTimeZone $SystemWordLength $TemplatePath $TemporaryDirectory $TemporaryPrefix $TestFileName $TextStyle $TimedOut $TimeUnit $TimeZone $TimeZoneEntity $TopDirectory $TraceOff $TraceOn $TracePattern $TracePostAction $TracePreAction $UnitSystem $Urgent $UserAddOnsDirectory $UserAgentLanguages $UserAgentMachine $UserAgentName $UserAgentOperatingSystem $UserAgentString $UserAgentVersion $UserBaseDirectory $UserDocumentsDirectory $Username $UserName $UserURLBase $Version $VersionNumber $VoiceStyles $WolframID $WolframUUID"},contains:[a.COMMENT("\\(\\*",
-"\\*\\)",{contains:["self"]}),a.QUOTE_STRING_MODE,a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("matlab",function(){return function(a){var b={relevance:0,contains:[{begin:"('|\\.')+"}]};return{name:"Matlab",keywords:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell "},illegal:'(//|"|#|/\\*|\\s+/\\w+)',
-contains:[{className:"function",beginKeywords:"function",end:"$",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}]}]},{className:"built_in",begin:/true|false/,relevance:0,starts:b},{begin:"[a-zA-Z][a-zA-Z_0-9]*('|\\.')+",relevance:0},{className:"number",begin:a.C_NUMBER_RE,relevance:0,starts:b},{className:"string",begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE,{begin:"''"}]},{begin:/\]|}|\)/,relevance:0,starts:b},{className:"string",begin:'"',
-end:'"',contains:[a.BACKSLASH_ESCAPE,{begin:'""'}],starts:b},a.COMMENT("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),a.COMMENT("\\%","$")]}}}());
-hljs.registerLanguage("maxima",function(){return function(a){return{name:"Maxima",keywords:{$pattern:"[A-Za-z_%][0-9A-Za-z_%]*",keyword:"if then else elseif for thru do while unless step in and or not",literal:"true false unknown inf minf ind und %e %i %pi %phi %gamma",built_in:" abasep abs absint absolute_real_time acos acosh acot acoth acsc acsch activate addcol add_edge add_edges addmatrices addrow add_vertex add_vertices adjacency_matrix adjoin adjoint af agd airy airy_ai airy_bi airy_dai airy_dbi algsys alg_type alias allroots alphacharp alphanumericp amortization %and annuity_fv annuity_pv antid antidiff AntiDifference append appendfile apply apply1 apply2 applyb1 apropos args arit_amortization arithmetic arithsum array arrayapply arrayinfo arraymake arraysetapply ascii asec asech asin asinh askinteger asksign assoc assoc_legendre_p assoc_legendre_q assume assume_external_byte_order asympa at atan atan2 atanh atensimp atom atvalue augcoefmatrix augmented_lagrangian_method av average_degree backtrace bars barsplot barsplot_description base64 base64_decode bashindices batch batchload bc2 bdvac belln benefit_cost bern bernpoly bernstein_approx bernstein_expand bernstein_poly bessel bessel_i bessel_j bessel_k bessel_simplify bessel_y beta beta_incomplete beta_incomplete_generalized beta_incomplete_regularized bezout bfallroots bffac bf_find_root bf_fmin_cobyla bfhzeta bfloat bfloatp bfpsi bfpsi0 bfzeta biconnected_components bimetric binomial bipartition block blockmatrixp bode_gain bode_phase bothcoef box boxplot boxplot_description break bug_report build_info|10 buildq build_sample burn cabs canform canten cardinality carg cartan cartesian_product catch cauchy_matrix cbffac cdf_bernoulli cdf_beta cdf_binomial cdf_cauchy cdf_chi2 cdf_continuous_uniform cdf_discrete_uniform cdf_exp cdf_f cdf_gamma cdf_general_finite_discrete cdf_geometric cdf_gumbel cdf_hypergeometric cdf_laplace cdf_logistic cdf_lognormal cdf_negative_binomial cdf_noncentral_chi2 cdf_noncentral_student_t cdf_normal cdf_pareto cdf_poisson cdf_rank_sum cdf_rayleigh cdf_signed_rank cdf_student_t cdf_weibull cdisplay ceiling central_moment cequal cequalignore cf cfdisrep cfexpand cgeodesic cgreaterp cgreaterpignore changename changevar chaosgame charat charfun charfun2 charlist charp charpoly chdir chebyshev_t chebyshev_u checkdiv check_overlaps chinese cholesky christof chromatic_index chromatic_number cint circulant_graph clear_edge_weight clear_rules clear_vertex_label clebsch_gordan clebsch_graph clessp clesspignore close closefile cmetric coeff coefmatrix cograd col collapse collectterms columnop columnspace columnswap columnvector combination combine comp2pui compare compfile compile compile_file complement_graph complete_bipartite_graph complete_graph complex_number_p components compose_functions concan concat conjugate conmetderiv connected_components connect_vertices cons constant constantp constituent constvalue cont2part content continuous_freq contortion contour_plot contract contract_edge contragrad contrib_ode convert coord copy copy_file copy_graph copylist copymatrix cor cos cosh cot coth cov cov1 covdiff covect covers crc24sum create_graph create_list csc csch csetup cspline ctaylor ct_coordsys ctransform ctranspose cube_graph cuboctahedron_graph cunlisp cv cycle_digraph cycle_graph cylindrical days360 dblint deactivate declare declare_constvalue declare_dimensions declare_fundamental_dimensions declare_fundamental_units declare_qty declare_translated declare_unit_conversion declare_units declare_weights decsym defcon define define_alt_display define_variable defint defmatch defrule defstruct deftaylor degree_sequence del delete deleten delta demo demoivre denom depends derivdegree derivlist describe desolve determinant dfloat dgauss_a dgauss_b dgeev dgemm dgeqrf dgesv dgesvd diag diagmatrix diag_matrix diagmatrixp diameter diff digitcharp dimacs_export dimacs_import dimension dimensionless dimensions dimensions_as_list direct directory discrete_freq disjoin disjointp disolate disp dispcon dispform dispfun dispJordan display disprule dispterms distrib divide divisors divsum dkummer_m dkummer_u dlange dodecahedron_graph dotproduct dotsimp dpart draw draw2d draw3d drawdf draw_file draw_graph dscalar echelon edge_coloring edge_connectivity edges eigens_by_jacobi eigenvalues eigenvectors eighth einstein eivals eivects elapsed_real_time elapsed_run_time ele2comp ele2polynome ele2pui elem elementp elevation_grid elim elim_allbut eliminate eliminate_using ellipse elliptic_e elliptic_ec elliptic_eu elliptic_f elliptic_kc elliptic_pi ematrix empty_graph emptyp endcons entermatrix entertensor entier equal equalp equiv_classes erf erfc erf_generalized erfi errcatch error errormsg errors euler ev eval_string evenp every evolution evolution2d evundiff example exp expand expandwrt expandwrt_factored expint expintegral_chi expintegral_ci expintegral_e expintegral_e1 expintegral_ei expintegral_e_simplify expintegral_li expintegral_shi expintegral_si explicit explose exponentialize express expt exsec extdiff extract_linear_equations extremal_subset ezgcd %f f90 facsum factcomb factor factorfacsum factorial factorout factorsum facts fast_central_elements fast_linsolve fasttimes featurep fernfale fft fib fibtophi fifth filename_merge file_search file_type fillarray findde find_root find_root_abs find_root_error find_root_rel first fix flatten flength float floatnump floor flower_snark flush flush1deriv flushd flushnd flush_output fmin_cobyla forget fortran fourcos fourexpand fourier fourier_elim fourint fourintcos fourintsin foursimp foursin fourth fposition frame_bracket freeof freshline fresnel_c fresnel_s from_adjacency_matrix frucht_graph full_listify fullmap fullmapl fullratsimp fullratsubst fullsetify funcsolve fundamental_dimensions fundamental_units fundef funmake funp fv g0 g1 gamma gamma_greek gamma_incomplete gamma_incomplete_generalized gamma_incomplete_regularized gauss gauss_a gauss_b gaussprob gcd gcdex gcdivide gcfac gcfactor gd generalized_lambert_w genfact gen_laguerre genmatrix gensym geo_amortization geo_annuity_fv geo_annuity_pv geomap geometric geometric_mean geosum get getcurrentdirectory get_edge_weight getenv get_lu_factors get_output_stream_string get_pixel get_plot_option get_tex_environment get_tex_environment_default get_vertex_label gfactor gfactorsum ggf girth global_variances gn gnuplot_close gnuplot_replot gnuplot_reset gnuplot_restart gnuplot_start go Gosper GosperSum gr2d gr3d gradef gramschmidt graph6_decode graph6_encode graph6_export graph6_import graph_center graph_charpoly graph_eigenvalues graph_flow graph_order graph_periphery graph_product graph_size graph_union great_rhombicosidodecahedron_graph great_rhombicuboctahedron_graph grid_graph grind grobner_basis grotzch_graph hamilton_cycle hamilton_path hankel hankel_1 hankel_2 harmonic harmonic_mean hav heawood_graph hermite hessian hgfred hilbertmap hilbert_matrix hipow histogram histogram_description hodge horner hypergeometric i0 i1 %ibes ic1 ic2 ic_convert ichr1 ichr2 icosahedron_graph icosidodecahedron_graph icurvature ident identfor identity idiff idim idummy ieqn %if ifactors iframes ifs igcdex igeodesic_coords ilt image imagpart imetric implicit implicit_derivative implicit_plot indexed_tensor indices induced_subgraph inferencep inference_result infix info_display init_atensor init_ctensor in_neighbors innerproduct inpart inprod inrt integerp integer_partitions integrate intersect intersection intervalp intopois intosum invariant1 invariant2 inverse_fft inverse_jacobi_cd inverse_jacobi_cn inverse_jacobi_cs inverse_jacobi_dc inverse_jacobi_dn inverse_jacobi_ds inverse_jacobi_nc inverse_jacobi_nd inverse_jacobi_ns inverse_jacobi_sc inverse_jacobi_sd inverse_jacobi_sn invert invert_by_adjoint invert_by_lu inv_mod irr is is_biconnected is_bipartite is_connected is_digraph is_edge_in_graph is_graph is_graph_or_digraph ishow is_isomorphic isolate isomorphism is_planar isqrt isreal_p is_sconnected is_tree is_vertex_in_graph items_inference %j j0 j1 jacobi jacobian jacobi_cd jacobi_cn jacobi_cs jacobi_dc jacobi_dn jacobi_ds jacobi_nc jacobi_nd jacobi_ns jacobi_p jacobi_sc jacobi_sd jacobi_sn JF jn join jordan julia julia_set julia_sin %k kdels kdelta kill killcontext kostka kron_delta kronecker_product kummer_m kummer_u kurtosis kurtosis_bernoulli kurtosis_beta kurtosis_binomial kurtosis_chi2 kurtosis_continuous_uniform kurtosis_discrete_uniform kurtosis_exp kurtosis_f kurtosis_gamma kurtosis_general_finite_discrete kurtosis_geometric kurtosis_gumbel kurtosis_hypergeometric kurtosis_laplace kurtosis_logistic kurtosis_lognormal kurtosis_negative_binomial kurtosis_noncentral_chi2 kurtosis_noncentral_student_t kurtosis_normal kurtosis_pareto kurtosis_poisson kurtosis_rayleigh kurtosis_student_t kurtosis_weibull label labels lagrange laguerre lambda lambert_w laplace laplacian_matrix last lbfgs lc2kdt lcharp lc_l lcm lc_u ldefint ldisp ldisplay legendre_p legendre_q leinstein length let letrules letsimp levi_civita lfreeof lgtreillis lhs li liediff limit Lindstedt linear linearinterpol linear_program linear_regression line_graph linsolve listarray list_correlations listify list_matrix_entries list_nc_monomials listoftens listofvars listp lmax lmin load loadfile local locate_matrix_entry log logcontract log_gamma lopow lorentz_gauge lowercasep lpart lratsubst lreduce lriemann lsquares_estimates lsquares_estimates_approximate lsquares_estimates_exact lsquares_mse lsquares_residual_mse lsquares_residuals lsum ltreillis lu_backsub lucas lu_factor %m macroexpand macroexpand1 make_array makebox makefact makegamma make_graph make_level_picture makelist makeOrders make_poly_continent make_poly_country make_polygon make_random_state make_rgb_picture makeset make_string_input_stream make_string_output_stream make_transform mandelbrot mandelbrot_set map mapatom maplist matchdeclare matchfix mat_cond mat_fullunblocker mat_function mathml_display mat_norm matrix matrixmap matrixp matrix_size mattrace mat_trace mat_unblocker max max_clique max_degree max_flow maximize_lp max_independent_set max_matching maybe md5sum mean mean_bernoulli mean_beta mean_binomial mean_chi2 mean_continuous_uniform mean_deviation mean_discrete_uniform mean_exp mean_f mean_gamma mean_general_finite_discrete mean_geometric mean_gumbel mean_hypergeometric mean_laplace mean_logistic mean_lognormal mean_negative_binomial mean_noncentral_chi2 mean_noncentral_student_t mean_normal mean_pareto mean_poisson mean_rayleigh mean_student_t mean_weibull median median_deviation member mesh metricexpandall mgf1_sha1 min min_degree min_edge_cut minfactorial minimalPoly minimize_lp minimum_spanning_tree minor minpack_lsquares minpack_solve min_vertex_cover min_vertex_cut mkdir mnewton mod mode_declare mode_identity ModeMatrix moebius mon2schur mono monomial_dimensions multibernstein_poly multi_display_for_texinfo multi_elem multinomial multinomial_coeff multi_orbit multiplot_mode multi_pui multsym multthru mycielski_graph nary natural_unit nc_degree ncexpt ncharpoly negative_picture neighbors new newcontext newdet new_graph newline newton new_variable next_prime nicedummies niceindices ninth nofix nonarray noncentral_moment nonmetricity nonnegintegerp nonscalarp nonzeroandfreeof notequal nounify nptetrad npv nroots nterms ntermst nthroot nullity nullspace num numbered_boundaries numberp number_to_octets num_distinct_partitions numerval numfactor num_partitions nusum nzeta nzetai nzetar octets_to_number octets_to_oid odd_girth oddp ode2 ode_check odelin oid_to_octets op opena opena_binary openr openr_binary openw openw_binary operatorp opsubst optimize %or orbit orbits ordergreat ordergreatp orderless orderlessp orthogonal_complement orthopoly_recur orthopoly_weight outermap out_neighbors outofpois pade parabolic_cylinder_d parametric parametric_surface parg parGosper parse_string parse_timedate part part2cont partfrac partition partition_set partpol path_digraph path_graph pathname_directory pathname_name pathname_type pdf_bernoulli pdf_beta pdf_binomial pdf_cauchy pdf_chi2 pdf_continuous_uniform pdf_discrete_uniform pdf_exp pdf_f pdf_gamma pdf_general_finite_discrete pdf_geometric pdf_gumbel pdf_hypergeometric pdf_laplace pdf_logistic pdf_lognormal pdf_negative_binomial pdf_noncentral_chi2 pdf_noncentral_student_t pdf_normal pdf_pareto pdf_poisson pdf_rank_sum pdf_rayleigh pdf_signed_rank pdf_student_t pdf_weibull pearson_skewness permanent permut permutation permutations petersen_graph petrov pickapart picture_equalp picturep piechart piechart_description planar_embedding playback plog plot2d plot3d plotdf ploteq plsquares pochhammer points poisdiff poisexpt poisint poismap poisplus poissimp poissubst poistimes poistrim polar polarform polartorect polar_to_xy poly_add poly_buchberger poly_buchberger_criterion poly_colon_ideal poly_content polydecomp poly_depends_p poly_elimination_ideal poly_exact_divide poly_expand poly_expt poly_gcd polygon poly_grobner poly_grobner_equal poly_grobner_member poly_grobner_subsetp poly_ideal_intersection poly_ideal_polysaturation poly_ideal_polysaturation1 poly_ideal_saturation poly_ideal_saturation1 poly_lcm poly_minimization polymod poly_multiply polynome2ele polynomialp poly_normal_form poly_normalize poly_normalize_list poly_polysaturation_extension poly_primitive_part poly_pseudo_divide poly_reduced_grobner poly_reduction poly_saturation_extension poly_s_polynomial poly_subtract polytocompanion pop postfix potential power_mod powerseries powerset prefix prev_prime primep primes principal_components print printf printfile print_graph printpois printprops prodrac product properties propvars psi psubst ptriangularize pui pui2comp pui2ele pui2polynome pui_direct puireduc push put pv qput qrange qty quad_control quad_qag quad_qagi quad_qagp quad_qags quad_qawc quad_qawf quad_qawo quad_qaws quadrilateral quantile quantile_bernoulli quantile_beta quantile_binomial quantile_cauchy quantile_chi2 quantile_continuous_uniform quantile_discrete_uniform quantile_exp quantile_f quantile_gamma quantile_general_finite_discrete quantile_geometric quantile_gumbel quantile_hypergeometric quantile_laplace quantile_logistic quantile_lognormal quantile_negative_binomial quantile_noncentral_chi2 quantile_noncentral_student_t quantile_normal quantile_pareto quantile_poisson quantile_rayleigh quantile_student_t quantile_weibull quartile_skewness quit qunit quotient racah_v racah_w radcan radius random random_bernoulli random_beta random_binomial random_bipartite_graph random_cauchy random_chi2 random_continuous_uniform random_digraph random_discrete_uniform random_exp random_f random_gamma random_general_finite_discrete random_geometric random_graph random_graph1 random_gumbel random_hypergeometric random_laplace random_logistic random_lognormal random_negative_binomial random_network random_noncentral_chi2 random_noncentral_student_t random_normal random_pareto random_permutation random_poisson random_rayleigh random_regular_graph random_student_t random_tournament random_tree random_weibull range rank rat ratcoef ratdenom ratdiff ratdisrep ratexpand ratinterpol rational rationalize ratnumer ratnump ratp ratsimp ratsubst ratvars ratweight read read_array read_binary_array read_binary_list read_binary_matrix readbyte readchar read_hashed_array readline read_list read_matrix read_nested_list readonly read_xpm real_imagpart_to_conjugate realpart realroots rearray rectangle rectform rectform_log_if_constant recttopolar rediff reduce_consts reduce_order region region_boundaries region_boundaries_plus rem remainder remarray rembox remcomps remcon remcoord remfun remfunction remlet remove remove_constvalue remove_dimensions remove_edge remove_fundamental_dimensions remove_fundamental_units remove_plot_option remove_vertex rempart remrule remsym remvalue rename rename_file reset reset_displays residue resolvante resolvante_alternee1 resolvante_bipartite resolvante_diedrale resolvante_klein resolvante_klein3 resolvante_produit_sym resolvante_unitaire resolvante_vierer rest resultant return reveal reverse revert revert2 rgb2level rhs ricci riemann rinvariant risch rk rmdir rncombine romberg room rootscontract round row rowop rowswap rreduce run_testsuite %s save saving scalarp scaled_bessel_i scaled_bessel_i0 scaled_bessel_i1 scalefactors scanmap scatterplot scatterplot_description scene schur2comp sconcat scopy scsimp scurvature sdowncase sec sech second sequal sequalignore set_alt_display setdifference set_draw_defaults set_edge_weight setelmx setequalp setify setp set_partitions set_plot_option set_prompt set_random_state set_tex_environment set_tex_environment_default setunits setup_autoload set_up_dot_simplifications set_vertex_label seventh sexplode sf sha1sum sha256sum shortest_path shortest_weighted_path show showcomps showratvars sierpinskiale sierpinskimap sign signum similaritytransform simp_inequality simplify_sum simplode simpmetderiv simtran sin sinh sinsert sinvertcase sixth skewness skewness_bernoulli skewness_beta skewness_binomial skewness_chi2 skewness_continuous_uniform skewness_discrete_uniform skewness_exp skewness_f skewness_gamma skewness_general_finite_discrete skewness_geometric skewness_gumbel skewness_hypergeometric skewness_laplace skewness_logistic skewness_lognormal skewness_negative_binomial skewness_noncentral_chi2 skewness_noncentral_student_t skewness_normal skewness_pareto skewness_poisson skewness_rayleigh skewness_student_t skewness_weibull slength smake small_rhombicosidodecahedron_graph small_rhombicuboctahedron_graph smax smin smismatch snowmap snub_cube_graph snub_dodecahedron_graph solve solve_rec solve_rec_rat some somrac sort sparse6_decode sparse6_encode sparse6_export sparse6_import specint spherical spherical_bessel_j spherical_bessel_y spherical_hankel1 spherical_hankel2 spherical_harmonic spherical_to_xyz splice split sposition sprint sqfr sqrt sqrtdenest sremove sremovefirst sreverse ssearch ssort sstatus ssubst ssubstfirst staircase standardize standardize_inverse_trig starplot starplot_description status std std1 std_bernoulli std_beta std_binomial std_chi2 std_continuous_uniform std_discrete_uniform std_exp std_f std_gamma std_general_finite_discrete std_geometric std_gumbel std_hypergeometric std_laplace std_logistic std_lognormal std_negative_binomial std_noncentral_chi2 std_noncentral_student_t std_normal std_pareto std_poisson std_rayleigh std_student_t std_weibull stemplot stirling stirling1 stirling2 strim striml strimr string stringout stringp strong_components struve_h struve_l sublis sublist sublist_indices submatrix subsample subset subsetp subst substinpart subst_parallel substpart substring subvar subvarp sum sumcontract summand_to_rec supcase supcontext symbolp symmdifference symmetricp system take_channel take_inference tan tanh taylor taylorinfo taylorp taylor_simplifier taytorat tcl_output tcontract tellrat tellsimp tellsimpafter tentex tenth test_mean test_means_difference test_normality test_proportion test_proportions_difference test_rank_sum test_sign test_signed_rank test_variance test_variance_ratio tex tex1 tex_display texput %th third throw time timedate timer timer_info tldefint tlimit todd_coxeter toeplitz tokens to_lisp topological_sort to_poly to_poly_solve totaldisrep totalfourier totient tpartpol trace tracematrix trace_options transform_sample translate translate_file transpose treefale tree_reduce treillis treinat triangle triangularize trigexpand trigrat trigreduce trigsimp trunc truncate truncated_cube_graph truncated_dodecahedron_graph truncated_icosahedron_graph truncated_tetrahedron_graph tr_warnings_get tube tutte_graph ueivects uforget ultraspherical underlying_graph undiff union unique uniteigenvectors unitp units unit_step unitvector unorder unsum untellrat untimer untrace uppercasep uricci uriemann uvect vandermonde_matrix var var1 var_bernoulli var_beta var_binomial var_chi2 var_continuous_uniform var_discrete_uniform var_exp var_f var_gamma var_general_finite_discrete var_geometric var_gumbel var_hypergeometric var_laplace var_logistic var_lognormal var_negative_binomial var_noncentral_chi2 var_noncentral_student_t var_normal var_pareto var_poisson var_rayleigh var_student_t var_weibull vector vectorpotential vectorsimp verbify vers vertex_coloring vertex_connectivity vertex_degree vertex_distance vertex_eccentricity vertex_in_degree vertex_out_degree vertices vertices_to_cycle vertices_to_path %w weyl wheel_graph wiener_index wigner_3j wigner_6j wigner_9j with_stdout write_binary_data writebyte write_data writefile wronskian xreduce xthru %y Zeilberger zeroequiv zerofor zeromatrix zeromatrixp zeta zgeev zheev zlange zn_add_table zn_carmichael_lambda zn_characteristic_factors zn_determinant zn_factor_generators zn_invert_by_lu zn_log zn_mult_table absboxchar activecontexts adapt_depth additive adim aform algebraic algepsilon algexact aliases allbut all_dotsimp_denoms allocation allsym alphabetic animation antisymmetric arrays askexp assume_pos assume_pos_pred assumescalar asymbol atomgrad atrig1 axes axis_3d axis_bottom axis_left axis_right axis_top azimuth background background_color backsubst berlefact bernstein_explicit besselexpand beta_args_sum_to_integer beta_expand bftorat bftrunc bindtest border boundaries_array box boxchar breakup %c capping cauchysum cbrange cbtics center cflength cframe_flag cnonmet_flag color color_bar color_bar_tics colorbox columns commutative complex cone context contexts contour contour_levels cosnpiflag ctaypov ctaypt ctayswitch ctayvar ct_coords ctorsion_flag ctrgsimp cube current_let_rule_package cylinder data_file_name debugmode decreasing default_let_rule_package delay dependencies derivabbrev derivsubst detout diagmetric diff dim dimensions dispflag display2d|10 display_format_internal distribute_over doallmxops domain domxexpt domxmxops domxnctimes dontfactor doscmxops doscmxplus dot0nscsimp dot0simp dot1simp dotassoc dotconstrules dotdistrib dotexptsimp dotident dotscrules draw_graph_program draw_realpart edge_color edge_coloring edge_partition edge_type edge_width %edispflag elevation %emode endphi endtheta engineering_format_floats enhanced3d %enumer epsilon_lp erfflag erf_representation errormsg error_size error_syms error_type %e_to_numlog eval even evenfun evflag evfun ev_point expandwrt_denom expintexpand expintrep expon expop exptdispflag exptisolate exptsubst facexpand facsum_combine factlim factorflag factorial_expand factors_only fb feature features file_name file_output_append file_search_demo file_search_lisp file_search_maxima|10 file_search_tests file_search_usage file_type_lisp file_type_maxima|10 fill_color fill_density filled_func fixed_vertices flipflag float2bf font font_size fortindent fortspaces fpprec fpprintprec functions gamma_expand gammalim gdet genindex gensumnum GGFCFMAX GGFINFINITY globalsolve gnuplot_command gnuplot_curve_styles gnuplot_curve_titles gnuplot_default_term_command gnuplot_dumb_term_command gnuplot_file_args gnuplot_file_name gnuplot_out_file gnuplot_pdf_term_command gnuplot_pm3d gnuplot_png_term_command gnuplot_postamble gnuplot_preamble gnuplot_ps_term_command gnuplot_svg_term_command gnuplot_term gnuplot_view_args Gosper_in_Zeilberger gradefs grid grid2d grind halfangles head_angle head_both head_length head_type height hypergeometric_representation %iargs ibase icc1 icc2 icounter idummyx ieqnprint ifb ifc1 ifc2 ifg ifgi ifr iframe_bracket_form ifri igeowedge_flag ikt1 ikt2 imaginary inchar increasing infeval infinity inflag infolists inm inmc1 inmc2 intanalysis integer integervalued integrate_use_rootsof integration_constant integration_constant_counter interpolate_color intfaclim ip_grid ip_grid_in irrational isolate_wrt_times iterations itr julia_parameter %k1 %k2 keepfloat key key_pos kinvariant kt label label_alignment label_orientation labels lassociative lbfgs_ncorrections lbfgs_nfeval_max leftjust legend letrat let_rule_packages lfg lg lhospitallim limsubst linear linear_solver linechar linel|10 linenum line_type linewidth line_width linsolve_params linsolvewarn lispdisp listarith listconstvars listdummyvars lmxchar load_pathname loadprint logabs logarc logcb logconcoeffp logexpand lognegint logsimp logx logx_secondary logy logy_secondary logz lriem m1pbranch macroexpansion macros mainvar manual_demo maperror mapprint matrix_element_add matrix_element_mult matrix_element_transpose maxapplydepth maxapplyheight maxima_tempdir|10 maxima_userdir|10 maxnegex MAX_ORD maxposex maxpsifracdenom maxpsifracnum maxpsinegint maxpsiposint maxtayorder mesh_lines_color method mod_big_prime mode_check_errorp mode_checkp mode_check_warnp mod_test mod_threshold modular_linear_solver modulus multiplicative multiplicities myoptions nary negdistrib negsumdispflag newline newtonepsilon newtonmaxiter nextlayerfactor niceindicespref nm nmc noeval nolabels nonegative_lp noninteger nonscalar noun noundisp nouns np npi nticks ntrig numer numer_pbranch obase odd oddfun opacity opproperties opsubst optimprefix optionset orientation origin orthopoly_returns_intervals outative outchar packagefile palette partswitch pdf_file pfeformat phiresolution %piargs piece pivot_count_sx pivot_max_sx plot_format plot_options plot_realpart png_file pochhammer_max_index points pointsize point_size points_joined point_type poislim poisson poly_coefficient_ring poly_elimination_order polyfactor poly_grobner_algorithm poly_grobner_debug poly_monomial_order poly_primary_elimination_order poly_return_term_list poly_secondary_elimination_order poly_top_reduction_only posfun position powerdisp pred prederror primep_number_of_tests product_use_gamma program programmode promote_float_to_bigfloat prompt proportional_axes props psexpand ps_file radexpand radius radsubstflag rassociative ratalgdenom ratchristof ratdenomdivide rateinstein ratepsilon ratfac rational ratmx ratprint ratriemann ratsimpexpons ratvarswitch ratweights ratweyl ratwtlvl real realonly redraw refcheck resolution restart resultant ric riem rmxchar %rnum_list rombergabs rombergit rombergmin rombergtol rootsconmode rootsepsilon run_viewer same_xy same_xyz savedef savefactors scalar scalarmatrixp scale scale_lp setcheck setcheckbreak setval show_edge_color show_edges show_edge_type show_edge_width show_id show_label showtime show_vertex_color show_vertex_size show_vertex_type show_vertices show_weight simp simplified_output simplify_products simpproduct simpsum sinnpiflag solvedecomposes solveexplicit solvefactors solvenullwarn solveradcan solvetrigwarn space sparse sphere spring_embedding_depth sqrtdispflag stardisp startphi starttheta stats_numer stringdisp structures style sublis_apply_lambda subnumsimp sumexpand sumsplitfact surface surface_hide svg_file symmetric tab taylordepth taylor_logexpand taylor_order_coefficients taylor_truncate_polynomials tensorkill terminal testsuite_files thetaresolution timer_devalue title tlimswitch tr track transcompile transform transform_xy translate_fast_arrays transparent transrun tr_array_as_ref tr_bound_function_applyp tr_file_tty_messagesp tr_float_can_branch_complex tr_function_call_default trigexpandplus trigexpandtimes triginverses trigsign trivial_solutions tr_numer tr_optimize_max_loop tr_semicompile tr_state_vars tr_warn_bad_function_calls tr_warn_fexpr tr_warn_meval tr_warn_mode tr_warn_undeclared tr_warn_undefined_variable tstep ttyoff tube_extremes ufg ug %unitexpand unit_vectors uric uriem use_fast_arrays user_preamble usersetunits values vect_cross verbose vertex_color vertex_coloring vertex_partition vertex_size vertex_type view warnings weyl width windowname windowtitle wired_surface wireframe xaxis xaxis_color xaxis_secondary xaxis_type xaxis_width xlabel xlabel_secondary xlength xrange xrange_secondary xtics xtics_axis xtics_rotate xtics_rotate_secondary xtics_secondary xtics_secondary_axis xu_grid x_voxel xy_file xyplane xy_scale yaxis yaxis_color yaxis_secondary yaxis_type yaxis_width ylabel ylabel_secondary ylength yrange yrange_secondary ytics ytics_axis ytics_rotate ytics_rotate_secondary ytics_secondary ytics_secondary_axis yv_grid y_voxel yx_ratio zaxis zaxis_color zaxis_type zaxis_width zeroa zerob zerobern zeta%pi zlabel zlabel_rotate zlength zmin zn_primroot_limit zn_primroot_pretest",symbol:"_ __ %|0 %%|0"},
-contains:[{className:"comment",begin:"/\\*",end:"\\*/",contains:["self"]},a.QUOTE_STRING_MODE,{className:"number",relevance:0,variants:[{begin:"\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Ee][-+]?\\d+\\b"},{begin:"\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Bb][-+]?\\d+\\b",relevance:10},{begin:"\\b(\\.\\d+|\\d+\\.\\d+)\\b"},{begin:"\\b(\\d+|0[0-9A-Za-z]+)\\.?\\b"}]}],illegal:/@/}}}());
-hljs.registerLanguage("mel",function(){return function(a){return{name:"MEL",keywords:"int float string vector matrix if else switch case default while do for in break continue global proc return about abs addAttr addAttributeEditorNodeHelp addDynamic addNewShelfTab addPP addPanelCategory addPrefixToName advanceToNextDrivenKey affectedNet affects aimConstraint air alias aliasAttr align alignCtx alignCurve alignSurface allViewFit ambientLight angle angleBetween animCone animCurveEditor animDisplay animView annotate appendStringArray applicationName applyAttrPreset applyTake arcLenDimContext arcLengthDimension arclen arrayMapper art3dPaintCtx artAttrCtx artAttrPaintVertexCtx artAttrSkinPaintCtx artAttrTool artBuildPaintMenu artFluidAttrCtx artPuttyCtx artSelectCtx artSetPaintCtx artUserPaintCtx assignCommand assignInputDevice assignViewportFactories attachCurve attachDeviceAttr attachSurface attrColorSliderGrp attrCompatibility attrControlGrp attrEnumOptionMenu attrEnumOptionMenuGrp attrFieldGrp attrFieldSliderGrp attrNavigationControlGrp attrPresetEditWin attributeExists attributeInfo attributeMenu attributeQuery autoKeyframe autoPlace bakeClip bakeFluidShading bakePartialHistory bakeResults bakeSimulation basename basenameEx batchRender bessel bevel bevelPlus binMembership bindSkin blend2 blendShape blendShapeEditor blendShapePanel blendTwoAttr blindDataType boneLattice boundary boxDollyCtx boxZoomCtx bufferCurve buildBookmarkMenu buildKeyframeMenu button buttonManip CBG cacheFile cacheFileCombine cacheFileMerge cacheFileTrack camera cameraView canCreateManip canvas capitalizeString catch catchQuiet ceil changeSubdivComponentDisplayLevel changeSubdivRegion channelBox character characterMap characterOutlineEditor characterize chdir checkBox checkBoxGrp checkDefaultRenderGlobals choice circle circularFillet clamp clear clearCache clip clipEditor clipEditorCurrentTimeCtx clipSchedule clipSchedulerOutliner clipTrimBefore closeCurve closeSurface cluster cmdFileOutput cmdScrollFieldExecuter cmdScrollFieldReporter cmdShell coarsenSubdivSelectionList collision color colorAtPoint colorEditor colorIndex colorIndexSliderGrp colorSliderButtonGrp colorSliderGrp columnLayout commandEcho commandLine commandPort compactHairSystem componentEditor compositingInterop computePolysetVolume condition cone confirmDialog connectAttr connectControl connectDynamic connectJoint connectionInfo constrain constrainValue constructionHistory container containsMultibyte contextInfo control convertFromOldLayers convertIffToPsd convertLightmap convertSolidTx convertTessellation convertUnit copyArray copyFlexor copyKey copySkinWeights cos cpButton cpCache cpClothSet cpCollision cpConstraint cpConvClothToMesh cpForces cpGetSolverAttr cpPanel cpProperty cpRigidCollisionFilter cpSeam cpSetEdit cpSetSolverAttr cpSolver cpSolverTypes cpTool cpUpdateClothUVs createDisplayLayer createDrawCtx createEditor createLayeredPsdFile createMotionField createNewShelf createNode createRenderLayer createSubdivRegion cross crossProduct ctxAbort ctxCompletion ctxEditMode ctxTraverse currentCtx currentTime currentTimeCtx currentUnit curve curveAddPtCtx curveCVCtx curveEPCtx curveEditorCtx curveIntersect curveMoveEPCtx curveOnSurface curveSketchCtx cutKey cycleCheck cylinder dagPose date defaultLightListCheckBox defaultNavigation defineDataServer defineVirtualDevice deformer deg_to_rad delete deleteAttr deleteShadingGroupsAndMaterials deleteShelfTab deleteUI deleteUnusedBrushes delrandstr detachCurve detachDeviceAttr detachSurface deviceEditor devicePanel dgInfo dgdirty dgeval dgtimer dimWhen directKeyCtx directionalLight dirmap dirname disable disconnectAttr disconnectJoint diskCache displacementToPoly displayAffected displayColor displayCull displayLevelOfDetail displayPref displayRGBColor displaySmoothness displayStats displayString displaySurface distanceDimContext distanceDimension doBlur dolly dollyCtx dopeSheetEditor dot dotProduct doubleProfileBirailSurface drag dragAttrContext draggerContext dropoffLocator duplicate duplicateCurve duplicateSurface dynCache dynControl dynExport dynExpression dynGlobals dynPaintEditor dynParticleCtx dynPref dynRelEdPanel dynRelEditor dynamicLoad editAttrLimits editDisplayLayerGlobals editDisplayLayerMembers editRenderLayerAdjustment editRenderLayerGlobals editRenderLayerMembers editor editorTemplate effector emit emitter enableDevice encodeString endString endsWith env equivalent equivalentTol erf error eval evalDeferred evalEcho event exactWorldBoundingBox exclusiveLightCheckBox exec executeForEachObject exists exp expression expressionEditorListen extendCurve extendSurface extrude fcheck fclose feof fflush fgetline fgetword file fileBrowserDialog fileDialog fileExtension fileInfo filetest filletCurve filter filterCurve filterExpand filterStudioImport findAllIntersections findAnimCurves findKeyframe findMenuItem findRelatedSkinCluster finder firstParentOf fitBspline flexor floatEq floatField floatFieldGrp floatScrollBar floatSlider floatSlider2 floatSliderButtonGrp floatSliderGrp floor flow fluidCacheInfo fluidEmitter fluidVoxelInfo flushUndo fmod fontDialog fopen formLayout format fprint frameLayout fread freeFormFillet frewind fromNativePath fwrite gamma gauss geometryConstraint getApplicationVersionAsFloat getAttr getClassification getDefaultBrush getFileList getFluidAttr getInputDeviceRange getMayaPanelTypes getModifiers getPanel getParticleAttr getPluginResource getenv getpid glRender glRenderEditor globalStitch gmatch goal gotoBindPose grabColor gradientControl gradientControlNoAttr graphDollyCtx graphSelectContext graphTrackCtx gravity grid gridLayout group groupObjectsByName HfAddAttractorToAS HfAssignAS HfBuildEqualMap HfBuildFurFiles HfBuildFurImages HfCancelAFR HfConnectASToHF HfCreateAttractor HfDeleteAS HfEditAS HfPerformCreateAS HfRemoveAttractorFromAS HfSelectAttached HfSelectAttractors HfUnAssignAS hardenPointCurve hardware hardwareRenderPanel headsUpDisplay headsUpMessage help helpLine hermite hide hilite hitTest hotBox hotkey hotkeyCheck hsv_to_rgb hudButton hudSlider hudSliderButton hwReflectionMap hwRender hwRenderLoad hyperGraph hyperPanel hyperShade hypot iconTextButton iconTextCheckBox iconTextRadioButton iconTextRadioCollection iconTextScrollList iconTextStaticLabel ikHandle ikHandleCtx ikHandleDisplayScale ikSolver ikSplineHandleCtx ikSystem ikSystemInfo ikfkDisplayMethod illustratorCurves image imfPlugins inheritTransform insertJoint insertJointCtx insertKeyCtx insertKnotCurve insertKnotSurface instance instanceable instancer intField intFieldGrp intScrollBar intSlider intSliderGrp interToUI internalVar intersect iprEngine isAnimCurve isConnected isDirty isParentOf isSameObject isTrue isValidObjectName isValidString isValidUiName isolateSelect itemFilter itemFilterAttr itemFilterRender itemFilterType joint jointCluster jointCtx jointDisplayScale jointLattice keyTangent keyframe keyframeOutliner keyframeRegionCurrentTimeCtx keyframeRegionDirectKeyCtx keyframeRegionDollyCtx keyframeRegionInsertKeyCtx keyframeRegionMoveKeyCtx keyframeRegionScaleKeyCtx keyframeRegionSelectKeyCtx keyframeRegionSetKeyCtx keyframeRegionTrackCtx keyframeStats lassoContext lattice latticeDeformKeyCtx launch launchImageEditor layerButton layeredShaderPort layeredTexturePort layout layoutDialog lightList lightListEditor lightListPanel lightlink lineIntersection linearPrecision linstep listAnimatable listAttr listCameras listConnections listDeviceAttachments listHistory listInputDeviceAxes listInputDeviceButtons listInputDevices listMenuAnnotation listNodeTypes listPanelCategories listRelatives listSets listTransforms listUnselected listerEditor loadFluid loadNewShelf loadPlugin loadPluginLanguageResources loadPrefObjects localizedPanelLabel lockNode loft log longNameOf lookThru ls lsThroughFilter lsType lsUI Mayatomr mag makeIdentity makeLive makePaintable makeRoll makeSingleSurface makeTubeOn makebot manipMoveContext manipMoveLimitsCtx manipOptions manipRotateContext manipRotateLimitsCtx manipScaleContext manipScaleLimitsCtx marker match max memory menu menuBarLayout menuEditor menuItem menuItemToShelf menuSet menuSetPref messageLine min minimizeApp mirrorJoint modelCurrentTimeCtx modelEditor modelPanel mouse movIn movOut move moveIKtoFK moveKeyCtx moveVertexAlongDirection multiProfileBirailSurface mute nParticle nameCommand nameField namespace namespaceInfo newPanelItems newton nodeCast nodeIconButton nodeOutliner nodePreset nodeType noise nonLinear normalConstraint normalize nurbsBoolean nurbsCopyUVSet nurbsCube nurbsEditUV nurbsPlane nurbsSelect nurbsSquare nurbsToPoly nurbsToPolygonsPref nurbsToSubdiv nurbsToSubdivPref nurbsUVSet nurbsViewDirectionVector objExists objectCenter objectLayer objectType objectTypeUI obsoleteProc oceanNurbsPreviewPlane offsetCurve offsetCurveOnSurface offsetSurface openGLExtension openMayaPref optionMenu optionMenuGrp optionVar orbit orbitCtx orientConstraint outlinerEditor outlinerPanel overrideModifier paintEffectsDisplay pairBlend palettePort paneLayout panel panelConfiguration panelHistory paramDimContext paramDimension paramLocator parent parentConstraint particle particleExists particleInstancer particleRenderInfo partition pasteKey pathAnimation pause pclose percent performanceOptions pfxstrokes pickWalk picture pixelMove planarSrf plane play playbackOptions playblast plugAttr plugNode pluginInfo pluginResourceUtil pointConstraint pointCurveConstraint pointLight pointMatrixMult pointOnCurve pointOnSurface pointPosition poleVectorConstraint polyAppend polyAppendFacetCtx polyAppendVertex polyAutoProjection polyAverageNormal polyAverageVertex polyBevel polyBlendColor polyBlindData polyBoolOp polyBridgeEdge polyCacheMonitor polyCheck polyChipOff polyClipboard polyCloseBorder polyCollapseEdge polyCollapseFacet polyColorBlindData polyColorDel polyColorPerVertex polyColorSet polyCompare polyCone polyCopyUV polyCrease polyCreaseCtx polyCreateFacet polyCreateFacetCtx polyCube polyCut polyCutCtx polyCylinder polyCylindricalProjection polyDelEdge polyDelFacet polyDelVertex polyDuplicateAndConnect polyDuplicateEdge polyEditUV polyEditUVShell polyEvaluate polyExtrudeEdge polyExtrudeFacet polyExtrudeVertex polyFlipEdge polyFlipUV polyForceUV polyGeoSampler polyHelix polyInfo polyInstallAction polyLayoutUV polyListComponentConversion polyMapCut polyMapDel polyMapSew polyMapSewMove polyMergeEdge polyMergeEdgeCtx polyMergeFacet polyMergeFacetCtx polyMergeUV polyMergeVertex polyMirrorFace polyMoveEdge polyMoveFacet polyMoveFacetUV polyMoveUV polyMoveVertex polyNormal polyNormalPerVertex polyNormalizeUV polyOptUvs polyOptions polyOutput polyPipe polyPlanarProjection polyPlane polyPlatonicSolid polyPoke polyPrimitive polyPrism polyProjection polyPyramid polyQuad polyQueryBlindData polyReduce polySelect polySelectConstraint polySelectConstraintMonitor polySelectCtx polySelectEditCtx polySeparate polySetToFaceNormal polySewEdge polyShortestPathCtx polySmooth polySoftEdge polySphere polySphericalProjection polySplit polySplitCtx polySplitEdge polySplitRing polySplitVertex polyStraightenUVBorder polySubdivideEdge polySubdivideFacet polyToSubdiv polyTorus polyTransfer polyTriangulate polyUVSet polyUnite polyWedgeFace popen popupMenu pose pow preloadRefEd print progressBar progressWindow projFileViewer projectCurve projectTangent projectionContext projectionManip promptDialog propModCtx propMove psdChannelOutliner psdEditTextureFile psdExport psdTextureFile putenv pwd python querySubdiv quit rad_to_deg radial radioButton radioButtonGrp radioCollection radioMenuItemCollection rampColorPort rand randomizeFollicles randstate rangeControl readTake rebuildCurve rebuildSurface recordAttr recordDevice redo reference referenceEdit referenceQuery refineSubdivSelectionList refresh refreshAE registerPluginResource rehash reloadImage removeJoint removeMultiInstance removePanelCategory rename renameAttr renameSelectionList renameUI render renderGlobalsNode renderInfo renderLayerButton renderLayerParent renderLayerPostProcess renderLayerUnparent renderManip renderPartition renderQualityNode renderSettings renderThumbnailUpdate renderWindowEditor renderWindowSelectContext renderer reorder reorderDeformers requires reroot resampleFluid resetAE resetPfxToPolyCamera resetTool resolutionNode retarget reverseCurve reverseSurface revolve rgb_to_hsv rigidBody rigidSolver roll rollCtx rootOf rot rotate rotationInterpolation roundConstantRadius rowColumnLayout rowLayout runTimeCommand runup sampleImage saveAllShelves saveAttrPreset saveFluid saveImage saveInitialState saveMenu savePrefObjects savePrefs saveShelf saveToolSettings scale scaleBrushBrightness scaleComponents scaleConstraint scaleKey scaleKeyCtx sceneEditor sceneUIReplacement scmh scriptCtx scriptEditorInfo scriptJob scriptNode scriptTable scriptToShelf scriptedPanel scriptedPanelType scrollField scrollLayout sculpt searchPathArray seed selLoadSettings select selectContext selectCurveCV selectKey selectKeyCtx selectKeyframeRegionCtx selectMode selectPref selectPriority selectType selectedNodes selectionConnection separator setAttr setAttrEnumResource setAttrMapping setAttrNiceNameResource setConstraintRestPosition setDefaultShadingGroup setDrivenKeyframe setDynamic setEditCtx setEditor setFluidAttr setFocus setInfinity setInputDeviceMapping setKeyCtx setKeyPath setKeyframe setKeyframeBlendshapeTargetWts setMenuMode setNodeNiceNameResource setNodeTypeFlag setParent setParticleAttr setPfxToPolyCamera setPluginResource setProject setStampDensity setStartupMessage setState setToolTo setUITemplate setXformManip sets shadingConnection shadingGeometryRelCtx shadingLightRelCtx shadingNetworkCompare shadingNode shapeCompare shelfButton shelfLayout shelfTabLayout shellField shortNameOf showHelp showHidden showManipCtx showSelectionInTitle showShadingGroupAttrEditor showWindow sign simplify sin singleProfileBirailSurface size sizeBytes skinCluster skinPercent smoothCurve smoothTangentSurface smoothstep snap2to2 snapKey snapMode snapTogetherCtx snapshot soft softMod softModCtx sort sound soundControl source spaceLocator sphere sphrand spotLight spotLightPreviewPort spreadSheetEditor spring sqrt squareSurface srtContext stackTrace startString startsWith stitchAndExplodeShell stitchSurface stitchSurfacePoints strcmp stringArrayCatenate stringArrayContains stringArrayCount stringArrayInsertAtIndex stringArrayIntersector stringArrayRemove stringArrayRemoveAtIndex stringArrayRemoveDuplicates stringArrayRemoveExact stringArrayToString stringToStringArray strip stripPrefixFromName stroke subdAutoProjection subdCleanTopology subdCollapse subdDuplicateAndConnect subdEditUV subdListComponentConversion subdMapCut subdMapSewMove subdMatchTopology subdMirror subdToBlind subdToPoly subdTransferUVsToCache subdiv subdivCrease subdivDisplaySmoothness substitute substituteAllString substituteGeometry substring surface surfaceSampler surfaceShaderList swatchDisplayPort switchTable symbolButton symbolCheckBox sysFile system tabLayout tan tangentConstraint texLatticeDeformContext texManipContext texMoveContext texMoveUVShellContext texRotateContext texScaleContext texSelectContext texSelectShortestPathCtx texSmudgeUVContext texWinToolCtx text textCurves textField textFieldButtonGrp textFieldGrp textManip textScrollList textToShelf textureDisplacePlane textureHairColor texturePlacementContext textureWindow threadCount threePointArcCtx timeControl timePort timerX toNativePath toggle toggleAxis toggleWindowVisibility tokenize tokenizeList tolerance tolower toolButton toolCollection toolDropped toolHasOptions toolPropertyWindow torus toupper trace track trackCtx transferAttributes transformCompare transformLimits translator trim trunc truncateFluidCache truncateHairCache tumble tumbleCtx turbulence twoPointArcCtx uiRes uiTemplate unassignInputDevice undo undoInfo ungroup uniform unit unloadPlugin untangleUV untitledFileName untrim upAxis updateAE userCtx uvLink uvSnapshot validateShelfName vectorize view2dToolCtx viewCamera viewClipPlane viewFit viewHeadOn viewLookAt viewManip viewPlace viewSet visor volumeAxis vortex waitCursor warning webBrowser webBrowserPrefs whatIs window windowPref wire wireContext workspace wrinkle wrinkleContext writeTake xbmLangPathList xform",illegal:"</",
-contains:[a.C_NUMBER_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE]},{begin:"[\\$\\%\\@](\\^\\w\\b|#\\w+|[^\\s\\w{]|{\\w+}|\\w+)"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}}}());
-hljs.registerLanguage("mercury",function(){return function(a){var b=a.COMMENT("%","$"),c=a.inherit(a.APOS_STRING_MODE,{relevance:0}),d=a.inherit(a.QUOTE_STRING_MODE,{relevance:0});d.contains=d.contains.slice();d.contains.push({className:"subst",begin:"\\\\[abfnrtv]\\|\\\\x[0-9a-fA-F]*\\\\\\|%[-+# *.0-9]*[dioxXucsfeEgGp]",relevance:0});return{name:"Mercury",aliases:["m","moo"],keywords:{keyword:"module use_module import_module include_module end_module initialise mutable initialize finalize finalise interface implementation pred mode func type inst solver any_pred any_func is semidet det nondet multi erroneous failure cc_nondet cc_multi typeclass instance where pragma promise external trace atomic or_else require_complete_switch require_det require_semidet require_multi require_nondet require_cc_multi require_cc_nondet require_erroneous require_failure",
+built_in:"put abs acos aliasReference annuity arrayDecode arrayEncode asin atan atan2 average avg avgDev base64Decode base64Encode baseConvert binaryDecode binaryEncode byteOffset byteToNum cachedURL cachedURLs charToNum cipherNames codepointOffset codepointProperty codepointToNum codeunitOffset commandNames compound compress constantNames cos date dateFormat decompress difference directories diskSpace DNSServers exp exp1 exp2 exp10 extents files flushEvents folders format functionNames geometricMean global globals hasMemory harmonicMean hostAddress hostAddressToName hostName hostNameToAddress isNumber ISOToMac itemOffset keys len length libURLErrorData libUrlFormData libURLftpCommand libURLLastHTTPHeaders libURLLastRHHeaders libUrlMultipartFormAddPart libUrlMultipartFormData libURLVersion lineOffset ln ln1 localNames log log2 log10 longFilePath lower macToISO matchChunk matchText matrixMultiply max md5Digest median merge messageAuthenticationCode messageDigest millisec millisecs millisecond milliseconds min monthNames nativeCharToNum normalizeText num number numToByte numToChar numToCodepoint numToNativeChar offset open openfiles openProcesses openProcessIDs openSockets paragraphOffset paramCount param params peerAddress pendingMessages platform popStdDev populationStandardDeviation populationVariance popVariance processID random randomBytes replaceText result revCreateXMLTree revCreateXMLTreeFromFile revCurrentRecord revCurrentRecordIsFirst revCurrentRecordIsLast revDatabaseColumnCount revDatabaseColumnIsNull revDatabaseColumnLengths revDatabaseColumnNames revDatabaseColumnNamed revDatabaseColumnNumbered revDatabaseColumnTypes revDatabaseConnectResult revDatabaseCursors revDatabaseID revDatabaseTableNames revDatabaseType revDataFromQuery revdb_closeCursor revdb_columnbynumber revdb_columncount revdb_columnisnull revdb_columnlengths revdb_columnnames revdb_columntypes revdb_commit revdb_connect revdb_connections revdb_connectionerr revdb_currentrecord revdb_cursorconnection revdb_cursorerr revdb_cursors revdb_dbtype revdb_disconnect revdb_execute revdb_iseof revdb_isbof revdb_movefirst revdb_movelast revdb_movenext revdb_moveprev revdb_query revdb_querylist revdb_recordcount revdb_rollback revdb_tablenames revGetDatabaseDriverPath revNumberOfRecords revOpenDatabase revOpenDatabases revQueryDatabase revQueryDatabaseBlob revQueryResult revQueryIsAtStart revQueryIsAtEnd revUnixFromMacPath revXMLAttribute revXMLAttributes revXMLAttributeValues revXMLChildContents revXMLChildNames revXMLCreateTreeFromFileWithNamespaces revXMLCreateTreeWithNamespaces revXMLDataFromXPathQuery revXMLEvaluateXPath revXMLFirstChild revXMLMatchingNode revXMLNextSibling revXMLNodeContents revXMLNumberOfChildren revXMLParent revXMLPreviousSibling revXMLRootNode revXMLRPC_CreateRequest revXMLRPC_Documents revXMLRPC_Error revXMLRPC_GetHost revXMLRPC_GetMethod revXMLRPC_GetParam revXMLText revXMLRPC_Execute revXMLRPC_GetParamCount revXMLRPC_GetParamNode revXMLRPC_GetParamType revXMLRPC_GetPath revXMLRPC_GetPort revXMLRPC_GetProtocol revXMLRPC_GetRequest revXMLRPC_GetResponse revXMLRPC_GetSocket revXMLTree revXMLTrees revXMLValidateDTD revZipDescribeItem revZipEnumerateItems revZipOpenArchives round sampVariance sec secs seconds sentenceOffset sha1Digest shell shortFilePath sin specialFolderPath sqrt standardDeviation statRound stdDev sum sysError systemVersion tan tempName textDecode textEncode tick ticks time to tokenOffset toLower toUpper transpose truewordOffset trunc uniDecode uniEncode upper URLDecode URLEncode URLStatus uuid value variableNames variance version waitDepth weekdayNames wordOffset xsltApplyStylesheet xsltApplyStylesheetFromFile xsltLoadStylesheet xsltLoadStylesheetFromFile add breakpoint cancel clear local variable file word line folder directory URL close socket process combine constant convert create new alias folder directory decrypt delete variable word line folder directory URL dispatch divide do encrypt filter get include intersect kill libURLDownloadToFile libURLFollowHttpRedirects libURLftpUpload libURLftpUploadFile libURLresetAll libUrlSetAuthCallback libURLSetDriver libURLSetCustomHTTPHeaders libUrlSetExpect100 libURLSetFTPListCommand libURLSetFTPMode libURLSetFTPStopTime libURLSetStatusCallback load extension loadedExtensions multiply socket prepare process post seek rel relative read from process rename replace require resetAll resolve revAddXMLNode revAppendXML revCloseCursor revCloseDatabase revCommitDatabase revCopyFile revCopyFolder revCopyXMLNode revDeleteFolder revDeleteXMLNode revDeleteAllXMLTrees revDeleteXMLTree revExecuteSQL revGoURL revInsertXMLNode revMoveFolder revMoveToFirstRecord revMoveToLastRecord revMoveToNextRecord revMoveToPreviousRecord revMoveToRecord revMoveXMLNode revPutIntoXMLNode revRollBackDatabase revSetDatabaseDriverPath revSetXMLAttribute revXMLRPC_AddParam revXMLRPC_DeleteAllDocuments revXMLAddDTD revXMLRPC_Free revXMLRPC_FreeAll revXMLRPC_DeleteDocument revXMLRPC_DeleteParam revXMLRPC_SetHost revXMLRPC_SetMethod revXMLRPC_SetPort revXMLRPC_SetProtocol revXMLRPC_SetSocket revZipAddItemWithData revZipAddItemWithFile revZipAddUncompressedItemWithData revZipAddUncompressedItemWithFile revZipCancel revZipCloseArchive revZipDeleteItem revZipExtractItemToFile revZipExtractItemToVariable revZipSetProgressCallback revZipRenameItem revZipReplaceItemWithData revZipReplaceItemWithFile revZipOpenArchive send set sort split start stop subtract symmetric union unload vectorDotProduct wait write"
+},contains:[r,{className:"keyword",begin:"\\bend\\sif\\b"},{
+className:"function",beginKeywords:"function",end:"$",
+contains:[r,o,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,a]
+},{className:"function",begin:"\\bend\\s+",end:"$",keywords:"end",
+contains:[o,a],relevance:0},{beginKeywords:"command on",end:"$",
+contains:[r,o,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,a]
+},{className:"meta",variants:[{begin:"<\\?(rev|lc|livecode)",relevance:10},{
+begin:"<\\?"},{begin:"\\?>"}]
+},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE,a].concat(t),
+illegal:";$|^\\[|^=|&|\\{"}}})());
+hljs.registerLanguage("livescript",(()=>{"use strict"
+;const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"])
+;return t=>{const r={
+keyword:e.concat(["then","unless","until","loop","of","by","when","and","or","is","isnt","not","it","that","otherwise","from","to","til","fallthrough","case","enum","native","list","map","__hasProp","__extends","__slice","__bind","__indexOf"]),
+literal:n.concat(["yes","no","on","off","it","that","void"]),
+built_in:a.concat(["npm","print"])
+},i="[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*",s=t.inherit(t.TITLE_MODE,{
+begin:i}),o={className:"subst",begin:/#\{/,end:/\}/,keywords:r},c={
+className:"subst",begin:/#[A-Za-z$_]/,end:/(?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*/,
+keywords:r},l=[t.BINARY_NUMBER_MODE,{className:"number",
+begin:"(\\b0[xX][a-fA-F0-9_]+)|(\\b\\d(\\d|_\\d)*(\\.(\\d(\\d|_\\d)*)?)?(_*[eE]([-+]\\d(_\\d|\\d)*)?)?[_a-z]*)",
+relevance:0,starts:{end:"(\\s*/)?",relevance:0}},{className:"string",variants:[{
+begin:/'''/,end:/'''/,contains:[t.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,
+contains:[t.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,
+contains:[t.BACKSLASH_ESCAPE,o,c]},{begin:/"/,end:/"/,
+contains:[t.BACKSLASH_ESCAPE,o,c]},{begin:/\\/,end:/(\s|$)/,excludeEnd:!0}]},{
+className:"regexp",variants:[{begin:"//",end:"//[gim]*",
+contains:[o,t.HASH_COMMENT_MODE]},{
+begin:/\/(?![ *])(\\.|[^\\\n])*?\/[gim]*(?=\W)/}]},{begin:"@"+i},{begin:"``",
+end:"``",excludeBegin:!0,excludeEnd:!0,subLanguage:"javascript"}];o.contains=l
+;const d={className:"params",begin:"\\(",returnBegin:!0,contains:[{begin:/\(/,
+end:/\)/,keywords:r,contains:["self"].concat(l)}]};return{name:"LiveScript",
+aliases:["ls"],keywords:r,illegal:/\/\*/,
+contains:l.concat([t.COMMENT("\\/\\*","\\*\\/"),t.HASH_COMMENT_MODE,{
+begin:"(#=>|=>|\\|>>|-?->|!->)"},{className:"function",contains:[s,d],
+returnBegin:!0,variants:[{
+begin:"("+i+"\\s*(?:=|:=)\\s*)?(\\(.*\\)\\s*)?\\B->\\*?",end:"->\\*?"},{
+begin:"("+i+"\\s*(?:=|:=)\\s*)?!?(\\(.*\\)\\s*)?\\B[-~]{1,2}>\\*?",
+end:"[-~]{1,2}>\\*?"},{
+begin:"("+i+"\\s*(?:=|:=)\\s*)?(\\(.*\\)\\s*)?\\B!?[-~]{1,2}>\\*?",
+end:"!?[-~]{1,2}>\\*?"}]},{className:"class",beginKeywords:"class",end:"$",
+illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,
+illegal:/[:="\[\]]/,contains:[s]},s]},{begin:i+":",end:":",returnBegin:!0,
+returnEnd:!0,relevance:0}])}}})());
+hljs.registerLanguage("llvm",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const a=/([-a-zA-Z$._][\w$.-]*)/,t={
+className:"variable",variants:[{begin:e(/%/,a)},{begin:/%\d+/},{begin:/#\d+/}]
+},i={className:"title",variants:[{begin:e(/@/,a)},{begin:/@\d+/},{begin:e(/!/,a)
+},{begin:e(/!\d+/,a)},{begin:/!\d+/}]};return{name:"LLVM IR",
+keywords:"begin end true false declare define global constant private linker_private internal available_externally linkonce linkonce_odr weak weak_odr appending dllimport dllexport common default hidden protected extern_weak external thread_local zeroinitializer undef null to tail target triple datalayout volatile nuw nsw nnan ninf nsz arcp fast exact inbounds align addrspace section alias module asm sideeffect gc dbg linker_private_weak attributes blockaddress initialexec localdynamic localexec prefix unnamed_addr ccc fastcc coldcc x86_stdcallcc x86_fastcallcc arm_apcscc arm_aapcscc arm_aapcs_vfpcc ptx_device ptx_kernel intel_ocl_bicc msp430_intrcc spir_func spir_kernel x86_64_sysvcc x86_64_win64cc x86_thiscallcc cc c signext zeroext inreg sret nounwind noreturn noalias nocapture byval nest readnone readonly inlinehint noinline alwaysinline optsize ssp sspreq noredzone noimplicitfloat naked builtin cold nobuiltin noduplicate nonlazybind optnone returns_twice sanitize_address sanitize_memory sanitize_thread sspstrong uwtable returned type opaque eq ne slt sgt sle sge ult ugt ule uge oeq one olt ogt ole oge ord uno ueq une x acq_rel acquire alignstack atomic catch cleanup filter inteldialect max min monotonic nand personality release seq_cst singlethread umax umin unordered xchg add fadd sub fsub mul fmul udiv sdiv fdiv urem srem frem shl lshr ashr and or xor icmp fcmp phi call trunc zext sext fptrunc fpext uitofp sitofp fptoui fptosi inttoptr ptrtoint bitcast addrspacecast select va_arg ret br switch invoke unwind unreachable indirectbr landingpad resume malloc alloca free load store getelementptr extractelement insertelement shufflevector getresult extractvalue insertvalue atomicrmw cmpxchg fence argmemonly double",
+contains:[{className:"type",begin:/\bi\d+(?=\s|\b)/},n.COMMENT(/;\s*$/,null,{
+relevance:0}),n.COMMENT(/;/,/$/),n.QUOTE_STRING_MODE,{className:"string",
+variants:[{begin:/"/,end:/[^\\]"/}]},i,{className:"punctuation",relevance:0,
+begin:/,/},{className:"operator",relevance:0,begin:/=/},t,{className:"symbol",
+variants:[{begin:/^\s*[a-z]+:/}],relevance:0},{className:"number",variants:[{
+begin:/0[xX][a-fA-F0-9]+/},{begin:/-?\d+(?:[.]\d+)?(?:[eE][-+]?\d+(?:[.]\d+)?)?/
+}],relevance:0}]}}})());
+hljs.registerLanguage("lsl",(()=>{"use strict";return E=>{var T={
+className:"number",relevance:0,begin:E.C_NUMBER_RE};return{
+name:"LSL (Linden Scripting Language)",illegal:":",contains:[{
+className:"string",begin:'"',end:'"',contains:[{className:"subst",
+begin:/\\[tn"\\]/}]},{className:"comment",
+variants:[E.COMMENT("//","$"),E.COMMENT("/\\*","\\*/")],relevance:0},T,{
+className:"section",variants:[{begin:"\\b(state|default)\\b"},{
+begin:"\\b(state_(entry|exit)|touch(_(start|end))?|(land_)?collision(_(start|end))?|timer|listen|(no_)?sensor|control|(not_)?at_(rot_)?target|money|email|experience_permissions(_denied)?|run_time_permissions|changed|attach|dataserver|moving_(start|end)|link_message|(on|object)_rez|remote_data|http_re(sponse|quest)|path_update|transaction_result)\\b"
+}]},{className:"built_in",
+begin:"\\b(ll(AgentInExperience|(Create|DataSize|Delete|KeyCount|Keys|Read|Update)KeyValue|GetExperience(Details|ErrorMessage)|ReturnObjectsBy(ID|Owner)|Json(2List|[GS]etValue|ValueType)|Sin|Cos|Tan|Atan2|Sqrt|Pow|Abs|Fabs|Frand|Floor|Ceil|Round|Vec(Mag|Norm|Dist)|Rot(Between|2(Euler|Fwd|Left|Up))|(Euler|Axes)2Rot|Whisper|(Region|Owner)?Say|Shout|Listen(Control|Remove)?|Sensor(Repeat|Remove)?|Detected(Name|Key|Owner|Type|Pos|Vel|Grab|Rot|Group|LinkNumber)|Die|Ground|Wind|([GS]et)(AnimationOverride|MemoryLimit|PrimMediaParams|ParcelMusicURL|Object(Desc|Name)|PhysicsMaterial|Status|Scale|Color|Alpha|Texture|Pos|Rot|Force|Torque)|ResetAnimationOverride|(Scale|Offset|Rotate)Texture|(Rot)?Target(Remove)?|(Stop)?MoveToTarget|Apply(Rotational)?Impulse|Set(KeyframedMotion|ContentType|RegionPos|(Angular)?Velocity|Buoyancy|HoverHeight|ForceAndTorque|TimerEvent|ScriptState|Damage|TextureAnim|Sound(Queueing|Radius)|Vehicle(Type|(Float|Vector|Rotation)Param)|(Touch|Sit)?Text|Camera(Eye|At)Offset|PrimitiveParams|ClickAction|Link(Alpha|Color|PrimitiveParams(Fast)?|Texture(Anim)?|Camera|Media)|RemoteScriptAccessPin|PayPrice|LocalRot)|ScaleByFactor|Get((Max|Min)ScaleFactor|ClosestNavPoint|StaticPath|SimStats|Env|PrimitiveParams|Link(PrimitiveParams|Number(OfSides)?|Key|Name|Media)|HTTPHeader|FreeURLs|Object(Details|PermMask|PrimCount)|Parcel(MaxPrims|Details|Prim(Count|Owners))|Attached(List)?|(SPMax|Free|Used)Memory|Region(Name|TimeDilation|FPS|Corner|AgentCount)|Root(Position|Rotation)|UnixTime|(Parcel|Region)Flags|(Wall|GMT)clock|SimulatorHostname|BoundingBox|GeometricCenter|Creator|NumberOf(Prims|NotecardLines|Sides)|Animation(List)?|(Camera|Local)(Pos|Rot)|Vel|Accel|Omega|Time(stamp|OfDay)|(Object|CenterOf)?Mass|MassMKS|Energy|Owner|(Owner)?Key|SunDirection|Texture(Offset|Scale|Rot)|Inventory(Number|Name|Key|Type|Creator|PermMask)|Permissions(Key)?|StartParameter|List(Length|EntryType)|Date|Agent(Size|Info|Language|List)|LandOwnerAt|NotecardLine|Script(Name|State))|(Get|Reset|GetAndReset)Time|PlaySound(Slave)?|LoopSound(Master|Slave)?|(Trigger|Stop|Preload)Sound|((Get|Delete)Sub|Insert)String|To(Upper|Lower)|Give(InventoryList|Money)|RezObject|(Stop)?LookAt|Sleep|CollisionFilter|(Take|Release)Controls|DetachFromAvatar|AttachToAvatar(Temp)?|InstantMessage|(GetNext)?Email|StopHover|MinEventDelay|RotLookAt|String(Length|Trim)|(Start|Stop)Animation|TargetOmega|Request(Experience)?Permissions|(Create|Break)Link|BreakAllLinks|(Give|Remove)Inventory|Water|PassTouches|Request(Agent|Inventory)Data|TeleportAgent(Home|GlobalCoords)?|ModifyLand|CollisionSound|ResetScript|MessageLinked|PushObject|PassCollisions|AxisAngle2Rot|Rot2(Axis|Angle)|A(cos|sin)|AngleBetween|AllowInventoryDrop|SubStringIndex|List2(CSV|Integer|Json|Float|String|Key|Vector|Rot|List(Strided)?)|DeleteSubList|List(Statistics|Sort|Randomize|(Insert|Find|Replace)List)|EdgeOfWorld|AdjustSoundVolume|Key2Name|TriggerSoundLimited|EjectFromLand|(CSV|ParseString)2List|OverMyLand|SameGroup|UnSit|Ground(Slope|Normal|Contour)|GroundRepel|(Set|Remove)VehicleFlags|SitOnLink|(AvatarOn)?(Link)?SitTarget|Script(Danger|Profiler)|Dialog|VolumeDetect|ResetOtherScript|RemoteLoadScriptPin|(Open|Close)RemoteDataChannel|SendRemoteData|RemoteDataReply|(Integer|String)ToBase64|XorBase64|Log(10)?|Base64To(String|Integer)|ParseStringKeepNulls|RezAtRoot|RequestSimulatorData|ForceMouselook|(Load|Release|(E|Une)scape)URL|ParcelMedia(CommandList|Query)|ModPow|MapDestination|(RemoveFrom|AddTo|Reset)Land(Pass|Ban)List|(Set|Clear)CameraParams|HTTP(Request|Response)|TextBox|DetectedTouch(UV|Face|Pos|(N|Bin)ormal|ST)|(MD5|SHA1|DumpList2)String|Request(Secure)?URL|Clear(Prim|Link)Media|(Link)?ParticleSystem|(Get|Request)(Username|DisplayName)|RegionSayTo|CastRay|GenerateKey|TransferLindenDollars|ManageEstateAccess|(Create|Delete)Character|ExecCharacterCmd|Evade|FleeFrom|NavigateTo|PatrolPoints|Pursue|UpdateCharacter|WanderWithin))\\b"
+},{className:"literal",variants:[{
+begin:"\\b(PI|TWO_PI|PI_BY_TWO|DEG_TO_RAD|RAD_TO_DEG|SQRT2)\\b"},{
+begin:"\\b(XP_ERROR_(EXPERIENCES_DISABLED|EXPERIENCE_(DISABLED|SUSPENDED)|INVALID_(EXPERIENCE|PARAMETERS)|KEY_NOT_FOUND|MATURITY_EXCEEDED|NONE|NOT_(FOUND|PERMITTED(_LAND)?)|NO_EXPERIENCE|QUOTA_EXCEEDED|RETRY_UPDATE|STORAGE_EXCEPTION|STORE_DISABLED|THROTTLED|UNKNOWN_ERROR)|JSON_APPEND|STATUS_(PHYSICS|ROTATE_[XYZ]|PHANTOM|SANDBOX|BLOCK_GRAB(_OBJECT)?|(DIE|RETURN)_AT_EDGE|CAST_SHADOWS|OK|MALFORMED_PARAMS|TYPE_MISMATCH|BOUNDS_ERROR|NOT_(FOUND|SUPPORTED)|INTERNAL_ERROR|WHITELIST_FAILED)|AGENT(_(BY_(LEGACY_|USER)NAME|FLYING|ATTACHMENTS|SCRIPTED|MOUSELOOK|SITTING|ON_OBJECT|AWAY|WALKING|IN_AIR|TYPING|CROUCHING|BUSY|ALWAYS_RUN|AUTOPILOT|LIST_(PARCEL(_OWNER)?|REGION)))?|CAMERA_(PITCH|DISTANCE|BEHINDNESS_(ANGLE|LAG)|(FOCUS|POSITION)(_(THRESHOLD|LOCKED|LAG))?|FOCUS_OFFSET|ACTIVE)|ANIM_ON|LOOP|REVERSE|PING_PONG|SMOOTH|ROTATE|SCALE|ALL_SIDES|LINK_(ROOT|SET|ALL_(OTHERS|CHILDREN)|THIS)|ACTIVE|PASS(IVE|_(ALWAYS|IF_NOT_HANDLED|NEVER))|SCRIPTED|CONTROL_(FWD|BACK|(ROT_)?(LEFT|RIGHT)|UP|DOWN|(ML_)?LBUTTON)|PERMISSION_(RETURN_OBJECTS|DEBIT|OVERRIDE_ANIMATIONS|SILENT_ESTATE_MANAGEMENT|TAKE_CONTROLS|TRIGGER_ANIMATION|ATTACH|CHANGE_LINKS|(CONTROL|TRACK)_CAMERA|TELEPORT)|INVENTORY_(TEXTURE|SOUND|OBJECT|SCRIPT|LANDMARK|CLOTHING|NOTECARD|BODYPART|ANIMATION|GESTURE|ALL|NONE)|CHANGED_(INVENTORY|COLOR|SHAPE|SCALE|TEXTURE|LINK|ALLOWED_DROP|OWNER|REGION(_START)?|TELEPORT|MEDIA)|OBJECT_(CLICK_ACTION|HOVER_HEIGHT|LAST_OWNER_ID|(PHYSICS|SERVER|STREAMING)_COST|UNKNOWN_DETAIL|CHARACTER_TIME|PHANTOM|PHYSICS|TEMP_(ATTACHED|ON_REZ)|NAME|DESC|POS|PRIM_(COUNT|EQUIVALENCE)|RETURN_(PARCEL(_OWNER)?|REGION)|REZZER_KEY|ROO?T|VELOCITY|OMEGA|OWNER|GROUP(_TAG)?|CREATOR|ATTACHED_(POINT|SLOTS_AVAILABLE)|RENDER_WEIGHT|(BODY_SHAPE|PATHFINDING)_TYPE|(RUNNING|TOTAL)_SCRIPT_COUNT|TOTAL_INVENTORY_COUNT|SCRIPT_(MEMORY|TIME))|TYPE_(INTEGER|FLOAT|STRING|KEY|VECTOR|ROTATION|INVALID)|(DEBUG|PUBLIC)_CHANNEL|ATTACH_(AVATAR_CENTER|CHEST|HEAD|BACK|PELVIS|MOUTH|CHIN|NECK|NOSE|BELLY|[LR](SHOULDER|HAND|FOOT|EAR|EYE|[UL](ARM|LEG)|HIP)|(LEFT|RIGHT)_PEC|HUD_(CENTER_[12]|TOP_(RIGHT|CENTER|LEFT)|BOTTOM(_(RIGHT|LEFT))?)|[LR]HAND_RING1|TAIL_(BASE|TIP)|[LR]WING|FACE_(JAW|[LR]EAR|[LR]EYE|TOUNGE)|GROIN|HIND_[LR]FOOT)|LAND_(LEVEL|RAISE|LOWER|SMOOTH|NOISE|REVERT)|DATA_(ONLINE|NAME|BORN|SIM_(POS|STATUS|RATING)|PAYINFO)|PAYMENT_INFO_(ON_FILE|USED)|REMOTE_DATA_(CHANNEL|REQUEST|REPLY)|PSYS_(PART_(BF_(ZERO|ONE(_MINUS_(DEST_COLOR|SOURCE_(ALPHA|COLOR)))?|DEST_COLOR|SOURCE_(ALPHA|COLOR))|BLEND_FUNC_(DEST|SOURCE)|FLAGS|(START|END)_(COLOR|ALPHA|SCALE|GLOW)|MAX_AGE|(RIBBON|WIND|INTERP_(COLOR|SCALE)|BOUNCE|FOLLOW_(SRC|VELOCITY)|TARGET_(POS|LINEAR)|EMISSIVE)_MASK)|SRC_(MAX_AGE|PATTERN|ANGLE_(BEGIN|END)|BURST_(RATE|PART_COUNT|RADIUS|SPEED_(MIN|MAX))|ACCEL|TEXTURE|TARGET_KEY|OMEGA|PATTERN_(DROP|EXPLODE|ANGLE(_CONE(_EMPTY)?)?)))|VEHICLE_(REFERENCE_FRAME|TYPE_(NONE|SLED|CAR|BOAT|AIRPLANE|BALLOON)|(LINEAR|ANGULAR)_(FRICTION_TIMESCALE|MOTOR_DIRECTION)|LINEAR_MOTOR_OFFSET|HOVER_(HEIGHT|EFFICIENCY|TIMESCALE)|BUOYANCY|(LINEAR|ANGULAR)_(DEFLECTION_(EFFICIENCY|TIMESCALE)|MOTOR_(DECAY_)?TIMESCALE)|VERTICAL_ATTRACTION_(EFFICIENCY|TIMESCALE)|BANKING_(EFFICIENCY|MIX|TIMESCALE)|FLAG_(NO_DEFLECTION_UP|LIMIT_(ROLL_ONLY|MOTOR_UP)|HOVER_((WATER|TERRAIN|UP)_ONLY|GLOBAL_HEIGHT)|MOUSELOOK_(STEER|BANK)|CAMERA_DECOUPLED))|PRIM_(ALLOW_UNSIT|ALPHA_MODE(_(BLEND|EMISSIVE|MASK|NONE))?|NORMAL|SPECULAR|TYPE(_(BOX|CYLINDER|PRISM|SPHERE|TORUS|TUBE|RING|SCULPT))?|HOLE_(DEFAULT|CIRCLE|SQUARE|TRIANGLE)|MATERIAL(_(STONE|METAL|GLASS|WOOD|FLESH|PLASTIC|RUBBER))?|SHINY_(NONE|LOW|MEDIUM|HIGH)|BUMP_(NONE|BRIGHT|DARK|WOOD|BARK|BRICKS|CHECKER|CONCRETE|TILE|STONE|DISKS|GRAVEL|BLOBS|SIDING|LARGETILE|STUCCO|SUCTION|WEAVE)|TEXGEN_(DEFAULT|PLANAR)|SCRIPTED_SIT_ONLY|SCULPT_(TYPE_(SPHERE|TORUS|PLANE|CYLINDER|MASK)|FLAG_(MIRROR|INVERT))|PHYSICS(_(SHAPE_(CONVEX|NONE|PRIM|TYPE)))?|(POS|ROT)_LOCAL|SLICE|TEXT|FLEXIBLE|POINT_LIGHT|TEMP_ON_REZ|PHANTOM|POSITION|SIT_TARGET|SIZE|ROTATION|TEXTURE|NAME|OMEGA|DESC|LINK_TARGET|COLOR|BUMP_SHINY|FULLBRIGHT|TEXGEN|GLOW|MEDIA_(ALT_IMAGE_ENABLE|CONTROLS|(CURRENT|HOME)_URL|AUTO_(LOOP|PLAY|SCALE|ZOOM)|FIRST_CLICK_INTERACT|(WIDTH|HEIGHT)_PIXELS|WHITELIST(_ENABLE)?|PERMS_(INTERACT|CONTROL)|PARAM_MAX|CONTROLS_(STANDARD|MINI)|PERM_(NONE|OWNER|GROUP|ANYONE)|MAX_(URL_LENGTH|WHITELIST_(SIZE|COUNT)|(WIDTH|HEIGHT)_PIXELS)))|MASK_(BASE|OWNER|GROUP|EVERYONE|NEXT)|PERM_(TRANSFER|MODIFY|COPY|MOVE|ALL)|PARCEL_(MEDIA_COMMAND_(STOP|PAUSE|PLAY|LOOP|TEXTURE|URL|TIME|AGENT|UNLOAD|AUTO_ALIGN|TYPE|SIZE|DESC|LOOP_SET)|FLAG_(ALLOW_(FLY|(GROUP_)?SCRIPTS|LANDMARK|TERRAFORM|DAMAGE|CREATE_(GROUP_)?OBJECTS)|USE_(ACCESS_(GROUP|LIST)|BAN_LIST|LAND_PASS_LIST)|LOCAL_SOUND_ONLY|RESTRICT_PUSHOBJECT|ALLOW_(GROUP|ALL)_OBJECT_ENTRY)|COUNT_(TOTAL|OWNER|GROUP|OTHER|SELECTED|TEMP)|DETAILS_(NAME|DESC|OWNER|GROUP|AREA|ID|SEE_AVATARS))|LIST_STAT_(MAX|MIN|MEAN|MEDIAN|STD_DEV|SUM(_SQUARES)?|NUM_COUNT|GEOMETRIC_MEAN|RANGE)|PAY_(HIDE|DEFAULT)|REGION_FLAG_(ALLOW_DAMAGE|FIXED_SUN|BLOCK_TERRAFORM|SANDBOX|DISABLE_(COLLISIONS|PHYSICS)|BLOCK_FLY|ALLOW_DIRECT_TELEPORT|RESTRICT_PUSHOBJECT)|HTTP_(METHOD|MIMETYPE|BODY_(MAXLENGTH|TRUNCATED)|CUSTOM_HEADER|PRAGMA_NO_CACHE|VERBOSE_THROTTLE|VERIFY_CERT)|SIT_(INVALID_(AGENT|LINK_OBJECT)|NO(T_EXPERIENCE|_(ACCESS|EXPERIENCE_PERMISSION|SIT_TARGET)))|STRING_(TRIM(_(HEAD|TAIL))?)|CLICK_ACTION_(NONE|TOUCH|SIT|BUY|PAY|OPEN(_MEDIA)?|PLAY|ZOOM)|TOUCH_INVALID_FACE|PROFILE_(NONE|SCRIPT_MEMORY)|RC_(DATA_FLAGS|DETECT_PHANTOM|GET_(LINK_NUM|NORMAL|ROOT_KEY)|MAX_HITS|REJECT_(TYPES|AGENTS|(NON)?PHYSICAL|LAND))|RCERR_(CAST_TIME_EXCEEDED|SIM_PERF_LOW|UNKNOWN)|ESTATE_ACCESS_(ALLOWED_(AGENT|GROUP)_(ADD|REMOVE)|BANNED_AGENT_(ADD|REMOVE))|DENSITY|FRICTION|RESTITUTION|GRAVITY_MULTIPLIER|KFM_(COMMAND|CMD_(PLAY|STOP|PAUSE)|MODE|FORWARD|LOOP|PING_PONG|REVERSE|DATA|ROTATION|TRANSLATION)|ERR_(GENERIC|PARCEL_PERMISSIONS|MALFORMED_PARAMS|RUNTIME_PERMISSIONS|THROTTLED)|CHARACTER_(CMD_((SMOOTH_)?STOP|JUMP)|DESIRED_(TURN_)?SPEED|RADIUS|STAY_WITHIN_PARCEL|LENGTH|ORIENTATION|ACCOUNT_FOR_SKIPPED_FRAMES|AVOIDANCE_MODE|TYPE(_([ABCD]|NONE))?|MAX_(DECEL|TURN_RADIUS|(ACCEL|SPEED)))|PURSUIT_(OFFSET|FUZZ_FACTOR|GOAL_TOLERANCE|INTERCEPT)|REQUIRE_LINE_OF_SIGHT|FORCE_DIRECT_PATH|VERTICAL|HORIZONTAL|AVOID_(CHARACTERS|DYNAMIC_OBSTACLES|NONE)|PU_(EVADE_(HIDDEN|SPOTTED)|FAILURE_(DYNAMIC_PATHFINDING_DISABLED|INVALID_(GOAL|START)|NO_(NAVMESH|VALID_DESTINATION)|OTHER|TARGET_GONE|(PARCEL_)?UNREACHABLE)|(GOAL|SLOWDOWN_DISTANCE)_REACHED)|TRAVERSAL_TYPE(_(FAST|NONE|SLOW))?|CONTENT_TYPE_(ATOM|FORM|HTML|JSON|LLSD|RSS|TEXT|XHTML|XML)|GCNP_(RADIUS|STATIC)|(PATROL|WANDER)_PAUSE_AT_WAYPOINTS|OPT_(AVATAR|CHARACTER|EXCLUSION_VOLUME|LEGACY_LINKSET|MATERIAL_VOLUME|OTHER|STATIC_OBSTACLE|WALKABLE)|SIM_STAT_PCT_CHARS_STEPPED)\\b"
+},{begin:"\\b(FALSE|TRUE)\\b"},{begin:"\\b(ZERO_ROTATION)\\b"},{
+begin:"\\b(EOF|JSON_(ARRAY|DELETE|FALSE|INVALID|NULL|NUMBER|OBJECT|STRING|TRUE)|NULL_KEY|TEXTURE_(BLANK|DEFAULT|MEDIA|PLYWOOD|TRANSPARENT)|URL_REQUEST_(GRANTED|DENIED))\\b"
+},{begin:"\\b(ZERO_VECTOR|TOUCH_INVALID_(TEXCOORD|VECTOR))\\b"}]},{
+className:"type",
+begin:"\\b(integer|float|string|key|vector|quaternion|rotation|list)\\b"}]}}
+})());
+hljs.registerLanguage("lua",(()=>{"use strict";return e=>{
+const t="\\[=*\\[",a="\\]=*\\]",n={begin:t,end:a,contains:["self"]
+},o=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[",a,{contains:[n],
+relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,
+literal:"true false nil",
+keyword:"and break do else elseif end for goto if in local not or repeat return then until while",
+built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"
+},contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)",
+contains:[e.inherit(e.TITLE_MODE,{
+begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",
+begin:"\\(",endsWithParent:!0,contains:o}].concat(o)
+},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",
+begin:t,end:a,contains:[n],relevance:5}])}}})());
+hljs.registerLanguage("makefile",(()=>{"use strict";return e=>{const i={
+className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",
+contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},a={className:"string",
+begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},n={className:"variable",
+begin:/\$\([\w-]+\s/,end:/\)/,keywords:{
+built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"
+},contains:[i]},s={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},r={
+className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{
+name:"Makefile",aliases:["mk","mak","make"],keywords:{$pattern:/[\w-]+/,
+keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"
+},contains:[e.HASH_COMMENT_MODE,i,a,n,s,{className:"meta",begin:/^\.PHONY:/,
+end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},r]}}})());
+hljs.registerLanguage("mathematica",(()=>{"use strict"
+;const e=["AASTriangle","AbelianGroup","Abort","AbortKernels","AbortProtect","AbortScheduledTask","Above","Abs","AbsArg","AbsArgPlot","Absolute","AbsoluteCorrelation","AbsoluteCorrelationFunction","AbsoluteCurrentValue","AbsoluteDashing","AbsoluteFileName","AbsoluteOptions","AbsolutePointSize","AbsoluteThickness","AbsoluteTime","AbsoluteTiming","AcceptanceThreshold","AccountingForm","Accumulate","Accuracy","AccuracyGoal","ActionDelay","ActionMenu","ActionMenuBox","ActionMenuBoxOptions","Activate","Active","ActiveClassification","ActiveClassificationObject","ActiveItem","ActivePrediction","ActivePredictionObject","ActiveStyle","AcyclicGraphQ","AddOnHelpPath","AddSides","AddTo","AddToSearchIndex","AddUsers","AdjacencyGraph","AdjacencyList","AdjacencyMatrix","AdjacentMeshCells","AdjustmentBox","AdjustmentBoxOptions","AdjustTimeSeriesForecast","AdministrativeDivisionData","AffineHalfSpace","AffineSpace","AffineStateSpaceModel","AffineTransform","After","AggregatedEntityClass","AggregationLayer","AircraftData","AirportData","AirPressureData","AirTemperatureData","AiryAi","AiryAiPrime","AiryAiZero","AiryBi","AiryBiPrime","AiryBiZero","AlgebraicIntegerQ","AlgebraicNumber","AlgebraicNumberDenominator","AlgebraicNumberNorm","AlgebraicNumberPolynomial","AlgebraicNumberTrace","AlgebraicRules","AlgebraicRulesData","Algebraics","AlgebraicUnitQ","Alignment","AlignmentMarker","AlignmentPoint","All","AllowAdultContent","AllowedCloudExtraParameters","AllowedCloudParameterExtensions","AllowedDimensions","AllowedFrequencyRange","AllowedHeads","AllowGroupClose","AllowIncomplete","AllowInlineCells","AllowKernelInitialization","AllowLooseGrammar","AllowReverseGroupClose","AllowScriptLevelChange","AllowVersionUpdate","AllTrue","Alphabet","AlphabeticOrder","AlphabeticSort","AlphaChannel","AlternateImage","AlternatingFactorial","AlternatingGroup","AlternativeHypothesis","Alternatives","AltitudeMethod","AmbientLight","AmbiguityFunction","AmbiguityList","Analytic","AnatomyData","AnatomyForm","AnatomyPlot3D","AnatomySkinStyle","AnatomyStyling","AnchoredSearch","And","AndersonDarlingTest","AngerJ","AngleBisector","AngleBracket","AnglePath","AnglePath3D","AngleVector","AngularGauge","Animate","AnimationCycleOffset","AnimationCycleRepetitions","AnimationDirection","AnimationDisplayTime","AnimationRate","AnimationRepetitions","AnimationRunning","AnimationRunTime","AnimationTimeIndex","Animator","AnimatorBox","AnimatorBoxOptions","AnimatorElements","Annotate","Annotation","AnnotationDelete","AnnotationKeys","AnnotationRules","AnnotationValue","Annuity","AnnuityDue","Annulus","AnomalyDetection","AnomalyDetector","AnomalyDetectorFunction","Anonymous","Antialiasing","AntihermitianMatrixQ","Antisymmetric","AntisymmetricMatrixQ","Antonyms","AnyOrder","AnySubset","AnyTrue","Apart","ApartSquareFree","APIFunction","Appearance","AppearanceElements","AppearanceRules","AppellF1","Append","AppendCheck","AppendLayer","AppendTo","Apply","ApplySides","ArcCos","ArcCosh","ArcCot","ArcCoth","ArcCsc","ArcCsch","ArcCurvature","ARCHProcess","ArcLength","ArcSec","ArcSech","ArcSin","ArcSinDistribution","ArcSinh","ArcTan","ArcTanh","Area","Arg","ArgMax","ArgMin","ArgumentCountQ","ARIMAProcess","ArithmeticGeometricMean","ARMAProcess","Around","AroundReplace","ARProcess","Array","ArrayComponents","ArrayDepth","ArrayFilter","ArrayFlatten","ArrayMesh","ArrayPad","ArrayPlot","ArrayQ","ArrayResample","ArrayReshape","ArrayRules","Arrays","Arrow","Arrow3DBox","ArrowBox","Arrowheads","ASATriangle","Ask","AskAppend","AskConfirm","AskDisplay","AskedQ","AskedValue","AskFunction","AskState","AskTemplateDisplay","AspectRatio","AspectRatioFixed","Assert","AssociateTo","Association","AssociationFormat","AssociationMap","AssociationQ","AssociationThread","AssumeDeterministic","Assuming","Assumptions","AstronomicalData","Asymptotic","AsymptoticDSolveValue","AsymptoticEqual","AsymptoticEquivalent","AsymptoticGreater","AsymptoticGreaterEqual","AsymptoticIntegrate","AsymptoticLess","AsymptoticLessEqual","AsymptoticOutputTracker","AsymptoticProduct","AsymptoticRSolveValue","AsymptoticSolve","AsymptoticSum","Asynchronous","AsynchronousTaskObject","AsynchronousTasks","Atom","AtomCoordinates","AtomCount","AtomDiagramCoordinates","AtomList","AtomQ","AttentionLayer","Attributes","Audio","AudioAmplify","AudioAnnotate","AudioAnnotationLookup","AudioBlockMap","AudioCapture","AudioChannelAssignment","AudioChannelCombine","AudioChannelMix","AudioChannels","AudioChannelSeparate","AudioData","AudioDelay","AudioDelete","AudioDevice","AudioDistance","AudioEncoding","AudioFade","AudioFrequencyShift","AudioGenerator","AudioIdentify","AudioInputDevice","AudioInsert","AudioInstanceQ","AudioIntervals","AudioJoin","AudioLabel","AudioLength","AudioLocalMeasurements","AudioLooping","AudioLoudness","AudioMeasurements","AudioNormalize","AudioOutputDevice","AudioOverlay","AudioPad","AudioPan","AudioPartition","AudioPause","AudioPitchShift","AudioPlay","AudioPlot","AudioQ","AudioRecord","AudioReplace","AudioResample","AudioReverb","AudioReverse","AudioSampleRate","AudioSpectralMap","AudioSpectralTransformation","AudioSplit","AudioStop","AudioStream","AudioStreams","AudioTimeStretch","AudioTracks","AudioTrim","AudioType","AugmentedPolyhedron","AugmentedSymmetricPolynomial","Authenticate","Authentication","AuthenticationDialog","AutoAction","Autocomplete","AutocompletionFunction","AutoCopy","AutocorrelationTest","AutoDelete","AutoEvaluateEvents","AutoGeneratedPackage","AutoIndent","AutoIndentSpacings","AutoItalicWords","AutoloadPath","AutoMatch","Automatic","AutomaticImageSize","AutoMultiplicationSymbol","AutoNumberFormatting","AutoOpenNotebooks","AutoOpenPalettes","AutoQuoteCharacters","AutoRefreshed","AutoRemove","AutorunSequencing","AutoScaling","AutoScroll","AutoSpacing","AutoStyleOptions","AutoStyleWords","AutoSubmitting","Axes","AxesEdge","AxesLabel","AxesOrigin","AxesStyle","AxiomaticTheory","Axis","BabyMonsterGroupB","Back","Background","BackgroundAppearance","BackgroundTasksSettings","Backslash","Backsubstitution","Backward","Ball","Band","BandpassFilter","BandstopFilter","BarabasiAlbertGraphDistribution","BarChart","BarChart3D","BarcodeImage","BarcodeRecognize","BaringhausHenzeTest","BarLegend","BarlowProschanImportance","BarnesG","BarOrigin","BarSpacing","BartlettHannWindow","BartlettWindow","BaseDecode","BaseEncode","BaseForm","Baseline","BaselinePosition","BaseStyle","BasicRecurrentLayer","BatchNormalizationLayer","BatchSize","BatesDistribution","BattleLemarieWavelet","BayesianMaximization","BayesianMaximizationObject","BayesianMinimization","BayesianMinimizationObject","Because","BeckmannDistribution","Beep","Before","Begin","BeginDialogPacket","BeginFrontEndInteractionPacket","BeginPackage","BellB","BellY","Below","BenfordDistribution","BeniniDistribution","BenktanderGibratDistribution","BenktanderWeibullDistribution","BernoulliB","BernoulliDistribution","BernoulliGraphDistribution","BernoulliProcess","BernsteinBasis","BesselFilterModel","BesselI","BesselJ","BesselJZero","BesselK","BesselY","BesselYZero","Beta","BetaBinomialDistribution","BetaDistribution","BetaNegativeBinomialDistribution","BetaPrimeDistribution","BetaRegularized","Between","BetweennessCentrality","BeveledPolyhedron","BezierCurve","BezierCurve3DBox","BezierCurve3DBoxOptions","BezierCurveBox","BezierCurveBoxOptions","BezierFunction","BilateralFilter","Binarize","BinaryDeserialize","BinaryDistance","BinaryFormat","BinaryImageQ","BinaryRead","BinaryReadList","BinarySerialize","BinaryWrite","BinCounts","BinLists","Binomial","BinomialDistribution","BinomialProcess","BinormalDistribution","BiorthogonalSplineWavelet","BipartiteGraphQ","BiquadraticFilterModel","BirnbaumImportance","BirnbaumSaundersDistribution","BitAnd","BitClear","BitGet","BitLength","BitNot","BitOr","BitSet","BitShiftLeft","BitShiftRight","BitXor","BiweightLocation","BiweightMidvariance","Black","BlackmanHarrisWindow","BlackmanNuttallWindow","BlackmanWindow","Blank","BlankForm","BlankNullSequence","BlankSequence","Blend","Block","BlockchainAddressData","BlockchainBase","BlockchainBlockData","BlockchainContractValue","BlockchainData","BlockchainGet","BlockchainKeyEncode","BlockchainPut","BlockchainTokenData","BlockchainTransaction","BlockchainTransactionData","BlockchainTransactionSign","BlockchainTransactionSubmit","BlockMap","BlockRandom","BlomqvistBeta","BlomqvistBetaTest","Blue","Blur","BodePlot","BohmanWindow","Bold","Bond","BondCount","BondList","BondQ","Bookmarks","Boole","BooleanConsecutiveFunction","BooleanConvert","BooleanCountingFunction","BooleanFunction","BooleanGraph","BooleanMaxterms","BooleanMinimize","BooleanMinterms","BooleanQ","BooleanRegion","Booleans","BooleanStrings","BooleanTable","BooleanVariables","BorderDimensions","BorelTannerDistribution","Bottom","BottomHatTransform","BoundaryDiscretizeGraphics","BoundaryDiscretizeRegion","BoundaryMesh","BoundaryMeshRegion","BoundaryMeshRegionQ","BoundaryStyle","BoundedRegionQ","BoundingRegion","Bounds","Box","BoxBaselineShift","BoxData","BoxDimensions","Boxed","Boxes","BoxForm","BoxFormFormatTypes","BoxFrame","BoxID","BoxMargins","BoxMatrix","BoxObject","BoxRatios","BoxRotation","BoxRotationPoint","BoxStyle","BoxWhiskerChart","Bra","BracketingBar","BraKet","BrayCurtisDistance","BreadthFirstScan","Break","BridgeData","BrightnessEqualize","BroadcastStationData","Brown","BrownForsytheTest","BrownianBridgeProcess","BrowserCategory","BSplineBasis","BSplineCurve","BSplineCurve3DBox","BSplineCurve3DBoxOptions","BSplineCurveBox","BSplineCurveBoxOptions","BSplineFunction","BSplineSurface","BSplineSurface3DBox","BSplineSurface3DBoxOptions","BubbleChart","BubbleChart3D","BubbleScale","BubbleSizes","BuildingData","BulletGauge","BusinessDayQ","ButterflyGraph","ButterworthFilterModel","Button","ButtonBar","ButtonBox","ButtonBoxOptions","ButtonCell","ButtonContents","ButtonData","ButtonEvaluator","ButtonExpandable","ButtonFrame","ButtonFunction","ButtonMargins","ButtonMinHeight","ButtonNote","ButtonNotebook","ButtonSource","ButtonStyle","ButtonStyleMenuListing","Byte","ByteArray","ByteArrayFormat","ByteArrayQ","ByteArrayToString","ByteCount","ByteOrdering","C","CachedValue","CacheGraphics","CachePersistence","CalendarConvert","CalendarData","CalendarType","Callout","CalloutMarker","CalloutStyle","CallPacket","CanberraDistance","Cancel","CancelButton","CandlestickChart","CanonicalGraph","CanonicalizePolygon","CanonicalizePolyhedron","CanonicalName","CanonicalWarpingCorrespondence","CanonicalWarpingDistance","CantorMesh","CantorStaircase","Cap","CapForm","CapitalDifferentialD","Capitalize","CapsuleShape","CaptureRunning","CardinalBSplineBasis","CarlemanLinearize","CarmichaelLambda","CaseOrdering","Cases","CaseSensitive","Cashflow","Casoratian","Catalan","CatalanNumber","Catch","CategoricalDistribution","Catenate","CatenateLayer","CauchyDistribution","CauchyWindow","CayleyGraph","CDF","CDFDeploy","CDFInformation","CDFWavelet","Ceiling","CelestialSystem","Cell","CellAutoOverwrite","CellBaseline","CellBoundingBox","CellBracketOptions","CellChangeTimes","CellContents","CellContext","CellDingbat","CellDynamicExpression","CellEditDuplicate","CellElementsBoundingBox","CellElementSpacings","CellEpilog","CellEvaluationDuplicate","CellEvaluationFunction","CellEvaluationLanguage","CellEventActions","CellFrame","CellFrameColor","CellFrameLabelMargins","CellFrameLabels","CellFrameMargins","CellGroup","CellGroupData","CellGrouping","CellGroupingRules","CellHorizontalScrolling","CellID","CellLabel","CellLabelAutoDelete","CellLabelMargins","CellLabelPositioning","CellLabelStyle","CellLabelTemplate","CellMargins","CellObject","CellOpen","CellPrint","CellProlog","Cells","CellSize","CellStyle","CellTags","CellularAutomaton","CensoredDistribution","Censoring","Center","CenterArray","CenterDot","CentralFeature","CentralMoment","CentralMomentGeneratingFunction","Cepstrogram","CepstrogramArray","CepstrumArray","CForm","ChampernowneNumber","ChangeOptions","ChannelBase","ChannelBrokerAction","ChannelDatabin","ChannelHistoryLength","ChannelListen","ChannelListener","ChannelListeners","ChannelListenerWait","ChannelObject","ChannelPreSendFunction","ChannelReceiverFunction","ChannelSend","ChannelSubscribers","ChanVeseBinarize","Character","CharacterCounts","CharacterEncoding","CharacterEncodingsPath","CharacteristicFunction","CharacteristicPolynomial","CharacterName","CharacterNormalize","CharacterRange","Characters","ChartBaseStyle","ChartElementData","ChartElementDataFunction","ChartElementFunction","ChartElements","ChartLabels","ChartLayout","ChartLegends","ChartStyle","Chebyshev1FilterModel","Chebyshev2FilterModel","ChebyshevDistance","ChebyshevT","ChebyshevU","Check","CheckAbort","CheckAll","Checkbox","CheckboxBar","CheckboxBox","CheckboxBoxOptions","ChemicalData","ChessboardDistance","ChiDistribution","ChineseRemainder","ChiSquareDistribution","ChoiceButtons","ChoiceDialog","CholeskyDecomposition","Chop","ChromaticityPlot","ChromaticityPlot3D","ChromaticPolynomial","Circle","CircleBox","CircleDot","CircleMinus","CirclePlus","CirclePoints","CircleThrough","CircleTimes","CirculantGraph","CircularOrthogonalMatrixDistribution","CircularQuaternionMatrixDistribution","CircularRealMatrixDistribution","CircularSymplecticMatrixDistribution","CircularUnitaryMatrixDistribution","Circumsphere","CityData","ClassifierFunction","ClassifierInformation","ClassifierMeasurements","ClassifierMeasurementsObject","Classify","ClassPriors","Clear","ClearAll","ClearAttributes","ClearCookies","ClearPermissions","ClearSystemCache","ClebschGordan","ClickPane","Clip","ClipboardNotebook","ClipFill","ClippingStyle","ClipPlanes","ClipPlanesStyle","ClipRange","Clock","ClockGauge","ClockwiseContourIntegral","Close","Closed","CloseKernels","ClosenessCentrality","Closing","ClosingAutoSave","ClosingEvent","ClosingSaveDialog","CloudAccountData","CloudBase","CloudConnect","CloudConnections","CloudDeploy","CloudDirectory","CloudDisconnect","CloudEvaluate","CloudExport","CloudExpression","CloudExpressions","CloudFunction","CloudGet","CloudImport","CloudLoggingData","CloudObject","CloudObjectInformation","CloudObjectInformationData","CloudObjectNameFormat","CloudObjects","CloudObjectURLType","CloudPublish","CloudPut","CloudRenderingMethod","CloudSave","CloudShare","CloudSubmit","CloudSymbol","CloudUnshare","CloudUserID","ClusterClassify","ClusterDissimilarityFunction","ClusteringComponents","ClusteringTree","CMYKColor","Coarse","CodeAssistOptions","Coefficient","CoefficientArrays","CoefficientDomain","CoefficientList","CoefficientRules","CoifletWavelet","Collect","Colon","ColonForm","ColorBalance","ColorCombine","ColorConvert","ColorCoverage","ColorData","ColorDataFunction","ColorDetect","ColorDistance","ColorFunction","ColorFunctionScaling","Colorize","ColorNegate","ColorOutput","ColorProfileData","ColorQ","ColorQuantize","ColorReplace","ColorRules","ColorSelectorSettings","ColorSeparate","ColorSetter","ColorSetterBox","ColorSetterBoxOptions","ColorSlider","ColorsNear","ColorSpace","ColorToneMapping","Column","ColumnAlignments","ColumnBackgrounds","ColumnForm","ColumnLines","ColumnsEqual","ColumnSpacings","ColumnWidths","CombinedEntityClass","CombinerFunction","CometData","CommonDefaultFormatTypes","Commonest","CommonestFilter","CommonName","CommonUnits","CommunityBoundaryStyle","CommunityGraphPlot","CommunityLabels","CommunityRegionStyle","CompanyData","CompatibleUnitQ","CompilationOptions","CompilationTarget","Compile","Compiled","CompiledCodeFunction","CompiledFunction","CompilerOptions","Complement","ComplementedEntityClass","CompleteGraph","CompleteGraphQ","CompleteKaryTree","CompletionsListPacket","Complex","ComplexContourPlot","Complexes","ComplexExpand","ComplexInfinity","ComplexityFunction","ComplexListPlot","ComplexPlot","ComplexPlot3D","ComplexRegionPlot","ComplexStreamPlot","ComplexVectorPlot","ComponentMeasurements","ComponentwiseContextMenu","Compose","ComposeList","ComposeSeries","CompositeQ","Composition","CompoundElement","CompoundExpression","CompoundPoissonDistribution","CompoundPoissonProcess","CompoundRenewalProcess","Compress","CompressedData","CompressionLevel","ComputeUncertainty","Condition","ConditionalExpression","Conditioned","Cone","ConeBox","ConfidenceLevel","ConfidenceRange","ConfidenceTransform","ConfigurationPath","ConformAudio","ConformImages","Congruent","ConicHullRegion","ConicHullRegion3DBox","ConicHullRegionBox","ConicOptimization","Conjugate","ConjugateTranspose","Conjunction","Connect","ConnectedComponents","ConnectedGraphComponents","ConnectedGraphQ","ConnectedMeshComponents","ConnectedMoleculeComponents","ConnectedMoleculeQ","ConnectionSettings","ConnectLibraryCallbackFunction","ConnectSystemModelComponents","ConnesWindow","ConoverTest","ConsoleMessage","ConsoleMessagePacket","Constant","ConstantArray","ConstantArrayLayer","ConstantImage","ConstantPlusLayer","ConstantRegionQ","Constants","ConstantTimesLayer","ConstellationData","ConstrainedMax","ConstrainedMin","Construct","Containing","ContainsAll","ContainsAny","ContainsExactly","ContainsNone","ContainsOnly","ContentFieldOptions","ContentLocationFunction","ContentObject","ContentPadding","ContentsBoundingBox","ContentSelectable","ContentSize","Context","ContextMenu","Contexts","ContextToFileName","Continuation","Continue","ContinuedFraction","ContinuedFractionK","ContinuousAction","ContinuousMarkovProcess","ContinuousTask","ContinuousTimeModelQ","ContinuousWaveletData","ContinuousWaveletTransform","ContourDetect","ContourGraphics","ContourIntegral","ContourLabels","ContourLines","ContourPlot","ContourPlot3D","Contours","ContourShading","ContourSmoothing","ContourStyle","ContraharmonicMean","ContrastiveLossLayer","Control","ControlActive","ControlAlignment","ControlGroupContentsBox","ControllabilityGramian","ControllabilityMatrix","ControllableDecomposition","ControllableModelQ","ControllerDuration","ControllerInformation","ControllerInformationData","ControllerLinking","ControllerManipulate","ControllerMethod","ControllerPath","ControllerState","ControlPlacement","ControlsRendering","ControlType","Convergents","ConversionOptions","ConversionRules","ConvertToBitmapPacket","ConvertToPostScript","ConvertToPostScriptPacket","ConvexHullMesh","ConvexPolygonQ","ConvexPolyhedronQ","ConvolutionLayer","Convolve","ConwayGroupCo1","ConwayGroupCo2","ConwayGroupCo3","CookieFunction","Cookies","CoordinateBoundingBox","CoordinateBoundingBoxArray","CoordinateBounds","CoordinateBoundsArray","CoordinateChartData","CoordinatesToolOptions","CoordinateTransform","CoordinateTransformData","CoprimeQ","Coproduct","CopulaDistribution","Copyable","CopyDatabin","CopyDirectory","CopyFile","CopyTag","CopyToClipboard","CornerFilter","CornerNeighbors","Correlation","CorrelationDistance","CorrelationFunction","CorrelationTest","Cos","Cosh","CoshIntegral","CosineDistance","CosineWindow","CosIntegral","Cot","Coth","Count","CountDistinct","CountDistinctBy","CounterAssignments","CounterBox","CounterBoxOptions","CounterClockwiseContourIntegral","CounterEvaluator","CounterFunction","CounterIncrements","CounterStyle","CounterStyleMenuListing","CountRoots","CountryData","Counts","CountsBy","Covariance","CovarianceEstimatorFunction","CovarianceFunction","CoxianDistribution","CoxIngersollRossProcess","CoxModel","CoxModelFit","CramerVonMisesTest","CreateArchive","CreateCellID","CreateChannel","CreateCloudExpression","CreateDatabin","CreateDataStructure","CreateDataSystemModel","CreateDialog","CreateDirectory","CreateDocument","CreateFile","CreateIntermediateDirectories","CreateManagedLibraryExpression","CreateNotebook","CreatePacletArchive","CreatePalette","CreatePalettePacket","CreatePermissionsGroup","CreateScheduledTask","CreateSearchIndex","CreateSystemModel","CreateTemporary","CreateUUID","CreateWindow","CriterionFunction","CriticalityFailureImportance","CriticalitySuccessImportance","CriticalSection","Cross","CrossEntropyLossLayer","CrossingCount","CrossingDetect","CrossingPolygon","CrossMatrix","Csc","Csch","CTCLossLayer","Cube","CubeRoot","Cubics","Cuboid","CuboidBox","Cumulant","CumulantGeneratingFunction","Cup","CupCap","Curl","CurlyDoubleQuote","CurlyQuote","CurrencyConvert","CurrentDate","CurrentImage","CurrentlySpeakingPacket","CurrentNotebookImage","CurrentScreenImage","CurrentValue","Curry","CurryApplied","CurvatureFlowFilter","CurveClosed","Cyan","CycleGraph","CycleIndexPolynomial","Cycles","CyclicGroup","Cyclotomic","Cylinder","CylinderBox","CylindricalDecomposition","D","DagumDistribution","DamData","DamerauLevenshteinDistance","DampingFactor","Darker","Dashed","Dashing","DatabaseConnect","DatabaseDisconnect","DatabaseReference","Databin","DatabinAdd","DatabinRemove","Databins","DatabinUpload","DataCompression","DataDistribution","DataRange","DataReversed","Dataset","DatasetDisplayPanel","DataStructure","DataStructureQ","Date","DateBounds","Dated","DateDelimiters","DateDifference","DatedUnit","DateFormat","DateFunction","DateHistogram","DateInterval","DateList","DateListLogPlot","DateListPlot","DateListStepPlot","DateObject","DateObjectQ","DateOverlapsQ","DatePattern","DatePlus","DateRange","DateReduction","DateString","DateTicksFormat","DateValue","DateWithinQ","DaubechiesWavelet","DavisDistribution","DawsonF","DayCount","DayCountConvention","DayHemisphere","DaylightQ","DayMatchQ","DayName","DayNightTerminator","DayPlus","DayRange","DayRound","DeBruijnGraph","DeBruijnSequence","Debug","DebugTag","Decapitalize","Decimal","DecimalForm","DeclareKnownSymbols","DeclarePackage","Decompose","DeconvolutionLayer","Decrement","Decrypt","DecryptFile","DedekindEta","DeepSpaceProbeData","Default","DefaultAxesStyle","DefaultBaseStyle","DefaultBoxStyle","DefaultButton","DefaultColor","DefaultControlPlacement","DefaultDuplicateCellStyle","DefaultDuration","DefaultElement","DefaultFaceGridsStyle","DefaultFieldHintStyle","DefaultFont","DefaultFontProperties","DefaultFormatType","DefaultFormatTypeForStyle","DefaultFrameStyle","DefaultFrameTicksStyle","DefaultGridLinesStyle","DefaultInlineFormatType","DefaultInputFormatType","DefaultLabelStyle","DefaultMenuStyle","DefaultNaturalLanguage","DefaultNewCellStyle","DefaultNewInlineCellStyle","DefaultNotebook","DefaultOptions","DefaultOutputFormatType","DefaultPrintPrecision","DefaultStyle","DefaultStyleDefinitions","DefaultTextFormatType","DefaultTextInlineFormatType","DefaultTicksStyle","DefaultTooltipStyle","DefaultValue","DefaultValues","Defer","DefineExternal","DefineInputStreamMethod","DefineOutputStreamMethod","DefineResourceFunction","Definition","Degree","DegreeCentrality","DegreeGraphDistribution","DegreeLexicographic","DegreeReverseLexicographic","DEigensystem","DEigenvalues","Deinitialization","Del","DelaunayMesh","Delayed","Deletable","Delete","DeleteAnomalies","DeleteBorderComponents","DeleteCases","DeleteChannel","DeleteCloudExpression","DeleteContents","DeleteDirectory","DeleteDuplicates","DeleteDuplicatesBy","DeleteFile","DeleteMissing","DeleteObject","DeletePermissionsKey","DeleteSearchIndex","DeleteSmallComponents","DeleteStopwords","DeleteWithContents","DeletionWarning","DelimitedArray","DelimitedSequence","Delimiter","DelimiterFlashTime","DelimiterMatching","Delimiters","DeliveryFunction","Dendrogram","Denominator","DensityGraphics","DensityHistogram","DensityPlot","DensityPlot3D","DependentVariables","Deploy","Deployed","Depth","DepthFirstScan","Derivative","DerivativeFilter","DerivedKey","DescriptorStateSpace","DesignMatrix","DestroyAfterEvaluation","Det","DeviceClose","DeviceConfigure","DeviceExecute","DeviceExecuteAsynchronous","DeviceObject","DeviceOpen","DeviceOpenQ","DeviceRead","DeviceReadBuffer","DeviceReadLatest","DeviceReadList","DeviceReadTimeSeries","Devices","DeviceStreams","DeviceWrite","DeviceWriteBuffer","DGaussianWavelet","DiacriticalPositioning","Diagonal","DiagonalizableMatrixQ","DiagonalMatrix","DiagonalMatrixQ","Dialog","DialogIndent","DialogInput","DialogLevel","DialogNotebook","DialogProlog","DialogReturn","DialogSymbols","Diamond","DiamondMatrix","DiceDissimilarity","DictionaryLookup","DictionaryWordQ","DifferenceDelta","DifferenceOrder","DifferenceQuotient","DifferenceRoot","DifferenceRootReduce","Differences","DifferentialD","DifferentialRoot","DifferentialRootReduce","DifferentiatorFilter","DigitalSignature","DigitBlock","DigitBlockMinimum","DigitCharacter","DigitCount","DigitQ","DihedralAngle","DihedralGroup","Dilation","DimensionalCombinations","DimensionalMeshComponents","DimensionReduce","DimensionReducerFunction","DimensionReduction","Dimensions","DiracComb","DiracDelta","DirectedEdge","DirectedEdges","DirectedGraph","DirectedGraphQ","DirectedInfinity","Direction","Directive","Directory","DirectoryName","DirectoryQ","DirectoryStack","DirichletBeta","DirichletCharacter","DirichletCondition","DirichletConvolve","DirichletDistribution","DirichletEta","DirichletL","DirichletLambda","DirichletTransform","DirichletWindow","DisableConsolePrintPacket","DisableFormatting","DiscreteAsymptotic","DiscreteChirpZTransform","DiscreteConvolve","DiscreteDelta","DiscreteHadamardTransform","DiscreteIndicator","DiscreteLimit","DiscreteLQEstimatorGains","DiscreteLQRegulatorGains","DiscreteLyapunovSolve","DiscreteMarkovProcess","DiscreteMaxLimit","DiscreteMinLimit","DiscretePlot","DiscretePlot3D","DiscreteRatio","DiscreteRiccatiSolve","DiscreteShift","DiscreteTimeModelQ","DiscreteUniformDistribution","DiscreteVariables","DiscreteWaveletData","DiscreteWaveletPacketTransform","DiscreteWaveletTransform","DiscretizeGraphics","DiscretizeRegion","Discriminant","DisjointQ","Disjunction","Disk","DiskBox","DiskMatrix","DiskSegment","Dispatch","DispatchQ","DispersionEstimatorFunction","Display","DisplayAllSteps","DisplayEndPacket","DisplayFlushImagePacket","DisplayForm","DisplayFunction","DisplayPacket","DisplayRules","DisplaySetSizePacket","DisplayString","DisplayTemporary","DisplayWith","DisplayWithRef","DisplayWithVariable","DistanceFunction","DistanceMatrix","DistanceTransform","Distribute","Distributed","DistributedContexts","DistributeDefinitions","DistributionChart","DistributionDomain","DistributionFitTest","DistributionParameterAssumptions","DistributionParameterQ","Dithering","Div","Divergence","Divide","DivideBy","Dividers","DivideSides","Divisible","Divisors","DivisorSigma","DivisorSum","DMSList","DMSString","Do","DockedCells","DocumentGenerator","DocumentGeneratorInformation","DocumentGeneratorInformationData","DocumentGenerators","DocumentNotebook","DocumentWeightingRules","Dodecahedron","DomainRegistrationInformation","DominantColors","DOSTextFormat","Dot","DotDashed","DotEqual","DotLayer","DotPlusLayer","Dotted","DoubleBracketingBar","DoubleContourIntegral","DoubleDownArrow","DoubleLeftArrow","DoubleLeftRightArrow","DoubleLeftTee","DoubleLongLeftArrow","DoubleLongLeftRightArrow","DoubleLongRightArrow","DoubleRightArrow","DoubleRightTee","DoubleUpArrow","DoubleUpDownArrow","DoubleVerticalBar","DoublyInfinite","Down","DownArrow","DownArrowBar","DownArrowUpArrow","DownLeftRightVector","DownLeftTeeVector","DownLeftVector","DownLeftVectorBar","DownRightTeeVector","DownRightVector","DownRightVectorBar","Downsample","DownTee","DownTeeArrow","DownValues","DragAndDrop","DrawEdges","DrawFrontFaces","DrawHighlighted","Drop","DropoutLayer","DSolve","DSolveValue","Dt","DualLinearProgramming","DualPolyhedron","DualSystemsModel","DumpGet","DumpSave","DuplicateFreeQ","Duration","Dynamic","DynamicBox","DynamicBoxOptions","DynamicEvaluationTimeout","DynamicGeoGraphics","DynamicImage","DynamicLocation","DynamicModule","DynamicModuleBox","DynamicModuleBoxOptions","DynamicModuleParent","DynamicModuleValues","DynamicName","DynamicNamespace","DynamicReference","DynamicSetting","DynamicUpdating","DynamicWrapper","DynamicWrapperBox","DynamicWrapperBoxOptions","E","EarthImpactData","EarthquakeData","EccentricityCentrality","Echo","EchoFunction","EclipseType","EdgeAdd","EdgeBetweennessCentrality","EdgeCapacity","EdgeCapForm","EdgeColor","EdgeConnectivity","EdgeContract","EdgeCost","EdgeCount","EdgeCoverQ","EdgeCycleMatrix","EdgeDashing","EdgeDelete","EdgeDetect","EdgeForm","EdgeIndex","EdgeJoinForm","EdgeLabeling","EdgeLabels","EdgeLabelStyle","EdgeList","EdgeOpacity","EdgeQ","EdgeRenderingFunction","EdgeRules","EdgeShapeFunction","EdgeStyle","EdgeTaggedGraph","EdgeTaggedGraphQ","EdgeTags","EdgeThickness","EdgeWeight","EdgeWeightedGraphQ","Editable","EditButtonSettings","EditCellTagsSettings","EditDistance","EffectiveInterest","Eigensystem","Eigenvalues","EigenvectorCentrality","Eigenvectors","Element","ElementData","ElementwiseLayer","ElidedForms","Eliminate","EliminationOrder","Ellipsoid","EllipticE","EllipticExp","EllipticExpPrime","EllipticF","EllipticFilterModel","EllipticK","EllipticLog","EllipticNomeQ","EllipticPi","EllipticReducedHalfPeriods","EllipticTheta","EllipticThetaPrime","EmbedCode","EmbeddedHTML","EmbeddedService","EmbeddingLayer","EmbeddingObject","EmitSound","EmphasizeSyntaxErrors","EmpiricalDistribution","Empty","EmptyGraphQ","EmptyRegion","EnableConsolePrintPacket","Enabled","Encode","Encrypt","EncryptedObject","EncryptFile","End","EndAdd","EndDialogPacket","EndFrontEndInteractionPacket","EndOfBuffer","EndOfFile","EndOfLine","EndOfString","EndPackage","EngineEnvironment","EngineeringForm","Enter","EnterExpressionPacket","EnterTextPacket","Entity","EntityClass","EntityClassList","EntityCopies","EntityFunction","EntityGroup","EntityInstance","EntityList","EntityPrefetch","EntityProperties","EntityProperty","EntityPropertyClass","EntityRegister","EntityStore","EntityStores","EntityTypeName","EntityUnregister","EntityValue","Entropy","EntropyFilter","Environment","Epilog","EpilogFunction","Equal","EqualColumns","EqualRows","EqualTilde","EqualTo","EquatedTo","Equilibrium","EquirippleFilterKernel","Equivalent","Erf","Erfc","Erfi","ErlangB","ErlangC","ErlangDistribution","Erosion","ErrorBox","ErrorBoxOptions","ErrorNorm","ErrorPacket","ErrorsDialogSettings","EscapeRadius","EstimatedBackground","EstimatedDistribution","EstimatedProcess","EstimatorGains","EstimatorRegulator","EuclideanDistance","EulerAngles","EulerCharacteristic","EulerE","EulerGamma","EulerianGraphQ","EulerMatrix","EulerPhi","Evaluatable","Evaluate","Evaluated","EvaluatePacket","EvaluateScheduledTask","EvaluationBox","EvaluationCell","EvaluationCompletionAction","EvaluationData","EvaluationElements","EvaluationEnvironment","EvaluationMode","EvaluationMonitor","EvaluationNotebook","EvaluationObject","EvaluationOrder","Evaluator","EvaluatorNames","EvenQ","EventData","EventEvaluator","EventHandler","EventHandlerTag","EventLabels","EventSeries","ExactBlackmanWindow","ExactNumberQ","ExactRootIsolation","ExampleData","Except","ExcludedForms","ExcludedLines","ExcludedPhysicalQuantities","ExcludePods","Exclusions","ExclusionsStyle","Exists","Exit","ExitDialog","ExoplanetData","Exp","Expand","ExpandAll","ExpandDenominator","ExpandFileName","ExpandNumerator","Expectation","ExpectationE","ExpectedValue","ExpGammaDistribution","ExpIntegralE","ExpIntegralEi","ExpirationDate","Exponent","ExponentFunction","ExponentialDistribution","ExponentialFamily","ExponentialGeneratingFunction","ExponentialMovingAverage","ExponentialPowerDistribution","ExponentPosition","ExponentStep","Export","ExportAutoReplacements","ExportByteArray","ExportForm","ExportPacket","ExportString","Expression","ExpressionCell","ExpressionGraph","ExpressionPacket","ExpressionUUID","ExpToTrig","ExtendedEntityClass","ExtendedGCD","Extension","ExtentElementFunction","ExtentMarkers","ExtentSize","ExternalBundle","ExternalCall","ExternalDataCharacterEncoding","ExternalEvaluate","ExternalFunction","ExternalFunctionName","ExternalIdentifier","ExternalObject","ExternalOptions","ExternalSessionObject","ExternalSessions","ExternalStorageBase","ExternalStorageDownload","ExternalStorageGet","ExternalStorageObject","ExternalStoragePut","ExternalStorageUpload","ExternalTypeSignature","ExternalValue","Extract","ExtractArchive","ExtractLayer","ExtractPacletArchive","ExtremeValueDistribution","FaceAlign","FaceForm","FaceGrids","FaceGridsStyle","FacialFeatures","Factor","FactorComplete","Factorial","Factorial2","FactorialMoment","FactorialMomentGeneratingFunction","FactorialPower","FactorInteger","FactorList","FactorSquareFree","FactorSquareFreeList","FactorTerms","FactorTermsList","Fail","Failure","FailureAction","FailureDistribution","FailureQ","False","FareySequence","FARIMAProcess","FeatureDistance","FeatureExtract","FeatureExtraction","FeatureExtractor","FeatureExtractorFunction","FeatureNames","FeatureNearest","FeatureSpacePlot","FeatureSpacePlot3D","FeatureTypes","FEDisableConsolePrintPacket","FeedbackLinearize","FeedbackSector","FeedbackSectorStyle","FeedbackType","FEEnableConsolePrintPacket","FetalGrowthData","Fibonacci","Fibonorial","FieldCompletionFunction","FieldHint","FieldHintStyle","FieldMasked","FieldSize","File","FileBaseName","FileByteCount","FileConvert","FileDate","FileExistsQ","FileExtension","FileFormat","FileHandler","FileHash","FileInformation","FileName","FileNameDepth","FileNameDialogSettings","FileNameDrop","FileNameForms","FileNameJoin","FileNames","FileNameSetter","FileNameSplit","FileNameTake","FilePrint","FileSize","FileSystemMap","FileSystemScan","FileTemplate","FileTemplateApply","FileType","FilledCurve","FilledCurveBox","FilledCurveBoxOptions","Filling","FillingStyle","FillingTransform","FilteredEntityClass","FilterRules","FinancialBond","FinancialData","FinancialDerivative","FinancialIndicator","Find","FindAnomalies","FindArgMax","FindArgMin","FindChannels","FindClique","FindClusters","FindCookies","FindCurvePath","FindCycle","FindDevices","FindDistribution","FindDistributionParameters","FindDivisions","FindEdgeCover","FindEdgeCut","FindEdgeIndependentPaths","FindEquationalProof","FindEulerianCycle","FindExternalEvaluators","FindFaces","FindFile","FindFit","FindFormula","FindFundamentalCycles","FindGeneratingFunction","FindGeoLocation","FindGeometricConjectures","FindGeometricTransform","FindGraphCommunities","FindGraphIsomorphism","FindGraphPartition","FindHamiltonianCycle","FindHamiltonianPath","FindHiddenMarkovStates","FindImageText","FindIndependentEdgeSet","FindIndependentVertexSet","FindInstance","FindIntegerNullVector","FindKClan","FindKClique","FindKClub","FindKPlex","FindLibrary","FindLinearRecurrence","FindList","FindMatchingColor","FindMaximum","FindMaximumCut","FindMaximumFlow","FindMaxValue","FindMeshDefects","FindMinimum","FindMinimumCostFlow","FindMinimumCut","FindMinValue","FindMoleculeSubstructure","FindPath","FindPeaks","FindPermutation","FindPostmanTour","FindProcessParameters","FindRepeat","FindRoot","FindSequenceFunction","FindSettings","FindShortestPath","FindShortestTour","FindSpanningTree","FindSystemModelEquilibrium","FindTextualAnswer","FindThreshold","FindTransientRepeat","FindVertexCover","FindVertexCut","FindVertexIndependentPaths","Fine","FinishDynamic","FiniteAbelianGroupCount","FiniteGroupCount","FiniteGroupData","First","FirstCase","FirstPassageTimeDistribution","FirstPosition","FischerGroupFi22","FischerGroupFi23","FischerGroupFi24Prime","FisherHypergeometricDistribution","FisherRatioTest","FisherZDistribution","Fit","FitAll","FitRegularization","FittedModel","FixedOrder","FixedPoint","FixedPointList","FlashSelection","Flat","Flatten","FlattenAt","FlattenLayer","FlatTopWindow","FlipView","Floor","FlowPolynomial","FlushPrintOutputPacket","Fold","FoldList","FoldPair","FoldPairList","FollowRedirects","Font","FontColor","FontFamily","FontForm","FontName","FontOpacity","FontPostScriptName","FontProperties","FontReencoding","FontSize","FontSlant","FontSubstitutions","FontTracking","FontVariations","FontWeight","For","ForAll","ForceVersionInstall","Format","FormatRules","FormatType","FormatTypeAutoConvert","FormatValues","FormBox","FormBoxOptions","FormControl","FormFunction","FormLayoutFunction","FormObject","FormPage","FormTheme","FormulaData","FormulaLookup","FortranForm","Forward","ForwardBackward","Fourier","FourierCoefficient","FourierCosCoefficient","FourierCosSeries","FourierCosTransform","FourierDCT","FourierDCTFilter","FourierDCTMatrix","FourierDST","FourierDSTMatrix","FourierMatrix","FourierParameters","FourierSequenceTransform","FourierSeries","FourierSinCoefficient","FourierSinSeries","FourierSinTransform","FourierTransform","FourierTrigSeries","FractionalBrownianMotionProcess","FractionalGaussianNoiseProcess","FractionalPart","FractionBox","FractionBoxOptions","FractionLine","Frame","FrameBox","FrameBoxOptions","Framed","FrameInset","FrameLabel","Frameless","FrameMargins","FrameRate","FrameStyle","FrameTicks","FrameTicksStyle","FRatioDistribution","FrechetDistribution","FreeQ","FrenetSerretSystem","FrequencySamplingFilterKernel","FresnelC","FresnelF","FresnelG","FresnelS","Friday","FrobeniusNumber","FrobeniusSolve","FromAbsoluteTime","FromCharacterCode","FromCoefficientRules","FromContinuedFraction","FromDate","FromDigits","FromDMS","FromEntity","FromJulianDate","FromLetterNumber","FromPolarCoordinates","FromRomanNumeral","FromSphericalCoordinates","FromUnixTime","Front","FrontEndDynamicExpression","FrontEndEventActions","FrontEndExecute","FrontEndObject","FrontEndResource","FrontEndResourceString","FrontEndStackSize","FrontEndToken","FrontEndTokenExecute","FrontEndValueCache","FrontEndVersion","FrontFaceColor","FrontFaceOpacity","Full","FullAxes","FullDefinition","FullForm","FullGraphics","FullInformationOutputRegulator","FullOptions","FullRegion","FullSimplify","Function","FunctionCompile","FunctionCompileExport","FunctionCompileExportByteArray","FunctionCompileExportLibrary","FunctionCompileExportString","FunctionDomain","FunctionExpand","FunctionInterpolation","FunctionPeriod","FunctionRange","FunctionSpace","FussellVeselyImportance","GaborFilter","GaborMatrix","GaborWavelet","GainMargins","GainPhaseMargins","GalaxyData","GalleryView","Gamma","GammaDistribution","GammaRegularized","GapPenalty","GARCHProcess","GatedRecurrentLayer","Gather","GatherBy","GaugeFaceElementFunction","GaugeFaceStyle","GaugeFrameElementFunction","GaugeFrameSize","GaugeFrameStyle","GaugeLabels","GaugeMarkers","GaugeStyle","GaussianFilter","GaussianIntegers","GaussianMatrix","GaussianOrthogonalMatrixDistribution","GaussianSymplecticMatrixDistribution","GaussianUnitaryMatrixDistribution","GaussianWindow","GCD","GegenbauerC","General","GeneralizedLinearModelFit","GenerateAsymmetricKeyPair","GenerateConditions","GeneratedCell","GeneratedDocumentBinding","GenerateDerivedKey","GenerateDigitalSignature","GenerateDocument","GeneratedParameters","GeneratedQuantityMagnitudes","GenerateFileSignature","GenerateHTTPResponse","GenerateSecuredAuthenticationKey","GenerateSymmetricKey","GeneratingFunction","GeneratorDescription","GeneratorHistoryLength","GeneratorOutputType","Generic","GenericCylindricalDecomposition","GenomeData","GenomeLookup","GeoAntipode","GeoArea","GeoArraySize","GeoBackground","GeoBoundingBox","GeoBounds","GeoBoundsRegion","GeoBubbleChart","GeoCenter","GeoCircle","GeoContourPlot","GeoDensityPlot","GeodesicClosing","GeodesicDilation","GeodesicErosion","GeodesicOpening","GeoDestination","GeodesyData","GeoDirection","GeoDisk","GeoDisplacement","GeoDistance","GeoDistanceList","GeoElevationData","GeoEntities","GeoGraphics","GeogravityModelData","GeoGridDirectionDifference","GeoGridLines","GeoGridLinesStyle","GeoGridPosition","GeoGridRange","GeoGridRangePadding","GeoGridUnitArea","GeoGridUnitDistance","GeoGridVector","GeoGroup","GeoHemisphere","GeoHemisphereBoundary","GeoHistogram","GeoIdentify","GeoImage","GeoLabels","GeoLength","GeoListPlot","GeoLocation","GeologicalPeriodData","GeomagneticModelData","GeoMarker","GeometricAssertion","GeometricBrownianMotionProcess","GeometricDistribution","GeometricMean","GeometricMeanFilter","GeometricOptimization","GeometricScene","GeometricTransformation","GeometricTransformation3DBox","GeometricTransformation3DBoxOptions","GeometricTransformationBox","GeometricTransformationBoxOptions","GeoModel","GeoNearest","GeoPath","GeoPosition","GeoPositionENU","GeoPositionXYZ","GeoProjection","GeoProjectionData","GeoRange","GeoRangePadding","GeoRegionValuePlot","GeoResolution","GeoScaleBar","GeoServer","GeoSmoothHistogram","GeoStreamPlot","GeoStyling","GeoStylingImageFunction","GeoVariant","GeoVector","GeoVectorENU","GeoVectorPlot","GeoVectorXYZ","GeoVisibleRegion","GeoVisibleRegionBoundary","GeoWithinQ","GeoZoomLevel","GestureHandler","GestureHandlerTag","Get","GetBoundingBoxSizePacket","GetContext","GetEnvironment","GetFileName","GetFrontEndOptionsDataPacket","GetLinebreakInformationPacket","GetMenusPacket","GetPageBreakInformationPacket","Glaisher","GlobalClusteringCoefficient","GlobalPreferences","GlobalSession","Glow","GoldenAngle","GoldenRatio","GompertzMakehamDistribution","GoochShading","GoodmanKruskalGamma","GoodmanKruskalGammaTest","Goto","Grad","Gradient","GradientFilter","GradientOrientationFilter","GrammarApply","GrammarRules","GrammarToken","Graph","Graph3D","GraphAssortativity","GraphAutomorphismGroup","GraphCenter","GraphComplement","GraphData","GraphDensity","GraphDiameter","GraphDifference","GraphDisjointUnion","GraphDistance","GraphDistanceMatrix","GraphElementData","GraphEmbedding","GraphHighlight","GraphHighlightStyle","GraphHub","Graphics","Graphics3D","Graphics3DBox","Graphics3DBoxOptions","GraphicsArray","GraphicsBaseline","GraphicsBox","GraphicsBoxOptions","GraphicsColor","GraphicsColumn","GraphicsComplex","GraphicsComplex3DBox","GraphicsComplex3DBoxOptions","GraphicsComplexBox","GraphicsComplexBoxOptions","GraphicsContents","GraphicsData","GraphicsGrid","GraphicsGridBox","GraphicsGroup","GraphicsGroup3DBox","GraphicsGroup3DBoxOptions","GraphicsGroupBox","GraphicsGroupBoxOptions","GraphicsGrouping","GraphicsHighlightColor","GraphicsRow","GraphicsSpacing","GraphicsStyle","GraphIntersection","GraphLayout","GraphLinkEfficiency","GraphPeriphery","GraphPlot","GraphPlot3D","GraphPower","GraphPropertyDistribution","GraphQ","GraphRadius","GraphReciprocity","GraphRoot","GraphStyle","GraphUnion","Gray","GrayLevel","Greater","GreaterEqual","GreaterEqualLess","GreaterEqualThan","GreaterFullEqual","GreaterGreater","GreaterLess","GreaterSlantEqual","GreaterThan","GreaterTilde","Green","GreenFunction","Grid","GridBaseline","GridBox","GridBoxAlignment","GridBoxBackground","GridBoxDividers","GridBoxFrame","GridBoxItemSize","GridBoxItemStyle","GridBoxOptions","GridBoxSpacings","GridCreationSettings","GridDefaultElement","GridElementStyleOptions","GridFrame","GridFrameMargins","GridGraph","GridLines","GridLinesStyle","GroebnerBasis","GroupActionBase","GroupBy","GroupCentralizer","GroupElementFromWord","GroupElementPosition","GroupElementQ","GroupElements","GroupElementToWord","GroupGenerators","Groupings","GroupMultiplicationTable","GroupOrbits","GroupOrder","GroupPageBreakWithin","GroupSetwiseStabilizer","GroupStabilizer","GroupStabilizerChain","GroupTogetherGrouping","GroupTogetherNestedGrouping","GrowCutComponents","Gudermannian","GuidedFilter","GumbelDistribution","HaarWavelet","HadamardMatrix","HalfLine","HalfNormalDistribution","HalfPlane","HalfSpace","HalftoneShading","HamiltonianGraphQ","HammingDistance","HammingWindow","HandlerFunctions","HandlerFunctionsKeys","HankelH1","HankelH2","HankelMatrix","HankelTransform","HannPoissonWindow","HannWindow","HaradaNortonGroupHN","HararyGraph","HarmonicMean","HarmonicMeanFilter","HarmonicNumber","Hash","HatchFilling","HatchShading","Haversine","HazardFunction","Head","HeadCompose","HeaderAlignment","HeaderBackground","HeaderDisplayFunction","HeaderLines","HeaderSize","HeaderStyle","Heads","HeavisideLambda","HeavisidePi","HeavisideTheta","HeldGroupHe","HeldPart","HelpBrowserLookup","HelpBrowserNotebook","HelpBrowserSettings","Here","HermiteDecomposition","HermiteH","HermitianMatrixQ","HessenbergDecomposition","Hessian","HeunB","HeunBPrime","HeunC","HeunCPrime","HeunD","HeunDPrime","HeunG","HeunGPrime","HeunT","HeunTPrime","HexadecimalCharacter","Hexahedron","HexahedronBox","HexahedronBoxOptions","HiddenItems","HiddenMarkovProcess","HiddenSurface","Highlighted","HighlightGraph","HighlightImage","HighlightMesh","HighpassFilter","HigmanSimsGroupHS","HilbertCurve","HilbertFilter","HilbertMatrix","Histogram","Histogram3D","HistogramDistribution","HistogramList","HistogramTransform","HistogramTransformInterpolation","HistoricalPeriodData","HitMissTransform","HITSCentrality","HjorthDistribution","HodgeDual","HoeffdingD","HoeffdingDTest","Hold","HoldAll","HoldAllComplete","HoldComplete","HoldFirst","HoldForm","HoldPattern","HoldRest","HolidayCalendar","HomeDirectory","HomePage","Horizontal","HorizontalForm","HorizontalGauge","HorizontalScrollPosition","HornerForm","HostLookup","HotellingTSquareDistribution","HoytDistribution","HTMLSave","HTTPErrorResponse","HTTPRedirect","HTTPRequest","HTTPRequestData","HTTPResponse","Hue","HumanGrowthData","HumpDownHump","HumpEqual","HurwitzLerchPhi","HurwitzZeta","HyperbolicDistribution","HypercubeGraph","HyperexponentialDistribution","Hyperfactorial","Hypergeometric0F1","Hypergeometric0F1Regularized","Hypergeometric1F1","Hypergeometric1F1Regularized","Hypergeometric2F1","Hypergeometric2F1Regularized","HypergeometricDistribution","HypergeometricPFQ","HypergeometricPFQRegularized","HypergeometricU","Hyperlink","HyperlinkAction","HyperlinkCreationSettings","Hyperplane","Hyphenation","HyphenationOptions","HypoexponentialDistribution","HypothesisTestData","I","IconData","Iconize","IconizedObject","IconRules","Icosahedron","Identity","IdentityMatrix","If","IgnoreCase","IgnoreDiacritics","IgnorePunctuation","IgnoreSpellCheck","IgnoringInactive","Im","Image","Image3D","Image3DProjection","Image3DSlices","ImageAccumulate","ImageAdd","ImageAdjust","ImageAlign","ImageApply","ImageApplyIndexed","ImageAspectRatio","ImageAssemble","ImageAugmentationLayer","ImageBoundingBoxes","ImageCache","ImageCacheValid","ImageCapture","ImageCaptureFunction","ImageCases","ImageChannels","ImageClip","ImageCollage","ImageColorSpace","ImageCompose","ImageContainsQ","ImageContents","ImageConvolve","ImageCooccurrence","ImageCorners","ImageCorrelate","ImageCorrespondingPoints","ImageCrop","ImageData","ImageDeconvolve","ImageDemosaic","ImageDifference","ImageDimensions","ImageDisplacements","ImageDistance","ImageEffect","ImageExposureCombine","ImageFeatureTrack","ImageFileApply","ImageFileFilter","ImageFileScan","ImageFilter","ImageFocusCombine","ImageForestingComponents","ImageFormattingWidth","ImageForwardTransformation","ImageGraphics","ImageHistogram","ImageIdentify","ImageInstanceQ","ImageKeypoints","ImageLabels","ImageLegends","ImageLevels","ImageLines","ImageMargins","ImageMarker","ImageMarkers","ImageMeasurements","ImageMesh","ImageMultiply","ImageOffset","ImagePad","ImagePadding","ImagePartition","ImagePeriodogram","ImagePerspectiveTransformation","ImagePosition","ImagePreviewFunction","ImagePyramid","ImagePyramidApply","ImageQ","ImageRangeCache","ImageRecolor","ImageReflect","ImageRegion","ImageResize","ImageResolution","ImageRestyle","ImageRotate","ImageRotated","ImageSaliencyFilter","ImageScaled","ImageScan","ImageSize","ImageSizeAction","ImageSizeCache","ImageSizeMultipliers","ImageSizeRaw","ImageSubtract","ImageTake","ImageTransformation","ImageTrim","ImageType","ImageValue","ImageValuePositions","ImagingDevice","ImplicitRegion","Implies","Import","ImportAutoReplacements","ImportByteArray","ImportOptions","ImportString","ImprovementImportance","In","Inactivate","Inactive","IncidenceGraph","IncidenceList","IncidenceMatrix","IncludeAromaticBonds","IncludeConstantBasis","IncludeDefinitions","IncludeDirectories","IncludeFileExtension","IncludeGeneratorTasks","IncludeHydrogens","IncludeInflections","IncludeMetaInformation","IncludePods","IncludeQuantities","IncludeRelatedTables","IncludeSingularTerm","IncludeWindowTimes","Increment","IndefiniteMatrixQ","Indent","IndentingNewlineSpacings","IndentMaxFraction","IndependenceTest","IndependentEdgeSetQ","IndependentPhysicalQuantity","IndependentUnit","IndependentUnitDimension","IndependentVertexSetQ","Indeterminate","IndeterminateThreshold","IndexCreationOptions","Indexed","IndexEdgeTaggedGraph","IndexGraph","IndexTag","Inequality","InexactNumberQ","InexactNumbers","InfiniteFuture","InfiniteLine","InfinitePast","InfinitePlane","Infinity","Infix","InflationAdjust","InflationMethod","Information","InformationData","InformationDataGrid","Inherited","InheritScope","InhomogeneousPoissonProcess","InitialEvaluationHistory","Initialization","InitializationCell","InitializationCellEvaluation","InitializationCellWarning","InitializationObjects","InitializationValue","Initialize","InitialSeeding","InlineCounterAssignments","InlineCounterIncrements","InlineRules","Inner","InnerPolygon","InnerPolyhedron","Inpaint","Input","InputAliases","InputAssumptions","InputAutoReplacements","InputField","InputFieldBox","InputFieldBoxOptions","InputForm","InputGrouping","InputNamePacket","InputNotebook","InputPacket","InputSettings","InputStream","InputString","InputStringPacket","InputToBoxFormPacket","Insert","InsertionFunction","InsertionPointObject","InsertLinebreaks","InsertResults","Inset","Inset3DBox","Inset3DBoxOptions","InsetBox","InsetBoxOptions","Insphere","Install","InstallService","InstanceNormalizationLayer","InString","Integer","IntegerDigits","IntegerExponent","IntegerLength","IntegerName","IntegerPart","IntegerPartitions","IntegerQ","IntegerReverse","Integers","IntegerString","Integral","Integrate","Interactive","InteractiveTradingChart","Interlaced","Interleaving","InternallyBalancedDecomposition","InterpolatingFunction","InterpolatingPolynomial","Interpolation","InterpolationOrder","InterpolationPoints","InterpolationPrecision","Interpretation","InterpretationBox","InterpretationBoxOptions","InterpretationFunction","Interpreter","InterpretTemplate","InterquartileRange","Interrupt","InterruptSettings","IntersectedEntityClass","IntersectingQ","Intersection","Interval","IntervalIntersection","IntervalMarkers","IntervalMarkersStyle","IntervalMemberQ","IntervalSlider","IntervalUnion","Into","Inverse","InverseBetaRegularized","InverseCDF","InverseChiSquareDistribution","InverseContinuousWaveletTransform","InverseDistanceTransform","InverseEllipticNomeQ","InverseErf","InverseErfc","InverseFourier","InverseFourierCosTransform","InverseFourierSequenceTransform","InverseFourierSinTransform","InverseFourierTransform","InverseFunction","InverseFunctions","InverseGammaDistribution","InverseGammaRegularized","InverseGaussianDistribution","InverseGudermannian","InverseHankelTransform","InverseHaversine","InverseImagePyramid","InverseJacobiCD","InverseJacobiCN","InverseJacobiCS","InverseJacobiDC","InverseJacobiDN","InverseJacobiDS","InverseJacobiNC","InverseJacobiND","InverseJacobiNS","InverseJacobiSC","InverseJacobiSD","InverseJacobiSN","InverseLaplaceTransform","InverseMellinTransform","InversePermutation","InverseRadon","InverseRadonTransform","InverseSeries","InverseShortTimeFourier","InverseSpectrogram","InverseSurvivalFunction","InverseTransformedRegion","InverseWaveletTransform","InverseWeierstrassP","InverseWishartMatrixDistribution","InverseZTransform","Invisible","InvisibleApplication","InvisibleTimes","IPAddress","IrreduciblePolynomialQ","IslandData","IsolatingInterval","IsomorphicGraphQ","IsotopeData","Italic","Item","ItemAspectRatio","ItemBox","ItemBoxOptions","ItemDisplayFunction","ItemSize","ItemStyle","ItoProcess","JaccardDissimilarity","JacobiAmplitude","Jacobian","JacobiCD","JacobiCN","JacobiCS","JacobiDC","JacobiDN","JacobiDS","JacobiNC","JacobiND","JacobiNS","JacobiP","JacobiSC","JacobiSD","JacobiSN","JacobiSymbol","JacobiZeta","JankoGroupJ1","JankoGroupJ2","JankoGroupJ3","JankoGroupJ4","JarqueBeraALMTest","JohnsonDistribution","Join","JoinAcross","Joined","JoinedCurve","JoinedCurveBox","JoinedCurveBoxOptions","JoinForm","JordanDecomposition","JordanModelDecomposition","JulianDate","JuliaSetBoettcher","JuliaSetIterationCount","JuliaSetPlot","JuliaSetPoints","K","KagiChart","KaiserBesselWindow","KaiserWindow","KalmanEstimator","KalmanFilter","KarhunenLoeveDecomposition","KaryTree","KatzCentrality","KCoreComponents","KDistribution","KEdgeConnectedComponents","KEdgeConnectedGraphQ","KeepExistingVersion","KelvinBei","KelvinBer","KelvinKei","KelvinKer","KendallTau","KendallTauTest","KernelExecute","KernelFunction","KernelMixtureDistribution","KernelObject","Kernels","Ket","Key","KeyCollisionFunction","KeyComplement","KeyDrop","KeyDropFrom","KeyExistsQ","KeyFreeQ","KeyIntersection","KeyMap","KeyMemberQ","KeypointStrength","Keys","KeySelect","KeySort","KeySortBy","KeyTake","KeyUnion","KeyValueMap","KeyValuePattern","Khinchin","KillProcess","KirchhoffGraph","KirchhoffMatrix","KleinInvariantJ","KnapsackSolve","KnightTourGraph","KnotData","KnownUnitQ","KochCurve","KolmogorovSmirnovTest","KroneckerDelta","KroneckerModelDecomposition","KroneckerProduct","KroneckerSymbol","KuiperTest","KumaraswamyDistribution","Kurtosis","KuwaharaFilter","KVertexConnectedComponents","KVertexConnectedGraphQ","LABColor","Label","Labeled","LabeledSlider","LabelingFunction","LabelingSize","LabelStyle","LabelVisibility","LaguerreL","LakeData","LambdaComponents","LambertW","LaminaData","LanczosWindow","LandauDistribution","Language","LanguageCategory","LanguageData","LanguageIdentify","LanguageOptions","LaplaceDistribution","LaplaceTransform","Laplacian","LaplacianFilter","LaplacianGaussianFilter","Large","Larger","Last","Latitude","LatitudeLongitude","LatticeData","LatticeReduce","Launch","LaunchKernels","LayeredGraphPlot","LayerSizeFunction","LayoutInformation","LCHColor","LCM","LeaderSize","LeafCount","LeapYearQ","LearnDistribution","LearnedDistribution","LearningRate","LearningRateMultipliers","LeastSquares","LeastSquaresFilterKernel","Left","LeftArrow","LeftArrowBar","LeftArrowRightArrow","LeftDownTeeVector","LeftDownVector","LeftDownVectorBar","LeftRightArrow","LeftRightVector","LeftTee","LeftTeeArrow","LeftTeeVector","LeftTriangle","LeftTriangleBar","LeftTriangleEqual","LeftUpDownVector","LeftUpTeeVector","LeftUpVector","LeftUpVectorBar","LeftVector","LeftVectorBar","LegendAppearance","Legended","LegendFunction","LegendLabel","LegendLayout","LegendMargins","LegendMarkers","LegendMarkerSize","LegendreP","LegendreQ","LegendreType","Length","LengthWhile","LerchPhi","Less","LessEqual","LessEqualGreater","LessEqualThan","LessFullEqual","LessGreater","LessLess","LessSlantEqual","LessThan","LessTilde","LetterCharacter","LetterCounts","LetterNumber","LetterQ","Level","LeveneTest","LeviCivitaTensor","LevyDistribution","Lexicographic","LibraryDataType","LibraryFunction","LibraryFunctionError","LibraryFunctionInformation","LibraryFunctionLoad","LibraryFunctionUnload","LibraryLoad","LibraryUnload","LicenseID","LiftingFilterData","LiftingWaveletTransform","LightBlue","LightBrown","LightCyan","Lighter","LightGray","LightGreen","Lighting","LightingAngle","LightMagenta","LightOrange","LightPink","LightPurple","LightRed","LightSources","LightYellow","Likelihood","Limit","LimitsPositioning","LimitsPositioningTokens","LindleyDistribution","Line","Line3DBox","Line3DBoxOptions","LinearFilter","LinearFractionalOptimization","LinearFractionalTransform","LinearGradientImage","LinearizingTransformationData","LinearLayer","LinearModelFit","LinearOffsetFunction","LinearOptimization","LinearProgramming","LinearRecurrence","LinearSolve","LinearSolveFunction","LineBox","LineBoxOptions","LineBreak","LinebreakAdjustments","LineBreakChart","LinebreakSemicolonWeighting","LineBreakWithin","LineColor","LineGraph","LineIndent","LineIndentMaxFraction","LineIntegralConvolutionPlot","LineIntegralConvolutionScale","LineLegend","LineOpacity","LineSpacing","LineWrapParts","LinkActivate","LinkClose","LinkConnect","LinkConnectedQ","LinkCreate","LinkError","LinkFlush","LinkFunction","LinkHost","LinkInterrupt","LinkLaunch","LinkMode","LinkObject","LinkOpen","LinkOptions","LinkPatterns","LinkProtocol","LinkRankCentrality","LinkRead","LinkReadHeld","LinkReadyQ","Links","LinkService","LinkWrite","LinkWriteHeld","LiouvilleLambda","List","Listable","ListAnimate","ListContourPlot","ListContourPlot3D","ListConvolve","ListCorrelate","ListCurvePathPlot","ListDeconvolve","ListDensityPlot","ListDensityPlot3D","Listen","ListFormat","ListFourierSequenceTransform","ListInterpolation","ListLineIntegralConvolutionPlot","ListLinePlot","ListLogLinearPlot","ListLogLogPlot","ListLogPlot","ListPicker","ListPickerBox","ListPickerBoxBackground","ListPickerBoxOptions","ListPlay","ListPlot","ListPlot3D","ListPointPlot3D","ListPolarPlot","ListQ","ListSliceContourPlot3D","ListSliceDensityPlot3D","ListSliceVectorPlot3D","ListStepPlot","ListStreamDensityPlot","ListStreamPlot","ListSurfacePlot3D","ListVectorDensityPlot","ListVectorPlot","ListVectorPlot3D","ListZTransform","Literal","LiteralSearch","LocalAdaptiveBinarize","LocalCache","LocalClusteringCoefficient","LocalizeDefinitions","LocalizeVariables","LocalObject","LocalObjects","LocalResponseNormalizationLayer","LocalSubmit","LocalSymbol","LocalTime","LocalTimeZone","LocationEquivalenceTest","LocationTest","Locator","LocatorAutoCreate","LocatorBox","LocatorBoxOptions","LocatorCentering","LocatorPane","LocatorPaneBox","LocatorPaneBoxOptions","LocatorRegion","Locked","Log","Log10","Log2","LogBarnesG","LogGamma","LogGammaDistribution","LogicalExpand","LogIntegral","LogisticDistribution","LogisticSigmoid","LogitModelFit","LogLikelihood","LogLinearPlot","LogLogisticDistribution","LogLogPlot","LogMultinormalDistribution","LogNormalDistribution","LogPlot","LogRankTest","LogSeriesDistribution","LongEqual","Longest","LongestCommonSequence","LongestCommonSequencePositions","LongestCommonSubsequence","LongestCommonSubsequencePositions","LongestMatch","LongestOrderedSequence","LongForm","Longitude","LongLeftArrow","LongLeftRightArrow","LongRightArrow","LongShortTermMemoryLayer","Lookup","Loopback","LoopFreeGraphQ","Looping","LossFunction","LowerCaseQ","LowerLeftArrow","LowerRightArrow","LowerTriangularize","LowerTriangularMatrixQ","LowpassFilter","LQEstimatorGains","LQGRegulator","LQOutputRegulatorGains","LQRegulatorGains","LUBackSubstitution","LucasL","LuccioSamiComponents","LUDecomposition","LunarEclipse","LUVColor","LyapunovSolve","LyonsGroupLy","MachineID","MachineName","MachineNumberQ","MachinePrecision","MacintoshSystemPageSetup","Magenta","Magnification","Magnify","MailAddressValidation","MailExecute","MailFolder","MailItem","MailReceiverFunction","MailResponseFunction","MailSearch","MailServerConnect","MailServerConnection","MailSettings","MainSolve","MaintainDynamicCaches","Majority","MakeBoxes","MakeExpression","MakeRules","ManagedLibraryExpressionID","ManagedLibraryExpressionQ","MandelbrotSetBoettcher","MandelbrotSetDistance","MandelbrotSetIterationCount","MandelbrotSetMemberQ","MandelbrotSetPlot","MangoldtLambda","ManhattanDistance","Manipulate","Manipulator","MannedSpaceMissionData","MannWhitneyTest","MantissaExponent","Manual","Map","MapAll","MapAt","MapIndexed","MAProcess","MapThread","MarchenkoPasturDistribution","MarcumQ","MardiaCombinedTest","MardiaKurtosisTest","MardiaSkewnessTest","MarginalDistribution","MarkovProcessProperties","Masking","MatchingDissimilarity","MatchLocalNameQ","MatchLocalNames","MatchQ","Material","MathematicalFunctionData","MathematicaNotation","MathieuC","MathieuCharacteristicA","MathieuCharacteristicB","MathieuCharacteristicExponent","MathieuCPrime","MathieuGroupM11","MathieuGroupM12","MathieuGroupM22","MathieuGroupM23","MathieuGroupM24","MathieuS","MathieuSPrime","MathMLForm","MathMLText","Matrices","MatrixExp","MatrixForm","MatrixFunction","MatrixLog","MatrixNormalDistribution","MatrixPlot","MatrixPower","MatrixPropertyDistribution","MatrixQ","MatrixRank","MatrixTDistribution","Max","MaxBend","MaxCellMeasure","MaxColorDistance","MaxDate","MaxDetect","MaxDuration","MaxExtraBandwidths","MaxExtraConditions","MaxFeatureDisplacement","MaxFeatures","MaxFilter","MaximalBy","Maximize","MaxItems","MaxIterations","MaxLimit","MaxMemoryUsed","MaxMixtureKernels","MaxOverlapFraction","MaxPlotPoints","MaxPoints","MaxRecursion","MaxStableDistribution","MaxStepFraction","MaxSteps","MaxStepSize","MaxTrainingRounds","MaxValue","MaxwellDistribution","MaxWordGap","McLaughlinGroupMcL","Mean","MeanAbsoluteLossLayer","MeanAround","MeanClusteringCoefficient","MeanDegreeConnectivity","MeanDeviation","MeanFilter","MeanGraphDistance","MeanNeighborDegree","MeanShift","MeanShiftFilter","MeanSquaredLossLayer","Median","MedianDeviation","MedianFilter","MedicalTestData","Medium","MeijerG","MeijerGReduce","MeixnerDistribution","MellinConvolve","MellinTransform","MemberQ","MemoryAvailable","MemoryConstrained","MemoryConstraint","MemoryInUse","MengerMesh","Menu","MenuAppearance","MenuCommandKey","MenuEvaluator","MenuItem","MenuList","MenuPacket","MenuSortingValue","MenuStyle","MenuView","Merge","MergeDifferences","MergingFunction","MersennePrimeExponent","MersennePrimeExponentQ","Mesh","MeshCellCentroid","MeshCellCount","MeshCellHighlight","MeshCellIndex","MeshCellLabel","MeshCellMarker","MeshCellMeasure","MeshCellQuality","MeshCells","MeshCellShapeFunction","MeshCellStyle","MeshConnectivityGraph","MeshCoordinates","MeshFunctions","MeshPrimitives","MeshQualityGoal","MeshRange","MeshRefinementFunction","MeshRegion","MeshRegionQ","MeshShading","MeshStyle","Message","MessageDialog","MessageList","MessageName","MessageObject","MessageOptions","MessagePacket","Messages","MessagesNotebook","MetaCharacters","MetaInformation","MeteorShowerData","Method","MethodOptions","MexicanHatWavelet","MeyerWavelet","Midpoint","Min","MinColorDistance","MinDate","MinDetect","MineralData","MinFilter","MinimalBy","MinimalPolynomial","MinimalStateSpaceModel","Minimize","MinimumTimeIncrement","MinIntervalSize","MinkowskiQuestionMark","MinLimit","MinMax","MinorPlanetData","Minors","MinRecursion","MinSize","MinStableDistribution","Minus","MinusPlus","MinValue","Missing","MissingBehavior","MissingDataMethod","MissingDataRules","MissingQ","MissingString","MissingStyle","MissingValuePattern","MittagLefflerE","MixedFractionParts","MixedGraphQ","MixedMagnitude","MixedRadix","MixedRadixQuantity","MixedUnit","MixtureDistribution","Mod","Modal","Mode","Modular","ModularInverse","ModularLambda","Module","Modulus","MoebiusMu","Molecule","MoleculeContainsQ","MoleculeEquivalentQ","MoleculeGraph","MoleculeModify","MoleculePattern","MoleculePlot","MoleculePlot3D","MoleculeProperty","MoleculeQ","MoleculeRecognize","MoleculeValue","Moment","Momentary","MomentConvert","MomentEvaluate","MomentGeneratingFunction","MomentOfInertia","Monday","Monitor","MonomialList","MonomialOrder","MonsterGroupM","MoonPhase","MoonPosition","MorletWavelet","MorphologicalBinarize","MorphologicalBranchPoints","MorphologicalComponents","MorphologicalEulerNumber","MorphologicalGraph","MorphologicalPerimeter","MorphologicalTransform","MortalityData","Most","MountainData","MouseAnnotation","MouseAppearance","MouseAppearanceTag","MouseButtons","Mouseover","MousePointerNote","MousePosition","MovieData","MovingAverage","MovingMap","MovingMedian","MoyalDistribution","Multicolumn","MultiedgeStyle","MultigraphQ","MultilaunchWarning","MultiLetterItalics","MultiLetterStyle","MultilineFunction","Multinomial","MultinomialDistribution","MultinormalDistribution","MultiplicativeOrder","Multiplicity","MultiplySides","Multiselection","MultivariateHypergeometricDistribution","MultivariatePoissonDistribution","MultivariateTDistribution","N","NakagamiDistribution","NameQ","Names","NamespaceBox","NamespaceBoxOptions","Nand","NArgMax","NArgMin","NBernoulliB","NBodySimulation","NBodySimulationData","NCache","NDEigensystem","NDEigenvalues","NDSolve","NDSolveValue","Nearest","NearestFunction","NearestMeshCells","NearestNeighborGraph","NearestTo","NebulaData","NeedCurrentFrontEndPackagePacket","NeedCurrentFrontEndSymbolsPacket","NeedlemanWunschSimilarity","Needs","Negative","NegativeBinomialDistribution","NegativeDefiniteMatrixQ","NegativeIntegers","NegativeMultinomialDistribution","NegativeRationals","NegativeReals","NegativeSemidefiniteMatrixQ","NeighborhoodData","NeighborhoodGraph","Nest","NestedGreaterGreater","NestedLessLess","NestedScriptRules","NestGraph","NestList","NestWhile","NestWhileList","NetAppend","NetBidirectionalOperator","NetChain","NetDecoder","NetDelete","NetDrop","NetEncoder","NetEvaluationMode","NetExtract","NetFlatten","NetFoldOperator","NetGANOperator","NetGraph","NetInformation","NetInitialize","NetInsert","NetInsertSharedArrays","NetJoin","NetMapOperator","NetMapThreadOperator","NetMeasurements","NetModel","NetNestOperator","NetPairEmbeddingOperator","NetPort","NetPortGradient","NetPrepend","NetRename","NetReplace","NetReplacePart","NetSharedArray","NetStateObject","NetTake","NetTrain","NetTrainResultsObject","NetworkPacketCapture","NetworkPacketRecording","NetworkPacketRecordingDuring","NetworkPacketTrace","NeumannValue","NevilleThetaC","NevilleThetaD","NevilleThetaN","NevilleThetaS","NewPrimitiveStyle","NExpectation","Next","NextCell","NextDate","NextPrime","NextScheduledTaskTime","NHoldAll","NHoldFirst","NHoldRest","NicholsGridLines","NicholsPlot","NightHemisphere","NIntegrate","NMaximize","NMaxValue","NMinimize","NMinValue","NominalVariables","NonAssociative","NoncentralBetaDistribution","NoncentralChiSquareDistribution","NoncentralFRatioDistribution","NoncentralStudentTDistribution","NonCommutativeMultiply","NonConstants","NondimensionalizationTransform","None","NoneTrue","NonlinearModelFit","NonlinearStateSpaceModel","NonlocalMeansFilter","NonNegative","NonNegativeIntegers","NonNegativeRationals","NonNegativeReals","NonPositive","NonPositiveIntegers","NonPositiveRationals","NonPositiveReals","Nor","NorlundB","Norm","Normal","NormalDistribution","NormalGrouping","NormalizationLayer","Normalize","Normalized","NormalizedSquaredEuclideanDistance","NormalMatrixQ","NormalsFunction","NormFunction","Not","NotCongruent","NotCupCap","NotDoubleVerticalBar","Notebook","NotebookApply","NotebookAutoSave","NotebookClose","NotebookConvertSettings","NotebookCreate","NotebookCreateReturnObject","NotebookDefault","NotebookDelete","NotebookDirectory","NotebookDynamicExpression","NotebookEvaluate","NotebookEventActions","NotebookFileName","NotebookFind","NotebookFindReturnObject","NotebookGet","NotebookGetLayoutInformationPacket","NotebookGetMisspellingsPacket","NotebookImport","NotebookInformation","NotebookInterfaceObject","NotebookLocate","NotebookObject","NotebookOpen","NotebookOpenReturnObject","NotebookPath","NotebookPrint","NotebookPut","NotebookPutReturnObject","NotebookRead","NotebookResetGeneratedCells","Notebooks","NotebookSave","NotebookSaveAs","NotebookSelection","NotebookSetupLayoutInformationPacket","NotebooksMenu","NotebookTemplate","NotebookWrite","NotElement","NotEqualTilde","NotExists","NotGreater","NotGreaterEqual","NotGreaterFullEqual","NotGreaterGreater","NotGreaterLess","NotGreaterSlantEqual","NotGreaterTilde","Nothing","NotHumpDownHump","NotHumpEqual","NotificationFunction","NotLeftTriangle","NotLeftTriangleBar","NotLeftTriangleEqual","NotLess","NotLessEqual","NotLessFullEqual","NotLessGreater","NotLessLess","NotLessSlantEqual","NotLessTilde","NotNestedGreaterGreater","NotNestedLessLess","NotPrecedes","NotPrecedesEqual","NotPrecedesSlantEqual","NotPrecedesTilde","NotReverseElement","NotRightTriangle","NotRightTriangleBar","NotRightTriangleEqual","NotSquareSubset","NotSquareSubsetEqual","NotSquareSuperset","NotSquareSupersetEqual","NotSubset","NotSubsetEqual","NotSucceeds","NotSucceedsEqual","NotSucceedsSlantEqual","NotSucceedsTilde","NotSuperset","NotSupersetEqual","NotTilde","NotTildeEqual","NotTildeFullEqual","NotTildeTilde","NotVerticalBar","Now","NoWhitespace","NProbability","NProduct","NProductFactors","NRoots","NSolve","NSum","NSumTerms","NuclearExplosionData","NuclearReactorData","Null","NullRecords","NullSpace","NullWords","Number","NumberCompose","NumberDecompose","NumberExpand","NumberFieldClassNumber","NumberFieldDiscriminant","NumberFieldFundamentalUnits","NumberFieldIntegralBasis","NumberFieldNormRepresentatives","NumberFieldRegulator","NumberFieldRootsOfUnity","NumberFieldSignature","NumberForm","NumberFormat","NumberLinePlot","NumberMarks","NumberMultiplier","NumberPadding","NumberPoint","NumberQ","NumberSeparator","NumberSigns","NumberString","Numerator","NumeratorDenominator","NumericalOrder","NumericalSort","NumericArray","NumericArrayQ","NumericArrayType","NumericFunction","NumericQ","NuttallWindow","NValues","NyquistGridLines","NyquistPlot","O","ObservabilityGramian","ObservabilityMatrix","ObservableDecomposition","ObservableModelQ","OceanData","Octahedron","OddQ","Off","Offset","OLEData","On","ONanGroupON","Once","OneIdentity","Opacity","OpacityFunction","OpacityFunctionScaling","Open","OpenAppend","Opener","OpenerBox","OpenerBoxOptions","OpenerView","OpenFunctionInspectorPacket","Opening","OpenRead","OpenSpecialOptions","OpenTemporary","OpenWrite","Operate","OperatingSystem","OperatorApplied","OptimumFlowData","Optional","OptionalElement","OptionInspectorSettings","OptionQ","Options","OptionsPacket","OptionsPattern","OptionValue","OptionValueBox","OptionValueBoxOptions","Or","Orange","Order","OrderDistribution","OrderedQ","Ordering","OrderingBy","OrderingLayer","Orderless","OrderlessPatternSequence","OrnsteinUhlenbeckProcess","Orthogonalize","OrthogonalMatrixQ","Out","Outer","OuterPolygon","OuterPolyhedron","OutputAutoOverwrite","OutputControllabilityMatrix","OutputControllableModelQ","OutputForm","OutputFormData","OutputGrouping","OutputMathEditExpression","OutputNamePacket","OutputResponse","OutputSizeLimit","OutputStream","Over","OverBar","OverDot","Overflow","OverHat","Overlaps","Overlay","OverlayBox","OverlayBoxOptions","Overscript","OverscriptBox","OverscriptBoxOptions","OverTilde","OverVector","OverwriteTarget","OwenT","OwnValues","Package","PackingMethod","PackPaclet","PacletDataRebuild","PacletDirectoryAdd","PacletDirectoryLoad","PacletDirectoryRemove","PacletDirectoryUnload","PacletDisable","PacletEnable","PacletFind","PacletFindRemote","PacletInformation","PacletInstall","PacletInstallSubmit","PacletNewerQ","PacletObject","PacletObjectQ","PacletSite","PacletSiteObject","PacletSiteRegister","PacletSites","PacletSiteUnregister","PacletSiteUpdate","PacletUninstall","PacletUpdate","PaddedForm","Padding","PaddingLayer","PaddingSize","PadeApproximant","PadLeft","PadRight","PageBreakAbove","PageBreakBelow","PageBreakWithin","PageFooterLines","PageFooters","PageHeaderLines","PageHeaders","PageHeight","PageRankCentrality","PageTheme","PageWidth","Pagination","PairedBarChart","PairedHistogram","PairedSmoothHistogram","PairedTTest","PairedZTest","PaletteNotebook","PalettePath","PalindromeQ","Pane","PaneBox","PaneBoxOptions","Panel","PanelBox","PanelBoxOptions","Paneled","PaneSelector","PaneSelectorBox","PaneSelectorBoxOptions","PaperWidth","ParabolicCylinderD","ParagraphIndent","ParagraphSpacing","ParallelArray","ParallelCombine","ParallelDo","Parallelepiped","ParallelEvaluate","Parallelization","Parallelize","ParallelMap","ParallelNeeds","Parallelogram","ParallelProduct","ParallelSubmit","ParallelSum","ParallelTable","ParallelTry","Parameter","ParameterEstimator","ParameterMixtureDistribution","ParameterVariables","ParametricFunction","ParametricNDSolve","ParametricNDSolveValue","ParametricPlot","ParametricPlot3D","ParametricRampLayer","ParametricRegion","ParentBox","ParentCell","ParentConnect","ParentDirectory","ParentForm","Parenthesize","ParentList","ParentNotebook","ParetoDistribution","ParetoPickandsDistribution","ParkData","Part","PartBehavior","PartialCorrelationFunction","PartialD","ParticleAcceleratorData","ParticleData","Partition","PartitionGranularity","PartitionsP","PartitionsQ","PartLayer","PartOfSpeech","PartProtection","ParzenWindow","PascalDistribution","PassEventsDown","PassEventsUp","Paste","PasteAutoQuoteCharacters","PasteBoxFormInlineCells","PasteButton","Path","PathGraph","PathGraphQ","Pattern","PatternFilling","PatternSequence","PatternTest","PauliMatrix","PaulWavelet","Pause","PausedTime","PDF","PeakDetect","PeanoCurve","PearsonChiSquareTest","PearsonCorrelationTest","PearsonDistribution","PercentForm","PerfectNumber","PerfectNumberQ","PerformanceGoal","Perimeter","PeriodicBoundaryCondition","PeriodicInterpolation","Periodogram","PeriodogramArray","Permanent","Permissions","PermissionsGroup","PermissionsGroupMemberQ","PermissionsGroups","PermissionsKey","PermissionsKeys","PermutationCycles","PermutationCyclesQ","PermutationGroup","PermutationLength","PermutationList","PermutationListQ","PermutationMax","PermutationMin","PermutationOrder","PermutationPower","PermutationProduct","PermutationReplace","Permutations","PermutationSupport","Permute","PeronaMalikFilter","Perpendicular","PerpendicularBisector","PersistenceLocation","PersistenceTime","PersistentObject","PersistentObjects","PersistentValue","PersonData","PERTDistribution","PetersenGraph","PhaseMargins","PhaseRange","PhysicalSystemData","Pi","Pick","PIDData","PIDDerivativeFilter","PIDFeedforward","PIDTune","Piecewise","PiecewiseExpand","PieChart","PieChart3D","PillaiTrace","PillaiTraceTest","PingTime","Pink","PitchRecognize","Pivoting","PixelConstrained","PixelValue","PixelValuePositions","Placed","Placeholder","PlaceholderReplace","Plain","PlanarAngle","PlanarGraph","PlanarGraphQ","PlanckRadiationLaw","PlaneCurveData","PlanetaryMoonData","PlanetData","PlantData","Play","PlayRange","Plot","Plot3D","Plot3Matrix","PlotDivision","PlotJoined","PlotLabel","PlotLabels","PlotLayout","PlotLegends","PlotMarkers","PlotPoints","PlotRange","PlotRangeClipping","PlotRangeClipPlanesStyle","PlotRangePadding","PlotRegion","PlotStyle","PlotTheme","Pluralize","Plus","PlusMinus","Pochhammer","PodStates","PodWidth","Point","Point3DBox","Point3DBoxOptions","PointBox","PointBoxOptions","PointFigureChart","PointLegend","PointSize","PoissonConsulDistribution","PoissonDistribution","PoissonProcess","PoissonWindow","PolarAxes","PolarAxesOrigin","PolarGridLines","PolarPlot","PolarTicks","PoleZeroMarkers","PolyaAeppliDistribution","PolyGamma","Polygon","Polygon3DBox","Polygon3DBoxOptions","PolygonalNumber","PolygonAngle","PolygonBox","PolygonBoxOptions","PolygonCoordinates","PolygonDecomposition","PolygonHoleScale","PolygonIntersections","PolygonScale","Polyhedron","PolyhedronAngle","PolyhedronCoordinates","PolyhedronData","PolyhedronDecomposition","PolyhedronGenus","PolyLog","PolynomialExtendedGCD","PolynomialForm","PolynomialGCD","PolynomialLCM","PolynomialMod","PolynomialQ","PolynomialQuotient","PolynomialQuotientRemainder","PolynomialReduce","PolynomialRemainder","Polynomials","PoolingLayer","PopupMenu","PopupMenuBox","PopupMenuBoxOptions","PopupView","PopupWindow","Position","PositionIndex","Positive","PositiveDefiniteMatrixQ","PositiveIntegers","PositiveRationals","PositiveReals","PositiveSemidefiniteMatrixQ","PossibleZeroQ","Postfix","PostScript","Power","PowerDistribution","PowerExpand","PowerMod","PowerModList","PowerRange","PowerSpectralDensity","PowersRepresentations","PowerSymmetricPolynomial","Precedence","PrecedenceForm","Precedes","PrecedesEqual","PrecedesSlantEqual","PrecedesTilde","Precision","PrecisionGoal","PreDecrement","Predict","PredictionRoot","PredictorFunction","PredictorInformation","PredictorMeasurements","PredictorMeasurementsObject","PreemptProtect","PreferencesPath","Prefix","PreIncrement","Prepend","PrependLayer","PrependTo","PreprocessingRules","PreserveColor","PreserveImageOptions","Previous","PreviousCell","PreviousDate","PriceGraphDistribution","PrimaryPlaceholder","Prime","PrimeNu","PrimeOmega","PrimePi","PrimePowerQ","PrimeQ","Primes","PrimeZetaP","PrimitivePolynomialQ","PrimitiveRoot","PrimitiveRootList","PrincipalComponents","PrincipalValue","Print","PrintableASCIIQ","PrintAction","PrintForm","PrintingCopies","PrintingOptions","PrintingPageRange","PrintingStartingPageNumber","PrintingStyleEnvironment","Printout3D","Printout3DPreviewer","PrintPrecision","PrintTemporary","Prism","PrismBox","PrismBoxOptions","PrivateCellOptions","PrivateEvaluationOptions","PrivateFontOptions","PrivateFrontEndOptions","PrivateKey","PrivateNotebookOptions","PrivatePaths","Probability","ProbabilityDistribution","ProbabilityPlot","ProbabilityPr","ProbabilityScalePlot","ProbitModelFit","ProcessConnection","ProcessDirectory","ProcessEnvironment","Processes","ProcessEstimator","ProcessInformation","ProcessObject","ProcessParameterAssumptions","ProcessParameterQ","ProcessStateDomain","ProcessStatus","ProcessTimeDomain","Product","ProductDistribution","ProductLog","ProgressIndicator","ProgressIndicatorBox","ProgressIndicatorBoxOptions","Projection","Prolog","PromptForm","ProofObject","Properties","Property","PropertyList","PropertyValue","Proportion","Proportional","Protect","Protected","ProteinData","Pruning","PseudoInverse","PsychrometricPropertyData","PublicKey","PublisherID","PulsarData","PunctuationCharacter","Purple","Put","PutAppend","Pyramid","PyramidBox","PyramidBoxOptions","QBinomial","QFactorial","QGamma","QHypergeometricPFQ","QnDispersion","QPochhammer","QPolyGamma","QRDecomposition","QuadraticIrrationalQ","QuadraticOptimization","Quantile","QuantilePlot","Quantity","QuantityArray","QuantityDistribution","QuantityForm","QuantityMagnitude","QuantityQ","QuantityUnit","QuantityVariable","QuantityVariableCanonicalUnit","QuantityVariableDimensions","QuantityVariableIdentifier","QuantityVariablePhysicalQuantity","Quartics","QuartileDeviation","Quartiles","QuartileSkewness","Query","QueueingNetworkProcess","QueueingProcess","QueueProperties","Quiet","Quit","Quotient","QuotientRemainder","RadialGradientImage","RadialityCentrality","RadicalBox","RadicalBoxOptions","RadioButton","RadioButtonBar","RadioButtonBox","RadioButtonBoxOptions","Radon","RadonTransform","RamanujanTau","RamanujanTauL","RamanujanTauTheta","RamanujanTauZ","Ramp","Random","RandomChoice","RandomColor","RandomComplex","RandomEntity","RandomFunction","RandomGeoPosition","RandomGraph","RandomImage","RandomInstance","RandomInteger","RandomPermutation","RandomPoint","RandomPolygon","RandomPolyhedron","RandomPrime","RandomReal","RandomSample","RandomSeed","RandomSeeding","RandomVariate","RandomWalkProcess","RandomWord","Range","RangeFilter","RangeSpecification","RankedMax","RankedMin","RarerProbability","Raster","Raster3D","Raster3DBox","Raster3DBoxOptions","RasterArray","RasterBox","RasterBoxOptions","Rasterize","RasterSize","Rational","RationalFunctions","Rationalize","Rationals","Ratios","RawArray","RawBoxes","RawData","RawMedium","RayleighDistribution","Re","Read","ReadByteArray","ReadLine","ReadList","ReadProtected","ReadString","Real","RealAbs","RealBlockDiagonalForm","RealDigits","RealExponent","Reals","RealSign","Reap","RebuildPacletData","RecognitionPrior","RecognitionThreshold","Record","RecordLists","RecordSeparators","Rectangle","RectangleBox","RectangleBoxOptions","RectangleChart","RectangleChart3D","RectangularRepeatingElement","RecurrenceFilter","RecurrenceTable","RecurringDigitsForm","Red","Reduce","RefBox","ReferenceLineStyle","ReferenceMarkers","ReferenceMarkerStyle","Refine","ReflectionMatrix","ReflectionTransform","Refresh","RefreshRate","Region","RegionBinarize","RegionBoundary","RegionBoundaryStyle","RegionBounds","RegionCentroid","RegionDifference","RegionDimension","RegionDisjoint","RegionDistance","RegionDistanceFunction","RegionEmbeddingDimension","RegionEqual","RegionFillingStyle","RegionFunction","RegionImage","RegionIntersection","RegionMeasure","RegionMember","RegionMemberFunction","RegionMoment","RegionNearest","RegionNearestFunction","RegionPlot","RegionPlot3D","RegionProduct","RegionQ","RegionResize","RegionSize","RegionSymmetricDifference","RegionUnion","RegionWithin","RegisterExternalEvaluator","RegularExpression","Regularization","RegularlySampledQ","RegularPolygon","ReIm","ReImLabels","ReImPlot","ReImStyle","Reinstall","RelationalDatabase","RelationGraph","Release","ReleaseHold","ReliabilityDistribution","ReliefImage","ReliefPlot","RemoteAuthorizationCaching","RemoteConnect","RemoteConnectionObject","RemoteFile","RemoteRun","RemoteRunProcess","Remove","RemoveAlphaChannel","RemoveAsynchronousTask","RemoveAudioStream","RemoveBackground","RemoveChannelListener","RemoveChannelSubscribers","Removed","RemoveDiacritics","RemoveInputStreamMethod","RemoveOutputStreamMethod","RemoveProperty","RemoveScheduledTask","RemoveUsers","RemoveVideoStream","RenameDirectory","RenameFile","RenderAll","RenderingOptions","RenewalProcess","RenkoChart","RepairMesh","Repeated","RepeatedNull","RepeatedString","RepeatedTiming","RepeatingElement","Replace","ReplaceAll","ReplaceHeldPart","ReplaceImageValue","ReplaceList","ReplacePart","ReplacePixelValue","ReplaceRepeated","ReplicateLayer","RequiredPhysicalQuantities","Resampling","ResamplingAlgorithmData","ResamplingMethod","Rescale","RescalingTransform","ResetDirectory","ResetMenusPacket","ResetScheduledTask","ReshapeLayer","Residue","ResizeLayer","Resolve","ResourceAcquire","ResourceData","ResourceFunction","ResourceObject","ResourceRegister","ResourceRemove","ResourceSearch","ResourceSubmissionObject","ResourceSubmit","ResourceSystemBase","ResourceSystemPath","ResourceUpdate","ResourceVersion","ResponseForm","Rest","RestartInterval","Restricted","Resultant","ResumePacket","Return","ReturnEntersInput","ReturnExpressionPacket","ReturnInputFormPacket","ReturnPacket","ReturnReceiptFunction","ReturnTextPacket","Reverse","ReverseApplied","ReverseBiorthogonalSplineWavelet","ReverseElement","ReverseEquilibrium","ReverseGraph","ReverseSort","ReverseSortBy","ReverseUpEquilibrium","RevolutionAxis","RevolutionPlot3D","RGBColor","RiccatiSolve","RiceDistribution","RidgeFilter","RiemannR","RiemannSiegelTheta","RiemannSiegelZ","RiemannXi","Riffle","Right","RightArrow","RightArrowBar","RightArrowLeftArrow","RightComposition","RightCosetRepresentative","RightDownTeeVector","RightDownVector","RightDownVectorBar","RightTee","RightTeeArrow","RightTeeVector","RightTriangle","RightTriangleBar","RightTriangleEqual","RightUpDownVector","RightUpTeeVector","RightUpVector","RightUpVectorBar","RightVector","RightVectorBar","RiskAchievementImportance","RiskReductionImportance","RogersTanimotoDissimilarity","RollPitchYawAngles","RollPitchYawMatrix","RomanNumeral","Root","RootApproximant","RootIntervals","RootLocusPlot","RootMeanSquare","RootOfUnityQ","RootReduce","Roots","RootSum","Rotate","RotateLabel","RotateLeft","RotateRight","RotationAction","RotationBox","RotationBoxOptions","RotationMatrix","RotationTransform","Round","RoundImplies","RoundingRadius","Row","RowAlignments","RowBackgrounds","RowBox","RowHeights","RowLines","RowMinHeight","RowReduce","RowsEqual","RowSpacings","RSolve","RSolveValue","RudinShapiro","RudvalisGroupRu","Rule","RuleCondition","RuleDelayed","RuleForm","RulePlot","RulerUnits","Run","RunProcess","RunScheduledTask","RunThrough","RuntimeAttributes","RuntimeOptions","RussellRaoDissimilarity","SameQ","SameTest","SameTestProperties","SampledEntityClass","SampleDepth","SampledSoundFunction","SampledSoundList","SampleRate","SamplingPeriod","SARIMAProcess","SARMAProcess","SASTriangle","SatelliteData","SatisfiabilityCount","SatisfiabilityInstances","SatisfiableQ","Saturday","Save","Saveable","SaveAutoDelete","SaveConnection","SaveDefinitions","SavitzkyGolayMatrix","SawtoothWave","Scale","Scaled","ScaleDivisions","ScaledMousePosition","ScaleOrigin","ScalePadding","ScaleRanges","ScaleRangeStyle","ScalingFunctions","ScalingMatrix","ScalingTransform","Scan","ScheduledTask","ScheduledTaskActiveQ","ScheduledTaskInformation","ScheduledTaskInformationData","ScheduledTaskObject","ScheduledTasks","SchurDecomposition","ScientificForm","ScientificNotationThreshold","ScorerGi","ScorerGiPrime","ScorerHi","ScorerHiPrime","ScreenRectangle","ScreenStyleEnvironment","ScriptBaselineShifts","ScriptForm","ScriptLevel","ScriptMinSize","ScriptRules","ScriptSizeMultipliers","Scrollbars","ScrollingOptions","ScrollPosition","SearchAdjustment","SearchIndexObject","SearchIndices","SearchQueryString","SearchResultObject","Sec","Sech","SechDistribution","SecondOrderConeOptimization","SectionGrouping","SectorChart","SectorChart3D","SectorOrigin","SectorSpacing","SecuredAuthenticationKey","SecuredAuthenticationKeys","SeedRandom","Select","Selectable","SelectComponents","SelectedCells","SelectedNotebook","SelectFirst","Selection","SelectionAnimate","SelectionCell","SelectionCellCreateCell","SelectionCellDefaultStyle","SelectionCellParentStyle","SelectionCreateCell","SelectionDebuggerTag","SelectionDuplicateCell","SelectionEvaluate","SelectionEvaluateCreateCell","SelectionMove","SelectionPlaceholder","SelectionSetStyle","SelectWithContents","SelfLoops","SelfLoopStyle","SemanticImport","SemanticImportString","SemanticInterpretation","SemialgebraicComponentInstances","SemidefiniteOptimization","SendMail","SendMessage","Sequence","SequenceAlignment","SequenceAttentionLayer","SequenceCases","SequenceCount","SequenceFold","SequenceFoldList","SequenceForm","SequenceHold","SequenceLastLayer","SequenceMostLayer","SequencePosition","SequencePredict","SequencePredictorFunction","SequenceReplace","SequenceRestLayer","SequenceReverseLayer","SequenceSplit","Series","SeriesCoefficient","SeriesData","SeriesTermGoal","ServiceConnect","ServiceDisconnect","ServiceExecute","ServiceObject","ServiceRequest","ServiceResponse","ServiceSubmit","SessionSubmit","SessionTime","Set","SetAccuracy","SetAlphaChannel","SetAttributes","Setbacks","SetBoxFormNamesPacket","SetCloudDirectory","SetCookies","SetDelayed","SetDirectory","SetEnvironment","SetEvaluationNotebook","SetFileDate","SetFileLoadingContext","SetNotebookStatusLine","SetOptions","SetOptionsPacket","SetPermissions","SetPrecision","SetProperty","SetSecuredAuthenticationKey","SetSelectedNotebook","SetSharedFunction","SetSharedVariable","SetSpeechParametersPacket","SetStreamPosition","SetSystemModel","SetSystemOptions","Setter","SetterBar","SetterBox","SetterBoxOptions","Setting","SetUsers","SetValue","Shading","Shallow","ShannonWavelet","ShapiroWilkTest","Share","SharingList","Sharpen","ShearingMatrix","ShearingTransform","ShellRegion","ShenCastanMatrix","ShiftedGompertzDistribution","ShiftRegisterSequence","Short","ShortDownArrow","Shortest","ShortestMatch","ShortestPathFunction","ShortLeftArrow","ShortRightArrow","ShortTimeFourier","ShortTimeFourierData","ShortUpArrow","Show","ShowAutoConvert","ShowAutoSpellCheck","ShowAutoStyles","ShowCellBracket","ShowCellLabel","ShowCellTags","ShowClosedCellArea","ShowCodeAssist","ShowContents","ShowControls","ShowCursorTracker","ShowGroupOpenCloseIcon","ShowGroupOpener","ShowInvisibleCharacters","ShowPageBreaks","ShowPredictiveInterface","ShowSelection","ShowShortBoxForm","ShowSpecialCharacters","ShowStringCharacters","ShowSyntaxStyles","ShrinkingDelay","ShrinkWrapBoundingBox","SiderealTime","SiegelTheta","SiegelTukeyTest","SierpinskiCurve","SierpinskiMesh","Sign","Signature","SignedRankTest","SignedRegionDistance","SignificanceLevel","SignPadding","SignTest","SimilarityRules","SimpleGraph","SimpleGraphQ","SimplePolygonQ","SimplePolyhedronQ","Simplex","Simplify","Sin","Sinc","SinghMaddalaDistribution","SingleEvaluation","SingleLetterItalics","SingleLetterStyle","SingularValueDecomposition","SingularValueList","SingularValuePlot","SingularValues","Sinh","SinhIntegral","SinIntegral","SixJSymbol","Skeleton","SkeletonTransform","SkellamDistribution","Skewness","SkewNormalDistribution","SkinStyle","Skip","SliceContourPlot3D","SliceDensityPlot3D","SliceDistribution","SliceVectorPlot3D","Slider","Slider2D","Slider2DBox","Slider2DBoxOptions","SliderBox","SliderBoxOptions","SlideView","Slot","SlotSequence","Small","SmallCircle","Smaller","SmithDecomposition","SmithDelayCompensator","SmithWatermanSimilarity","SmoothDensityHistogram","SmoothHistogram","SmoothHistogram3D","SmoothKernelDistribution","SnDispersion","Snippet","SnubPolyhedron","SocialMediaData","Socket","SocketConnect","SocketListen","SocketListener","SocketObject","SocketOpen","SocketReadMessage","SocketReadyQ","Sockets","SocketWaitAll","SocketWaitNext","SoftmaxLayer","SokalSneathDissimilarity","SolarEclipse","SolarSystemFeatureData","SolidAngle","SolidData","SolidRegionQ","Solve","SolveAlways","SolveDelayed","Sort","SortBy","SortedBy","SortedEntityClass","Sound","SoundAndGraphics","SoundNote","SoundVolume","SourceLink","Sow","Space","SpaceCurveData","SpaceForm","Spacer","Spacings","Span","SpanAdjustments","SpanCharacterRounding","SpanFromAbove","SpanFromBoth","SpanFromLeft","SpanLineThickness","SpanMaxSize","SpanMinSize","SpanningCharacters","SpanSymmetric","SparseArray","SpatialGraphDistribution","SpatialMedian","SpatialTransformationLayer","Speak","SpeakerMatchQ","SpeakTextPacket","SpearmanRankTest","SpearmanRho","SpeciesData","SpecificityGoal","SpectralLineData","Spectrogram","SpectrogramArray","Specularity","SpeechCases","SpeechInterpreter","SpeechRecognize","SpeechSynthesize","SpellingCorrection","SpellingCorrectionList","SpellingDictionaries","SpellingDictionariesPath","SpellingOptions","SpellingSuggestionsPacket","Sphere","SphereBox","SpherePoints","SphericalBesselJ","SphericalBesselY","SphericalHankelH1","SphericalHankelH2","SphericalHarmonicY","SphericalPlot3D","SphericalRegion","SphericalShell","SpheroidalEigenvalue","SpheroidalJoiningFactor","SpheroidalPS","SpheroidalPSPrime","SpheroidalQS","SpheroidalQSPrime","SpheroidalRadialFactor","SpheroidalS1","SpheroidalS1Prime","SpheroidalS2","SpheroidalS2Prime","Splice","SplicedDistribution","SplineClosed","SplineDegree","SplineKnots","SplineWeights","Split","SplitBy","SpokenString","Sqrt","SqrtBox","SqrtBoxOptions","Square","SquaredEuclideanDistance","SquareFreeQ","SquareIntersection","SquareMatrixQ","SquareRepeatingElement","SquaresR","SquareSubset","SquareSubsetEqual","SquareSuperset","SquareSupersetEqual","SquareUnion","SquareWave","SSSTriangle","StabilityMargins","StabilityMarginsStyle","StableDistribution","Stack","StackBegin","StackComplete","StackedDateListPlot","StackedListPlot","StackInhibit","StadiumShape","StandardAtmosphereData","StandardDeviation","StandardDeviationFilter","StandardForm","Standardize","Standardized","StandardOceanData","StandbyDistribution","Star","StarClusterData","StarData","StarGraph","StartAsynchronousTask","StartExternalSession","StartingStepSize","StartOfLine","StartOfString","StartProcess","StartScheduledTask","StartupSound","StartWebSession","StateDimensions","StateFeedbackGains","StateOutputEstimator","StateResponse","StateSpaceModel","StateSpaceRealization","StateSpaceTransform","StateTransformationLinearize","StationaryDistribution","StationaryWaveletPacketTransform","StationaryWaveletTransform","StatusArea","StatusCentrality","StepMonitor","StereochemistryElements","StieltjesGamma","StippleShading","StirlingS1","StirlingS2","StopAsynchronousTask","StoppingPowerData","StopScheduledTask","StrataVariables","StratonovichProcess","StreamColorFunction","StreamColorFunctionScaling","StreamDensityPlot","StreamMarkers","StreamPlot","StreamPoints","StreamPosition","Streams","StreamScale","StreamStyle","String","StringBreak","StringByteCount","StringCases","StringContainsQ","StringCount","StringDelete","StringDrop","StringEndsQ","StringExpression","StringExtract","StringForm","StringFormat","StringFreeQ","StringInsert","StringJoin","StringLength","StringMatchQ","StringPadLeft","StringPadRight","StringPart","StringPartition","StringPosition","StringQ","StringRepeat","StringReplace","StringReplaceList","StringReplacePart","StringReverse","StringRiffle","StringRotateLeft","StringRotateRight","StringSkeleton","StringSplit","StringStartsQ","StringTake","StringTemplate","StringToByteArray","StringToStream","StringTrim","StripBoxes","StripOnInput","StripWrapperBoxes","StrokeForm","StructuralImportance","StructuredArray","StructuredArrayHeadQ","StructuredSelection","StruveH","StruveL","Stub","StudentTDistribution","Style","StyleBox","StyleBoxAutoDelete","StyleData","StyleDefinitions","StyleForm","StyleHints","StyleKeyMapping","StyleMenuListing","StyleNameDialogSettings","StyleNames","StylePrint","StyleSheetPath","Subdivide","Subfactorial","Subgraph","SubMinus","SubPlus","SubresultantPolynomialRemainders","SubresultantPolynomials","Subresultants","Subscript","SubscriptBox","SubscriptBoxOptions","Subscripted","Subsequences","Subset","SubsetCases","SubsetCount","SubsetEqual","SubsetMap","SubsetPosition","SubsetQ","SubsetReplace","Subsets","SubStar","SubstitutionSystem","Subsuperscript","SubsuperscriptBox","SubsuperscriptBoxOptions","SubtitleEncoding","SubtitleTracks","Subtract","SubtractFrom","SubtractSides","SubValues","Succeeds","SucceedsEqual","SucceedsSlantEqual","SucceedsTilde","Success","SuchThat","Sum","SumConvergence","SummationLayer","Sunday","SunPosition","Sunrise","Sunset","SuperDagger","SuperMinus","SupernovaData","SuperPlus","Superscript","SuperscriptBox","SuperscriptBoxOptions","Superset","SupersetEqual","SuperStar","Surd","SurdForm","SurfaceAppearance","SurfaceArea","SurfaceColor","SurfaceData","SurfaceGraphics","SurvivalDistribution","SurvivalFunction","SurvivalModel","SurvivalModelFit","SuspendPacket","SuzukiDistribution","SuzukiGroupSuz","SwatchLegend","Switch","Symbol","SymbolName","SymletWavelet","Symmetric","SymmetricGroup","SymmetricKey","SymmetricMatrixQ","SymmetricPolynomial","SymmetricReduction","Symmetrize","SymmetrizedArray","SymmetrizedArrayRules","SymmetrizedDependentComponents","SymmetrizedIndependentComponents","SymmetrizedReplacePart","SynchronousInitialization","SynchronousUpdating","Synonyms","Syntax","SyntaxForm","SyntaxInformation","SyntaxLength","SyntaxPacket","SyntaxQ","SynthesizeMissingValues","SystemCredential","SystemCredentialData","SystemCredentialKey","SystemCredentialKeys","SystemCredentialStoreObject","SystemDialogInput","SystemException","SystemGet","SystemHelpPath","SystemInformation","SystemInformationData","SystemInstall","SystemModel","SystemModeler","SystemModelExamples","SystemModelLinearize","SystemModelParametricSimulate","SystemModelPlot","SystemModelProgressReporting","SystemModelReliability","SystemModels","SystemModelSimulate","SystemModelSimulateSensitivity","SystemModelSimulationData","SystemOpen","SystemOptions","SystemProcessData","SystemProcesses","SystemsConnectionsModel","SystemsModelDelay","SystemsModelDelayApproximate","SystemsModelDelete","SystemsModelDimensions","SystemsModelExtract","SystemsModelFeedbackConnect","SystemsModelLabels","SystemsModelLinearity","SystemsModelMerge","SystemsModelOrder","SystemsModelParallelConnect","SystemsModelSeriesConnect","SystemsModelStateFeedbackConnect","SystemsModelVectorRelativeOrders","SystemStub","SystemTest","Tab","TabFilling","Table","TableAlignments","TableDepth","TableDirections","TableForm","TableHeadings","TableSpacing","TableView","TableViewBox","TableViewBoxBackground","TableViewBoxItemSize","TableViewBoxOptions","TabSpacings","TabView","TabViewBox","TabViewBoxOptions","TagBox","TagBoxNote","TagBoxOptions","TaggingRules","TagSet","TagSetDelayed","TagStyle","TagUnset","Take","TakeDrop","TakeLargest","TakeLargestBy","TakeList","TakeSmallest","TakeSmallestBy","TakeWhile","Tally","Tan","Tanh","TargetDevice","TargetFunctions","TargetSystem","TargetUnits","TaskAbort","TaskExecute","TaskObject","TaskRemove","TaskResume","Tasks","TaskSuspend","TaskWait","TautologyQ","TelegraphProcess","TemplateApply","TemplateArgBox","TemplateBox","TemplateBoxOptions","TemplateEvaluate","TemplateExpression","TemplateIf","TemplateObject","TemplateSequence","TemplateSlot","TemplateSlotSequence","TemplateUnevaluated","TemplateVerbatim","TemplateWith","TemporalData","TemporalRegularity","Temporary","TemporaryVariable","TensorContract","TensorDimensions","TensorExpand","TensorProduct","TensorQ","TensorRank","TensorReduce","TensorSymmetry","TensorTranspose","TensorWedge","TestID","TestReport","TestReportObject","TestResultObject","Tetrahedron","TetrahedronBox","TetrahedronBoxOptions","TeXForm","TeXSave","Text","Text3DBox","Text3DBoxOptions","TextAlignment","TextBand","TextBoundingBox","TextBox","TextCases","TextCell","TextClipboardType","TextContents","TextData","TextElement","TextForm","TextGrid","TextJustification","TextLine","TextPacket","TextParagraph","TextPosition","TextRecognize","TextSearch","TextSearchReport","TextSentences","TextString","TextStructure","TextStyle","TextTranslation","Texture","TextureCoordinateFunction","TextureCoordinateScaling","TextWords","Therefore","ThermodynamicData","ThermometerGauge","Thick","Thickness","Thin","Thinning","ThisLink","ThompsonGroupTh","Thread","ThreadingLayer","ThreeJSymbol","Threshold","Through","Throw","ThueMorse","Thumbnail","Thursday","Ticks","TicksStyle","TideData","Tilde","TildeEqual","TildeFullEqual","TildeTilde","TimeConstrained","TimeConstraint","TimeDirection","TimeFormat","TimeGoal","TimelinePlot","TimeObject","TimeObjectQ","TimeRemaining","Times","TimesBy","TimeSeries","TimeSeriesAggregate","TimeSeriesForecast","TimeSeriesInsert","TimeSeriesInvertibility","TimeSeriesMap","TimeSeriesMapThread","TimeSeriesModel","TimeSeriesModelFit","TimeSeriesResample","TimeSeriesRescale","TimeSeriesShift","TimeSeriesThread","TimeSeriesWindow","TimeUsed","TimeValue","TimeWarpingCorrespondence","TimeWarpingDistance","TimeZone","TimeZoneConvert","TimeZoneOffset","Timing","Tiny","TitleGrouping","TitsGroupT","ToBoxes","ToCharacterCode","ToColor","ToContinuousTimeModel","ToDate","Today","ToDiscreteTimeModel","ToEntity","ToeplitzMatrix","ToExpression","ToFileName","Together","Toggle","ToggleFalse","Toggler","TogglerBar","TogglerBox","TogglerBoxOptions","ToHeldExpression","ToInvertibleTimeSeries","TokenWords","Tolerance","ToLowerCase","Tomorrow","ToNumberField","TooBig","Tooltip","TooltipBox","TooltipBoxOptions","TooltipDelay","TooltipStyle","ToonShading","Top","TopHatTransform","ToPolarCoordinates","TopologicalSort","ToRadicals","ToRules","ToSphericalCoordinates","ToString","Total","TotalHeight","TotalLayer","TotalVariationFilter","TotalWidth","TouchPosition","TouchscreenAutoZoom","TouchscreenControlPlacement","ToUpperCase","Tr","Trace","TraceAbove","TraceAction","TraceBackward","TraceDepth","TraceDialog","TraceForward","TraceInternal","TraceLevel","TraceOff","TraceOn","TraceOriginal","TracePrint","TraceScan","TrackedSymbols","TrackingFunction","TracyWidomDistribution","TradingChart","TraditionalForm","TraditionalFunctionNotation","TraditionalNotation","TraditionalOrder","TrainingProgressCheckpointing","TrainingProgressFunction","TrainingProgressMeasurements","TrainingProgressReporting","TrainingStoppingCriterion","TrainingUpdateSchedule","TransferFunctionCancel","TransferFunctionExpand","TransferFunctionFactor","TransferFunctionModel","TransferFunctionPoles","TransferFunctionTransform","TransferFunctionZeros","TransformationClass","TransformationFunction","TransformationFunctions","TransformationMatrix","TransformedDistribution","TransformedField","TransformedProcess","TransformedRegion","TransitionDirection","TransitionDuration","TransitionEffect","TransitiveClosureGraph","TransitiveReductionGraph","Translate","TranslationOptions","TranslationTransform","Transliterate","Transparent","TransparentColor","Transpose","TransposeLayer","TrapSelection","TravelDirections","TravelDirectionsData","TravelDistance","TravelDistanceList","TravelMethod","TravelTime","TreeForm","TreeGraph","TreeGraphQ","TreePlot","TrendStyle","Triangle","TriangleCenter","TriangleConstruct","TriangleMeasurement","TriangleWave","TriangularDistribution","TriangulateMesh","Trig","TrigExpand","TrigFactor","TrigFactorList","Trigger","TrigReduce","TrigToExp","TrimmedMean","TrimmedVariance","TropicalStormData","True","TrueQ","TruncatedDistribution","TruncatedPolyhedron","TsallisQExponentialDistribution","TsallisQGaussianDistribution","TTest","Tube","TubeBezierCurveBox","TubeBezierCurveBoxOptions","TubeBox","TubeBoxOptions","TubeBSplineCurveBox","TubeBSplineCurveBoxOptions","Tuesday","TukeyLambdaDistribution","TukeyWindow","TunnelData","Tuples","TuranGraph","TuringMachine","TuttePolynomial","TwoWayRule","Typed","TypeSpecifier","UnateQ","Uncompress","UnconstrainedParameters","Undefined","UnderBar","Underflow","Underlined","Underoverscript","UnderoverscriptBox","UnderoverscriptBoxOptions","Underscript","UnderscriptBox","UnderscriptBoxOptions","UnderseaFeatureData","UndirectedEdge","UndirectedGraph","UndirectedGraphQ","UndoOptions","UndoTrackedVariables","Unequal","UnequalTo","Unevaluated","UniformDistribution","UniformGraphDistribution","UniformPolyhedron","UniformSumDistribution","Uninstall","Union","UnionedEntityClass","UnionPlus","Unique","UnitaryMatrixQ","UnitBox","UnitConvert","UnitDimensions","Unitize","UnitRootTest","UnitSimplify","UnitStep","UnitSystem","UnitTriangle","UnitVector","UnitVectorLayer","UnityDimensions","UniverseModelData","UniversityData","UnixTime","Unprotect","UnregisterExternalEvaluator","UnsameQ","UnsavedVariables","Unset","UnsetShared","UntrackedVariables","Up","UpArrow","UpArrowBar","UpArrowDownArrow","Update","UpdateDynamicObjects","UpdateDynamicObjectsSynchronous","UpdateInterval","UpdatePacletSites","UpdateSearchIndex","UpDownArrow","UpEquilibrium","UpperCaseQ","UpperLeftArrow","UpperRightArrow","UpperTriangularize","UpperTriangularMatrixQ","Upsample","UpSet","UpSetDelayed","UpTee","UpTeeArrow","UpTo","UpValues","URL","URLBuild","URLDecode","URLDispatcher","URLDownload","URLDownloadSubmit","URLEncode","URLExecute","URLExpand","URLFetch","URLFetchAsynchronous","URLParse","URLQueryDecode","URLQueryEncode","URLRead","URLResponseTime","URLSave","URLSaveAsynchronous","URLShorten","URLSubmit","UseGraphicsRange","UserDefinedWavelet","Using","UsingFrontEnd","UtilityFunction","V2Get","ValenceErrorHandling","ValidationLength","ValidationSet","Value","ValueBox","ValueBoxOptions","ValueDimensions","ValueForm","ValuePreprocessingFunction","ValueQ","Values","ValuesData","Variables","Variance","VarianceEquivalenceTest","VarianceEstimatorFunction","VarianceGammaDistribution","VarianceTest","VectorAngle","VectorAround","VectorAspectRatio","VectorColorFunction","VectorColorFunctionScaling","VectorDensityPlot","VectorGlyphData","VectorGreater","VectorGreaterEqual","VectorLess","VectorLessEqual","VectorMarkers","VectorPlot","VectorPlot3D","VectorPoints","VectorQ","VectorRange","Vectors","VectorScale","VectorScaling","VectorSizes","VectorStyle","Vee","Verbatim","Verbose","VerboseConvertToPostScriptPacket","VerificationTest","VerifyConvergence","VerifyDerivedKey","VerifyDigitalSignature","VerifyFileSignature","VerifyInterpretation","VerifySecurityCertificates","VerifySolutions","VerifyTestAssumptions","Version","VersionedPreferences","VersionNumber","VertexAdd","VertexCapacity","VertexColors","VertexComponent","VertexConnectivity","VertexContract","VertexCoordinateRules","VertexCoordinates","VertexCorrelationSimilarity","VertexCosineSimilarity","VertexCount","VertexCoverQ","VertexDataCoordinates","VertexDegree","VertexDelete","VertexDiceSimilarity","VertexEccentricity","VertexInComponent","VertexInDegree","VertexIndex","VertexJaccardSimilarity","VertexLabeling","VertexLabels","VertexLabelStyle","VertexList","VertexNormals","VertexOutComponent","VertexOutDegree","VertexQ","VertexRenderingFunction","VertexReplace","VertexShape","VertexShapeFunction","VertexSize","VertexStyle","VertexTextureCoordinates","VertexWeight","VertexWeightedGraphQ","Vertical","VerticalBar","VerticalForm","VerticalGauge","VerticalSeparator","VerticalSlider","VerticalTilde","Video","VideoEncoding","VideoExtractFrames","VideoFrameList","VideoFrameMap","VideoPause","VideoPlay","VideoQ","VideoStop","VideoStream","VideoStreams","VideoTimeSeries","VideoTracks","VideoTrim","ViewAngle","ViewCenter","ViewMatrix","ViewPoint","ViewPointSelectorSettings","ViewPort","ViewProjection","ViewRange","ViewVector","ViewVertical","VirtualGroupData","Visible","VisibleCell","VoiceStyleData","VoigtDistribution","VolcanoData","Volume","VonMisesDistribution","VoronoiMesh","WaitAll","WaitAsynchronousTask","WaitNext","WaitUntil","WakebyDistribution","WalleniusHypergeometricDistribution","WaringYuleDistribution","WarpingCorrespondence","WarpingDistance","WatershedComponents","WatsonUSquareTest","WattsStrogatzGraphDistribution","WaveletBestBasis","WaveletFilterCoefficients","WaveletImagePlot","WaveletListPlot","WaveletMapIndexed","WaveletMatrixPlot","WaveletPhi","WaveletPsi","WaveletScale","WaveletScalogram","WaveletThreshold","WeaklyConnectedComponents","WeaklyConnectedGraphComponents","WeaklyConnectedGraphQ","WeakStationarity","WeatherData","WeatherForecastData","WebAudioSearch","WebElementObject","WeberE","WebExecute","WebImage","WebImageSearch","WebSearch","WebSessionObject","WebSessions","WebWindowObject","Wedge","Wednesday","WeibullDistribution","WeierstrassE1","WeierstrassE2","WeierstrassE3","WeierstrassEta1","WeierstrassEta2","WeierstrassEta3","WeierstrassHalfPeriods","WeierstrassHalfPeriodW1","WeierstrassHalfPeriodW2","WeierstrassHalfPeriodW3","WeierstrassInvariantG2","WeierstrassInvariantG3","WeierstrassInvariants","WeierstrassP","WeierstrassPPrime","WeierstrassSigma","WeierstrassZeta","WeightedAdjacencyGraph","WeightedAdjacencyMatrix","WeightedData","WeightedGraphQ","Weights","WelchWindow","WheelGraph","WhenEvent","Which","While","White","WhiteNoiseProcess","WhitePoint","Whitespace","WhitespaceCharacter","WhittakerM","WhittakerW","WienerFilter","WienerProcess","WignerD","WignerSemicircleDistribution","WikidataData","WikidataSearch","WikipediaData","WikipediaSearch","WilksW","WilksWTest","WindDirectionData","WindingCount","WindingPolygon","WindowClickSelect","WindowElements","WindowFloating","WindowFrame","WindowFrameElements","WindowMargins","WindowMovable","WindowOpacity","WindowPersistentStyles","WindowSelected","WindowSize","WindowStatusArea","WindowTitle","WindowToolbars","WindowWidth","WindSpeedData","WindVectorData","WinsorizedMean","WinsorizedVariance","WishartMatrixDistribution","With","WolframAlpha","WolframAlphaDate","WolframAlphaQuantity","WolframAlphaResult","WolframLanguageData","Word","WordBoundary","WordCharacter","WordCloud","WordCount","WordCounts","WordData","WordDefinition","WordFrequency","WordFrequencyData","WordList","WordOrientation","WordSearch","WordSelectionFunction","WordSeparators","WordSpacings","WordStem","WordTranslation","WorkingPrecision","WrapAround","Write","WriteLine","WriteString","Wronskian","XMLElement","XMLObject","XMLTemplate","Xnor","Xor","XYZColor","Yellow","Yesterday","YuleDissimilarity","ZernikeR","ZeroSymmetric","ZeroTest","ZeroWidthTimes","Zeta","ZetaZero","ZIPCodeData","ZipfDistribution","ZoomCenter","ZoomFactor","ZTest","ZTransform","$Aborted","$ActivationGroupID","$ActivationKey","$ActivationUserRegistered","$AddOnsDirectory","$AllowDataUpdates","$AllowExternalChannelFunctions","$AllowInternet","$AssertFunction","$Assumptions","$AsynchronousTask","$AudioDecoders","$AudioEncoders","$AudioInputDevices","$AudioOutputDevices","$BaseDirectory","$BasePacletsDirectory","$BatchInput","$BatchOutput","$BlockchainBase","$BoxForms","$ByteOrdering","$CacheBaseDirectory","$Canceled","$ChannelBase","$CharacterEncoding","$CharacterEncodings","$CloudAccountName","$CloudBase","$CloudConnected","$CloudConnection","$CloudCreditsAvailable","$CloudEvaluation","$CloudExpressionBase","$CloudObjectNameFormat","$CloudObjectURLType","$CloudRootDirectory","$CloudSymbolBase","$CloudUserID","$CloudUserUUID","$CloudVersion","$CloudVersionNumber","$CloudWolframEngineVersionNumber","$CommandLine","$CompilationTarget","$ConditionHold","$ConfiguredKernels","$Context","$ContextPath","$ControlActiveSetting","$Cookies","$CookieStore","$CreationDate","$CurrentLink","$CurrentTask","$CurrentWebSession","$DataStructures","$DateStringFormat","$DefaultAudioInputDevice","$DefaultAudioOutputDevice","$DefaultFont","$DefaultFrontEnd","$DefaultImagingDevice","$DefaultLocalBase","$DefaultMailbox","$DefaultNetworkInterface","$DefaultPath","$DefaultProxyRules","$DefaultSystemCredentialStore","$Display","$DisplayFunction","$DistributedContexts","$DynamicEvaluation","$Echo","$EmbedCodeEnvironments","$EmbeddableServices","$EntityStores","$Epilog","$EvaluationCloudBase","$EvaluationCloudObject","$EvaluationEnvironment","$ExportFormats","$ExternalIdentifierTypes","$ExternalStorageBase","$Failed","$FinancialDataSource","$FontFamilies","$FormatType","$FrontEnd","$FrontEndSession","$GeoEntityTypes","$GeoLocation","$GeoLocationCity","$GeoLocationCountry","$GeoLocationPrecision","$GeoLocationSource","$HistoryLength","$HomeDirectory","$HTMLExportRules","$HTTPCookies","$HTTPRequest","$IgnoreEOF","$ImageFormattingWidth","$ImageResolution","$ImagingDevice","$ImagingDevices","$ImportFormats","$IncomingMailSettings","$InitialDirectory","$Initialization","$InitializationContexts","$Input","$InputFileName","$InputStreamMethods","$Inspector","$InstallationDate","$InstallationDirectory","$InterfaceEnvironment","$InterpreterTypes","$IterationLimit","$KernelCount","$KernelID","$Language","$LaunchDirectory","$LibraryPath","$LicenseExpirationDate","$LicenseID","$LicenseProcesses","$LicenseServer","$LicenseSubprocesses","$LicenseType","$Line","$Linked","$LinkSupported","$LoadedFiles","$LocalBase","$LocalSymbolBase","$MachineAddresses","$MachineDomain","$MachineDomains","$MachineEpsilon","$MachineID","$MachineName","$MachinePrecision","$MachineType","$MaxExtraPrecision","$MaxLicenseProcesses","$MaxLicenseSubprocesses","$MaxMachineNumber","$MaxNumber","$MaxPiecewiseCases","$MaxPrecision","$MaxRootDegree","$MessageGroups","$MessageList","$MessagePrePrint","$Messages","$MinMachineNumber","$MinNumber","$MinorReleaseNumber","$MinPrecision","$MobilePhone","$ModuleNumber","$NetworkConnected","$NetworkInterfaces","$NetworkLicense","$NewMessage","$NewSymbol","$NotebookInlineStorageLimit","$Notebooks","$NoValue","$NumberMarks","$Off","$OperatingSystem","$Output","$OutputForms","$OutputSizeLimit","$OutputStreamMethods","$Packages","$ParentLink","$ParentProcessID","$PasswordFile","$PatchLevelID","$Path","$PathnameSeparator","$PerformanceGoal","$Permissions","$PermissionsGroupBase","$PersistenceBase","$PersistencePath","$PipeSupported","$PlotTheme","$Post","$Pre","$PreferencesDirectory","$PreInitialization","$PrePrint","$PreRead","$PrintForms","$PrintLiteral","$Printout3DPreviewer","$ProcessID","$ProcessorCount","$ProcessorType","$ProductInformation","$ProgramName","$PublisherID","$RandomState","$RecursionLimit","$RegisteredDeviceClasses","$RegisteredUserName","$ReleaseNumber","$RequesterAddress","$RequesterWolframID","$RequesterWolframUUID","$RootDirectory","$ScheduledTask","$ScriptCommandLine","$ScriptInputString","$SecuredAuthenticationKeyTokens","$ServiceCreditsAvailable","$Services","$SessionID","$SetParentLink","$SharedFunctions","$SharedVariables","$SoundDisplay","$SoundDisplayFunction","$SourceLink","$SSHAuthentication","$SubtitleDecoders","$SubtitleEncoders","$SummaryBoxDataSizeLimit","$SuppressInputFormHeads","$SynchronousEvaluation","$SyntaxHandler","$System","$SystemCharacterEncoding","$SystemCredentialStore","$SystemID","$SystemMemory","$SystemShell","$SystemTimeZone","$SystemWordLength","$TemplatePath","$TemporaryDirectory","$TemporaryPrefix","$TestFileName","$TextStyle","$TimedOut","$TimeUnit","$TimeZone","$TimeZoneEntity","$TopDirectory","$TraceOff","$TraceOn","$TracePattern","$TracePostAction","$TracePreAction","$UnitSystem","$Urgent","$UserAddOnsDirectory","$UserAgentLanguages","$UserAgentMachine","$UserAgentName","$UserAgentOperatingSystem","$UserAgentString","$UserAgentVersion","$UserBaseDirectory","$UserBasePacletsDirectory","$UserDocumentsDirectory","$Username","$UserName","$UserURLBase","$Version","$VersionNumber","$VideoDecoders","$VideoEncoders","$VoiceStyles","$WolframDocumentsDirectory","$WolframID","$WolframUUID"]
+;function t(e){return e?"string"==typeof e?e:e.source:null}function i(e){
+return o("(",e,")?")}function o(...e){return e.map((e=>t(e))).join("")}
+function a(...e){return"("+e.map((e=>t(e))).join("|")+")"}return t=>{
+const n=a(o(/([2-9]|[1-2]\d|[3][0-5])\^\^/,/(\w*\.\w+|\w+\.\w*|\w+)/),/(\d*\.\d+|\d+\.\d*|\d+)/),r={
+className:"number",relevance:0,
+begin:o(n,i(a(/``[+-]?(\d*\.\d+|\d+\.\d*|\d+)/,/`([+-]?(\d*\.\d+|\d+\.\d*|\d+))?/)),i(/\*\^[+-]?\d+/))
+},l=/[a-zA-Z$][a-zA-Z0-9$]*/,s=new Set(e),c={variants:[{
+className:"builtin-symbol",begin:l,"on:begin":(e,t)=>{
+s.has(e[0])||t.ignoreMatch()}},{className:"symbol",relevance:0,begin:l}]},u={
+className:"message-name",relevance:0,begin:o("::",l)};return{name:"Mathematica",
+aliases:["mma","wl"],classNameAliases:{brace:"punctuation",pattern:"type",
+slot:"type",symbol:"variable","named-character":"variable",
+"builtin-symbol":"built_in","message-name":"string"},
+contains:[t.COMMENT(/\(\*/,/\*\)/,{contains:["self"]}),{className:"pattern",
+relevance:0,begin:/([a-zA-Z$][a-zA-Z0-9$]*)?_+([a-zA-Z$][a-zA-Z0-9$]*)?/},{
+className:"slot",relevance:0,begin:/#[a-zA-Z$][a-zA-Z0-9$]*|#+[0-9]?/},u,c,{
+className:"named-character",begin:/\\\[[$a-zA-Z][$a-zA-Z0-9]+\]/
+},t.QUOTE_STRING_MODE,r,{className:"operator",relevance:0,
+begin:/[+\-*/,;.:@~=><&|_`'^?!%]+/},{className:"brace",relevance:0,
+begin:/[[\](){}]/}]}}})());
+hljs.registerLanguage("matlab",(()=>{"use strict";return e=>{var a={relevance:0,
+contains:[{begin:"('|\\.')+"}]};return{name:"Matlab",keywords:{
+keyword:"arguments break case catch classdef continue else elseif end enumeration events for function global if methods otherwise parfor persistent properties return spmd switch try while",
+built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i|0 inf nan isnan isinf isfinite j|0 why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell "
+},illegal:'(//|"|#|/\\*|\\s+/\\w+)',contains:[{className:"function",
+beginKeywords:"function",end:"$",contains:[e.UNDERSCORE_TITLE_MODE,{
+className:"params",variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}]}]
+},{className:"built_in",begin:/true|false/,relevance:0,starts:a},{
+begin:"[a-zA-Z][a-zA-Z_0-9]*('|\\.')+",relevance:0},{className:"number",
+begin:e.C_NUMBER_RE,relevance:0,starts:a},{className:"string",begin:"'",end:"'",
+contains:[e.BACKSLASH_ESCAPE,{begin:"''"}]},{begin:/\]|\}|\)/,relevance:0,
+starts:a},{className:"string",begin:'"',end:'"',contains:[e.BACKSLASH_ESCAPE,{
+begin:'""'}],starts:a
+},e.COMMENT("^\\s*%\\{\\s*$","^\\s*%\\}\\s*$"),e.COMMENT("%","$")]}}})());
+hljs.registerLanguage("maxima",(()=>{"use strict";return e=>({name:"Maxima",
+keywords:{$pattern:"[A-Za-z_%][0-9A-Za-z_%]*",
+keyword:"if then else elseif for thru do while unless step in and or not",
+literal:"true false unknown inf minf ind und %e %i %pi %phi %gamma",
+built_in:" abasep abs absint absolute_real_time acos acosh acot acoth acsc acsch activate addcol add_edge add_edges addmatrices addrow add_vertex add_vertices adjacency_matrix adjoin adjoint af agd airy airy_ai airy_bi airy_dai airy_dbi algsys alg_type alias allroots alphacharp alphanumericp amortization %and annuity_fv annuity_pv antid antidiff AntiDifference append appendfile apply apply1 apply2 applyb1 apropos args arit_amortization arithmetic arithsum array arrayapply arrayinfo arraymake arraysetapply ascii asec asech asin asinh askinteger asksign assoc assoc_legendre_p assoc_legendre_q assume assume_external_byte_order asympa at atan atan2 atanh atensimp atom atvalue augcoefmatrix augmented_lagrangian_method av average_degree backtrace bars barsplot barsplot_description base64 base64_decode bashindices batch batchload bc2 bdvac belln benefit_cost bern bernpoly bernstein_approx bernstein_expand bernstein_poly bessel bessel_i bessel_j bessel_k bessel_simplify bessel_y beta beta_incomplete beta_incomplete_generalized beta_incomplete_regularized bezout bfallroots bffac bf_find_root bf_fmin_cobyla bfhzeta bfloat bfloatp bfpsi bfpsi0 bfzeta biconnected_components bimetric binomial bipartition block blockmatrixp bode_gain bode_phase bothcoef box boxplot boxplot_description break bug_report build_info|10 buildq build_sample burn cabs canform canten cardinality carg cartan cartesian_product catch cauchy_matrix cbffac cdf_bernoulli cdf_beta cdf_binomial cdf_cauchy cdf_chi2 cdf_continuous_uniform cdf_discrete_uniform cdf_exp cdf_f cdf_gamma cdf_general_finite_discrete cdf_geometric cdf_gumbel cdf_hypergeometric cdf_laplace cdf_logistic cdf_lognormal cdf_negative_binomial cdf_noncentral_chi2 cdf_noncentral_student_t cdf_normal cdf_pareto cdf_poisson cdf_rank_sum cdf_rayleigh cdf_signed_rank cdf_student_t cdf_weibull cdisplay ceiling central_moment cequal cequalignore cf cfdisrep cfexpand cgeodesic cgreaterp cgreaterpignore changename changevar chaosgame charat charfun charfun2 charlist charp charpoly chdir chebyshev_t chebyshev_u checkdiv check_overlaps chinese cholesky christof chromatic_index chromatic_number cint circulant_graph clear_edge_weight clear_rules clear_vertex_label clebsch_gordan clebsch_graph clessp clesspignore close closefile cmetric coeff coefmatrix cograd col collapse collectterms columnop columnspace columnswap columnvector combination combine comp2pui compare compfile compile compile_file complement_graph complete_bipartite_graph complete_graph complex_number_p components compose_functions concan concat conjugate conmetderiv connected_components connect_vertices cons constant constantp constituent constvalue cont2part content continuous_freq contortion contour_plot contract contract_edge contragrad contrib_ode convert coord copy copy_file copy_graph copylist copymatrix cor cos cosh cot coth cov cov1 covdiff covect covers crc24sum create_graph create_list csc csch csetup cspline ctaylor ct_coordsys ctransform ctranspose cube_graph cuboctahedron_graph cunlisp cv cycle_digraph cycle_graph cylindrical days360 dblint deactivate declare declare_constvalue declare_dimensions declare_fundamental_dimensions declare_fundamental_units declare_qty declare_translated declare_unit_conversion declare_units declare_weights decsym defcon define define_alt_display define_variable defint defmatch defrule defstruct deftaylor degree_sequence del delete deleten delta demo demoivre denom depends derivdegree derivlist describe desolve determinant dfloat dgauss_a dgauss_b dgeev dgemm dgeqrf dgesv dgesvd diag diagmatrix diag_matrix diagmatrixp diameter diff digitcharp dimacs_export dimacs_import dimension dimensionless dimensions dimensions_as_list direct directory discrete_freq disjoin disjointp disolate disp dispcon dispform dispfun dispJordan display disprule dispterms distrib divide divisors divsum dkummer_m dkummer_u dlange dodecahedron_graph dotproduct dotsimp dpart draw draw2d draw3d drawdf draw_file draw_graph dscalar echelon edge_coloring edge_connectivity edges eigens_by_jacobi eigenvalues eigenvectors eighth einstein eivals eivects elapsed_real_time elapsed_run_time ele2comp ele2polynome ele2pui elem elementp elevation_grid elim elim_allbut eliminate eliminate_using ellipse elliptic_e elliptic_ec elliptic_eu elliptic_f elliptic_kc elliptic_pi ematrix empty_graph emptyp endcons entermatrix entertensor entier equal equalp equiv_classes erf erfc erf_generalized erfi errcatch error errormsg errors euler ev eval_string evenp every evolution evolution2d evundiff example exp expand expandwrt expandwrt_factored expint expintegral_chi expintegral_ci expintegral_e expintegral_e1 expintegral_ei expintegral_e_simplify expintegral_li expintegral_shi expintegral_si explicit explose exponentialize express expt exsec extdiff extract_linear_equations extremal_subset ezgcd %f f90 facsum factcomb factor factorfacsum factorial factorout factorsum facts fast_central_elements fast_linsolve fasttimes featurep fernfale fft fib fibtophi fifth filename_merge file_search file_type fillarray findde find_root find_root_abs find_root_error find_root_rel first fix flatten flength float floatnump floor flower_snark flush flush1deriv flushd flushnd flush_output fmin_cobyla forget fortran fourcos fourexpand fourier fourier_elim fourint fourintcos fourintsin foursimp foursin fourth fposition frame_bracket freeof freshline fresnel_c fresnel_s from_adjacency_matrix frucht_graph full_listify fullmap fullmapl fullratsimp fullratsubst fullsetify funcsolve fundamental_dimensions fundamental_units fundef funmake funp fv g0 g1 gamma gamma_greek gamma_incomplete gamma_incomplete_generalized gamma_incomplete_regularized gauss gauss_a gauss_b gaussprob gcd gcdex gcdivide gcfac gcfactor gd generalized_lambert_w genfact gen_laguerre genmatrix gensym geo_amortization geo_annuity_fv geo_annuity_pv geomap geometric geometric_mean geosum get getcurrentdirectory get_edge_weight getenv get_lu_factors get_output_stream_string get_pixel get_plot_option get_tex_environment get_tex_environment_default get_vertex_label gfactor gfactorsum ggf girth global_variances gn gnuplot_close gnuplot_replot gnuplot_reset gnuplot_restart gnuplot_start go Gosper GosperSum gr2d gr3d gradef gramschmidt graph6_decode graph6_encode graph6_export graph6_import graph_center graph_charpoly graph_eigenvalues graph_flow graph_order graph_periphery graph_product graph_size graph_union great_rhombicosidodecahedron_graph great_rhombicuboctahedron_graph grid_graph grind grobner_basis grotzch_graph hamilton_cycle hamilton_path hankel hankel_1 hankel_2 harmonic harmonic_mean hav heawood_graph hermite hessian hgfred hilbertmap hilbert_matrix hipow histogram histogram_description hodge horner hypergeometric i0 i1 %ibes ic1 ic2 ic_convert ichr1 ichr2 icosahedron_graph icosidodecahedron_graph icurvature ident identfor identity idiff idim idummy ieqn %if ifactors iframes ifs igcdex igeodesic_coords ilt image imagpart imetric implicit implicit_derivative implicit_plot indexed_tensor indices induced_subgraph inferencep inference_result infix info_display init_atensor init_ctensor in_neighbors innerproduct inpart inprod inrt integerp integer_partitions integrate intersect intersection intervalp intopois intosum invariant1 invariant2 inverse_fft inverse_jacobi_cd inverse_jacobi_cn inverse_jacobi_cs inverse_jacobi_dc inverse_jacobi_dn inverse_jacobi_ds inverse_jacobi_nc inverse_jacobi_nd inverse_jacobi_ns inverse_jacobi_sc inverse_jacobi_sd inverse_jacobi_sn invert invert_by_adjoint invert_by_lu inv_mod irr is is_biconnected is_bipartite is_connected is_digraph is_edge_in_graph is_graph is_graph_or_digraph ishow is_isomorphic isolate isomorphism is_planar isqrt isreal_p is_sconnected is_tree is_vertex_in_graph items_inference %j j0 j1 jacobi jacobian jacobi_cd jacobi_cn jacobi_cs jacobi_dc jacobi_dn jacobi_ds jacobi_nc jacobi_nd jacobi_ns jacobi_p jacobi_sc jacobi_sd jacobi_sn JF jn join jordan julia julia_set julia_sin %k kdels kdelta kill killcontext kostka kron_delta kronecker_product kummer_m kummer_u kurtosis kurtosis_bernoulli kurtosis_beta kurtosis_binomial kurtosis_chi2 kurtosis_continuous_uniform kurtosis_discrete_uniform kurtosis_exp kurtosis_f kurtosis_gamma kurtosis_general_finite_discrete kurtosis_geometric kurtosis_gumbel kurtosis_hypergeometric kurtosis_laplace kurtosis_logistic kurtosis_lognormal kurtosis_negative_binomial kurtosis_noncentral_chi2 kurtosis_noncentral_student_t kurtosis_normal kurtosis_pareto kurtosis_poisson kurtosis_rayleigh kurtosis_student_t kurtosis_weibull label labels lagrange laguerre lambda lambert_w laplace laplacian_matrix last lbfgs lc2kdt lcharp lc_l lcm lc_u ldefint ldisp ldisplay legendre_p legendre_q leinstein length let letrules letsimp levi_civita lfreeof lgtreillis lhs li liediff limit Lindstedt linear linearinterpol linear_program linear_regression line_graph linsolve listarray list_correlations listify list_matrix_entries list_nc_monomials listoftens listofvars listp lmax lmin load loadfile local locate_matrix_entry log logcontract log_gamma lopow lorentz_gauge lowercasep lpart lratsubst lreduce lriemann lsquares_estimates lsquares_estimates_approximate lsquares_estimates_exact lsquares_mse lsquares_residual_mse lsquares_residuals lsum ltreillis lu_backsub lucas lu_factor %m macroexpand macroexpand1 make_array makebox makefact makegamma make_graph make_level_picture makelist makeOrders make_poly_continent make_poly_country make_polygon make_random_state make_rgb_picture makeset make_string_input_stream make_string_output_stream make_transform mandelbrot mandelbrot_set map mapatom maplist matchdeclare matchfix mat_cond mat_fullunblocker mat_function mathml_display mat_norm matrix matrixmap matrixp matrix_size mattrace mat_trace mat_unblocker max max_clique max_degree max_flow maximize_lp max_independent_set max_matching maybe md5sum mean mean_bernoulli mean_beta mean_binomial mean_chi2 mean_continuous_uniform mean_deviation mean_discrete_uniform mean_exp mean_f mean_gamma mean_general_finite_discrete mean_geometric mean_gumbel mean_hypergeometric mean_laplace mean_logistic mean_lognormal mean_negative_binomial mean_noncentral_chi2 mean_noncentral_student_t mean_normal mean_pareto mean_poisson mean_rayleigh mean_student_t mean_weibull median median_deviation member mesh metricexpandall mgf1_sha1 min min_degree min_edge_cut minfactorial minimalPoly minimize_lp minimum_spanning_tree minor minpack_lsquares minpack_solve min_vertex_cover min_vertex_cut mkdir mnewton mod mode_declare mode_identity ModeMatrix moebius mon2schur mono monomial_dimensions multibernstein_poly multi_display_for_texinfo multi_elem multinomial multinomial_coeff multi_orbit multiplot_mode multi_pui multsym multthru mycielski_graph nary natural_unit nc_degree ncexpt ncharpoly negative_picture neighbors new newcontext newdet new_graph newline newton new_variable next_prime nicedummies niceindices ninth nofix nonarray noncentral_moment nonmetricity nonnegintegerp nonscalarp nonzeroandfreeof notequal nounify nptetrad npv nroots nterms ntermst nthroot nullity nullspace num numbered_boundaries numberp number_to_octets num_distinct_partitions numerval numfactor num_partitions nusum nzeta nzetai nzetar octets_to_number octets_to_oid odd_girth oddp ode2 ode_check odelin oid_to_octets op opena opena_binary openr openr_binary openw openw_binary operatorp opsubst optimize %or orbit orbits ordergreat ordergreatp orderless orderlessp orthogonal_complement orthopoly_recur orthopoly_weight outermap out_neighbors outofpois pade parabolic_cylinder_d parametric parametric_surface parg parGosper parse_string parse_timedate part part2cont partfrac partition partition_set partpol path_digraph path_graph pathname_directory pathname_name pathname_type pdf_bernoulli pdf_beta pdf_binomial pdf_cauchy pdf_chi2 pdf_continuous_uniform pdf_discrete_uniform pdf_exp pdf_f pdf_gamma pdf_general_finite_discrete pdf_geometric pdf_gumbel pdf_hypergeometric pdf_laplace pdf_logistic pdf_lognormal pdf_negative_binomial pdf_noncentral_chi2 pdf_noncentral_student_t pdf_normal pdf_pareto pdf_poisson pdf_rank_sum pdf_rayleigh pdf_signed_rank pdf_student_t pdf_weibull pearson_skewness permanent permut permutation permutations petersen_graph petrov pickapart picture_equalp picturep piechart piechart_description planar_embedding playback plog plot2d plot3d plotdf ploteq plsquares pochhammer points poisdiff poisexpt poisint poismap poisplus poissimp poissubst poistimes poistrim polar polarform polartorect polar_to_xy poly_add poly_buchberger poly_buchberger_criterion poly_colon_ideal poly_content polydecomp poly_depends_p poly_elimination_ideal poly_exact_divide poly_expand poly_expt poly_gcd polygon poly_grobner poly_grobner_equal poly_grobner_member poly_grobner_subsetp poly_ideal_intersection poly_ideal_polysaturation poly_ideal_polysaturation1 poly_ideal_saturation poly_ideal_saturation1 poly_lcm poly_minimization polymod poly_multiply polynome2ele polynomialp poly_normal_form poly_normalize poly_normalize_list poly_polysaturation_extension poly_primitive_part poly_pseudo_divide poly_reduced_grobner poly_reduction poly_saturation_extension poly_s_polynomial poly_subtract polytocompanion pop postfix potential power_mod powerseries powerset prefix prev_prime primep primes principal_components print printf printfile print_graph printpois printprops prodrac product properties propvars psi psubst ptriangularize pui pui2comp pui2ele pui2polynome pui_direct puireduc push put pv qput qrange qty quad_control quad_qag quad_qagi quad_qagp quad_qags quad_qawc quad_qawf quad_qawo quad_qaws quadrilateral quantile quantile_bernoulli quantile_beta quantile_binomial quantile_cauchy quantile_chi2 quantile_continuous_uniform quantile_discrete_uniform quantile_exp quantile_f quantile_gamma quantile_general_finite_discrete quantile_geometric quantile_gumbel quantile_hypergeometric quantile_laplace quantile_logistic quantile_lognormal quantile_negative_binomial quantile_noncentral_chi2 quantile_noncentral_student_t quantile_normal quantile_pareto quantile_poisson quantile_rayleigh quantile_student_t quantile_weibull quartile_skewness quit qunit quotient racah_v racah_w radcan radius random random_bernoulli random_beta random_binomial random_bipartite_graph random_cauchy random_chi2 random_continuous_uniform random_digraph random_discrete_uniform random_exp random_f random_gamma random_general_finite_discrete random_geometric random_graph random_graph1 random_gumbel random_hypergeometric random_laplace random_logistic random_lognormal random_negative_binomial random_network random_noncentral_chi2 random_noncentral_student_t random_normal random_pareto random_permutation random_poisson random_rayleigh random_regular_graph random_student_t random_tournament random_tree random_weibull range rank rat ratcoef ratdenom ratdiff ratdisrep ratexpand ratinterpol rational rationalize ratnumer ratnump ratp ratsimp ratsubst ratvars ratweight read read_array read_binary_array read_binary_list read_binary_matrix readbyte readchar read_hashed_array readline read_list read_matrix read_nested_list readonly read_xpm real_imagpart_to_conjugate realpart realroots rearray rectangle rectform rectform_log_if_constant recttopolar rediff reduce_consts reduce_order region region_boundaries region_boundaries_plus rem remainder remarray rembox remcomps remcon remcoord remfun remfunction remlet remove remove_constvalue remove_dimensions remove_edge remove_fundamental_dimensions remove_fundamental_units remove_plot_option remove_vertex rempart remrule remsym remvalue rename rename_file reset reset_displays residue resolvante resolvante_alternee1 resolvante_bipartite resolvante_diedrale resolvante_klein resolvante_klein3 resolvante_produit_sym resolvante_unitaire resolvante_vierer rest resultant return reveal reverse revert revert2 rgb2level rhs ricci riemann rinvariant risch rk rmdir rncombine romberg room rootscontract round row rowop rowswap rreduce run_testsuite %s save saving scalarp scaled_bessel_i scaled_bessel_i0 scaled_bessel_i1 scalefactors scanmap scatterplot scatterplot_description scene schur2comp sconcat scopy scsimp scurvature sdowncase sec sech second sequal sequalignore set_alt_display setdifference set_draw_defaults set_edge_weight setelmx setequalp setify setp set_partitions set_plot_option set_prompt set_random_state set_tex_environment set_tex_environment_default setunits setup_autoload set_up_dot_simplifications set_vertex_label seventh sexplode sf sha1sum sha256sum shortest_path shortest_weighted_path show showcomps showratvars sierpinskiale sierpinskimap sign signum similaritytransform simp_inequality simplify_sum simplode simpmetderiv simtran sin sinh sinsert sinvertcase sixth skewness skewness_bernoulli skewness_beta skewness_binomial skewness_chi2 skewness_continuous_uniform skewness_discrete_uniform skewness_exp skewness_f skewness_gamma skewness_general_finite_discrete skewness_geometric skewness_gumbel skewness_hypergeometric skewness_laplace skewness_logistic skewness_lognormal skewness_negative_binomial skewness_noncentral_chi2 skewness_noncentral_student_t skewness_normal skewness_pareto skewness_poisson skewness_rayleigh skewness_student_t skewness_weibull slength smake small_rhombicosidodecahedron_graph small_rhombicuboctahedron_graph smax smin smismatch snowmap snub_cube_graph snub_dodecahedron_graph solve solve_rec solve_rec_rat some somrac sort sparse6_decode sparse6_encode sparse6_export sparse6_import specint spherical spherical_bessel_j spherical_bessel_y spherical_hankel1 spherical_hankel2 spherical_harmonic spherical_to_xyz splice split sposition sprint sqfr sqrt sqrtdenest sremove sremovefirst sreverse ssearch ssort sstatus ssubst ssubstfirst staircase standardize standardize_inverse_trig starplot starplot_description status std std1 std_bernoulli std_beta std_binomial std_chi2 std_continuous_uniform std_discrete_uniform std_exp std_f std_gamma std_general_finite_discrete std_geometric std_gumbel std_hypergeometric std_laplace std_logistic std_lognormal std_negative_binomial std_noncentral_chi2 std_noncentral_student_t std_normal std_pareto std_poisson std_rayleigh std_student_t std_weibull stemplot stirling stirling1 stirling2 strim striml strimr string stringout stringp strong_components struve_h struve_l sublis sublist sublist_indices submatrix subsample subset subsetp subst substinpart subst_parallel substpart substring subvar subvarp sum sumcontract summand_to_rec supcase supcontext symbolp symmdifference symmetricp system take_channel take_inference tan tanh taylor taylorinfo taylorp taylor_simplifier taytorat tcl_output tcontract tellrat tellsimp tellsimpafter tentex tenth test_mean test_means_difference test_normality test_proportion test_proportions_difference test_rank_sum test_sign test_signed_rank test_variance test_variance_ratio tex tex1 tex_display texput %th third throw time timedate timer timer_info tldefint tlimit todd_coxeter toeplitz tokens to_lisp topological_sort to_poly to_poly_solve totaldisrep totalfourier totient tpartpol trace tracematrix trace_options transform_sample translate translate_file transpose treefale tree_reduce treillis treinat triangle triangularize trigexpand trigrat trigreduce trigsimp trunc truncate truncated_cube_graph truncated_dodecahedron_graph truncated_icosahedron_graph truncated_tetrahedron_graph tr_warnings_get tube tutte_graph ueivects uforget ultraspherical underlying_graph undiff union unique uniteigenvectors unitp units unit_step unitvector unorder unsum untellrat untimer untrace uppercasep uricci uriemann uvect vandermonde_matrix var var1 var_bernoulli var_beta var_binomial var_chi2 var_continuous_uniform var_discrete_uniform var_exp var_f var_gamma var_general_finite_discrete var_geometric var_gumbel var_hypergeometric var_laplace var_logistic var_lognormal var_negative_binomial var_noncentral_chi2 var_noncentral_student_t var_normal var_pareto var_poisson var_rayleigh var_student_t var_weibull vector vectorpotential vectorsimp verbify vers vertex_coloring vertex_connectivity vertex_degree vertex_distance vertex_eccentricity vertex_in_degree vertex_out_degree vertices vertices_to_cycle vertices_to_path %w weyl wheel_graph wiener_index wigner_3j wigner_6j wigner_9j with_stdout write_binary_data writebyte write_data writefile wronskian xreduce xthru %y Zeilberger zeroequiv zerofor zeromatrix zeromatrixp zeta zgeev zheev zlange zn_add_table zn_carmichael_lambda zn_characteristic_factors zn_determinant zn_factor_generators zn_invert_by_lu zn_log zn_mult_table absboxchar activecontexts adapt_depth additive adim aform algebraic algepsilon algexact aliases allbut all_dotsimp_denoms allocation allsym alphabetic animation antisymmetric arrays askexp assume_pos assume_pos_pred assumescalar asymbol atomgrad atrig1 axes axis_3d axis_bottom axis_left axis_right axis_top azimuth background background_color backsubst berlefact bernstein_explicit besselexpand beta_args_sum_to_integer beta_expand bftorat bftrunc bindtest border boundaries_array box boxchar breakup %c capping cauchysum cbrange cbtics center cflength cframe_flag cnonmet_flag color color_bar color_bar_tics colorbox columns commutative complex cone context contexts contour contour_levels cosnpiflag ctaypov ctaypt ctayswitch ctayvar ct_coords ctorsion_flag ctrgsimp cube current_let_rule_package cylinder data_file_name debugmode decreasing default_let_rule_package delay dependencies derivabbrev derivsubst detout diagmetric diff dim dimensions dispflag display2d|10 display_format_internal distribute_over doallmxops domain domxexpt domxmxops domxnctimes dontfactor doscmxops doscmxplus dot0nscsimp dot0simp dot1simp dotassoc dotconstrules dotdistrib dotexptsimp dotident dotscrules draw_graph_program draw_realpart edge_color edge_coloring edge_partition edge_type edge_width %edispflag elevation %emode endphi endtheta engineering_format_floats enhanced3d %enumer epsilon_lp erfflag erf_representation errormsg error_size error_syms error_type %e_to_numlog eval even evenfun evflag evfun ev_point expandwrt_denom expintexpand expintrep expon expop exptdispflag exptisolate exptsubst facexpand facsum_combine factlim factorflag factorial_expand factors_only fb feature features file_name file_output_append file_search_demo file_search_lisp file_search_maxima|10 file_search_tests file_search_usage file_type_lisp file_type_maxima|10 fill_color fill_density filled_func fixed_vertices flipflag float2bf font font_size fortindent fortspaces fpprec fpprintprec functions gamma_expand gammalim gdet genindex gensumnum GGFCFMAX GGFINFINITY globalsolve gnuplot_command gnuplot_curve_styles gnuplot_curve_titles gnuplot_default_term_command gnuplot_dumb_term_command gnuplot_file_args gnuplot_file_name gnuplot_out_file gnuplot_pdf_term_command gnuplot_pm3d gnuplot_png_term_command gnuplot_postamble gnuplot_preamble gnuplot_ps_term_command gnuplot_svg_term_command gnuplot_term gnuplot_view_args Gosper_in_Zeilberger gradefs grid grid2d grind halfangles head_angle head_both head_length head_type height hypergeometric_representation %iargs ibase icc1 icc2 icounter idummyx ieqnprint ifb ifc1 ifc2 ifg ifgi ifr iframe_bracket_form ifri igeowedge_flag ikt1 ikt2 imaginary inchar increasing infeval infinity inflag infolists inm inmc1 inmc2 intanalysis integer integervalued integrate_use_rootsof integration_constant integration_constant_counter interpolate_color intfaclim ip_grid ip_grid_in irrational isolate_wrt_times iterations itr julia_parameter %k1 %k2 keepfloat key key_pos kinvariant kt label label_alignment label_orientation labels lassociative lbfgs_ncorrections lbfgs_nfeval_max leftjust legend letrat let_rule_packages lfg lg lhospitallim limsubst linear linear_solver linechar linel|10 linenum line_type linewidth line_width linsolve_params linsolvewarn lispdisp listarith listconstvars listdummyvars lmxchar load_pathname loadprint logabs logarc logcb logconcoeffp logexpand lognegint logsimp logx logx_secondary logy logy_secondary logz lriem m1pbranch macroexpansion macros mainvar manual_demo maperror mapprint matrix_element_add matrix_element_mult matrix_element_transpose maxapplydepth maxapplyheight maxima_tempdir|10 maxima_userdir|10 maxnegex MAX_ORD maxposex maxpsifracdenom maxpsifracnum maxpsinegint maxpsiposint maxtayorder mesh_lines_color method mod_big_prime mode_check_errorp mode_checkp mode_check_warnp mod_test mod_threshold modular_linear_solver modulus multiplicative multiplicities myoptions nary negdistrib negsumdispflag newline newtonepsilon newtonmaxiter nextlayerfactor niceindicespref nm nmc noeval nolabels nonegative_lp noninteger nonscalar noun noundisp nouns np npi nticks ntrig numer numer_pbranch obase odd oddfun opacity opproperties opsubst optimprefix optionset orientation origin orthopoly_returns_intervals outative outchar packagefile palette partswitch pdf_file pfeformat phiresolution %piargs piece pivot_count_sx pivot_max_sx plot_format plot_options plot_realpart png_file pochhammer_max_index points pointsize point_size points_joined point_type poislim poisson poly_coefficient_ring poly_elimination_order polyfactor poly_grobner_algorithm poly_grobner_debug poly_monomial_order poly_primary_elimination_order poly_return_term_list poly_secondary_elimination_order poly_top_reduction_only posfun position powerdisp pred prederror primep_number_of_tests product_use_gamma program programmode promote_float_to_bigfloat prompt proportional_axes props psexpand ps_file radexpand radius radsubstflag rassociative ratalgdenom ratchristof ratdenomdivide rateinstein ratepsilon ratfac rational ratmx ratprint ratriemann ratsimpexpons ratvarswitch ratweights ratweyl ratwtlvl real realonly redraw refcheck resolution restart resultant ric riem rmxchar %rnum_list rombergabs rombergit rombergmin rombergtol rootsconmode rootsepsilon run_viewer same_xy same_xyz savedef savefactors scalar scalarmatrixp scale scale_lp setcheck setcheckbreak setval show_edge_color show_edges show_edge_type show_edge_width show_id show_label showtime show_vertex_color show_vertex_size show_vertex_type show_vertices show_weight simp simplified_output simplify_products simpproduct simpsum sinnpiflag solvedecomposes solveexplicit solvefactors solvenullwarn solveradcan solvetrigwarn space sparse sphere spring_embedding_depth sqrtdispflag stardisp startphi starttheta stats_numer stringdisp structures style sublis_apply_lambda subnumsimp sumexpand sumsplitfact surface surface_hide svg_file symmetric tab taylordepth taylor_logexpand taylor_order_coefficients taylor_truncate_polynomials tensorkill terminal testsuite_files thetaresolution timer_devalue title tlimswitch tr track transcompile transform transform_xy translate_fast_arrays transparent transrun tr_array_as_ref tr_bound_function_applyp tr_file_tty_messagesp tr_float_can_branch_complex tr_function_call_default trigexpandplus trigexpandtimes triginverses trigsign trivial_solutions tr_numer tr_optimize_max_loop tr_semicompile tr_state_vars tr_warn_bad_function_calls tr_warn_fexpr tr_warn_meval tr_warn_mode tr_warn_undeclared tr_warn_undefined_variable tstep ttyoff tube_extremes ufg ug %unitexpand unit_vectors uric uriem use_fast_arrays user_preamble usersetunits values vect_cross verbose vertex_color vertex_coloring vertex_partition vertex_size vertex_type view warnings weyl width windowname windowtitle wired_surface wireframe xaxis xaxis_color xaxis_secondary xaxis_type xaxis_width xlabel xlabel_secondary xlength xrange xrange_secondary xtics xtics_axis xtics_rotate xtics_rotate_secondary xtics_secondary xtics_secondary_axis xu_grid x_voxel xy_file xyplane xy_scale yaxis yaxis_color yaxis_secondary yaxis_type yaxis_width ylabel ylabel_secondary ylength yrange yrange_secondary ytics ytics_axis ytics_rotate ytics_rotate_secondary ytics_secondary ytics_secondary_axis yv_grid y_voxel yx_ratio zaxis zaxis_color zaxis_type zaxis_width zeroa zerob zerobern zeta%pi zlabel zlabel_rotate zlength zmin zn_primroot_limit zn_primroot_pretest",
+symbol:"_ __ %|0 %%|0"},contains:[{className:"comment",begin:"/\\*",end:"\\*/",
+contains:["self"]},e.QUOTE_STRING_MODE,{className:"number",relevance:0,
+variants:[{begin:"\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Ee][-+]?\\d+\\b"},{
+begin:"\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Bb][-+]?\\d+\\b",relevance:10},{
+begin:"\\b(\\.\\d+|\\d+\\.\\d+)\\b"},{begin:"\\b(\\d+|0[0-9A-Za-z]+)\\.?\\b"}]
+}],illegal:/@/})})());
+hljs.registerLanguage("mel",(()=>{"use strict";return e=>({name:"MEL",
+keywords:"int float string vector matrix if else switch case default while do for in break continue global proc return about abs addAttr addAttributeEditorNodeHelp addDynamic addNewShelfTab addPP addPanelCategory addPrefixToName advanceToNextDrivenKey affectedNet affects aimConstraint air alias aliasAttr align alignCtx alignCurve alignSurface allViewFit ambientLight angle angleBetween animCone animCurveEditor animDisplay animView annotate appendStringArray applicationName applyAttrPreset applyTake arcLenDimContext arcLengthDimension arclen arrayMapper art3dPaintCtx artAttrCtx artAttrPaintVertexCtx artAttrSkinPaintCtx artAttrTool artBuildPaintMenu artFluidAttrCtx artPuttyCtx artSelectCtx artSetPaintCtx artUserPaintCtx assignCommand assignInputDevice assignViewportFactories attachCurve attachDeviceAttr attachSurface attrColorSliderGrp attrCompatibility attrControlGrp attrEnumOptionMenu attrEnumOptionMenuGrp attrFieldGrp attrFieldSliderGrp attrNavigationControlGrp attrPresetEditWin attributeExists attributeInfo attributeMenu attributeQuery autoKeyframe autoPlace bakeClip bakeFluidShading bakePartialHistory bakeResults bakeSimulation basename basenameEx batchRender bessel bevel bevelPlus binMembership bindSkin blend2 blendShape blendShapeEditor blendShapePanel blendTwoAttr blindDataType boneLattice boundary boxDollyCtx boxZoomCtx bufferCurve buildBookmarkMenu buildKeyframeMenu button buttonManip CBG cacheFile cacheFileCombine cacheFileMerge cacheFileTrack camera cameraView canCreateManip canvas capitalizeString catch catchQuiet ceil changeSubdivComponentDisplayLevel changeSubdivRegion channelBox character characterMap characterOutlineEditor characterize chdir checkBox checkBoxGrp checkDefaultRenderGlobals choice circle circularFillet clamp clear clearCache clip clipEditor clipEditorCurrentTimeCtx clipSchedule clipSchedulerOutliner clipTrimBefore closeCurve closeSurface cluster cmdFileOutput cmdScrollFieldExecuter cmdScrollFieldReporter cmdShell coarsenSubdivSelectionList collision color colorAtPoint colorEditor colorIndex colorIndexSliderGrp colorSliderButtonGrp colorSliderGrp columnLayout commandEcho commandLine commandPort compactHairSystem componentEditor compositingInterop computePolysetVolume condition cone confirmDialog connectAttr connectControl connectDynamic connectJoint connectionInfo constrain constrainValue constructionHistory container containsMultibyte contextInfo control convertFromOldLayers convertIffToPsd convertLightmap convertSolidTx convertTessellation convertUnit copyArray copyFlexor copyKey copySkinWeights cos cpButton cpCache cpClothSet cpCollision cpConstraint cpConvClothToMesh cpForces cpGetSolverAttr cpPanel cpProperty cpRigidCollisionFilter cpSeam cpSetEdit cpSetSolverAttr cpSolver cpSolverTypes cpTool cpUpdateClothUVs createDisplayLayer createDrawCtx createEditor createLayeredPsdFile createMotionField createNewShelf createNode createRenderLayer createSubdivRegion cross crossProduct ctxAbort ctxCompletion ctxEditMode ctxTraverse currentCtx currentTime currentTimeCtx currentUnit curve curveAddPtCtx curveCVCtx curveEPCtx curveEditorCtx curveIntersect curveMoveEPCtx curveOnSurface curveSketchCtx cutKey cycleCheck cylinder dagPose date defaultLightListCheckBox defaultNavigation defineDataServer defineVirtualDevice deformer deg_to_rad delete deleteAttr deleteShadingGroupsAndMaterials deleteShelfTab deleteUI deleteUnusedBrushes delrandstr detachCurve detachDeviceAttr detachSurface deviceEditor devicePanel dgInfo dgdirty dgeval dgtimer dimWhen directKeyCtx directionalLight dirmap dirname disable disconnectAttr disconnectJoint diskCache displacementToPoly displayAffected displayColor displayCull displayLevelOfDetail displayPref displayRGBColor displaySmoothness displayStats displayString displaySurface distanceDimContext distanceDimension doBlur dolly dollyCtx dopeSheetEditor dot dotProduct doubleProfileBirailSurface drag dragAttrContext draggerContext dropoffLocator duplicate duplicateCurve duplicateSurface dynCache dynControl dynExport dynExpression dynGlobals dynPaintEditor dynParticleCtx dynPref dynRelEdPanel dynRelEditor dynamicLoad editAttrLimits editDisplayLayerGlobals editDisplayLayerMembers editRenderLayerAdjustment editRenderLayerGlobals editRenderLayerMembers editor editorTemplate effector emit emitter enableDevice encodeString endString endsWith env equivalent equivalentTol erf error eval evalDeferred evalEcho event exactWorldBoundingBox exclusiveLightCheckBox exec executeForEachObject exists exp expression expressionEditorListen extendCurve extendSurface extrude fcheck fclose feof fflush fgetline fgetword file fileBrowserDialog fileDialog fileExtension fileInfo filetest filletCurve filter filterCurve filterExpand filterStudioImport findAllIntersections findAnimCurves findKeyframe findMenuItem findRelatedSkinCluster finder firstParentOf fitBspline flexor floatEq floatField floatFieldGrp floatScrollBar floatSlider floatSlider2 floatSliderButtonGrp floatSliderGrp floor flow fluidCacheInfo fluidEmitter fluidVoxelInfo flushUndo fmod fontDialog fopen formLayout format fprint frameLayout fread freeFormFillet frewind fromNativePath fwrite gamma gauss geometryConstraint getApplicationVersionAsFloat getAttr getClassification getDefaultBrush getFileList getFluidAttr getInputDeviceRange getMayaPanelTypes getModifiers getPanel getParticleAttr getPluginResource getenv getpid glRender glRenderEditor globalStitch gmatch goal gotoBindPose grabColor gradientControl gradientControlNoAttr graphDollyCtx graphSelectContext graphTrackCtx gravity grid gridLayout group groupObjectsByName HfAddAttractorToAS HfAssignAS HfBuildEqualMap HfBuildFurFiles HfBuildFurImages HfCancelAFR HfConnectASToHF HfCreateAttractor HfDeleteAS HfEditAS HfPerformCreateAS HfRemoveAttractorFromAS HfSelectAttached HfSelectAttractors HfUnAssignAS hardenPointCurve hardware hardwareRenderPanel headsUpDisplay headsUpMessage help helpLine hermite hide hilite hitTest hotBox hotkey hotkeyCheck hsv_to_rgb hudButton hudSlider hudSliderButton hwReflectionMap hwRender hwRenderLoad hyperGraph hyperPanel hyperShade hypot iconTextButton iconTextCheckBox iconTextRadioButton iconTextRadioCollection iconTextScrollList iconTextStaticLabel ikHandle ikHandleCtx ikHandleDisplayScale ikSolver ikSplineHandleCtx ikSystem ikSystemInfo ikfkDisplayMethod illustratorCurves image imfPlugins inheritTransform insertJoint insertJointCtx insertKeyCtx insertKnotCurve insertKnotSurface instance instanceable instancer intField intFieldGrp intScrollBar intSlider intSliderGrp interToUI internalVar intersect iprEngine isAnimCurve isConnected isDirty isParentOf isSameObject isTrue isValidObjectName isValidString isValidUiName isolateSelect itemFilter itemFilterAttr itemFilterRender itemFilterType joint jointCluster jointCtx jointDisplayScale jointLattice keyTangent keyframe keyframeOutliner keyframeRegionCurrentTimeCtx keyframeRegionDirectKeyCtx keyframeRegionDollyCtx keyframeRegionInsertKeyCtx keyframeRegionMoveKeyCtx keyframeRegionScaleKeyCtx keyframeRegionSelectKeyCtx keyframeRegionSetKeyCtx keyframeRegionTrackCtx keyframeStats lassoContext lattice latticeDeformKeyCtx launch launchImageEditor layerButton layeredShaderPort layeredTexturePort layout layoutDialog lightList lightListEditor lightListPanel lightlink lineIntersection linearPrecision linstep listAnimatable listAttr listCameras listConnections listDeviceAttachments listHistory listInputDeviceAxes listInputDeviceButtons listInputDevices listMenuAnnotation listNodeTypes listPanelCategories listRelatives listSets listTransforms listUnselected listerEditor loadFluid loadNewShelf loadPlugin loadPluginLanguageResources loadPrefObjects localizedPanelLabel lockNode loft log longNameOf lookThru ls lsThroughFilter lsType lsUI Mayatomr mag makeIdentity makeLive makePaintable makeRoll makeSingleSurface makeTubeOn makebot manipMoveContext manipMoveLimitsCtx manipOptions manipRotateContext manipRotateLimitsCtx manipScaleContext manipScaleLimitsCtx marker match max memory menu menuBarLayout menuEditor menuItem menuItemToShelf menuSet menuSetPref messageLine min minimizeApp mirrorJoint modelCurrentTimeCtx modelEditor modelPanel mouse movIn movOut move moveIKtoFK moveKeyCtx moveVertexAlongDirection multiProfileBirailSurface mute nParticle nameCommand nameField namespace namespaceInfo newPanelItems newton nodeCast nodeIconButton nodeOutliner nodePreset nodeType noise nonLinear normalConstraint normalize nurbsBoolean nurbsCopyUVSet nurbsCube nurbsEditUV nurbsPlane nurbsSelect nurbsSquare nurbsToPoly nurbsToPolygonsPref nurbsToSubdiv nurbsToSubdivPref nurbsUVSet nurbsViewDirectionVector objExists objectCenter objectLayer objectType objectTypeUI obsoleteProc oceanNurbsPreviewPlane offsetCurve offsetCurveOnSurface offsetSurface openGLExtension openMayaPref optionMenu optionMenuGrp optionVar orbit orbitCtx orientConstraint outlinerEditor outlinerPanel overrideModifier paintEffectsDisplay pairBlend palettePort paneLayout panel panelConfiguration panelHistory paramDimContext paramDimension paramLocator parent parentConstraint particle particleExists particleInstancer particleRenderInfo partition pasteKey pathAnimation pause pclose percent performanceOptions pfxstrokes pickWalk picture pixelMove planarSrf plane play playbackOptions playblast plugAttr plugNode pluginInfo pluginResourceUtil pointConstraint pointCurveConstraint pointLight pointMatrixMult pointOnCurve pointOnSurface pointPosition poleVectorConstraint polyAppend polyAppendFacetCtx polyAppendVertex polyAutoProjection polyAverageNormal polyAverageVertex polyBevel polyBlendColor polyBlindData polyBoolOp polyBridgeEdge polyCacheMonitor polyCheck polyChipOff polyClipboard polyCloseBorder polyCollapseEdge polyCollapseFacet polyColorBlindData polyColorDel polyColorPerVertex polyColorSet polyCompare polyCone polyCopyUV polyCrease polyCreaseCtx polyCreateFacet polyCreateFacetCtx polyCube polyCut polyCutCtx polyCylinder polyCylindricalProjection polyDelEdge polyDelFacet polyDelVertex polyDuplicateAndConnect polyDuplicateEdge polyEditUV polyEditUVShell polyEvaluate polyExtrudeEdge polyExtrudeFacet polyExtrudeVertex polyFlipEdge polyFlipUV polyForceUV polyGeoSampler polyHelix polyInfo polyInstallAction polyLayoutUV polyListComponentConversion polyMapCut polyMapDel polyMapSew polyMapSewMove polyMergeEdge polyMergeEdgeCtx polyMergeFacet polyMergeFacetCtx polyMergeUV polyMergeVertex polyMirrorFace polyMoveEdge polyMoveFacet polyMoveFacetUV polyMoveUV polyMoveVertex polyNormal polyNormalPerVertex polyNormalizeUV polyOptUvs polyOptions polyOutput polyPipe polyPlanarProjection polyPlane polyPlatonicSolid polyPoke polyPrimitive polyPrism polyProjection polyPyramid polyQuad polyQueryBlindData polyReduce polySelect polySelectConstraint polySelectConstraintMonitor polySelectCtx polySelectEditCtx polySeparate polySetToFaceNormal polySewEdge polyShortestPathCtx polySmooth polySoftEdge polySphere polySphericalProjection polySplit polySplitCtx polySplitEdge polySplitRing polySplitVertex polyStraightenUVBorder polySubdivideEdge polySubdivideFacet polyToSubdiv polyTorus polyTransfer polyTriangulate polyUVSet polyUnite polyWedgeFace popen popupMenu pose pow preloadRefEd print progressBar progressWindow projFileViewer projectCurve projectTangent projectionContext projectionManip promptDialog propModCtx propMove psdChannelOutliner psdEditTextureFile psdExport psdTextureFile putenv pwd python querySubdiv quit rad_to_deg radial radioButton radioButtonGrp radioCollection radioMenuItemCollection rampColorPort rand randomizeFollicles randstate rangeControl readTake rebuildCurve rebuildSurface recordAttr recordDevice redo reference referenceEdit referenceQuery refineSubdivSelectionList refresh refreshAE registerPluginResource rehash reloadImage removeJoint removeMultiInstance removePanelCategory rename renameAttr renameSelectionList renameUI render renderGlobalsNode renderInfo renderLayerButton renderLayerParent renderLayerPostProcess renderLayerUnparent renderManip renderPartition renderQualityNode renderSettings renderThumbnailUpdate renderWindowEditor renderWindowSelectContext renderer reorder reorderDeformers requires reroot resampleFluid resetAE resetPfxToPolyCamera resetTool resolutionNode retarget reverseCurve reverseSurface revolve rgb_to_hsv rigidBody rigidSolver roll rollCtx rootOf rot rotate rotationInterpolation roundConstantRadius rowColumnLayout rowLayout runTimeCommand runup sampleImage saveAllShelves saveAttrPreset saveFluid saveImage saveInitialState saveMenu savePrefObjects savePrefs saveShelf saveToolSettings scale scaleBrushBrightness scaleComponents scaleConstraint scaleKey scaleKeyCtx sceneEditor sceneUIReplacement scmh scriptCtx scriptEditorInfo scriptJob scriptNode scriptTable scriptToShelf scriptedPanel scriptedPanelType scrollField scrollLayout sculpt searchPathArray seed selLoadSettings select selectContext selectCurveCV selectKey selectKeyCtx selectKeyframeRegionCtx selectMode selectPref selectPriority selectType selectedNodes selectionConnection separator setAttr setAttrEnumResource setAttrMapping setAttrNiceNameResource setConstraintRestPosition setDefaultShadingGroup setDrivenKeyframe setDynamic setEditCtx setEditor setFluidAttr setFocus setInfinity setInputDeviceMapping setKeyCtx setKeyPath setKeyframe setKeyframeBlendshapeTargetWts setMenuMode setNodeNiceNameResource setNodeTypeFlag setParent setParticleAttr setPfxToPolyCamera setPluginResource setProject setStampDensity setStartupMessage setState setToolTo setUITemplate setXformManip sets shadingConnection shadingGeometryRelCtx shadingLightRelCtx shadingNetworkCompare shadingNode shapeCompare shelfButton shelfLayout shelfTabLayout shellField shortNameOf showHelp showHidden showManipCtx showSelectionInTitle showShadingGroupAttrEditor showWindow sign simplify sin singleProfileBirailSurface size sizeBytes skinCluster skinPercent smoothCurve smoothTangentSurface smoothstep snap2to2 snapKey snapMode snapTogetherCtx snapshot soft softMod softModCtx sort sound soundControl source spaceLocator sphere sphrand spotLight spotLightPreviewPort spreadSheetEditor spring sqrt squareSurface srtContext stackTrace startString startsWith stitchAndExplodeShell stitchSurface stitchSurfacePoints strcmp stringArrayCatenate stringArrayContains stringArrayCount stringArrayInsertAtIndex stringArrayIntersector stringArrayRemove stringArrayRemoveAtIndex stringArrayRemoveDuplicates stringArrayRemoveExact stringArrayToString stringToStringArray strip stripPrefixFromName stroke subdAutoProjection subdCleanTopology subdCollapse subdDuplicateAndConnect subdEditUV subdListComponentConversion subdMapCut subdMapSewMove subdMatchTopology subdMirror subdToBlind subdToPoly subdTransferUVsToCache subdiv subdivCrease subdivDisplaySmoothness substitute substituteAllString substituteGeometry substring surface surfaceSampler surfaceShaderList swatchDisplayPort switchTable symbolButton symbolCheckBox sysFile system tabLayout tan tangentConstraint texLatticeDeformContext texManipContext texMoveContext texMoveUVShellContext texRotateContext texScaleContext texSelectContext texSelectShortestPathCtx texSmudgeUVContext texWinToolCtx text textCurves textField textFieldButtonGrp textFieldGrp textManip textScrollList textToShelf textureDisplacePlane textureHairColor texturePlacementContext textureWindow threadCount threePointArcCtx timeControl timePort timerX toNativePath toggle toggleAxis toggleWindowVisibility tokenize tokenizeList tolerance tolower toolButton toolCollection toolDropped toolHasOptions toolPropertyWindow torus toupper trace track trackCtx transferAttributes transformCompare transformLimits translator trim trunc truncateFluidCache truncateHairCache tumble tumbleCtx turbulence twoPointArcCtx uiRes uiTemplate unassignInputDevice undo undoInfo ungroup uniform unit unloadPlugin untangleUV untitledFileName untrim upAxis updateAE userCtx uvLink uvSnapshot validateShelfName vectorize view2dToolCtx viewCamera viewClipPlane viewFit viewHeadOn viewLookAt viewManip viewPlace viewSet visor volumeAxis vortex waitCursor warning webBrowser webBrowserPrefs whatIs window windowPref wire wireContext workspace wrinkle wrinkleContext writeTake xbmLangPathList xform",
+illegal:"</",contains:[e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{
+className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{
+begin:/[$%@](\^\w\b|#\w+|[^\s\w{]|\{\w+\}|\w+)/
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]})})());
+hljs.registerLanguage("mercury",(()=>{"use strict";return e=>{
+const i=e.COMMENT("%","$"),n=e.inherit(e.APOS_STRING_MODE,{relevance:0
+}),r=e.inherit(e.QUOTE_STRING_MODE,{relevance:0})
+;return r.contains=r.contains.slice(),r.contains.push({className:"subst",
+begin:"\\\\[abfnrtv]\\|\\\\x[0-9a-fA-F]*\\\\\\|%[-+# *.0-9]*[dioxXucsfeEgGp]",
+relevance:0}),{name:"Mercury",aliases:["m","moo"],keywords:{
+keyword:"module use_module import_module include_module end_module initialise mutable initialize finalize finalise interface implementation pred mode func type inst solver any_pred any_func is semidet det nondet multi erroneous failure cc_nondet cc_multi typeclass instance where pragma promise external trace atomic or_else require_complete_switch require_det require_semidet require_multi require_nondet require_cc_multi require_cc_nondet require_erroneous require_failure",
meta:"inline no_inline type_spec source_file fact_table obsolete memo loop_check minimal_model terminates does_not_terminate check_termination promise_equivalent_clauses foreign_proc foreign_decl foreign_code foreign_type foreign_import_module foreign_export_enum foreign_export foreign_enum may_call_mercury will_not_call_mercury thread_safe not_thread_safe maybe_thread_safe promise_pure promise_semipure tabled_for_io local untrailed trailed attach_to_io_state can_pass_as_mercury_type stable will_not_throw_exception may_modify_trail will_not_modify_trail may_duplicate may_not_duplicate affects_liveness does_not_affect_liveness doesnt_affect_liveness no_sharing unknown_sharing sharing",
-built_in:"some all not if then else true fail false try catch catch_any semidet_true semidet_false semidet_fail impure_true impure semipure"},contains:[{className:"built_in",variants:[{begin:"<=>"},{begin:"<=",relevance:0},{begin:"=>",relevance:0},{begin:"/\\\\"},{begin:"\\\\/"}]},{className:"built_in",variants:[{begin:":-\\|--\x3e"},{begin:"=",relevance:0}]},b,a.C_BLOCK_COMMENT_MODE,{className:"number",begin:"0'.\\|0[box][0-9a-fA-F]*"},a.NUMBER_MODE,c,d,{begin:/:-/},{begin:/\.$/}]}}}());
-hljs.registerLanguage("mipsasm",function(){return function(a){return{name:"MIPS Assembly",case_insensitive:!0,aliases:["mips"],keywords:{$pattern:"\\.?"+a.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .ltorg ",built_in:"$0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $30 $31 zero at v0 v1 a0 a1 a2 a3 a4 a5 a6 a7 t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 s0 s1 s2 s3 s4 s5 s6 s7 s8 k0 k1 gp sp fp ra $f0 $f1 $f2 $f2 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 Context Random EntryLo0 EntryLo1 Context PageMask Wired EntryHi HWREna BadVAddr Count Compare SR IntCtl SRSCtl SRSMap Cause EPC PRId EBase Config Config1 Config2 Config3 LLAddr Debug DEPC DESAVE CacheErr ECC ErrorEPC TagLo DataLo TagHi DataHi WatchLo WatchHi PerfCtl PerfCnt "},
-contains:[{className:"keyword",begin:"\\b(addi?u?|andi?|b(al)?|beql?|bgez(al)?l?|bgtzl?|blezl?|bltz(al)?l?|bnel?|cl[oz]|divu?|ext|ins|j(al)?|jalr(.hb)?|jr(.hb)?|lbu?|lhu?|ll|lui|lw[lr]?|maddu?|mfhi|mflo|movn|movz|move|msubu?|mthi|mtlo|mul|multu?|nop|nor|ori?|rotrv?|sb|sc|se[bh]|sh|sllv?|slti?u?|srav?|srlv?|subu?|sw[lr]?|xori?|wsbh|abs.[sd]|add.[sd]|alnv.ps|bc1[ft]l?|c.(s?f|un|u?eq|[ou]lt|[ou]le|ngle?|seq|l[et]|ng[et]).[sd]|(ceil|floor|round|trunc).[lw].[sd]|cfc1|cvt.d.[lsw]|cvt.l.[dsw]|cvt.ps.s|cvt.s.[dlw]|cvt.s.p[lu]|cvt.w.[dls]|div.[ds]|ldx?c1|luxc1|lwx?c1|madd.[sd]|mfc1|mov[fntz]?.[ds]|msub.[sd]|mth?c1|mul.[ds]|neg.[ds]|nmadd.[ds]|nmsub.[ds]|p[lu][lu].ps|recip.fmt|r?sqrt.[ds]|sdx?c1|sub.[ds]|suxc1|swx?c1|break|cache|d?eret|[de]i|ehb|mfc0|mtc0|pause|prefx?|rdhwr|rdpgpr|sdbbp|ssnop|synci?|syscall|teqi?|tgei?u?|tlb(p|r|w[ir])|tlti?u?|tnei?|wait|wrpgpr)",
-end:"\\s"},a.COMMENT("[;#](?!s*$)","$"),a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"0x[0-9a-f]+"},{begin:"\\b-?\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^\\s*[0-9]+:"},{begin:"[0-9]+[bf]"}],relevance:0}],illegal:"/"}}}());
-hljs.registerLanguage("mizar",function(){return function(a){return{name:"Mizar",keywords:"environ vocabularies notations constructors definitions registrations theorems schemes requirements begin end definition registration cluster existence pred func defpred deffunc theorem proof let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from be being by means equals implies iff redefine define now not or attr is mode suppose per cases set thesis contradiction scheme reserve struct correctness compatibility coherence symmetry assymetry reflexivity irreflexivity connectedness uniqueness commutativity idempotence involutiveness projectivity",
-contains:[a.COMMENT("::","$")]}}}());
-hljs.registerLanguage("perl",function(){return function(a){var b={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},c=
-{className:"subst",begin:"[$@]\\{",end:"\\}",keywords:b},d={begin:"->{",end:"}"},e={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},f=[a.BACKSLASH_ESCAPE,c,e];a=[e,a.HASH_COMMENT_MODE,a.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),d,{className:"string",contains:f,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",
-end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+a.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",
-keywords:"split return print reverse grep",relevance:0,contains:[a.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[a.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[a.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",
-end:"$",className:"comment"}]}];c.contains=a;d.contains=a;return{name:"Perl",aliases:["pl","pm"],keywords:b,contains:a}}}());hljs.registerLanguage("mojolicious",function(){return function(a){return{name:"Mojolicious",subLanguage:"xml",contains:[{className:"meta",begin:"^__(END|DATA)__$"},{begin:"^\\s*%{1,2}={0,2}",end:"$",subLanguage:"perl"},{begin:"<%{1,2}={0,2}",end:"={0,1}%>",subLanguage:"perl",excludeBegin:!0,excludeEnd:!0}]}}}());
-hljs.registerLanguage("monkey",function(){return function(a){var b={className:"number",relevance:0,variants:[{begin:"[$][a-fA-F0-9]+"},a.NUMBER_MODE]};return{name:"Monkey",case_insensitive:!0,keywords:{keyword:"public private property continue exit extern new try catch eachin not abstract final select case default const local global field end if then else elseif endif while wend repeat until forever for to step next return module inline throw import",built_in:"DebugLog DebugStop Error Print ACos ACosr ASin ASinr ATan ATan2 ATan2r ATanr Abs Abs Ceil Clamp Clamp Cos Cosr Exp Floor Log Max Max Min Min Pow Sgn Sgn Sin Sinr Sqrt Tan Tanr Seed PI HALFPI TWOPI",
-literal:"true false null and or shl shr mod"},illegal:/\/\*/,contains:[a.COMMENT("#rem","#end"),a.COMMENT("'","$",{relevance:0}),{className:"function",beginKeywords:"function method",end:"[(=:]|$",illegal:/\n/,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"class",beginKeywords:"class interface",end:"$",contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{className:"built_in",begin:"\\b(self|super)\\b"},{className:"meta",begin:"\\s*#",end:"$",keywords:{"meta-keyword":"if else elseif endif end then"}},
-{className:"meta",begin:"^\\s*strict\\b"},{beginKeywords:"alias",end:"=",contains:[a.UNDERSCORE_TITLE_MODE]},a.QUOTE_STRING_MODE,b]}}}());
-hljs.registerLanguage("moonscript",function(){return function(a){var b={keyword:"if then not for in while do return else elseif break continue switch and or unless when class extends super local import export from using",literal:"true false nil",built_in:"_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug io math os package string table"},c=
-{className:"subst",begin:/#\{/,end:/}/,keywords:b},d=[a.inherit(a.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'/,end:/'/,contains:[a.BACKSLASH_ESCAPE]},{begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,c]}]},{className:"built_in",begin:"@__"+a.IDENT_RE},{begin:"@"+a.IDENT_RE},{begin:a.IDENT_RE+"\\\\"+a.IDENT_RE}];c.contains=d;c=a.inherit(a.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"});var e={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,
-end:/\)/,keywords:b,contains:["self"].concat(d)}]};return{name:"MoonScript",aliases:["moon"],keywords:b,illegal:/\/\*/,contains:d.concat([a.COMMENT("--","$"),{className:"function",begin:"^\\s*[A-Za-z$_][0-9A-Za-z$_]*\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,e]},{begin:/[\(,:=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[e]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,
-contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{className:"name",begin:"[A-Za-z$_][0-9A-Za-z$_]*:",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());
-hljs.registerLanguage("n1ql",function(){return function(a){return{name:"N1QL",case_insensitive:!0,contains:[{beginKeywords:"build create index delete drop explain infer|10 insert merge prepare select update upsert|10",end:/;/,endsWithParent:!0,keywords:{keyword:"all alter analyze and any array as asc begin between binary boolean break bucket build by call case cast cluster collate collection commit connect continue correlate cover create database dataset datastore declare decrement delete derived desc describe distinct do drop each element else end every except exclude execute exists explain fetch first flatten for force from function grant group gsi having if ignore ilike in include increment index infer inline inner insert intersect into is join key keys keyspace known last left let letting like limit lsm map mapping matched materialized merge minus namespace nest not number object offset on option or order outer over parse partition password path pool prepare primary private privilege procedure public raw realm reduce rename return returning revoke right role rollback satisfies schema select self semi set show some start statistics string system then to transaction trigger truncate under union unique unknown unnest unset update upsert use user using validate value valued values via view when where while with within work xor",literal:"true false null missing|5",
-built_in:"array_agg array_append array_concat array_contains array_count array_distinct array_ifnull array_length array_max array_min array_position array_prepend array_put array_range array_remove array_repeat array_replace array_reverse array_sort array_sum avg count max min sum greatest least ifmissing ifmissingornull ifnull missingif nullif ifinf ifnan ifnanorinf naninf neginfif posinfif clock_millis clock_str date_add_millis date_add_str date_diff_millis date_diff_str date_part_millis date_part_str date_trunc_millis date_trunc_str duration_to_str millis str_to_millis millis_to_str millis_to_utc millis_to_zone_name now_millis now_str str_to_duration str_to_utc str_to_zone_name decode_json encode_json encoded_size poly_length base64 base64_encode base64_decode meta uuid abs acos asin atan atan2 ceil cos degrees e exp ln log floor pi power radians random round sign sin sqrt tan trunc object_length object_names object_pairs object_inner_pairs object_values object_inner_values object_add object_put object_remove object_unwrap regexp_contains regexp_like regexp_position regexp_replace contains initcap length lower ltrim position repeat replace rtrim split substr title trim upper isarray isatom isboolean isnumber isobject isstring type toarray toatom toboolean tonumber toobject tostring"},
-contains:[{className:"string",begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE],relevance:0},{className:"string",begin:'"',end:'"',contains:[a.BACKSLASH_ESCAPE],relevance:0},{className:"symbol",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE],relevance:2},a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE]},a.C_BLOCK_COMMENT_MODE]}}}());
-hljs.registerLanguage("nginx",function(){return function(a){var b={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+a.UNDERSCORE_IDENT_RE}]};return{name:"Nginx config",aliases:["nginxconf"],contains:[a.HASH_COMMENT_MODE,{begin:a.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:a.UNDERSCORE_IDENT_RE}],relevance:0},{begin:a.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:a.UNDERSCORE_IDENT_RE,
-starts:{endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[a.HASH_COMMENT_MODE,{className:"string",contains:[a.BACKSLASH_ESCAPE,b],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[b]},{className:"regexp",contains:[a.BACKSLASH_ESCAPE,b],variants:[{begin:"\\s\\^",
-end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},b]}}],relevance:0}],illegal:"[^\\s\\}]"}}}());
-hljs.registerLanguage("nim",function(){return function(a){return{name:"Nim",aliases:["nim"],keywords:{keyword:"addr and as asm bind block break case cast const continue converter discard distinct div do elif else end enum except export finally for from func generic if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while with without xor yield",literal:"shared guarded stdin stdout stderr result true false",
-built_in:"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 bool char string cstring pointer expr stmt void auto any range array openarray varargs seq set clong culong cchar cschar cshort cint csize clonglong cfloat cdouble clongdouble cuchar cushort cuint culonglong cstringarray semistatic"},contains:[{className:"meta",begin:/{\./,end:/\.}/,relevance:10},{className:"string",begin:/[a-zA-Z]\w*"/,end:/"/,contains:[{begin:/""/}]},{className:"string",begin:/([a-zA-Z]\w*)?"""/,
-end:/"""/},a.QUOTE_STRING_MODE,{className:"type",begin:/\b[A-Z]\w+\b/,relevance:0},{className:"number",relevance:0,variants:[{begin:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/},{begin:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/}]},a.HASH_COMMENT_MODE]}}}());
-hljs.registerLanguage("nix",function(){return function(a){var b={keyword:"rec with let in inherit assert if else then",literal:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},c={className:"subst",begin:/\$\{/,end:/}/,keywords:b};a=[a.NUMBER_MODE,a.HASH_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",contains:[c],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]},{begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,
-contains:[{className:"attr",begin:/\S+/}]}];c.contains=a;return{name:"Nix",aliases:["nixos"],keywords:b,contains:a}}}());
-hljs.registerLanguage("nsis",function(){return function(a){var b={className:"variable",begin:/\$+{[\w\.:-]+}/},c={className:"variable",begin:/\$+\w+/,illegal:/\(\){}/},d={className:"variable",begin:/\$+\([\w\^\.:-]+\)/},e={className:"string",variants:[{begin:'"',end:'"'},{begin:"'",end:"'"},{begin:"`",end:"`"}],illegal:/\n/,contains:[{className:"meta",begin:/\$(\\[nrt]|\$)/},{className:"variable",begin:/\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)/},
-b,c,d]};return{name:"NSIS",case_insensitive:!1,keywords:{keyword:"Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecShellWait ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileSeek FileWrite FileWriteByte FileWriteUTF16LE FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText Int64Cmp Int64CmpU Int64Fmt IntCmp IntCmpU IntFmt IntOp IntPtrCmp IntPtrCmpU IntPtrOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionGroupEnd SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegMultiStr WriteRegNone WriteRegStr WriteUninstaller XPStyle",
-literal:"admin all auto both bottom bzip2 colored components current custom directory false force hide highest ifdiff ifnewer instfiles lastused leave left license listonly lzma nevershow none normal notset off on open print right show silent silentlog smooth textonly top true try un.components un.custom un.directory un.instfiles un.license uninstConfirm user Win10 Win7 Win8 WinVista zlib"},contains:[a.HASH_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.COMMENT(";","$",{relevance:0}),{className:"function",
-beginKeywords:"Function PageEx Section SectionGroup",end:"$"},e,{className:"keyword",begin:/!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversion|gettlbversion|if|ifdef|ifmacrodef|ifmacrondef|ifndef|include|insertmacro|macro|macroend|makensis|packhdr|searchparse|searchreplace|system|tempfile|undef|verbose|warning)/},b,c,d,{className:"params",begin:"(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)"},
-{className:"class",begin:/\w+::\w+/},a.NUMBER_MODE]}}}());
-hljs.registerLanguage("objectivec",function(){return function(a){var b=/[a-zA-Z@][a-zA-Z0-9_]*/,c={$pattern:b,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:b,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",
-built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.C_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},
-contains:[{begin:/\\\n/,relevance:0},a.inherit(a.QUOTE_STRING_MODE,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+c.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:c,contains:[a.UNDERSCORE_TITLE_MODE]},{begin:"\\."+a.UNDERSCORE_IDENT_RE,relevance:0}]}}}());
-hljs.registerLanguage("ocaml",function(){return function(a){return{name:"OCaml",aliases:["ml"],keywords:{$pattern:"[a-z_]\\w*!?",keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",
-literal:"true false"},illegal:/\/\/|>>/,contains:[{className:"literal",begin:"\\[(\\|\\|)?\\]|\\(\\)",relevance:0},a.COMMENT("\\(\\*","\\*\\)",{contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{className:"type",begin:"`[A-Z][\\w']*"},{className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*",relevance:0},a.inherit(a.APOS_STRING_MODE,{className:"string",relevance:0}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),{className:"number",begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",
-relevance:0},{begin:/[-=]>/}]}}}());
-hljs.registerLanguage("openscad",function(){return function(a){var b={className:"keyword",begin:"\\$(f[asn]|t|vp[rtd]|children)"},c={className:"number",begin:"\\b\\d+(\\.\\d+)?(e-?\\d+)?",relevance:0},d=a.inherit(a.QUOTE_STRING_MODE,{illegal:null});return{name:"OpenSCAD",aliases:["scad"],keywords:{keyword:"function module include use for intersection_for if else \\%",literal:"false true PI undef",built_in:"circle square polygon text sphere cube cylinder polyhedron translate rotate scale resize mirror multmatrix color offset hull minkowski union difference intersection abs sign sin cos tan acos asin atan atan2 floor round ceil ln log pow sqrt exp rands min max concat lookup str chr search version version_num norm cross parent_module echo import import_dxf dxf_linear_extrude linear_extrude rotate_extrude surface projection render children dxf_cross dxf_dim let assign"},contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,c,{className:"meta",keywords:{"meta-keyword":"include use"},begin:"include|use <",end:">"},d,b,{begin:"[*!#%]",relevance:0},{className:"function",beginKeywords:"module function",end:"\\=|\\{",contains:[{className:"params",begin:"\\(",end:"\\)",contains:["self",c,d,b,{className:"literal",begin:"false|true|PI|undef"}]},a.UNDERSCORE_TITLE_MODE]}]}}}());
-hljs.registerLanguage("oxygene",function(){return function(a){var b={$pattern:/\.?\w+/,keyword:"abstract add and array as asc aspect assembly async begin break block by case class concat const copy constructor continue create default delegate desc distinct div do downto dynamic each else empty end ensure enum equals event except exit extension external false final finalize finalizer finally flags for forward from function future global group has if implementation implements implies in index inherited inline interface into invariants is iterator join locked locking loop matching method mod module namespace nested new nil not notify nullable of old on operator or order out override parallel params partial pinned private procedure property protected public queryable raise read readonly record reintroduce remove repeat require result reverse sealed select self sequence set shl shr skip static step soft take then to true try tuple type union unit unsafe until uses using var virtual raises volatile where while with write xor yield await mapped deprecated stdcall cdecl pascal register safecall overload library platform reference packed strict published autoreleasepool selector strong weak unretained"},c=
-a.COMMENT("{","}",{relevance:0}),d=a.COMMENT("\\(\\*","\\*\\)",{relevance:10}),e={className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},f={className:"string",begin:"(#\\d+)+"},h={className:"function",beginKeywords:"function constructor destructor procedure method",end:"[:;]",keywords:"function constructor|10 destructor|10 procedure|10 method|10",contains:[a.TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",keywords:b,contains:[e,f]},c,d]};return{name:"Oxygene",case_insensitive:!0,keywords:b,
-illegal:'("|\\$[G-Zg-z]|\\/\\*|</|=>|->)',contains:[c,d,a.C_LINE_COMMENT_MODE,e,f,a.NUMBER_MODE,h,{className:"class",begin:"=\\bclass\\b",end:"end;",keywords:b,contains:[e,f,c,d,a.C_LINE_COMMENT_MODE,h]}]}}}());
-hljs.registerLanguage("parser3",function(){return function(a){var b=a.COMMENT("{","}",{contains:["self"]});return{name:"Parser3",subLanguage:"xml",relevance:0,contains:[a.COMMENT("^#","$"),a.COMMENT("\\^rem{","}",{relevance:10,contains:[b]}),{className:"meta",begin:"^@(?:BASE|USE|CLASS|OPTIONS)$",relevance:10},{className:"title",begin:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{className:"variable",begin:"\\$\\{?[\\w\\-\\.\\:]+\\}?"},{className:"keyword",begin:"\\^[\\w\\-\\.\\:]+"},
-{className:"number",begin:"\\^#[0-9a-fA-F]+"},a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("pf",function(){return function(a){return{name:"Packet Filter config",aliases:["pf.conf"],keywords:{$pattern:/[a-z0-9_<>-]+/,built_in:"block match pass load anchor|5 antispoof|10 set table",keyword:"in out log quick on rdomain inet inet6 proto from port os to route allow-opts divert-packet divert-reply divert-to flags group icmp-type icmp6-type label once probability recieved-on rtable prio queue tos tag tagged user keep fragment for os drop af-to|10 binat-to|10 nat-to|10 rdr-to|10 bitmask least-stats random round-robin source-hash static-port dup-to reply-to route-to parent bandwidth default min max qlimit block-policy debug fingerprints hostid limit loginterface optimization reassemble ruleset-optimization basic none profile skip state-defaults state-policy timeout const counters persist no modulate synproxy state|5 floating if-bound no-sync pflow|10 sloppy source-track global rule max-src-nodes max-src-states max-src-conn max-src-conn-rate overload flush scrub|5 max-mss min-ttl no-df|10 random-id",literal:"all any no-route self urpf-failed egress|5 unknown"},
-contains:[a.HASH_COMMENT_MODE,a.NUMBER_MODE,a.QUOTE_STRING_MODE,{className:"variable",begin:/\$[\w\d#@][\w\d_]*/},{className:"variable",begin:/<(?!\/)/,end:/>/}]}}}());
-hljs.registerLanguage("pgsql",function(){return function(a){var b=a.COMMENT("--","$"),c="BIGINT INT8 BIGSERIAL SERIAL8 BIT VARYING VARBIT BOOLEAN BOOL BOX BYTEA CHARACTER CHAR VARCHAR CIDR CIRCLE DATE DOUBLE PRECISION FLOAT8 FLOAT INET INTEGER INT INT4 INTERVAL JSON JSONB LINE LSEG|10 MACADDR MACADDR8 MONEY NUMERIC DEC DECIMAL PATH POINT POLYGON REAL FLOAT4 SMALLINT INT2 SMALLSERIAL|10 SERIAL2|10 SERIAL|10 SERIAL4|10 TEXT TIME ZONE TIMETZ|10 TIMESTAMP TIMESTAMPTZ|10 TSQUERY|10 TSVECTOR|10 TXID_SNAPSHOT|10 UUID XML NATIONAL NCHAR INT4RANGE|10 INT8RANGE|10 NUMRANGE|10 TSRANGE|10 TSTZRANGE|10 DATERANGE|10 ANYELEMENT ANYARRAY ANYNONARRAY ANYENUM ANYRANGE CSTRING INTERNAL RECORD PG_DDL_COMMAND VOID UNKNOWN OPAQUE REFCURSOR NAME OID REGPROC|10 REGPROCEDURE|10 REGOPER|10 REGOPERATOR|10 REGCLASS|10 REGTYPE|10 REGROLE|10 REGNAMESPACE|10 REGCONFIG|10 REGDICTIONARY|10".split(" ").map(function(a){return a.split("|")[0]}).join("|"),d=
-"ARRAY_AGG AVG BIT_AND BIT_OR BOOL_AND BOOL_OR COUNT EVERY JSON_AGG JSONB_AGG JSON_OBJECT_AGG JSONB_OBJECT_AGG MAX MIN MODE STRING_AGG SUM XMLAGG CORR COVAR_POP COVAR_SAMP REGR_AVGX REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY REGR_SYY STDDEV STDDEV_POP STDDEV_SAMP VARIANCE VAR_POP VAR_SAMP PERCENTILE_CONT PERCENTILE_DISC ROW_NUMBER RANK DENSE_RANK PERCENT_RANK CUME_DIST NTILE LAG LEAD FIRST_VALUE LAST_VALUE NTH_VALUE NUM_NONNULLS NUM_NULLS ABS CBRT CEIL CEILING DEGREES DIV EXP FLOOR LN LOG MOD PI POWER RADIANS ROUND SCALE SIGN SQRT TRUNC WIDTH_BUCKET RANDOM SETSEED ACOS ACOSD ASIN ASIND ATAN ATAND ATAN2 ATAN2D COS COSD COT COTD SIN SIND TAN TAND BIT_LENGTH CHAR_LENGTH CHARACTER_LENGTH LOWER OCTET_LENGTH OVERLAY POSITION SUBSTRING TREAT TRIM UPPER ASCII BTRIM CHR CONCAT CONCAT_WS CONVERT CONVERT_FROM CONVERT_TO DECODE ENCODE INITCAP LEFT LENGTH LPAD LTRIM MD5 PARSE_IDENT PG_CLIENT_ENCODING QUOTE_IDENT|10 QUOTE_LITERAL|10 QUOTE_NULLABLE|10 REGEXP_MATCH REGEXP_MATCHES REGEXP_REPLACE REGEXP_SPLIT_TO_ARRAY REGEXP_SPLIT_TO_TABLE REPEAT REPLACE REVERSE RIGHT RPAD RTRIM SPLIT_PART STRPOS SUBSTR TO_ASCII TO_HEX TRANSLATE OCTET_LENGTH GET_BIT GET_BYTE SET_BIT SET_BYTE TO_CHAR TO_DATE TO_NUMBER TO_TIMESTAMP AGE CLOCK_TIMESTAMP|10 DATE_PART DATE_TRUNC ISFINITE JUSTIFY_DAYS JUSTIFY_HOURS JUSTIFY_INTERVAL MAKE_DATE MAKE_INTERVAL|10 MAKE_TIME MAKE_TIMESTAMP|10 MAKE_TIMESTAMPTZ|10 NOW STATEMENT_TIMESTAMP|10 TIMEOFDAY TRANSACTION_TIMESTAMP|10 ENUM_FIRST ENUM_LAST ENUM_RANGE AREA CENTER DIAMETER HEIGHT ISCLOSED ISOPEN NPOINTS PCLOSE POPEN RADIUS WIDTH BOX BOUND_BOX CIRCLE LINE LSEG PATH POLYGON ABBREV BROADCAST HOST HOSTMASK MASKLEN NETMASK NETWORK SET_MASKLEN TEXT INET_SAME_FAMILY INET_MERGE MACADDR8_SET7BIT ARRAY_TO_TSVECTOR GET_CURRENT_TS_CONFIG NUMNODE PLAINTO_TSQUERY PHRASETO_TSQUERY WEBSEARCH_TO_TSQUERY QUERYTREE SETWEIGHT STRIP TO_TSQUERY TO_TSVECTOR JSON_TO_TSVECTOR JSONB_TO_TSVECTOR TS_DELETE TS_FILTER TS_HEADLINE TS_RANK TS_RANK_CD TS_REWRITE TSQUERY_PHRASE TSVECTOR_TO_ARRAY TSVECTOR_UPDATE_TRIGGER TSVECTOR_UPDATE_TRIGGER_COLUMN XMLCOMMENT XMLCONCAT XMLELEMENT XMLFOREST XMLPI XMLROOT XMLEXISTS XML_IS_WELL_FORMED XML_IS_WELL_FORMED_DOCUMENT XML_IS_WELL_FORMED_CONTENT XPATH XPATH_EXISTS XMLTABLE XMLNAMESPACES TABLE_TO_XML TABLE_TO_XMLSCHEMA TABLE_TO_XML_AND_XMLSCHEMA QUERY_TO_XML QUERY_TO_XMLSCHEMA QUERY_TO_XML_AND_XMLSCHEMA CURSOR_TO_XML CURSOR_TO_XMLSCHEMA SCHEMA_TO_XML SCHEMA_TO_XMLSCHEMA SCHEMA_TO_XML_AND_XMLSCHEMA DATABASE_TO_XML DATABASE_TO_XMLSCHEMA DATABASE_TO_XML_AND_XMLSCHEMA XMLATTRIBUTES TO_JSON TO_JSONB ARRAY_TO_JSON ROW_TO_JSON JSON_BUILD_ARRAY JSONB_BUILD_ARRAY JSON_BUILD_OBJECT JSONB_BUILD_OBJECT JSON_OBJECT JSONB_OBJECT JSON_ARRAY_LENGTH JSONB_ARRAY_LENGTH JSON_EACH JSONB_EACH JSON_EACH_TEXT JSONB_EACH_TEXT JSON_EXTRACT_PATH JSONB_EXTRACT_PATH JSON_OBJECT_KEYS JSONB_OBJECT_KEYS JSON_POPULATE_RECORD JSONB_POPULATE_RECORD JSON_POPULATE_RECORDSET JSONB_POPULATE_RECORDSET JSON_ARRAY_ELEMENTS JSONB_ARRAY_ELEMENTS JSON_ARRAY_ELEMENTS_TEXT JSONB_ARRAY_ELEMENTS_TEXT JSON_TYPEOF JSONB_TYPEOF JSON_TO_RECORD JSONB_TO_RECORD JSON_TO_RECORDSET JSONB_TO_RECORDSET JSON_STRIP_NULLS JSONB_STRIP_NULLS JSONB_SET JSONB_INSERT JSONB_PRETTY CURRVAL LASTVAL NEXTVAL SETVAL COALESCE NULLIF GREATEST LEAST ARRAY_APPEND ARRAY_CAT ARRAY_NDIMS ARRAY_DIMS ARRAY_FILL ARRAY_LENGTH ARRAY_LOWER ARRAY_POSITION ARRAY_POSITIONS ARRAY_PREPEND ARRAY_REMOVE ARRAY_REPLACE ARRAY_TO_STRING ARRAY_UPPER CARDINALITY STRING_TO_ARRAY UNNEST ISEMPTY LOWER_INC UPPER_INC LOWER_INF UPPER_INF RANGE_MERGE GENERATE_SERIES GENERATE_SUBSCRIPTS CURRENT_DATABASE CURRENT_QUERY CURRENT_SCHEMA|10 CURRENT_SCHEMAS|10 INET_CLIENT_ADDR INET_CLIENT_PORT INET_SERVER_ADDR INET_SERVER_PORT ROW_SECURITY_ACTIVE FORMAT_TYPE TO_REGCLASS TO_REGPROC TO_REGPROCEDURE TO_REGOPER TO_REGOPERATOR TO_REGTYPE TO_REGNAMESPACE TO_REGROLE COL_DESCRIPTION OBJ_DESCRIPTION SHOBJ_DESCRIPTION TXID_CURRENT TXID_CURRENT_IF_ASSIGNED TXID_CURRENT_SNAPSHOT TXID_SNAPSHOT_XIP TXID_SNAPSHOT_XMAX TXID_SNAPSHOT_XMIN TXID_VISIBLE_IN_SNAPSHOT TXID_STATUS CURRENT_SETTING SET_CONFIG BRIN_SUMMARIZE_NEW_VALUES BRIN_SUMMARIZE_RANGE BRIN_DESUMMARIZE_RANGE GIN_CLEAN_PENDING_LIST SUPPRESS_REDUNDANT_UPDATES_TRIGGER LO_FROM_BYTEA LO_PUT LO_GET LO_CREAT LO_CREATE LO_UNLINK LO_IMPORT LO_EXPORT LOREAD LOWRITE GROUPING CAST".split(" ").map(function(a){return a.split("|")[0]}).join("|");
-return{name:"PostgreSQL",aliases:["postgres","postgresql"],case_insensitive:!0,keywords:{keyword:"ABORT ALTER ANALYZE BEGIN CALL CHECKPOINT|10 CLOSE CLUSTER COMMENT COMMIT COPY CREATE DEALLOCATE DECLARE DELETE DISCARD DO DROP END EXECUTE EXPLAIN FETCH GRANT IMPORT INSERT LISTEN LOAD LOCK MOVE NOTIFY PREPARE REASSIGN|10 REFRESH REINDEX RELEASE RESET REVOKE ROLLBACK SAVEPOINT SECURITY SELECT SET SHOW START TRUNCATE UNLISTEN|10 UPDATE VACUUM|10 VALUES AGGREGATE COLLATION CONVERSION|10 DATABASE DEFAULT PRIVILEGES DOMAIN TRIGGER EXTENSION FOREIGN WRAPPER|10 TABLE FUNCTION GROUP LANGUAGE LARGE OBJECT MATERIALIZED VIEW OPERATOR CLASS FAMILY POLICY PUBLICATION|10 ROLE RULE SCHEMA SEQUENCE SERVER STATISTICS SUBSCRIPTION SYSTEM TABLESPACE CONFIGURATION DICTIONARY PARSER TEMPLATE TYPE USER MAPPING PREPARED ACCESS METHOD CAST AS TRANSFORM TRANSACTION OWNED TO INTO SESSION AUTHORIZATION INDEX PROCEDURE ASSERTION ALL ANALYSE AND ANY ARRAY ASC ASYMMETRIC|10 BOTH CASE CHECK COLLATE COLUMN CONCURRENTLY|10 CONSTRAINT CROSS DEFERRABLE RANGE DESC DISTINCT ELSE EXCEPT FOR FREEZE|10 FROM FULL HAVING ILIKE IN INITIALLY INNER INTERSECT IS ISNULL JOIN LATERAL LEADING LIKE LIMIT NATURAL NOT NOTNULL NULL OFFSET ON ONLY OR ORDER OUTER OVERLAPS PLACING PRIMARY REFERENCES RETURNING SIMILAR SOME SYMMETRIC TABLESAMPLE THEN TRAILING UNION UNIQUE USING VARIADIC|10 VERBOSE WHEN WHERE WINDOW WITH BY RETURNS INOUT OUT SETOF|10 IF STRICT CURRENT CONTINUE OWNER LOCATION OVER PARTITION WITHIN BETWEEN ESCAPE EXTERNAL INVOKER DEFINER WORK RENAME VERSION CONNECTION CONNECT TABLES TEMP TEMPORARY FUNCTIONS SEQUENCES TYPES SCHEMAS OPTION CASCADE RESTRICT ADD ADMIN EXISTS VALID VALIDATE ENABLE DISABLE REPLICA|10 ALWAYS PASSING COLUMNS PATH REF VALUE OVERRIDING IMMUTABLE STABLE VOLATILE BEFORE AFTER EACH ROW PROCEDURAL ROUTINE NO HANDLER VALIDATOR OPTIONS STORAGE OIDS|10 WITHOUT INHERIT DEPENDS CALLED INPUT LEAKPROOF|10 COST ROWS NOWAIT SEARCH UNTIL ENCRYPTED|10 PASSWORD CONFLICT|10 INSTEAD INHERITS CHARACTERISTICS WRITE CURSOR ALSO STATEMENT SHARE EXCLUSIVE INLINE ISOLATION REPEATABLE READ COMMITTED SERIALIZABLE UNCOMMITTED LOCAL GLOBAL SQL PROCEDURES RECURSIVE SNAPSHOT ROLLUP CUBE TRUSTED|10 INCLUDE FOLLOWING PRECEDING UNBOUNDED RANGE GROUPS UNENCRYPTED|10 SYSID FORMAT DELIMITER HEADER QUOTE ENCODING FILTER OFF FORCE_QUOTE FORCE_NOT_NULL FORCE_NULL COSTS BUFFERS TIMING SUMMARY DISABLE_PAGE_SKIPPING RESTART CYCLE GENERATED IDENTITY DEFERRED IMMEDIATE LEVEL LOGGED UNLOGGED OF NOTHING NONE EXCLUDE ATTRIBUTE USAGE ROUTINES TRUE FALSE NAN INFINITY ALIAS BEGIN CONSTANT DECLARE END EXCEPTION RETURN PERFORM|10 RAISE GET DIAGNOSTICS STACKED|10 FOREACH LOOP ELSIF EXIT WHILE REVERSE SLICE DEBUG LOG INFO NOTICE WARNING ASSERT OPEN SUPERUSER NOSUPERUSER CREATEDB NOCREATEDB CREATEROLE NOCREATEROLE INHERIT NOINHERIT LOGIN NOLOGIN REPLICATION NOREPLICATION BYPASSRLS NOBYPASSRLS ",
-built_in:"CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURRENT_CATALOG|10 CURRENT_DATE LOCALTIME LOCALTIMESTAMP CURRENT_ROLE|10 CURRENT_SCHEMA|10 SESSION_USER PUBLIC FOUND NEW OLD TG_NAME|10 TG_WHEN|10 TG_LEVEL|10 TG_OP|10 TG_RELID|10 TG_RELNAME|10 TG_TABLE_NAME|10 TG_TABLE_SCHEMA|10 TG_NARGS|10 TG_ARGV|10 TG_EVENT|10 TG_TAG|10 ROW_COUNT RESULT_OID|10 PG_CONTEXT|10 RETURNED_SQLSTATE COLUMN_NAME CONSTRAINT_NAME PG_DATATYPE_NAME|10 MESSAGE_TEXT TABLE_NAME SCHEMA_NAME PG_EXCEPTION_DETAIL|10 PG_EXCEPTION_HINT|10 PG_EXCEPTION_CONTEXT|10 SQLSTATE SQLERRM|10 SUCCESSFUL_COMPLETION WARNING DYNAMIC_RESULT_SETS_RETURNED IMPLICIT_ZERO_BIT_PADDING NULL_VALUE_ELIMINATED_IN_SET_FUNCTION PRIVILEGE_NOT_GRANTED PRIVILEGE_NOT_REVOKED STRING_DATA_RIGHT_TRUNCATION DEPRECATED_FEATURE NO_DATA NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED SQL_STATEMENT_NOT_YET_COMPLETE CONNECTION_EXCEPTION CONNECTION_DOES_NOT_EXIST CONNECTION_FAILURE SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION TRANSACTION_RESOLUTION_UNKNOWN PROTOCOL_VIOLATION TRIGGERED_ACTION_EXCEPTION FEATURE_NOT_SUPPORTED INVALID_TRANSACTION_INITIATION LOCATOR_EXCEPTION INVALID_LOCATOR_SPECIFICATION INVALID_GRANTOR INVALID_GRANT_OPERATION INVALID_ROLE_SPECIFICATION DIAGNOSTICS_EXCEPTION STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER CASE_NOT_FOUND CARDINALITY_VIOLATION DATA_EXCEPTION ARRAY_SUBSCRIPT_ERROR CHARACTER_NOT_IN_REPERTOIRE DATETIME_FIELD_OVERFLOW DIVISION_BY_ZERO ERROR_IN_ASSIGNMENT ESCAPE_CHARACTER_CONFLICT INDICATOR_OVERFLOW INTERVAL_FIELD_OVERFLOW INVALID_ARGUMENT_FOR_LOGARITHM INVALID_ARGUMENT_FOR_NTILE_FUNCTION INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION INVALID_ARGUMENT_FOR_POWER_FUNCTION INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION INVALID_CHARACTER_VALUE_FOR_CAST INVALID_DATETIME_FORMAT INVALID_ESCAPE_CHARACTER INVALID_ESCAPE_OCTET INVALID_ESCAPE_SEQUENCE NONSTANDARD_USE_OF_ESCAPE_CHARACTER INVALID_INDICATOR_PARAMETER_VALUE INVALID_PARAMETER_VALUE INVALID_REGULAR_EXPRESSION INVALID_ROW_COUNT_IN_LIMIT_CLAUSE INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE INVALID_TABLESAMPLE_ARGUMENT INVALID_TABLESAMPLE_REPEAT INVALID_TIME_ZONE_DISPLACEMENT_VALUE INVALID_USE_OF_ESCAPE_CHARACTER MOST_SPECIFIC_TYPE_MISMATCH NULL_VALUE_NOT_ALLOWED NULL_VALUE_NO_INDICATOR_PARAMETER NUMERIC_VALUE_OUT_OF_RANGE SEQUENCE_GENERATOR_LIMIT_EXCEEDED STRING_DATA_LENGTH_MISMATCH STRING_DATA_RIGHT_TRUNCATION SUBSTRING_ERROR TRIM_ERROR UNTERMINATED_C_STRING ZERO_LENGTH_CHARACTER_STRING FLOATING_POINT_EXCEPTION INVALID_TEXT_REPRESENTATION INVALID_BINARY_REPRESENTATION BAD_COPY_FILE_FORMAT UNTRANSLATABLE_CHARACTER NOT_AN_XML_DOCUMENT INVALID_XML_DOCUMENT INVALID_XML_CONTENT INVALID_XML_COMMENT INVALID_XML_PROCESSING_INSTRUCTION INTEGRITY_CONSTRAINT_VIOLATION RESTRICT_VIOLATION NOT_NULL_VIOLATION FOREIGN_KEY_VIOLATION UNIQUE_VIOLATION CHECK_VIOLATION EXCLUSION_VIOLATION INVALID_CURSOR_STATE INVALID_TRANSACTION_STATE ACTIVE_SQL_TRANSACTION BRANCH_TRANSACTION_ALREADY_ACTIVE HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION READ_ONLY_SQL_TRANSACTION SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED NO_ACTIVE_SQL_TRANSACTION IN_FAILED_SQL_TRANSACTION IDLE_IN_TRANSACTION_SESSION_TIMEOUT INVALID_SQL_STATEMENT_NAME TRIGGERED_DATA_CHANGE_VIOLATION INVALID_AUTHORIZATION_SPECIFICATION INVALID_PASSWORD DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST DEPENDENT_OBJECTS_STILL_EXIST INVALID_TRANSACTION_TERMINATION SQL_ROUTINE_EXCEPTION FUNCTION_EXECUTED_NO_RETURN_STATEMENT MODIFYING_SQL_DATA_NOT_PERMITTED PROHIBITED_SQL_STATEMENT_ATTEMPTED READING_SQL_DATA_NOT_PERMITTED INVALID_CURSOR_NAME EXTERNAL_ROUTINE_EXCEPTION CONTAINING_SQL_NOT_PERMITTED MODIFYING_SQL_DATA_NOT_PERMITTED PROHIBITED_SQL_STATEMENT_ATTEMPTED READING_SQL_DATA_NOT_PERMITTED EXTERNAL_ROUTINE_INVOCATION_EXCEPTION INVALID_SQLSTATE_RETURNED NULL_VALUE_NOT_ALLOWED TRIGGER_PROTOCOL_VIOLATED SRF_PROTOCOL_VIOLATED EVENT_TRIGGER_PROTOCOL_VIOLATED SAVEPOINT_EXCEPTION INVALID_SAVEPOINT_SPECIFICATION INVALID_CATALOG_NAME INVALID_SCHEMA_NAME TRANSACTION_ROLLBACK TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION SERIALIZATION_FAILURE STATEMENT_COMPLETION_UNKNOWN DEADLOCK_DETECTED SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION SYNTAX_ERROR INSUFFICIENT_PRIVILEGE CANNOT_COERCE GROUPING_ERROR WINDOWING_ERROR INVALID_RECURSION INVALID_FOREIGN_KEY INVALID_NAME NAME_TOO_LONG RESERVED_NAME DATATYPE_MISMATCH INDETERMINATE_DATATYPE COLLATION_MISMATCH INDETERMINATE_COLLATION WRONG_OBJECT_TYPE GENERATED_ALWAYS UNDEFINED_COLUMN UNDEFINED_FUNCTION UNDEFINED_TABLE UNDEFINED_PARAMETER UNDEFINED_OBJECT DUPLICATE_COLUMN DUPLICATE_CURSOR DUPLICATE_DATABASE DUPLICATE_FUNCTION DUPLICATE_PREPARED_STATEMENT DUPLICATE_SCHEMA DUPLICATE_TABLE DUPLICATE_ALIAS DUPLICATE_OBJECT AMBIGUOUS_COLUMN AMBIGUOUS_FUNCTION AMBIGUOUS_PARAMETER AMBIGUOUS_ALIAS INVALID_COLUMN_REFERENCE INVALID_COLUMN_DEFINITION INVALID_CURSOR_DEFINITION INVALID_DATABASE_DEFINITION INVALID_FUNCTION_DEFINITION INVALID_PREPARED_STATEMENT_DEFINITION INVALID_SCHEMA_DEFINITION INVALID_TABLE_DEFINITION INVALID_OBJECT_DEFINITION WITH_CHECK_OPTION_VIOLATION INSUFFICIENT_RESOURCES DISK_FULL OUT_OF_MEMORY TOO_MANY_CONNECTIONS CONFIGURATION_LIMIT_EXCEEDED PROGRAM_LIMIT_EXCEEDED STATEMENT_TOO_COMPLEX TOO_MANY_COLUMNS TOO_MANY_ARGUMENTS OBJECT_NOT_IN_PREREQUISITE_STATE OBJECT_IN_USE CANT_CHANGE_RUNTIME_PARAM LOCK_NOT_AVAILABLE OPERATOR_INTERVENTION QUERY_CANCELED ADMIN_SHUTDOWN CRASH_SHUTDOWN CANNOT_CONNECT_NOW DATABASE_DROPPED SYSTEM_ERROR IO_ERROR UNDEFINED_FILE DUPLICATE_FILE SNAPSHOT_TOO_OLD CONFIG_FILE_ERROR LOCK_FILE_EXISTS FDW_ERROR FDW_COLUMN_NAME_NOT_FOUND FDW_DYNAMIC_PARAMETER_VALUE_NEEDED FDW_FUNCTION_SEQUENCE_ERROR FDW_INCONSISTENT_DESCRIPTOR_INFORMATION FDW_INVALID_ATTRIBUTE_VALUE FDW_INVALID_COLUMN_NAME FDW_INVALID_COLUMN_NUMBER FDW_INVALID_DATA_TYPE FDW_INVALID_DATA_TYPE_DESCRIPTORS FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER FDW_INVALID_HANDLE FDW_INVALID_OPTION_INDEX FDW_INVALID_OPTION_NAME FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH FDW_INVALID_STRING_FORMAT FDW_INVALID_USE_OF_NULL_POINTER FDW_TOO_MANY_HANDLES FDW_OUT_OF_MEMORY FDW_NO_SCHEMAS FDW_OPTION_NAME_NOT_FOUND FDW_REPLY_HANDLE FDW_SCHEMA_NOT_FOUND FDW_TABLE_NOT_FOUND FDW_UNABLE_TO_CREATE_EXECUTION FDW_UNABLE_TO_CREATE_REPLY FDW_UNABLE_TO_ESTABLISH_CONNECTION PLPGSQL_ERROR RAISE_EXCEPTION NO_DATA_FOUND TOO_MANY_ROWS ASSERT_FAILURE INTERNAL_ERROR DATA_CORRUPTED INDEX_CORRUPTED "},
-illegal:/:==|\W\s*\(\*|(^|\s)\$[a-z]|{{|[a-z]:\s*$|\.\.\.|TO:|DO:/,contains:[{className:"keyword",variants:[{begin:/\bTEXT\s*SEARCH\b/},{begin:/\b(PRIMARY|FOREIGN|FOR(\s+NO)?)\s+KEY\b/},{begin:/\bPARALLEL\s+(UNSAFE|RESTRICTED|SAFE)\b/},{begin:/\bSTORAGE\s+(PLAIN|EXTERNAL|EXTENDED|MAIN)\b/},{begin:/\bMATCH\s+(FULL|PARTIAL|SIMPLE)\b/},{begin:/\bNULLS\s+(FIRST|LAST)\b/},{begin:/\bEVENT\s+TRIGGER\b/},{begin:/\b(MAPPING|OR)\s+REPLACE\b/},{begin:/\b(FROM|TO)\s+(PROGRAM|STDIN|STDOUT)\b/},{begin:/\b(SHARE|EXCLUSIVE)\s+MODE\b/},
-{begin:/\b(LEFT|RIGHT)\s+(OUTER\s+)?JOIN\b/},{begin:/\b(FETCH|MOVE)\s+(NEXT|PRIOR|FIRST|LAST|ABSOLUTE|RELATIVE|FORWARD|BACKWARD)\b/},{begin:/\bPRESERVE\s+ROWS\b/},{begin:/\bDISCARD\s+PLANS\b/},{begin:/\bREFERENCING\s+(OLD|NEW)\b/},{begin:/\bSKIP\s+LOCKED\b/},{begin:/\bGROUPING\s+SETS\b/},{begin:/\b(BINARY|INSENSITIVE|SCROLL|NO\s+SCROLL)\s+(CURSOR|FOR)\b/},{begin:/\b(WITH|WITHOUT)\s+HOLD\b/},{begin:/\bWITH\s+(CASCADED|LOCAL)\s+CHECK\s+OPTION\b/},{begin:/\bEXCLUDE\s+(TIES|NO\s+OTHERS)\b/},{begin:/\bFORMAT\s+(TEXT|XML|JSON|YAML)\b/},
-{begin:/\bSET\s+((SESSION|LOCAL)\s+)?NAMES\b/},{begin:/\bIS\s+(NOT\s+)?UNKNOWN\b/},{begin:/\bSECURITY\s+LABEL\b/},{begin:/\bSTANDALONE\s+(YES|NO|NO\s+VALUE)\b/},{begin:/\bWITH\s+(NO\s+)?DATA\b/},{begin:/\b(FOREIGN|SET)\s+DATA\b/},{begin:/\bSET\s+(CATALOG|CONSTRAINTS)\b/},{begin:/\b(WITH|FOR)\s+ORDINALITY\b/},{begin:/\bIS\s+(NOT\s+)?DOCUMENT\b/},{begin:/\bXML\s+OPTION\s+(DOCUMENT|CONTENT)\b/},{begin:/\b(STRIP|PRESERVE)\s+WHITESPACE\b/},{begin:/\bNO\s+(ACTION|MAXVALUE|MINVALUE)\b/},{begin:/\bPARTITION\s+BY\s+(RANGE|LIST|HASH)\b/},
-{begin:/\bAT\s+TIME\s+ZONE\b/},{begin:/\bGRANTED\s+BY\b/},{begin:/\bRETURN\s+(QUERY|NEXT)\b/},{begin:/\b(ATTACH|DETACH)\s+PARTITION\b/},{begin:/\bFORCE\s+ROW\s+LEVEL\s+SECURITY\b/},{begin:/\b(INCLUDING|EXCLUDING)\s+(COMMENTS|CONSTRAINTS|DEFAULTS|IDENTITY|INDEXES|STATISTICS|STORAGE|ALL)\b/},{begin:/\bAS\s+(ASSIGNMENT|IMPLICIT|PERMISSIVE|RESTRICTIVE|ENUM|RANGE)\b/}]},{begin:/\b(FORMAT|FAMILY|VERSION)\s*\(/},{begin:/\bINCLUDE\s*\(/,keywords:"INCLUDE"},{begin:/\bRANGE(?!\s*(BETWEEN|UNBOUNDED|CURRENT|[-0-9]+))/},
-{begin:/\b(VERSION|OWNER|TEMPLATE|TABLESPACE|CONNECTION\s+LIMIT|PROCEDURE|RESTRICT|JOIN|PARSER|COPY|START|END|COLLATION|INPUT|ANALYZE|STORAGE|LIKE|DEFAULT|DELIMITER|ENCODING|COLUMN|CONSTRAINT|TABLE|SCHEMA)\s*=/},{begin:/\b(PG_\w+?|HAS_[A-Z_]+_PRIVILEGE)\b/,relevance:10},{begin:/\bEXTRACT\s*\(/,end:/\bFROM\b/,returnEnd:!0,keywords:{type:"CENTURY DAY DECADE DOW DOY EPOCH HOUR ISODOW ISOYEAR MICROSECONDS MILLENNIUM MILLISECONDS MINUTE MONTH QUARTER SECOND TIMEZONE TIMEZONE_HOUR TIMEZONE_MINUTE WEEK YEAR"}},
-{begin:/\b(XMLELEMENT|XMLPI)\s*\(\s*NAME/,keywords:{keyword:"NAME"}},{begin:/\b(XMLPARSE|XMLSERIALIZE)\s*\(\s*(DOCUMENT|CONTENT)/,keywords:{keyword:"DOCUMENT CONTENT"}},{beginKeywords:"CACHE INCREMENT MAXVALUE MINVALUE",end:a.C_NUMBER_RE,returnEnd:!0,keywords:"BY CACHE INCREMENT MAXVALUE MINVALUE"},{className:"type",begin:/\b(WITH|WITHOUT)\s+TIME\s+ZONE\b/},{className:"type",begin:/\bINTERVAL\s+(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND)(\s+TO\s+(MONTH|HOUR|MINUTE|SECOND))?\b/},{begin:/\bRETURNS\s+(LANGUAGE_HANDLER|TRIGGER|EVENT_TRIGGER|FDW_HANDLER|INDEX_AM_HANDLER|TSM_HANDLER)\b/,
-keywords:{keyword:"RETURNS",type:"LANGUAGE_HANDLER TRIGGER EVENT_TRIGGER FDW_HANDLER INDEX_AM_HANDLER TSM_HANDLER"}},{begin:"\\b("+d+")\\s*\\("},{begin:"\\.("+c+")\\b"},{begin:"\\b("+c+")\\s+PATH\\b",keywords:{keyword:"PATH",type:"BIGINT INT8 BIGSERIAL SERIAL8 BIT VARYING VARBIT BOOLEAN BOOL BOX BYTEA CHARACTER CHAR VARCHAR CIDR CIRCLE DATE DOUBLE PRECISION FLOAT8 FLOAT INET INTEGER INT INT4 INTERVAL JSON JSONB LINE LSEG|10 MACADDR MACADDR8 MONEY NUMERIC DEC DECIMAL PATH POINT POLYGON REAL FLOAT4 SMALLINT INT2 SMALLSERIAL|10 SERIAL2|10 SERIAL|10 SERIAL4|10 TEXT TIME ZONE TIMETZ|10 TIMESTAMP TIMESTAMPTZ|10 TSQUERY|10 TSVECTOR|10 TXID_SNAPSHOT|10 UUID XML NATIONAL NCHAR INT4RANGE|10 INT8RANGE|10 NUMRANGE|10 TSRANGE|10 TSTZRANGE|10 DATERANGE|10 ANYELEMENT ANYARRAY ANYNONARRAY ANYENUM ANYRANGE CSTRING INTERNAL RECORD PG_DDL_COMMAND VOID UNKNOWN OPAQUE REFCURSOR NAME OID REGPROC|10 REGPROCEDURE|10 REGOPER|10 REGOPERATOR|10 REGCLASS|10 REGTYPE|10 REGROLE|10 REGNAMESPACE|10 REGCONFIG|10 REGDICTIONARY|10 ".replace("PATH ",
-"")}},{className:"type",begin:"\\b("+c+")\\b"},{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:"(e|E|u&|U&)'",end:"'",contains:[{begin:"\\\\."}],relevance:10},a.END_SAME_AS_BEGIN({begin:"\\$([a-zA-Z_]?|[a-zA-Z_][a-zA-Z_0-9]*)\\$",end:"\\$([a-zA-Z_]?|[a-zA-Z_][a-zA-Z_0-9]*)\\$",contains:[{subLanguage:"pgsql perl python tcl r lua java php ruby bash scheme xml json".split(" "),endsWithParent:!0}]}),{begin:'"',end:'"',contains:[{begin:'""'}]},a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE,
-b,{className:"meta",variants:[{begin:"%(ROW)?TYPE",relevance:10},{begin:"\\$\\d+"},{begin:"^#\\w",end:"$"}]},{className:"symbol",begin:"<<\\s*[a-zA-Z_][a-zA-Z_0-9$]*\\s*>>",relevance:10}]}}}());
-hljs.registerLanguage("php",function(){return function(a){var b={begin:"\\$+[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*"},c={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},d={className:"string",contains:[a.BACKSLASH_ESCAPE,c],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null})]},e={variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]},f={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",
-literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};
-return{aliases:"php php3 php4 php5 php6 php7".split(" "),case_insensitive:!0,keywords:f,contains:[a.HASH_COMMENT_MODE,a.COMMENT("//","$",{contains:[c]}),a.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),a.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[a.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},c,{className:"keyword",begin:/\$this\b/},
-b,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:f,contains:["self",b,a.C_BLOCK_COMMENT_MODE,d,e]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",
-end:";",illegal:/[\.']/,contains:[a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[a.UNDERSCORE_TITLE_MODE]},{begin:"=>"},d,e]}}}());
-hljs.registerLanguage("php-template",function(){return function(a){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},a.inherit(a.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());
-hljs.registerLanguage("plaintext",function(){return function(a){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());
-hljs.registerLanguage("pony",function(){return function(a){return{name:"Pony",keywords:{keyword:"actor addressof and as be break class compile_error compile_intrinsic consume continue delegate digestof do else elseif embed end error for fun if ifdef in interface is isnt lambda let match new not object or primitive recover repeat return struct then trait try type until use var where while with xor",meta:"iso val tag trn box ref",literal:"this false true"},contains:[{className:"type",begin:"\\b_?[A-Z][\\w]*",
-relevance:0},{className:"string",begin:'"""',end:'"""',relevance:10},{className:"string",begin:'"',end:'"',contains:[a.BACKSLASH_ESCAPE]},{className:"string",begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE],relevance:0},{begin:a.IDENT_RE+"'",relevance:0},{className:"number",begin:"(-?)(\\b0[xX][a-fA-F0-9]+|\\b0[bB][01]+|(\\b\\d+(_\\d+)?(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",relevance:0},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}}}());
-hljs.registerLanguage("powershell",function(){return function(a){var b={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},
-c={begin:"`[\\s\\S]",relevance:0},d={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},e={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[c,d,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},f={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},h=a.inherit(a.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},
-{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),g={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|New|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Complete|Confirm|Deny|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",
-")+(-)[\\w\\d]+")}]},k={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[a.TITLE_MODE]},l={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[d]}]},m={begin:/using\s/,end:/$/,returnBegin:!0,contains:[e,f,{className:"keyword",
-begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",
-")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},q={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(b.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},a.inherit(a.TITLE_MODE,{endsParent:!0})]};a=[q,h,c,a.NUMBER_MODE,e,f,g,d,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}];c={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,
-relevance:0,contains:[].concat("self",a,{begin:"(string|char|byte|int|long|bool|decimal|single|double|DateTime|xml|array|hashtable|void)",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};q.contains.unshift(c);return{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:b,contains:a.concat(k,l,m,p,c)}}}());
-hljs.registerLanguage("processing",function(){return function(a){return{name:"Processing",keywords:{keyword:"BufferedReader PVector PFont PImage PGraphics HashMap boolean byte char color double float int long String Array FloatDict FloatList IntDict IntList JSONArray JSONObject Object StringDict StringList Table TableRow XML false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",literal:"P2D P3D HALF_PI PI QUARTER_PI TAU TWO_PI",
-title:"setup draw",built_in:"displayHeight displayWidth mouseY mouseX mousePressed pmouseX pmouseY key keyCode pixels focused frameCount frameRate height width size createGraphics beginDraw createShape loadShape PShape arc ellipse line point quad rect triangle bezier bezierDetail bezierPoint bezierTangent curve curveDetail curvePoint curveTangent curveTightness shape shapeMode beginContour beginShape bezierVertex curveVertex endContour endShape quadraticVertex vertex ellipseMode noSmooth rectMode smooth strokeCap strokeJoin strokeWeight mouseClicked mouseDragged mouseMoved mousePressed mouseReleased mouseWheel keyPressed keyPressedkeyReleased keyTyped print println save saveFrame day hour millis minute month second year background clear colorMode fill noFill noStroke stroke alpha blue brightness color green hue lerpColor red saturation modelX modelY modelZ screenX screenY screenZ ambient emissive shininess specular add createImage beginCamera camera endCamera frustum ortho perspective printCamera printProjection cursor frameRate noCursor exit loop noLoop popStyle pushStyle redraw binary boolean byte char float hex int str unbinary unhex join match matchAll nf nfc nfp nfs split splitTokens trim append arrayCopy concat expand reverse shorten sort splice subset box sphere sphereDetail createInput createReader loadBytes loadJSONArray loadJSONObject loadStrings loadTable loadXML open parseXML saveTable selectFolder selectInput beginRaw beginRecord createOutput createWriter endRaw endRecord PrintWritersaveBytes saveJSONArray saveJSONObject saveStream saveStrings saveXML selectOutput popMatrix printMatrix pushMatrix resetMatrix rotate rotateX rotateY rotateZ scale shearX shearY translate ambientLight directionalLight lightFalloff lights lightSpecular noLights normal pointLight spotLight image imageMode loadImage noTint requestImage tint texture textureMode textureWrap blend copy filter get loadPixels set updatePixels blendMode loadShader PShaderresetShader shader createFont loadFont text textFont textAlign textLeading textMode textSize textWidth textAscent textDescent abs ceil constrain dist exp floor lerp log mag map max min norm pow round sq sqrt acos asin atan atan2 cos degrees radians sin tan noise noiseDetail noiseSeed random randomGaussian randomSeed"},
-contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("profile",function(){return function(a){return{name:"Python profiler",contains:[a.C_NUMBER_MODE,{begin:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",end:":",excludeEnd:!0},{begin:"(ncalls|tottime|cumtime)",end:"$",keywords:"ncalls tottime|10 cumtime|10 filename",relevance:10},{begin:"function calls",end:"$",contains:[a.C_NUMBER_MODE],relevance:10},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"\\(",end:"\\)$",excludeBegin:!0,excludeEnd:!0,relevance:0}]}}}());
-hljs.registerLanguage("prolog",function(){return function(a){var b={begin:/\(/,end:/\)/,relevance:0},c={begin:/\[/,end:/\]/};a=[{begin:/[a-z][A-Za-z0-9_]*/,relevance:0},{className:"symbol",variants:[{begin:/[A-Z][a-zA-Z0-9_]*/},{begin:/_[A-Za-z0-9_]*/}],relevance:0},b,{begin:/:-/},c,{className:"comment",begin:/%/,end:/$/,contains:[a.PHRASAL_WORDS_MODE]},a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{className:"string",begin:/`/,end:/`/,contains:[a.BACKSLASH_ESCAPE]},{className:"string",
-begin:/0'(\\'|.)/},{className:"string",begin:/0'\\s/},a.C_NUMBER_MODE];b.contains=a;c.contains=a;return{name:"Prolog",contains:a.concat([{begin:/\.$/}])}}}());
-hljs.registerLanguage("properties",function(){return function(a){var b={end:"([ \\t\\f]*[:=][ \\t\\f]*|[ \\t\\f]+)",relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[a.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+([ \\t\\f]*[:=][ \\t\\f]*|[ \\t\\f]+)",returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:b},
-{begin:"([^\\\\:= \\t\\f\\n]|\\\\.)+([ \\t\\f]*[:=][ \\t\\f]*|[ \\t\\f]+)",returnBegin:!0,relevance:0,contains:[{className:"meta",begin:"([^\\\\:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:b},{className:"attr",relevance:0,begin:"([^\\\\:= \\t\\f\\n]|\\\\.)+[ \\t\\f]*$"}]}}}());
-hljs.registerLanguage("protobuf",function(){return function(a){return{name:"Protocol Buffers",keywords:{keyword:"package import option optional required repeated group oneof",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},contains:[a.QUOTE_STRING_MODE,a.NUMBER_MODE,a.C_LINE_COMMENT_MODE,{className:"class",beginKeywords:"message enum service",end:/\{/,illegal:/\n/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,
-excludeEnd:!0}})]},{className:"function",beginKeywords:"rpc",end:/[{;]/,excludeEnd:!0,keywords:"rpc returns"},{begin:/^\s*[A-Z_]+/,end:/\s*=/,excludeEnd:!0}]}}}());
-hljs.registerLanguage("puppet",function(){return function(a){var b=a.COMMENT("#","$"),c=a.inherit(a.TITLE_MODE,{begin:"([A-Za-z_]|::)(\\w|::)*"}),d={className:"variable",begin:"\\$([A-Za-z_]|::)(\\w|::)*"},e={className:"string",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/}]};return{name:"Puppet",aliases:["pp"],contains:[b,d,e,{beginKeywords:"class",end:"\\{|;",illegal:/=/,contains:[c,b]},{beginKeywords:"define",end:/\{/,contains:[{className:"section",begin:a.IDENT_RE,
-endsParent:!0}]},{begin:a.IDENT_RE+"\\s+\\{",returnBegin:!0,end:/\S/,contains:[{className:"keyword",begin:a.IDENT_RE},{begin:/\{/,end:/\}/,keywords:{keyword:"and case default else elsif false if in import enherits node or true undef unless main settings $string ",literal:"alias audit before loglevel noop require subscribe tag owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check en_address ip_address realname command environment hour monute month monthday special target weekday creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey sslverify mounted",
-built_in:"architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version"},
-relevance:0,contains:[e,b,{begin:"[a-zA-Z_]+\\s*=>",returnBegin:!0,end:"=>",contains:[{className:"attr",begin:a.IDENT_RE}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},d]}],relevance:0}]}}}());
-hljs.registerLanguage("purebasic",function(){return function(a){return{name:"PureBASIC",aliases:["pb","pbi"],keywords:"Align And Array As Break CallDebugger Case CompilerCase CompilerDefault CompilerElse CompilerElseIf CompilerEndIf CompilerEndSelect CompilerError CompilerIf CompilerSelect CompilerWarning Continue Data DataSection Debug DebugLevel Declare DeclareC DeclareCDLL DeclareDLL DeclareModule Default Define Dim DisableASM DisableDebugger DisableExplicit Else ElseIf EnableASM EnableDebugger EnableExplicit End EndDataSection EndDeclareModule EndEnumeration EndIf EndImport EndInterface EndMacro EndModule EndProcedure EndSelect EndStructure EndStructureUnion EndWith Enumeration EnumerationBinary Extends FakeReturn For ForEach ForEver Global Gosub Goto If Import ImportC IncludeBinary IncludeFile IncludePath Interface List Macro MacroExpandedCount Map Module NewList NewMap Next Not Or Procedure ProcedureC ProcedureCDLL ProcedureDLL ProcedureReturn Protected Prototype PrototypeC ReDim Read Repeat Restore Return Runtime Select Shared Static Step Structure StructureUnion Swap Threaded To UndefineMacro Until Until UnuseModule UseModule Wend While With XIncludeFile XOr",contains:[a.COMMENT(";",
-"$",{relevance:0}),{className:"function",begin:"\\b(Procedure|Declare)(C|CDLL|DLL)?\\b",end:"\\(",excludeEnd:!0,returnBegin:!0,contains:[{className:"keyword",begin:"(Procedure|Declare)(C|CDLL|DLL)?",excludeEnd:!0},{className:"type",begin:"\\.\\w*"},a.UNDERSCORE_TITLE_MODE]},{className:"string",begin:'(~)?"',end:'"',illegal:"\\n"},{className:"symbol",begin:"#[a-zA-Z_]\\w*\\$?"}]}}}());
-hljs.registerLanguage("python",function(){return function(a){var b={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},c={className:"meta",begin:/^(>>>|\.\.\.) /},d={className:"subst",begin:/\{/,end:/\}/,keywords:b,illegal:/#/},e={begin:/\{\{/,relevance:0};e={className:"string",contains:[a.BACKSLASH_ESCAPE],
-variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[a.BACKSLASH_ESCAPE,c],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[a.BACKSLASH_ESCAPE,c],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[a.BACKSLASH_ESCAPE,c,e,d]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[a.BACKSLASH_ESCAPE,c,e,d]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[a.BACKSLASH_ESCAPE,e,d]},{begin:/(fr|rf|f)"/,
-end:/"/,contains:[a.BACKSLASH_ESCAPE,e,d]},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]};var f={className:"number",relevance:0,variants:[{begin:a.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:a.C_NUMBER_RE+"[lLjJ]?"}]},h={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",c,f,e,a.HASH_COMMENT_MODE]}]};d.contains=[e,f,c];return{name:"Python",aliases:["py","gyp","ipython"],keywords:b,illegal:/(<\/|->|\?)|=>/,
-contains:[c,f,{beginKeywords:"if",relevance:0},e,a.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[a.UNDERSCORE_TITLE_MODE,h,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());
-hljs.registerLanguage("python-repl",function(){return function(a){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());
-hljs.registerLanguage("q",function(){return function(a){return{name:"Q",aliases:["k","kdb"],keywords:{$pattern:/(`?)[A-Za-z0-9_]+\b/,keyword:"do while select delete by update from",literal:"0b 1b",built_in:"neg not null string reciprocal floor ceiling signum mod xbar xlog and or each scan over prior mmu lsq inv md5 ltime gtime count first var dev med cov cor all any rand sums prds mins maxs fills deltas ratios avgs differ prev next rank reverse iasc idesc asc desc msum mcount mavg mdev xrank mmin mmax xprev rotate distinct group where flip type key til get value attr cut set upsert raze union inter except cross sv vs sublist enlist read0 read1 hopen hclose hdel hsym hcount peach system ltrim rtrim trim lower upper ssr view tables views cols xcols keys xkey xcol xasc xdesc fkeys meta lj aj aj0 ij pj asof uj ww wj wj1 fby xgroup ungroup ej save load rsave rload show csv parse eval min max avg wavg wsum sin cos tan sum",type:"`float `double int `timestamp `timespan `datetime `time `boolean `symbol `char `byte `short `long `real `month `date `minute `second `guid"},
-contains:[a.C_LINE_COMMENT_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE]}}}());
-hljs.registerLanguage("qml",function(){return function(a){var b={begin:"[a-zA-Z_][a-zA-Z0-9\\._]*\\s*{",end:"{",returnBegin:!0,relevance:0,contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_][a-zA-Z0-9\\._]*"})]};return{name:"QML",aliases:["qt"],case_insensitive:!1,keywords:{keyword:"in of on if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import",literal:"true false null undefined NaN Infinity",
-built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Behavior bool color coordinate date double enumeration font geocircle georectangle geoshape int list matrix4x4 parent point quaternion real rect size string url variant vector2d vector3d vector4d Promise"},
-contains:[{className:"meta",begin:/^\s*['"]use (strict|asm)['"]/},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE,{className:"subst",begin:"\\$\\{",end:"\\}"}]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:a.C_NUMBER_RE}],relevance:0},{begin:"("+a.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[a.C_LINE_COMMENT_MODE,
-a.C_BLOCK_COMMENT_MODE,a.REGEXP_MODE,{begin:/</,end:/>\s*[);\]]/,relevance:0,subLanguage:"xml"}],relevance:0},{className:"keyword",begin:"\\bsignal\\b",starts:{className:"string",end:"(\\(|:|=|;|,|//|/\\*|$)",returnEnd:!0}},{className:"keyword",begin:"\\bproperty\\b",starts:{className:"string",end:"(:|=|;|,|//|/\\*|$)",returnEnd:!0}},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{className:"params",begin:/\(/,
-end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}],illegal:/\[|%/},{begin:"\\."+a.IDENT_RE,relevance:0},{className:"attribute",begin:"\\bid\\s*:",starts:{className:"string",end:"[a-zA-Z_][a-zA-Z0-9\\._]*",returnEnd:!1}},{begin:"[a-zA-Z_][a-zA-Z0-9\\._]*\\s*:",returnBegin:!0,contains:[{className:"attribute",begin:"[a-zA-Z_][a-zA-Z0-9\\._]*",end:"\\s*:",excludeEnd:!0,relevance:0}],relevance:0},b],illegal:/#/}}}());
-hljs.registerLanguage("r",function(){return function(a){return{name:"R",contains:[a.HASH_COMMENT_MODE,{begin:"([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*",keywords:{$pattern:"([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*",keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},
-{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());
-hljs.registerLanguage("reasonml",function(){return function(a){var b="("+function(a){return a.map(function(a){return a.split("").map(function(a){return"\\"+a}).join("")}).join("|")}("|| && ++ ** +. * / *. /. ... |>".split(" "))+"|==|===)",c="\\s+"+b+"\\s+",d={keyword:"and as asr assert begin class constraint do done downto else end exception external for fun function functor if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new nonrec object of open or private rec sig struct then to try type val virtual when while with",
-built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 ref string unit ",literal:"true false"},e={className:"number",relevance:0,variants:[{begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)"},{begin:"\\(\\-\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)\\)"}]},f={className:"operator",relevance:0,begin:b};b=[{className:"identifier",
-relevance:0,begin:"~?[a-z$_][0-9a-zA-Z$_]*"},f,e];var h=[a.QUOTE_STRING_MODE,f,{className:"module",begin:"\\b`?[A-Z$_][0-9a-zA-Z$_]*",returnBegin:!0,end:".",contains:[{className:"identifier",begin:"`?[A-Z$_][0-9a-zA-Z$_]*",relevance:0}]}],g=[{className:"module",begin:"\\b`?[A-Z$_][0-9a-zA-Z$_]*",returnBegin:!0,end:".",relevance:0,contains:[{className:"identifier",begin:"`?[A-Z$_][0-9a-zA-Z$_]*",relevance:0}]}],k={className:"function",relevance:0,keywords:d,variants:[{begin:"\\s(\\(\\.?.*?\\)|~?[a-z$_][0-9a-zA-Z$_]*)\\s*=>",
-end:"\\s*=>",returnBegin:!0,relevance:0,contains:[{className:"params",variants:[{begin:"~?[a-z$_][0-9a-zA-Z$_]*"},{begin:"~?[a-z$_][0-9a-zA-Z$_]*(s*:s*[a-z$_][0-9a-z$_]*((s*('?[a-z$_][0-9a-z$_]*s*(,'?[a-z$_][0-9a-z$_]*)*)?s*))?)?(s*:s*[a-z$_][0-9a-z$_]*((s*('?[a-z$_][0-9a-z$_]*s*(,'?[a-z$_][0-9a-z$_]*)*)?s*))?)?"},{begin:/\(\s*\)/}]}]},{begin:"\\s\\(\\.?[^;\\|]*\\)\\s*=>",end:"\\s=>",returnBegin:!0,relevance:0,contains:[{className:"params",relevance:0,variants:[{begin:"~?[a-z$_][0-9a-zA-Z$_]*",end:"(,|\\n|\\))",
-relevance:0,contains:[f,{className:"typing",begin:":",end:"(,|\\n)",returnBegin:!0,relevance:0,contains:g}]}]}]},{begin:"\\(\\.\\s~?[a-z$_][0-9a-zA-Z$_]*\\)\\s*=>"}]};h.push(k);var l={className:"constructor",begin:"`?[A-Z$_][0-9a-zA-Z$_]*\\(",end:"\\)",illegal:"\\n",keywords:d,contains:[a.QUOTE_STRING_MODE,f,{className:"params",begin:"\\b~?[a-z$_][0-9a-zA-Z$_]*"}]};f={className:"pattern-match",begin:"\\|",returnBegin:!0,keywords:d,end:"=>",relevance:0,contains:[l,f,{relevance:0,className:"constructor",
-begin:"`?[A-Z$_][0-9a-zA-Z$_]*"}]};var m={className:"module-access",keywords:d,returnBegin:!0,variants:[{begin:"\\b(`?[A-Z$_][0-9a-zA-Z$_]*\\.)+~?[a-z$_][0-9a-zA-Z$_]*"},{begin:"\\b(`?[A-Z$_][0-9a-zA-Z$_]*\\.)+\\(",end:"\\)",returnBegin:!0,contains:[k,{begin:"\\(",end:"\\)",skip:!0}].concat(h)},{begin:"\\b(`?[A-Z$_][0-9a-zA-Z$_]*\\.)+{",end:"}"}],contains:h};g.push(m);return{name:"ReasonML",aliases:["re"],keywords:d,illegal:"(:\\-|:=|\\${|\\+=)",contains:[a.COMMENT("/\\*","\\*/",{illegal:"^(\\#,\\/\\/)"}),
-{className:"character",begin:"'(\\\\[^']+|[^'])'",illegal:"\\n",relevance:0},a.QUOTE_STRING_MODE,{className:"literal",begin:"\\(\\)",relevance:0},{className:"literal",begin:"\\[\\|",end:"\\|\\]",relevance:0,contains:b},{className:"literal",begin:"\\[",end:"\\]",relevance:0,contains:b},l,{className:"operator",begin:c,illegal:"\\-\\->",relevance:0},e,a.C_LINE_COMMENT_MODE,f,k,{className:"module-def",begin:"\\bmodule\\s+~?[a-z$_][0-9a-zA-Z$_]*\\s+`?[A-Z$_][0-9a-zA-Z$_]*\\s+=\\s+{",end:"}",returnBegin:!0,
-keywords:d,relevance:0,contains:[{className:"module",relevance:0,begin:"`?[A-Z$_][0-9a-zA-Z$_]*"},{begin:"{",end:"}",skip:!0}].concat(h)},m]}}}());
-hljs.registerLanguage("rib",function(){return function(a){return{name:"RenderMan RIB",keywords:"ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry Hider Hyperboloid Identity Illuminate Imager Interior LightSource MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd TransformPoints Translate TrimCurve WorldBegin WorldEnd",illegal:"</",
-contains:[a.HASH_COMMENT_MODE,a.C_NUMBER_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]}}}());
-hljs.registerLanguage("roboconf",function(){return function(a){var b={className:"attribute",begin:/[a-zA-Z-_]+/,end:/\s*:/,excludeEnd:!0,starts:{end:";",relevance:0,contains:[{className:"variable",begin:/\.[a-zA-Z-_]+/},{className:"keyword",begin:/\(optional\)/}]}};return{name:"Roboconf",aliases:["graph","instances"],case_insensitive:!0,keywords:"import",contains:[{begin:"^facet [a-zA-Z-_][^\\n{]+\\{",end:"}",keywords:"facet",contains:[b,a.HASH_COMMENT_MODE]},{begin:"^\\s*instance of [a-zA-Z-_][^\\n{]+\\{",
-end:"}",keywords:"name count channels instance-data instance-state instance of",illegal:/\S/,contains:["self",b,a.HASH_COMMENT_MODE]},{begin:"^[a-zA-Z-_][^\\n{]+\\{",end:"}",contains:[b,a.HASH_COMMENT_MODE]},a.HASH_COMMENT_MODE]}}}());
-hljs.registerLanguage("routeros",function(){return function(a){var b={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},c={className:"string",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,b,{className:"variable",begin:/\$\(/,end:/\)/,contains:[a.BACKSLASH_ESCAPE]}]},d={className:"string",begin:/'/,end:/'/};return{name:"Microtik RouterOS script",aliases:["routeros","mikrotik"],case_insensitive:!0,keywords:{$pattern:/:?[\w-]+/,literal:"true false yes no nothing nil null",
-keyword:"foreach do while for if from to step else on-error and or not in :foreach :do :while :for :if :from :to :step :else :on-error :and :or :not :in :global :local :beep :delay :put :len :typeof :pick :log :time :set :find :environment :terminal :error :execute :parse :resolve :toarray :tobool :toid :toip :toip6 :tonum :tostr :totime"},contains:[{variants:[{begin:/^@/,end:/$/},{begin:/\/\*/,end:/\*\//},{begin:/%%/,end:/$/},{begin:/^'/,end:/$/},{begin:/^\s*\/[\w-]+=/,end:/$/},{begin:/\/\//,end:/$/},
-{begin:/^\[</,end:/>\]$/},{begin:/<\//,end:/>/},{begin:/^facet /,end:/\}/},{begin:"^1\\.\\.(\\d+)$",end:/$/}],illegal:/./},a.COMMENT("^#","$"),c,d,b,{begin:/[\w-]+=([^\s\{\}\[\]\(\)]+)/,relevance:0,returnBegin:!0,contains:[{className:"attribute",begin:/[^=]+/},{begin:/=/,endsWithParent:!0,relevance:0,contains:[c,d,b,{className:"literal",begin:"\\b(true|false|yes|no|nothing|nil|null)\\b"},{begin:/("[^"]*"|[^\s\{\}\[\]]+)/}]}]},{className:"number",begin:/\*[0-9a-fA-F]+/},{begin:"\\b(add|remove|enable|disable|set|get|print|export|edit|find|run|debug|error|info|warning)([\\s[(]|])",
-returnBegin:!0,contains:[{className:"builtin-name",begin:/\w+/}]},{className:"built_in",variants:[{begin:"(\\.\\./|/|\\s)((traffic-flow|traffic-generator|firewall|scheduler|aaa|accounting|address-list|address|align|area|bandwidth-server|bfd|bgp|bridge|client|clock|community|config|connection|console|customer|default|dhcp-client|dhcp-server|discovery|dns|e-mail|ethernet|filter|firewall|firmware|gps|graphing|group|hardware|health|hotspot|identity|igmp-proxy|incoming|instance|interface|ip|ipsec|ipv6|irq|l2tp-server|lcd|ldp|logging|mac-server|mac-winbox|mangle|manual|mirror|mme|mpls|nat|nd|neighbor|network|note|ntp|ospf|ospf-v3|ovpn-server|page|peer|pim|ping|policy|pool|port|ppp|pppoe-client|pptp-server|prefix|profile|proposal|proxy|queue|radius|resource|rip|ripng|route|routing|screen|script|security-profiles|server|service|service-port|settings|shares|smb|sms|sniffer|snmp|snooper|socks|sstp-server|system|tool|tracking|type|upgrade|upnp|user-manager|users|user|vlan|secret|vrrp|watchdog|web-access|wireless|pptp|pppoe|lan|wan|layer7-protocol|lease|simple|raw);?\\s)+",
-relevance:10},{begin:/\.\./}]}]}}}());
-hljs.registerLanguage("rsl",function(){return function(a){return{name:"RenderMan RSL",keywords:{keyword:"float color point normal vector matrix while for if do return else break extern continue",built_in:"abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp faceforward filterstep floor format fresnel incident length lightsource log match max min mod noise normalize ntransform opposite option phong pnoise pow printf ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan texture textureinfo trace transform vtransform xcomp ycomp zcomp"},illegal:"</",
-contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_NUMBER_MODE,{className:"meta",begin:"#",end:"$"},{className:"class",beginKeywords:"surface displacement light volume imager",end:"\\("},{beginKeywords:"illuminate illuminance gather",end:"\\("}]}}}());
-hljs.registerLanguage("ruleslanguage",function(){return function(a){return{name:"Oracle Rules Language",keywords:{keyword:"BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM NUMDAYS READ_DATE STAGING",built_in:"IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME"},
-contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,{className:"literal",variants:[{begin:"#\\s+[a-zA-Z\\ \\.]*",relevance:0},{begin:"#[a-zA-Z\\ \\.]+"}]}]}}}());
-hljs.registerLanguage("rust",function(){return function(a){return{name:"Rust",aliases:["rs"],keywords:{$pattern:a.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:"drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"},
-illegal:"</",contains:[a.C_LINE_COMMENT_MODE,a.COMMENT("/\\*","\\*/",{contains:["self"]}),a.inherit(a.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},{begin:"\\b0o([0-7_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},{begin:"\\b0x([A-Fa-f0-9_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},
-{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)([ui](8|16|32|64|128|size)|f(32|64))?"}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[a.inherit(a.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",
-contains:[a.inherit(a.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:a.IDENT_RE+"::",keywords:{built_in:"drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"}},
-{begin:"->"}]}}}());
-hljs.registerLanguage("sas",function(){return function(a){return{name:"SAS",aliases:["sas","SAS"],case_insensitive:!0,keywords:{literal:"null missing _all_ _automatic_ _character_ _infile_ _n_ _name_ _null_ _numeric_ _user_ _webout_",meta:"do if then else end until while abort array attrib by call cards cards4 catname continue datalines datalines4 delete delim delimiter display dm drop endsas error file filename footnote format goto in infile informat input keep label leave length libname link list lostcard merge missing modify options output out page put redirect remove rename replace retain return select set skip startsas stop title update waitsas where window x systask add and alter as cascade check create delete describe distinct drop foreign from group having index insert into in key like message modify msgtype not null on or order primary references reset restrict select set table unique update validate view where"},contains:[{className:"keyword",
-begin:/^\s*(proc [\w\d_]+|data|run|quit)[\s;]/},{className:"variable",begin:/&[a-zA-Z_&][a-zA-Z0-9_]*\.?/},{className:"emphasis",begin:/^\s*datalines|cards.*;/,end:/^\s*;\s*$/},{className:"built_in",begin:"%(bquote|nrbquote|cmpres|qcmpres|compstor|datatyp|display|do|else|end|eval|global|goto|if|index|input|keydef|label|left|length|let|local|lowcase|macro|mend|nrbquote|nrquote|nrstr|put|qcmpres|qleft|qlowcase|qscan|qsubstr|qsysfunc|qtrim|quote|qupcase|scan|str|substr|superq|syscall|sysevalf|sysexec|sysfunc|sysget|syslput|sysprod|sysrc|sysrput|then|to|trim|unquote|until|upcase|verify|while|window)"},
-{className:"name",begin:/%[a-zA-Z_][a-zA-Z_0-9]*/},{className:"meta",begin:"[^%](abs|addr|airy|arcos|arsin|atan|attrc|attrn|band|betainv|blshift|bnot|bor|brshift|bxor|byte|cdf|ceil|cexist|cinv|close|cnonct|collate|compbl|compound|compress|cos|cosh|css|curobs|cv|daccdb|daccdbsl|daccsl|daccsyd|dacctab|dairy|date|datejul|datepart|datetime|day|dclose|depdb|depdbsl|depdbsl|depsl|depsl|depsyd|depsyd|deptab|deptab|dequote|dhms|dif|digamma|dim|dinfo|dnum|dopen|doptname|doptnum|dread|dropnote|dsname|erf|erfc|exist|exp|fappend|fclose|fcol|fdelete|fetch|fetchobs|fexist|fget|fileexist|filename|fileref|finfo|finv|fipname|fipnamel|fipstate|floor|fnonct|fnote|fopen|foptname|foptnum|fpoint|fpos|fput|fread|frewind|frlen|fsep|fuzz|fwrite|gaminv|gamma|getoption|getvarc|getvarn|hbound|hms|hosthelp|hour|ibessel|index|indexc|indexw|input|inputc|inputn|int|intck|intnx|intrr|irr|jbessel|juldate|kurtosis|lag|lbound|left|length|lgamma|libname|libref|log|log10|log2|logpdf|logpmf|logsdf|lowcase|max|mdy|mean|min|minute|mod|month|mopen|mort|n|netpv|nmiss|normal|note|npv|open|ordinal|pathname|pdf|peek|peekc|pmf|point|poisson|poke|probbeta|probbnml|probchi|probf|probgam|probhypr|probit|probnegb|probnorm|probt|put|putc|putn|qtr|quote|ranbin|rancau|ranexp|rangam|range|rank|rannor|ranpoi|rantbl|rantri|ranuni|repeat|resolve|reverse|rewind|right|round|saving|scan|sdf|second|sign|sin|sinh|skewness|soundex|spedis|sqrt|std|stderr|stfips|stname|stnamel|substr|sum|symget|sysget|sysmsg|sysprod|sysrc|system|tan|tanh|time|timepart|tinv|tnonct|today|translate|tranwrd|trigamma|trim|trimn|trunc|uniform|upcase|uss|var|varfmt|varinfmt|varlabel|varlen|varname|varnum|varray|varrayx|vartype|verify|vformat|vformatd|vformatdx|vformatn|vformatnx|vformatw|vformatwx|vformatx|vinarray|vinarrayx|vinformat|vinformatd|vinformatdx|vinformatn|vinformatnx|vinformatw|vinformatwx|vinformatx|vlabel|vlabelx|vlength|vlengthx|vname|vnamex|vtype|vtypex|weekday|year|yyq|zipfips|zipname|zipnamel|zipstate)[(]"},
-{className:"string",variants:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},a.COMMENT("\\*",";"),a.C_BLOCK_COMMENT_MODE]}}}());
-hljs.registerLanguage("scala",function(){return function(a){var b={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},c={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},d={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},
-contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,b]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[b],relevance:10}]},{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},c,{className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[d]},{className:"class",beginKeywords:"class object trait type",
-end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[c]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[c]},d]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());
-hljs.registerLanguage("scheme",function(){return function(a){var b={className:"literal",begin:"(#t|#f|#\\\\[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+|#\\\\.)"},c={className:"number",variants:[{begin:"(\\-|\\+)?\\d+([./]\\d+)?",relevance:0},{begin:"(\\-|\\+)?\\d+([./]\\d+)?[+\\-](\\-|\\+)?\\d+([./]\\d+)?i",relevance:0},{begin:"#b[0-1]+(/[0-1]+)?"},{begin:"#o[0-7]+(/[0-7]+)?"},{begin:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},d=a.QUOTE_STRING_MODE,e=[a.COMMENT(";","$",{relevance:0}),a.COMMENT("#\\|","\\|#")],f={begin:"[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",
-relevance:0},h={className:"symbol",begin:"'[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+"},g={endsWithParent:!0,relevance:0},k={variants:[{begin:/'/},{begin:"`"}],contains:[{begin:"\\(",end:"\\)",contains:["self",b,d,c,f,h]}]},l={className:"name",begin:"[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",keywords:{$pattern:"[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+","builtin-name":"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"}};
-l={variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}],contains:[{begin:/lambda/,endsWithParent:!0,returnBegin:!0,contains:[l,{begin:/\(/,end:/\)/,endsParent:!0,contains:[f]}]},l,g]};g.contains=[b,c,d,f,h,k,l].concat(e);return{name:"Scheme",illegal:/\S/,contains:[a.SHEBANG(),c,d,h,k,l].concat(e)}}}());
-hljs.registerLanguage("scilab",function(){return function(a){var b=[a.C_NUMBER_MODE,{className:"string",begin:"'|\"",end:"'|\"",contains:[a.BACKSLASH_ESCAPE,{begin:"''"}]}];return{name:"Scilab",aliases:["sci"],keywords:{$pattern:/%?\w+/,keyword:"abort break case clear catch continue do elseif else endfunction end for function global if pause return resume select try then while",literal:"%f %F %t %T %pi %eps %inf %nan %e %i %z %s",built_in:"abs and acos asin atan ceil cd chdir clearglobal cosh cos cumprod deff disp error exec execstr exists exp eye gettext floor fprintf fread fsolve imag isdef isempty isinfisnan isvector lasterror length load linspace list listfiles log10 log2 log max min msprintf mclose mopen ones or pathconvert poly printf prod pwd rand real round sinh sin size gsort sprintf sqrt strcat strcmps tring sum system tanh tan type typename warning zeros matrix"},
-illegal:'("|#|/\\*|\\s+/\\w+)',contains:[{className:"function",beginKeywords:"function",end:"$",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]},{begin:"[a-zA-Z_][a-zA-Z_0-9]*('+[\\.']*|[\\.']+)",end:"",relevance:0},{begin:"\\[",end:"\\]'*[\\.']*",relevance:0,contains:b},a.COMMENT("//","$")].concat(b)}}}());
-hljs.registerLanguage("scss",function(){return function(a){var b={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},c={className:"number",begin:"#[0-9A-Fa-f]+"};return{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",
-begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",
-relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},
-b,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",
-illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},
-{begin:":",end:";",contains:[b,c,a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},b,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,c,a.CSS_NUMBER_MODE]}]}}}());
-hljs.registerLanguage("shell",function(){return function(a){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());
-hljs.registerLanguage("smali",function(){return function(a){var b="add and cmp cmpg cmpl const div double float goto if int long move mul neg new nop not or rem return shl shr sput sub throw ushr xor".split(" ");return{name:"Smali",aliases:["smali"],contains:[{className:"string",begin:'"',end:'"',relevance:0},a.COMMENT("#","$",{relevance:0}),{className:"keyword",variants:[{begin:"\\s*\\.end\\s[a-zA-Z0-9]*"},{begin:"^[ ]*\\.[a-zA-Z]*",relevance:0},{begin:"\\s:[a-zA-Z_0-9]*",relevance:0},{begin:"\\s(transient|constructor|abstract|final|synthetic|public|private|protected|static|bridge|system)"}]},
-{className:"built_in",variants:[{begin:"\\s("+b.join("|")+")\\s"},{begin:"\\s("+b.join("|")+")((\\-|/)[a-zA-Z0-9]+)+\\s",relevance:10},{begin:"\\s(aget|aput|array|check|execute|fill|filled|goto/16|goto/32|iget|instance|invoke|iput|monitor|packed|sget|sparse)((\\-|/)[a-zA-Z0-9]+)*\\s",relevance:10}]},{className:"class",begin:"L[^(;:\n]*;",relevance:0},{begin:"[vp][0-9]+"}]}}}());
-hljs.registerLanguage("smalltalk",function(){return function(a){var b={className:"string",begin:"\\$.{1}"},c={className:"symbol",begin:"#"+a.UNDERSCORE_IDENT_RE};return{name:"Smalltalk",aliases:["st"],keywords:"self super nil true false thisContext",contains:[a.COMMENT('"','"'),a.APOS_STRING_MODE,{className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},{begin:"[a-z][a-zA-Z0-9_]*:",relevance:0},a.C_NUMBER_MODE,c,b,{begin:"\\|[ ]*[a-z][a-zA-Z0-9_]*([ ]+[a-z][a-zA-Z0-9_]*)*[ ]*\\|",returnBegin:!0,
-end:/\|/,illegal:/\S/,contains:[{begin:"(\\|[ ]*)?[a-z][a-zA-Z0-9_]*"}]},{begin:"\\#\\(",end:"\\)",contains:[a.APOS_STRING_MODE,b,a.C_NUMBER_MODE,c]}]}}}());
-hljs.registerLanguage("sml",function(){return function(a){return{name:"SML (Standard ML)",aliases:["ml"],keywords:{$pattern:"[a-z_]\\w*!?",keyword:"abstype and andalso as case datatype do else end eqtype exception fn fun functor handle if in include infix infixr let local nonfix of op open orelse raise rec sharing sig signature struct structure then type val with withtype where while",built_in:"array bool char exn int list option order real ref string substring vector unit word",literal:"true false NONE SOME LESS EQUAL GREATER nil"},
-illegal:/\/\/|>>/,contains:[{className:"literal",begin:/\[(\|\|)?\]|\(\)/,relevance:0},a.COMMENT("\\(\\*","\\*\\)",{contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{className:"type",begin:"`[A-Z][\\w']*"},{className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*"},a.inherit(a.APOS_STRING_MODE,{className:"string",relevance:0}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),{className:"number",begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",
-relevance:0},{begin:/[-=]>/}]}}}());
-hljs.registerLanguage("soy",function(){return function(a){return{contains:[a.C_BLOCK_COMMENT_MODE,a.C_LINE_COMMENT_MODE,{begin:/\{delpackage/,ends:/\}/,relevance:10,keywords:"delpackage"},{begin:/\{namespace/,end:/\}/,relevance:10,keywords:"namespace autoescape",contains:[a.QUOTE_STRING_MODE]},{className:"template-tag",begin:/\{template/,end:/\{\/template\}/,relevance:10,keywords:"alias as autoescape call case default delcall else elseif fallbackmsg foreach if ifempty let msg namespace param print switch template",contains:[a.C_BLOCK_COMMENT_MODE,
-a.C_LINE_COMMENT_MODE,a.QUOTE_STRING_MODE,{className:"template-variable",relevance:0,begin:/\$[^}\s]+/},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},{endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/[^\s"'=<>`]+/}]}]}]}]}]}]}}}());
-hljs.registerLanguage("sqf",function(){return function(a){var b={className:"string",variants:[{begin:'"',end:'"',contains:[{begin:'""',relevance:0}]},{begin:"'",end:"'",contains:[{begin:"''",relevance:0}]}]},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"define undef ifdef ifndef else endif include"},contains:[{begin:/\\\n/,relevance:0},a.inherit(b,{className:"meta-string"}),{className:"meta-string",begin:/<[^\n>]*>/,end:/$/,illegal:"\\n"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]};
-return{name:"SQF",aliases:["sqf"],case_insensitive:!0,keywords:{keyword:"case catch default do else exit exitWith for forEach from if private switch then throw to try waitUntil while with",built_in:"abs accTime acos action actionIDs actionKeys actionKeysImages actionKeysNames actionKeysNamesArray actionName actionParams activateAddons activatedAddons activateKey add3DENConnection add3DENEventHandler add3DENLayer addAction addBackpack addBackpackCargo addBackpackCargoGlobal addBackpackGlobal addCamShake addCuratorAddons addCuratorCameraArea addCuratorEditableObjects addCuratorEditingArea addCuratorPoints addEditorObject addEventHandler addForce addGoggles addGroupIcon addHandgunItem addHeadgear addItem addItemCargo addItemCargoGlobal addItemPool addItemToBackpack addItemToUniform addItemToVest addLiveStats addMagazine addMagazineAmmoCargo addMagazineCargo addMagazineCargoGlobal addMagazineGlobal addMagazinePool addMagazines addMagazineTurret addMenu addMenuItem addMissionEventHandler addMPEventHandler addMusicEventHandler addOwnedMine addPlayerScores addPrimaryWeaponItem addPublicVariableEventHandler addRating addResources addScore addScoreSide addSecondaryWeaponItem addSwitchableUnit addTeamMember addToRemainsCollector addTorque addUniform addVehicle addVest addWaypoint addWeapon addWeaponCargo addWeaponCargoGlobal addWeaponGlobal addWeaponItem addWeaponPool addWeaponTurret admin agent agents AGLToASL aimedAtTarget aimPos airDensityRTD airplaneThrottle airportSide AISFinishHeal alive all3DENEntities allAirports allControls allCurators allCutLayers allDead allDeadMen allDisplays allGroups allMapMarkers allMines allMissionObjects allow3DMode allowCrewInImmobile allowCuratorLogicIgnoreAreas allowDamage allowDammage allowFileOperations allowFleeing allowGetIn allowSprint allPlayers allSimpleObjects allSites allTurrets allUnits allUnitsUAV allVariables ammo ammoOnPylon and animate animateBay animateDoor animatePylon animateSource animationNames animationPhase animationSourcePhase animationState append apply armoryPoints arrayIntersect asin ASLToAGL ASLToATL assert assignAsCargo assignAsCargoIndex assignAsCommander assignAsDriver assignAsGunner assignAsTurret assignCurator assignedCargo assignedCommander assignedDriver assignedGunner assignedItems assignedTarget assignedTeam assignedVehicle assignedVehicleRole assignItem assignTeam assignToAirport atan atan2 atg ATLToASL attachedObject attachedObjects attachedTo attachObject attachTo attackEnabled backpack backpackCargo backpackContainer backpackItems backpackMagazines backpackSpaceFor behaviour benchmark binocular boundingBox boundingBoxReal boundingCenter breakOut breakTo briefingName buildingExit buildingPos buttonAction buttonSetAction cadetMode call callExtension camCommand camCommit camCommitPrepared camCommitted camConstuctionSetParams camCreate camDestroy cameraEffect cameraEffectEnableHUD cameraInterest cameraOn cameraView campaignConfigFile camPreload camPreloaded camPrepareBank camPrepareDir camPrepareDive camPrepareFocus camPrepareFov camPrepareFovRange camPreparePos camPrepareRelPos camPrepareTarget camSetBank camSetDir camSetDive camSetFocus camSetFov camSetFovRange camSetPos camSetRelPos camSetTarget camTarget camUseNVG canAdd canAddItemToBackpack canAddItemToUniform canAddItemToVest cancelSimpleTaskDestination canFire canMove canSlingLoad canStand canSuspend canTriggerDynamicSimulation canUnloadInCombat canVehicleCargo captive captiveNum cbChecked cbSetChecked ceil channelEnabled cheatsEnabled checkAIFeature checkVisibility className clearAllItemsFromBackpack clearBackpackCargo clearBackpackCargoGlobal clearGroupIcons clearItemCargo clearItemCargoGlobal clearItemPool clearMagazineCargo clearMagazineCargoGlobal clearMagazinePool clearOverlay clearRadio clearWeaponCargo clearWeaponCargoGlobal clearWeaponPool clientOwner closeDialog closeDisplay closeOverlay collapseObjectTree collect3DENHistory collectiveRTD combatMode commandArtilleryFire commandChat commander commandFire commandFollow commandFSM commandGetOut commandingMenu commandMove commandRadio commandStop commandSuppressiveFire commandTarget commandWatch comment commitOverlay compile compileFinal completedFSM composeText configClasses configFile configHierarchy configName configProperties configSourceAddonList configSourceMod configSourceModList confirmSensorTarget connectTerminalToUAV controlsGroupCtrl copyFromClipboard copyToClipboard copyWaypoints cos count countEnemy countFriendly countSide countType countUnknown create3DENComposition create3DENEntity createAgent createCenter createDialog createDiaryLink createDiaryRecord createDiarySubject createDisplay createGearDialog createGroup createGuardedPoint createLocation createMarker createMarkerLocal createMenu createMine createMissionDisplay createMPCampaignDisplay createSimpleObject createSimpleTask createSite createSoundSource createTask createTeam createTrigger createUnit createVehicle createVehicleCrew createVehicleLocal crew ctAddHeader ctAddRow ctClear ctCurSel ctData ctFindHeaderRows ctFindRowHeader ctHeaderControls ctHeaderCount ctRemoveHeaders ctRemoveRows ctrlActivate ctrlAddEventHandler ctrlAngle ctrlAutoScrollDelay ctrlAutoScrollRewind ctrlAutoScrollSpeed ctrlChecked ctrlClassName ctrlCommit ctrlCommitted ctrlCreate ctrlDelete ctrlEnable ctrlEnabled ctrlFade ctrlHTMLLoaded ctrlIDC ctrlIDD ctrlMapAnimAdd ctrlMapAnimClear ctrlMapAnimCommit ctrlMapAnimDone ctrlMapCursor ctrlMapMouseOver ctrlMapScale ctrlMapScreenToWorld ctrlMapWorldToScreen ctrlModel ctrlModelDirAndUp ctrlModelScale ctrlParent ctrlParentControlsGroup ctrlPosition ctrlRemoveAllEventHandlers ctrlRemoveEventHandler ctrlScale ctrlSetActiveColor ctrlSetAngle ctrlSetAutoScrollDelay ctrlSetAutoScrollRewind ctrlSetAutoScrollSpeed ctrlSetBackgroundColor ctrlSetChecked ctrlSetEventHandler ctrlSetFade ctrlSetFocus ctrlSetFont ctrlSetFontH1 ctrlSetFontH1B ctrlSetFontH2 ctrlSetFontH2B ctrlSetFontH3 ctrlSetFontH3B ctrlSetFontH4 ctrlSetFontH4B ctrlSetFontH5 ctrlSetFontH5B ctrlSetFontH6 ctrlSetFontH6B ctrlSetFontHeight ctrlSetFontHeightH1 ctrlSetFontHeightH2 ctrlSetFontHeightH3 ctrlSetFontHeightH4 ctrlSetFontHeightH5 ctrlSetFontHeightH6 ctrlSetFontHeightSecondary ctrlSetFontP ctrlSetFontPB ctrlSetFontSecondary ctrlSetForegroundColor ctrlSetModel ctrlSetModelDirAndUp ctrlSetModelScale ctrlSetPixelPrecision ctrlSetPosition ctrlSetScale ctrlSetStructuredText ctrlSetText ctrlSetTextColor ctrlSetTooltip ctrlSetTooltipColorBox ctrlSetTooltipColorShade ctrlSetTooltipColorText ctrlShow ctrlShown ctrlText ctrlTextHeight ctrlTextWidth ctrlType ctrlVisible ctRowControls ctRowCount ctSetCurSel ctSetData ctSetHeaderTemplate ctSetRowTemplate ctSetValue ctValue curatorAddons curatorCamera curatorCameraArea curatorCameraAreaCeiling curatorCoef curatorEditableObjects curatorEditingArea curatorEditingAreaType curatorMouseOver curatorPoints curatorRegisteredObjects curatorSelected curatorWaypointCost current3DENOperation currentChannel currentCommand currentMagazine currentMagazineDetail currentMagazineDetailTurret currentMagazineTurret currentMuzzle currentNamespace currentTask currentTasks currentThrowable currentVisionMode currentWaypoint currentWeapon currentWeaponMode currentWeaponTurret currentZeroing cursorObject cursorTarget customChat customRadio cutFadeOut cutObj cutRsc cutText damage date dateToNumber daytime deActivateKey debriefingText debugFSM debugLog deg delete3DENEntities deleteAt deleteCenter deleteCollection deleteEditorObject deleteGroup deleteGroupWhenEmpty deleteIdentity deleteLocation deleteMarker deleteMarkerLocal deleteRange deleteResources deleteSite deleteStatus deleteTeam deleteVehicle deleteVehicleCrew deleteWaypoint detach detectedMines diag_activeMissionFSMs diag_activeScripts diag_activeSQFScripts diag_activeSQSScripts diag_captureFrame diag_captureFrameToFile diag_captureSlowFrame diag_codePerformance diag_drawMode diag_enable diag_enabled diag_fps diag_fpsMin diag_frameNo diag_lightNewLoad diag_list diag_log diag_logSlowFrame diag_mergeConfigFile diag_recordTurretLimits diag_setLightNew diag_tickTime diag_toggle dialog diarySubjectExists didJIP didJIPOwner difficulty difficultyEnabled difficultyEnabledRTD difficultyOption direction directSay disableAI disableCollisionWith disableConversation disableDebriefingStats disableMapIndicators disableNVGEquipment disableRemoteSensors disableSerialization disableTIEquipment disableUAVConnectability disableUserInput displayAddEventHandler displayCtrl displayParent displayRemoveAllEventHandlers displayRemoveEventHandler displaySetEventHandler dissolveTeam distance distance2D distanceSqr distributionRegion do3DENAction doArtilleryFire doFire doFollow doFSM doGetOut doMove doorPhase doStop doSuppressiveFire doTarget doWatch drawArrow drawEllipse drawIcon drawIcon3D drawLine drawLine3D drawLink drawLocation drawPolygon drawRectangle drawTriangle driver drop dynamicSimulationDistance dynamicSimulationDistanceCoef dynamicSimulationEnabled dynamicSimulationSystemEnabled echo edit3DENMissionAttributes editObject editorSetEventHandler effectiveCommander emptyPositions enableAI enableAIFeature enableAimPrecision enableAttack enableAudioFeature enableAutoStartUpRTD enableAutoTrimRTD enableCamShake enableCaustics enableChannel enableCollisionWith enableCopilot enableDebriefingStats enableDiagLegend enableDynamicSimulation enableDynamicSimulationSystem enableEndDialog enableEngineArtillery enableEnvironment enableFatigue enableGunLights enableInfoPanelComponent enableIRLasers enableMimics enablePersonTurret enableRadio enableReload enableRopeAttach enableSatNormalOnDetail enableSaving enableSentences enableSimulation enableSimulationGlobal enableStamina enableTeamSwitch enableTraffic enableUAVConnectability enableUAVWaypoints enableVehicleCargo enableVehicleSensor enableWeaponDisassembly endLoadingScreen endMission engineOn enginesIsOnRTD enginesRpmRTD enginesTorqueRTD entities environmentEnabled estimatedEndServerTime estimatedTimeLeft evalObjectArgument everyBackpack everyContainer exec execEditorScript execFSM execVM exp expectedDestination exportJIPMessages eyeDirection eyePos face faction fadeMusic fadeRadio fadeSound fadeSpeech failMission fillWeaponsFromPool find findCover findDisplay findEditorObject findEmptyPosition findEmptyPositionReady findIf findNearestEnemy finishMissionInit finite fire fireAtTarget firstBackpack flag flagAnimationPhase flagOwner flagSide flagTexture fleeing floor flyInHeight flyInHeightASL fog fogForecast fogParams forceAddUniform forcedMap forceEnd forceFlagTexture forceFollowRoad forceMap forceRespawn forceSpeed forceWalk forceWeaponFire forceWeatherChange forEachMember forEachMemberAgent forEachMemberTeam forgetTarget format formation formationDirection formationLeader formationMembers formationPosition formationTask formatText formLeader freeLook fromEditor fuel fullCrew gearIDCAmmoCount gearSlotAmmoCount gearSlotData get3DENActionState get3DENAttribute get3DENCamera get3DENConnections get3DENEntity get3DENEntityID get3DENGrid get3DENIconsVisible get3DENLayerEntities get3DENLinesVisible get3DENMissionAttribute get3DENMouseOver get3DENSelected getAimingCoef getAllEnvSoundControllers getAllHitPointsDamage getAllOwnedMines getAllSoundControllers getAmmoCargo getAnimAimPrecision getAnimSpeedCoef getArray getArtilleryAmmo getArtilleryComputerSettings getArtilleryETA getAssignedCuratorLogic getAssignedCuratorUnit getBackpackCargo getBleedingRemaining getBurningValue getCameraViewDirection getCargoIndex getCenterOfMass getClientState getClientStateNumber getCompatiblePylonMagazines getConnectedUAV getContainerMaxLoad getCursorObjectParams getCustomAimCoef getDammage getDescription getDir getDirVisual getDLCAssetsUsage getDLCAssetsUsageByName getDLCs getEditorCamera getEditorMode getEditorObjectScope getElevationOffset getEnvSoundController getFatigue getForcedFlagTexture getFriend getFSMVariable getFuelCargo getGroupIcon getGroupIconParams getGroupIcons getHideFrom getHit getHitIndex getHitPointDamage getItemCargo getMagazineCargo getMarkerColor getMarkerPos getMarkerSize getMarkerType getMass getMissionConfig getMissionConfigValue getMissionDLCs getMissionLayerEntities getModelInfo getMousePosition getMusicPlayedTime getNumber getObjectArgument getObjectChildren getObjectDLC getObjectMaterials getObjectProxy getObjectTextures getObjectType getObjectViewDistance getOxygenRemaining getPersonUsedDLCs getPilotCameraDirection getPilotCameraPosition getPilotCameraRotation getPilotCameraTarget getPlateNumber getPlayerChannel getPlayerScores getPlayerUID getPos getPosASL getPosASLVisual getPosASLW getPosATL getPosATLVisual getPosVisual getPosWorld getPylonMagazines getRelDir getRelPos getRemoteSensorsDisabled getRepairCargo getResolution getShadowDistance getShotParents getSlingLoad getSoundController getSoundControllerResult getSpeed getStamina getStatValue getSuppression getTerrainGrid getTerrainHeightASL getText getTotalDLCUsageTime getUnitLoadout getUnitTrait getUserMFDText getUserMFDvalue getVariable getVehicleCargo getWeaponCargo getWeaponSway getWingsOrientationRTD getWingsPositionRTD getWPPos glanceAt globalChat globalRadio goggles goto group groupChat groupFromNetId groupIconSelectable groupIconsVisible groupId groupOwner groupRadio groupSelectedUnits groupSelectUnit gunner gusts halt handgunItems handgunMagazine handgunWeapon handsHit hasInterface hasPilotCamera hasWeapon hcAllGroups hcGroupParams hcLeader hcRemoveAllGroups hcRemoveGroup hcSelected hcSelectGroup hcSetGroup hcShowBar hcShownBar headgear hideBody hideObject hideObjectGlobal hideSelection hint hintC hintCadet hintSilent hmd hostMission htmlLoad HUDMovementLevels humidity image importAllGroups importance in inArea inAreaArray incapacitatedState inflame inflamed infoPanel infoPanelComponentEnabled infoPanelComponents infoPanels inGameUISetEventHandler inheritsFrom initAmbientLife inPolygon inputAction inRangeOfArtillery insertEditorObject intersect is3DEN is3DENMultiplayer isAbleToBreathe isAgent isArray isAutoHoverOn isAutonomous isAutotest isBleeding isBurning isClass isCollisionLightOn isCopilotEnabled isDamageAllowed isDedicated isDLCAvailable isEngineOn isEqualTo isEqualType isEqualTypeAll isEqualTypeAny isEqualTypeArray isEqualTypeParams isFilePatchingEnabled isFlashlightOn isFlatEmpty isForcedWalk isFormationLeader isGroupDeletedWhenEmpty isHidden isInRemainsCollector isInstructorFigureEnabled isIRLaserOn isKeyActive isKindOf isLaserOn isLightOn isLocalized isManualFire isMarkedForCollection isMultiplayer isMultiplayerSolo isNil isNull isNumber isObjectHidden isObjectRTD isOnRoad isPipEnabled isPlayer isRealTime isRemoteExecuted isRemoteExecutedJIP isServer isShowing3DIcons isSimpleObject isSprintAllowed isStaminaEnabled isSteamMission isStreamFriendlyUIEnabled isText isTouchingGround isTurnedOut isTutHintsEnabled isUAVConnectable isUAVConnected isUIContext isUniformAllowed isVehicleCargo isVehicleRadarOn isVehicleSensorEnabled isWalking isWeaponDeployed isWeaponRested itemCargo items itemsWithMagazines join joinAs joinAsSilent joinSilent joinString kbAddDatabase kbAddDatabaseTargets kbAddTopic kbHasTopic kbReact kbRemoveTopic kbTell kbWasSaid keyImage keyName knowsAbout land landAt landResult language laserTarget lbAdd lbClear lbColor lbColorRight lbCurSel lbData lbDelete lbIsSelected lbPicture lbPictureRight lbSelection lbSetColor lbSetColorRight lbSetCurSel lbSetData lbSetPicture lbSetPictureColor lbSetPictureColorDisabled lbSetPictureColorSelected lbSetPictureRight lbSetPictureRightColor lbSetPictureRightColorDisabled lbSetPictureRightColorSelected lbSetSelectColor lbSetSelectColorRight lbSetSelected lbSetText lbSetTextRight lbSetTooltip lbSetValue lbSize lbSort lbSortByValue lbText lbTextRight lbValue leader leaderboardDeInit leaderboardGetRows leaderboardInit leaderboardRequestRowsFriends leaderboardsRequestUploadScore leaderboardsRequestUploadScoreKeepBest leaderboardState leaveVehicle libraryCredits libraryDisclaimers lifeState lightAttachObject lightDetachObject lightIsOn lightnings limitSpeed linearConversion lineIntersects lineIntersectsObjs lineIntersectsSurfaces lineIntersectsWith linkItem list listObjects listRemoteTargets listVehicleSensors ln lnbAddArray lnbAddColumn lnbAddRow lnbClear lnbColor lnbCurSelRow lnbData lnbDeleteColumn lnbDeleteRow lnbGetColumnsPosition lnbPicture lnbSetColor lnbSetColumnsPos lnbSetCurSelRow lnbSetData lnbSetPicture lnbSetText lnbSetValue lnbSize lnbSort lnbSortByValue lnbText lnbValue load loadAbs loadBackpack loadFile loadGame loadIdentity loadMagazine loadOverlay loadStatus loadUniform loadVest local localize locationPosition lock lockCameraTo lockCargo lockDriver locked lockedCargo lockedDriver lockedTurret lockIdentity lockTurret lockWP log logEntities logNetwork logNetworkTerminate lookAt lookAtPos magazineCargo magazines magazinesAllTurrets magazinesAmmo magazinesAmmoCargo magazinesAmmoFull magazinesDetail magazinesDetailBackpack magazinesDetailUniform magazinesDetailVest magazinesTurret magazineTurretAmmo mapAnimAdd mapAnimClear mapAnimCommit mapAnimDone mapCenterOnCamera mapGridPosition markAsFinishedOnSteam markerAlpha markerBrush markerColor markerDir markerPos markerShape markerSize markerText markerType max members menuAction menuAdd menuChecked menuClear menuCollapse menuData menuDelete menuEnable menuEnabled menuExpand menuHover menuPicture menuSetAction menuSetCheck menuSetData menuSetPicture menuSetValue menuShortcut menuShortcutText menuSize menuSort menuText menuURL menuValue min mineActive mineDetectedBy missionConfigFile missionDifficulty missionName missionNamespace missionStart missionVersion mod modelToWorld modelToWorldVisual modelToWorldVisualWorld modelToWorldWorld modParams moonIntensity moonPhase morale move move3DENCamera moveInAny moveInCargo moveInCommander moveInDriver moveInGunner moveInTurret moveObjectToEnd moveOut moveTime moveTo moveToCompleted moveToFailed musicVolume name nameSound nearEntities nearestBuilding nearestLocation nearestLocations nearestLocationWithDubbing nearestObject nearestObjects nearestTerrainObjects nearObjects nearObjectsReady nearRoads nearSupplies nearTargets needReload netId netObjNull newOverlay nextMenuItemIndex nextWeatherChange nMenuItems not numberOfEnginesRTD numberToDate objectCurators objectFromNetId objectParent objStatus onBriefingGroup onBriefingNotes onBriefingPlan onBriefingTeamSwitch onCommandModeChanged onDoubleClick onEachFrame onGroupIconClick onGroupIconOverEnter onGroupIconOverLeave onHCGroupSelectionChanged onMapSingleClick onPlayerConnected onPlayerDisconnected onPreloadFinished onPreloadStarted onShowNewObject onTeamSwitch openCuratorInterface openDLCPage openMap openSteamApp openYoutubeVideo or orderGetIn overcast overcastForecast owner param params parseNumber parseSimpleArray parseText parsingNamespace particlesQuality pickWeaponPool pitch pixelGrid pixelGridBase pixelGridNoUIScale pixelH pixelW playableSlotsNumber playableUnits playAction playActionNow player playerRespawnTime playerSide playersNumber playGesture playMission playMove playMoveNow playMusic playScriptedMission playSound playSound3D position positionCameraToWorld posScreenToWorld posWorldToScreen ppEffectAdjust ppEffectCommit ppEffectCommitted ppEffectCreate ppEffectDestroy ppEffectEnable ppEffectEnabled ppEffectForceInNVG precision preloadCamera preloadObject preloadSound preloadTitleObj preloadTitleRsc preprocessFile preprocessFileLineNumbers primaryWeapon primaryWeaponItems primaryWeaponMagazine priority processDiaryLink productVersion profileName profileNamespace profileNameSteam progressLoadingScreen progressPosition progressSetPosition publicVariable publicVariableClient publicVariableServer pushBack pushBackUnique putWeaponPool queryItemsPool queryMagazinePool queryWeaponPool rad radioChannelAdd radioChannelCreate radioChannelRemove radioChannelSetCallSign radioChannelSetLabel radioVolume rain rainbow random rank rankId rating rectangular registeredTasks registerTask reload reloadEnabled remoteControl remoteExec remoteExecCall remoteExecutedOwner remove3DENConnection remove3DENEventHandler remove3DENLayer removeAction removeAll3DENEventHandlers removeAllActions removeAllAssignedItems removeAllContainers removeAllCuratorAddons removeAllCuratorCameraAreas removeAllCuratorEditingAreas removeAllEventHandlers removeAllHandgunItems removeAllItems removeAllItemsWithMagazines removeAllMissionEventHandlers removeAllMPEventHandlers removeAllMusicEventHandlers removeAllOwnedMines removeAllPrimaryWeaponItems removeAllWeapons removeBackpack removeBackpackGlobal removeCuratorAddons removeCuratorCameraArea removeCuratorEditableObjects removeCuratorEditingArea removeDrawIcon removeDrawLinks removeEventHandler removeFromRemainsCollector removeGoggles removeGroupIcon removeHandgunItem removeHeadgear removeItem removeItemFromBackpack removeItemFromUniform removeItemFromVest removeItems removeMagazine removeMagazineGlobal removeMagazines removeMagazinesTurret removeMagazineTurret removeMenuItem removeMissionEventHandler removeMPEventHandler removeMusicEventHandler removeOwnedMine removePrimaryWeaponItem removeSecondaryWeaponItem removeSimpleTask removeSwitchableUnit removeTeamMember removeUniform removeVest removeWeapon removeWeaponAttachmentCargo removeWeaponCargo removeWeaponGlobal removeWeaponTurret reportRemoteTarget requiredVersion resetCamShake resetSubgroupDirection resize resources respawnVehicle restartEditorCamera reveal revealMine reverse reversedMouseY roadAt roadsConnectedTo roleDescription ropeAttachedObjects ropeAttachedTo ropeAttachEnabled ropeAttachTo ropeCreate ropeCut ropeDestroy ropeDetach ropeEndPosition ropeLength ropes ropeUnwind ropeUnwound rotorsForcesRTD rotorsRpmRTD round runInitScript safeZoneH safeZoneW safeZoneWAbs safeZoneX safeZoneXAbs safeZoneY save3DENInventory saveGame saveIdentity saveJoysticks saveOverlay saveProfileNamespace saveStatus saveVar savingEnabled say say2D say3D scopeName score scoreSide screenshot screenToWorld scriptDone scriptName scudState secondaryWeapon secondaryWeaponItems secondaryWeaponMagazine select selectBestPlaces selectDiarySubject selectedEditorObjects selectEditorObject selectionNames selectionPosition selectLeader selectMax selectMin selectNoPlayer selectPlayer selectRandom selectRandomWeighted selectWeapon selectWeaponTurret sendAUMessage sendSimpleCommand sendTask sendTaskResult sendUDPMessage serverCommand serverCommandAvailable serverCommandExecutable serverName serverTime set set3DENAttribute set3DENAttributes set3DENGrid set3DENIconsVisible set3DENLayer set3DENLinesVisible set3DENLogicType set3DENMissionAttribute set3DENMissionAttributes set3DENModelsVisible set3DENObjectType set3DENSelected setAccTime setActualCollectiveRTD setAirplaneThrottle setAirportSide setAmmo setAmmoCargo setAmmoOnPylon setAnimSpeedCoef setAperture setApertureNew setArmoryPoints setAttributes setAutonomous setBehaviour setBleedingRemaining setBrakesRTD setCameraInterest setCamShakeDefParams setCamShakeParams setCamUseTI setCaptive setCenterOfMass setCollisionLight setCombatMode setCompassOscillation setConvoySeparation setCuratorCameraAreaCeiling setCuratorCoef setCuratorEditingAreaType setCuratorWaypointCost setCurrentChannel setCurrentTask setCurrentWaypoint setCustomAimCoef setCustomWeightRTD setDamage setDammage setDate setDebriefingText setDefaultCamera setDestination setDetailMapBlendPars setDir setDirection setDrawIcon setDriveOnPath setDropInterval setDynamicSimulationDistance setDynamicSimulationDistanceCoef setEditorMode setEditorObjectScope setEffectCondition setEngineRPMRTD setFace setFaceAnimation setFatigue setFeatureType setFlagAnimationPhase setFlagOwner setFlagSide setFlagTexture setFog setFormation setFormationTask setFormDir setFriend setFromEditor setFSMVariable setFuel setFuelCargo setGroupIcon setGroupIconParams setGroupIconsSelectable setGroupIconsVisible setGroupId setGroupIdGlobal setGroupOwner setGusts setHideBehind setHit setHitIndex setHitPointDamage setHorizonParallaxCoef setHUDMovementLevels setIdentity setImportance setInfoPanel setLeader setLightAmbient setLightAttenuation setLightBrightness setLightColor setLightDayLight setLightFlareMaxDistance setLightFlareSize setLightIntensity setLightnings setLightUseFlare setLocalWindParams setMagazineTurretAmmo setMarkerAlpha setMarkerAlphaLocal setMarkerBrush setMarkerBrushLocal setMarkerColor setMarkerColorLocal setMarkerDir setMarkerDirLocal setMarkerPos setMarkerPosLocal setMarkerShape setMarkerShapeLocal setMarkerSize setMarkerSizeLocal setMarkerText setMarkerTextLocal setMarkerType setMarkerTypeLocal setMass setMimic setMousePosition setMusicEffect setMusicEventHandler setName setNameSound setObjectArguments setObjectMaterial setObjectMaterialGlobal setObjectProxy setObjectTexture setObjectTextureGlobal setObjectViewDistance setOvercast setOwner setOxygenRemaining setParticleCircle setParticleClass setParticleFire setParticleParams setParticleRandom setPilotCameraDirection setPilotCameraRotation setPilotCameraTarget setPilotLight setPiPEffect setPitch setPlateNumber setPlayable setPlayerRespawnTime setPos setPosASL setPosASL2 setPosASLW setPosATL setPosition setPosWorld setPylonLoadOut setPylonsPriority setRadioMsg setRain setRainbow setRandomLip setRank setRectangular setRepairCargo setRotorBrakeRTD setShadowDistance setShotParents setSide setSimpleTaskAlwaysVisible setSimpleTaskCustomData setSimpleTaskDescription setSimpleTaskDestination setSimpleTaskTarget setSimpleTaskType setSimulWeatherLayers setSize setSkill setSlingLoad setSoundEffect setSpeaker setSpeech setSpeedMode setStamina setStaminaScheme setStatValue setSuppression setSystemOfUnits setTargetAge setTaskMarkerOffset setTaskResult setTaskState setTerrainGrid setText setTimeMultiplier setTitleEffect setTrafficDensity setTrafficDistance setTrafficGap setTrafficSpeed setTriggerActivation setTriggerArea setTriggerStatements setTriggerText setTriggerTimeout setTriggerType setType setUnconscious setUnitAbility setUnitLoadout setUnitPos setUnitPosWeak setUnitRank setUnitRecoilCoefficient setUnitTrait setUnloadInCombat setUserActionText setUserMFDText setUserMFDvalue setVariable setVectorDir setVectorDirAndUp setVectorUp setVehicleAmmo setVehicleAmmoDef setVehicleArmor setVehicleCargo setVehicleId setVehicleLock setVehiclePosition setVehicleRadar setVehicleReceiveRemoteTargets setVehicleReportOwnPosition setVehicleReportRemoteTargets setVehicleTIPars setVehicleVarName setVelocity setVelocityModelSpace setVelocityTransformation setViewDistance setVisibleIfTreeCollapsed setWantedRPMRTD setWaves setWaypointBehaviour setWaypointCombatMode setWaypointCompletionRadius setWaypointDescription setWaypointForceBehaviour setWaypointFormation setWaypointHousePosition setWaypointLoiterRadius setWaypointLoiterType setWaypointName setWaypointPosition setWaypointScript setWaypointSpeed setWaypointStatements setWaypointTimeout setWaypointType setWaypointVisible setWeaponReloadingTime setWind setWindDir setWindForce setWindStr setWingForceScaleRTD setWPPos show3DIcons showChat showCinemaBorder showCommandingMenu showCompass showCuratorCompass showGPS showHUD showLegend showMap shownArtilleryComputer shownChat shownCompass shownCuratorCompass showNewEditorObject shownGPS shownHUD shownMap shownPad shownRadio shownScoretable shownUAVFeed shownWarrant shownWatch showPad showRadio showScoretable showSubtitles showUAVFeed showWarrant showWatch showWaypoint showWaypoints side sideChat sideEnemy sideFriendly sideRadio simpleTasks simulationEnabled simulCloudDensity simulCloudOcclusion simulInClouds simulWeatherSync sin size sizeOf skill skillFinal skipTime sleep sliderPosition sliderRange sliderSetPosition sliderSetRange sliderSetSpeed sliderSpeed slingLoadAssistantShown soldierMagazines someAmmo sort soundVolume spawn speaker speed speedMode splitString sqrt squadParams stance startLoadingScreen step stop stopEngineRTD stopped str sunOrMoon supportInfo suppressFor surfaceIsWater surfaceNormal surfaceType swimInDepth switchableUnits switchAction switchCamera switchGesture switchLight switchMove synchronizedObjects synchronizedTriggers synchronizedWaypoints synchronizeObjectsAdd synchronizeObjectsRemove synchronizeTrigger synchronizeWaypoint systemChat systemOfUnits tan targetKnowledge targets targetsAggregate targetsQuery taskAlwaysVisible taskChildren taskCompleted taskCustomData taskDescription taskDestination taskHint taskMarkerOffset taskParent taskResult taskState taskType teamMember teamName teams teamSwitch teamSwitchEnabled teamType terminate terrainIntersect terrainIntersectASL terrainIntersectAtASL text textLog textLogFormat tg time timeMultiplier titleCut titleFadeOut titleObj titleRsc titleText toArray toFixed toLower toString toUpper triggerActivated triggerActivation triggerArea triggerAttachedVehicle triggerAttachObject triggerAttachVehicle triggerDynamicSimulation triggerStatements triggerText triggerTimeout triggerTimeoutCurrent triggerType turretLocal turretOwner turretUnit tvAdd tvClear tvCollapse tvCollapseAll tvCount tvCurSel tvData tvDelete tvExpand tvExpandAll tvPicture tvSetColor tvSetCurSel tvSetData tvSetPicture tvSetPictureColor tvSetPictureColorDisabled tvSetPictureColorSelected tvSetPictureRight tvSetPictureRightColor tvSetPictureRightColorDisabled tvSetPictureRightColorSelected tvSetText tvSetTooltip tvSetValue tvSort tvSortByValue tvText tvTooltip tvValue type typeName typeOf UAVControl uiNamespace uiSleep unassignCurator unassignItem unassignTeam unassignVehicle underwater uniform uniformContainer uniformItems uniformMagazines unitAddons unitAimPosition unitAimPositionVisual unitBackpack unitIsUAV unitPos unitReady unitRecoilCoefficient units unitsBelowHeight unlinkItem unlockAchievement unregisterTask updateDrawIcon updateMenuItem updateObjectTree useAISteeringComponent useAudioTimeForMoves userInputDisabled vectorAdd vectorCos vectorCrossProduct vectorDiff vectorDir vectorDirVisual vectorDistance vectorDistanceSqr vectorDotProduct vectorFromTo vectorMagnitude vectorMagnitudeSqr vectorModelToWorld vectorModelToWorldVisual vectorMultiply vectorNormalized vectorUp vectorUpVisual vectorWorldToModel vectorWorldToModelVisual vehicle vehicleCargoEnabled vehicleChat vehicleRadio vehicleReceiveRemoteTargets vehicleReportOwnPosition vehicleReportRemoteTargets vehicles vehicleVarName velocity velocityModelSpace verifySignature vest vestContainer vestItems vestMagazines viewDistance visibleCompass visibleGPS visibleMap visiblePosition visiblePositionASL visibleScoretable visibleWatch waves waypointAttachedObject waypointAttachedVehicle waypointAttachObject waypointAttachVehicle waypointBehaviour waypointCombatMode waypointCompletionRadius waypointDescription waypointForceBehaviour waypointFormation waypointHousePosition waypointLoiterRadius waypointLoiterType waypointName waypointPosition waypoints waypointScript waypointsEnabledUAV waypointShow waypointSpeed waypointStatements waypointTimeout waypointTimeoutCurrent waypointType waypointVisible weaponAccessories weaponAccessoriesCargo weaponCargo weaponDirection weaponInertia weaponLowered weapons weaponsItems weaponsItemsCargo weaponState weaponsTurret weightRTD WFSideText wind ",
-literal:"blufor civilian configNull controlNull displayNull east endl false grpNull independent lineBreak locationNull nil objNull opfor pi resistance scriptNull sideAmbientLife sideEmpty sideLogic sideUnknown taskNull teamMemberNull true west"},contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.NUMBER_MODE,{className:"variable",begin:/\b_+[a-zA-Z_]\w*/},{className:"title",begin:/[a-zA-Z][a-zA-Z0-9]+_fnc_\w*/},b,c],illegal:/#|^\$ /}}}());
-hljs.registerLanguage("sql",function(){return function(a){var b=a.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",
-end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",
-literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE,b,a.HASH_COMMENT_MODE]},a.C_BLOCK_COMMENT_MODE,
-b,a.HASH_COMMENT_MODE]}}}());
-hljs.registerLanguage("stan",function(){return function(a){return{name:"Stan",aliases:["stanfuncs"],keywords:{$pattern:a.IDENT_RE,title:"functions model data parameters quantities transformed generated",keyword:"for in if else while break continue return".split(" ").concat("int real vector ordered positive_ordered simplex unit_vector row_vector matrix cholesky_factor_corr|10 cholesky_factor_cov|10 corr_matrix|10 cov_matrix|10 void".split(" ")).concat("print reject increment_log_prob|10 integrate_ode|10 integrate_ode_rk45|10 integrate_ode_bdf|10 algebra_solver".split(" ")).join(" "),built_in:"Phi Phi_approx abs acos acosh algebra_solver append_array append_col append_row asin asinh atan atan2 atanh bernoulli_cdf bernoulli_lccdf bernoulli_lcdf bernoulli_logit_lpmf bernoulli_logit_rng bernoulli_lpmf bernoulli_rng bessel_first_kind bessel_second_kind beta_binomial_cdf beta_binomial_lccdf beta_binomial_lcdf beta_binomial_lpmf beta_binomial_rng beta_cdf beta_lccdf beta_lcdf beta_lpdf beta_rng binary_log_loss binomial_cdf binomial_coefficient_log binomial_lccdf binomial_lcdf binomial_logit_lpmf binomial_lpmf binomial_rng block categorical_logit_lpmf categorical_logit_rng categorical_lpmf categorical_rng cauchy_cdf cauchy_lccdf cauchy_lcdf cauchy_lpdf cauchy_rng cbrt ceil chi_square_cdf chi_square_lccdf chi_square_lcdf chi_square_lpdf chi_square_rng cholesky_decompose choose col cols columns_dot_product columns_dot_self cos cosh cov_exp_quad crossprod csr_extract_u csr_extract_v csr_extract_w csr_matrix_times_vector csr_to_dense_matrix cumulative_sum determinant diag_matrix diag_post_multiply diag_pre_multiply diagonal digamma dims dirichlet_lpdf dirichlet_rng distance dot_product dot_self double_exponential_cdf double_exponential_lccdf double_exponential_lcdf double_exponential_lpdf double_exponential_rng e eigenvalues_sym eigenvectors_sym erf erfc exp exp2 exp_mod_normal_cdf exp_mod_normal_lccdf exp_mod_normal_lcdf exp_mod_normal_lpdf exp_mod_normal_rng expm1 exponential_cdf exponential_lccdf exponential_lcdf exponential_lpdf exponential_rng fabs falling_factorial fdim floor fma fmax fmin fmod frechet_cdf frechet_lccdf frechet_lcdf frechet_lpdf frechet_rng gamma_cdf gamma_lccdf gamma_lcdf gamma_lpdf gamma_p gamma_q gamma_rng gaussian_dlm_obs_lpdf get_lp gumbel_cdf gumbel_lccdf gumbel_lcdf gumbel_lpdf gumbel_rng head hypergeometric_lpmf hypergeometric_rng hypot inc_beta int_step integrate_ode integrate_ode_bdf integrate_ode_rk45 inv inv_Phi inv_chi_square_cdf inv_chi_square_lccdf inv_chi_square_lcdf inv_chi_square_lpdf inv_chi_square_rng inv_cloglog inv_gamma_cdf inv_gamma_lccdf inv_gamma_lcdf inv_gamma_lpdf inv_gamma_rng inv_logit inv_sqrt inv_square inv_wishart_lpdf inv_wishart_rng inverse inverse_spd is_inf is_nan lbeta lchoose lgamma lkj_corr_cholesky_lpdf lkj_corr_cholesky_rng lkj_corr_lpdf lkj_corr_rng lmgamma lmultiply log log10 log1m log1m_exp log1m_inv_logit log1p log1p_exp log2 log_determinant log_diff_exp log_falling_factorial log_inv_logit log_mix log_rising_factorial log_softmax log_sum_exp logistic_cdf logistic_lccdf logistic_lcdf logistic_lpdf logistic_rng logit lognormal_cdf lognormal_lccdf lognormal_lcdf lognormal_lpdf lognormal_rng machine_precision matrix_exp max mdivide_left_spd mdivide_left_tri_low mdivide_right_spd mdivide_right_tri_low mean min modified_bessel_first_kind modified_bessel_second_kind multi_gp_cholesky_lpdf multi_gp_lpdf multi_normal_cholesky_lpdf multi_normal_cholesky_rng multi_normal_lpdf multi_normal_prec_lpdf multi_normal_rng multi_student_t_lpdf multi_student_t_rng multinomial_lpmf multinomial_rng multiply_log multiply_lower_tri_self_transpose neg_binomial_2_cdf neg_binomial_2_lccdf neg_binomial_2_lcdf neg_binomial_2_log_lpmf neg_binomial_2_log_rng neg_binomial_2_lpmf neg_binomial_2_rng neg_binomial_cdf neg_binomial_lccdf neg_binomial_lcdf neg_binomial_lpmf neg_binomial_rng negative_infinity normal_cdf normal_lccdf normal_lcdf normal_lpdf normal_rng not_a_number num_elements ordered_logistic_lpmf ordered_logistic_rng owens_t pareto_cdf pareto_lccdf pareto_lcdf pareto_lpdf pareto_rng pareto_type_2_cdf pareto_type_2_lccdf pareto_type_2_lcdf pareto_type_2_lpdf pareto_type_2_rng pi poisson_cdf poisson_lccdf poisson_lcdf poisson_log_lpmf poisson_log_rng poisson_lpmf poisson_rng positive_infinity pow print prod qr_Q qr_R quad_form quad_form_diag quad_form_sym rank rayleigh_cdf rayleigh_lccdf rayleigh_lcdf rayleigh_lpdf rayleigh_rng reject rep_array rep_matrix rep_row_vector rep_vector rising_factorial round row rows rows_dot_product rows_dot_self scaled_inv_chi_square_cdf scaled_inv_chi_square_lccdf scaled_inv_chi_square_lcdf scaled_inv_chi_square_lpdf scaled_inv_chi_square_rng sd segment sin singular_values sinh size skew_normal_cdf skew_normal_lccdf skew_normal_lcdf skew_normal_lpdf skew_normal_rng softmax sort_asc sort_desc sort_indices_asc sort_indices_desc sqrt sqrt2 square squared_distance step student_t_cdf student_t_lccdf student_t_lcdf student_t_lpdf student_t_rng sub_col sub_row sum tail tan tanh target tcrossprod tgamma to_array_1d to_array_2d to_matrix to_row_vector to_vector trace trace_gen_quad_form trace_quad_form trigamma trunc uniform_cdf uniform_lccdf uniform_lcdf uniform_lpdf uniform_rng variance von_mises_lpdf von_mises_rng weibull_cdf weibull_lccdf weibull_lcdf weibull_lpdf weibull_rng wiener_lpdf wishart_lpdf wishart_rng"},
-contains:[a.C_LINE_COMMENT_MODE,a.COMMENT(/#/,/$/,{relevance:0,keywords:{"meta-keyword":"include"}}),a.COMMENT(/\/\*/,/\*\//,{relevance:0,contains:[{className:"doctag",begin:/@(return|param)/}]}),{begin:/<\s*lower\s*=/,keywords:"lower"},{begin:/[<,]\s*upper\s*=/,keywords:"upper"},{className:"keyword",begin:/\btarget\s*\+=/,relevance:10},{begin:"~\\s*("+a.IDENT_RE+")\\s*\\(",keywords:"bernoulli bernoulli_logit beta beta_binomial binomial binomial_logit categorical categorical_logit cauchy chi_square dirichlet double_exponential exp_mod_normal exponential frechet gamma gaussian_dlm_obs gumbel hypergeometric inv_chi_square inv_gamma inv_wishart lkj_corr lkj_corr_cholesky logistic lognormal multi_gp multi_gp_cholesky multi_normal multi_normal_cholesky multi_normal_prec multi_student_t multinomial neg_binomial neg_binomial_2 neg_binomial_2_log normal ordered_logistic pareto pareto_type_2 poisson poisson_log rayleigh scaled_inv_chi_square skew_normal student_t uniform von_mises weibull wiener wishart"},
-{className:"number",variants:[{begin:/\b\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/},{begin:/\.\d+(?:[eE][+-]?\d+)?\b/}],relevance:0},{className:"string",begin:'"',end:'"',relevance:0}]}}}());
-hljs.registerLanguage("stata",function(){return function(a){return{name:"Stata",aliases:["do","ado"],case_insensitive:!0,keywords:"if else in foreach for forv forva forval forvalu forvalue forvalues by bys bysort xi quietly qui capture about ac ac_7 acprplot acprplot_7 adjust ado adopath adoupdate alpha ameans an ano anov anova anova_estat anova_terms anovadef aorder ap app appe appen append arch arch_dr arch_estat arch_p archlm areg areg_p args arima arima_dr arima_estat arima_p as asmprobit asmprobit_estat asmprobit_lf asmprobit_mfx__dlg asmprobit_p ass asse asser assert avplot avplot_7 avplots avplots_7 bcskew0 bgodfrey bias binreg bip0_lf biplot bipp_lf bipr_lf bipr_p biprobit bitest bitesti bitowt blogit bmemsize boot bootsamp bootstrap bootstrap_8 boxco_l boxco_p boxcox boxcox_6 boxcox_p bprobit br break brier bro brow brows browse brr brrstat bs bs_7 bsampl_w bsample bsample_7 bsqreg bstat bstat_7 bstat_8 bstrap bstrap_7 bubble bubbleplot ca ca_estat ca_p cabiplot camat canon canon_8 canon_8_p canon_estat canon_p cap caprojection capt captu captur capture cat cc cchart cchart_7 cci cd censobs_table centile cf char chdir checkdlgfiles checkestimationsample checkhlpfiles checksum chelp ci cii cl class classutil clear cli clis clist clo clog clog_lf clog_p clogi clogi_sw clogit clogit_lf clogit_p clogitp clogl_sw cloglog clonevar clslistarray cluster cluster_measures cluster_stop cluster_tree cluster_tree_8 clustermat cmdlog cnr cnre cnreg cnreg_p cnreg_sw cnsreg codebook collaps4 collapse colormult_nb colormult_nw compare compress conf confi confir confirm conren cons const constr constra constrai constrain constraint continue contract copy copyright copysource cor corc corr corr2data corr_anti corr_kmo corr_smc corre correl correla correlat correlate corrgram cou coun count cox cox_p cox_sw coxbase coxhaz coxvar cprplot cprplot_7 crc cret cretu cretur creturn cross cs cscript cscript_log csi ct ct_is ctset ctst_5 ctst_st cttost cumsp cumsp_7 cumul cusum cusum_7 cutil d|0 datasig datasign datasigna datasignat datasignatu datasignatur datasignature datetof db dbeta de dec deco decod decode deff des desc descr descri describ describe destring dfbeta dfgls dfuller di di_g dir dirstats dis discard disp disp_res disp_s displ displa display distinct do doe doed doedi doedit dotplot dotplot_7 dprobit drawnorm drop ds ds_util dstdize duplicates durbina dwstat dydx e|0 ed edi edit egen eivreg emdef en enc enco encod encode eq erase ereg ereg_lf ereg_p ereg_sw ereghet ereghet_glf ereghet_glf_sh ereghet_gp ereghet_ilf ereghet_ilf_sh ereghet_ip eret eretu eretur ereturn err erro error esize est est_cfexist est_cfname est_clickable est_expand est_hold est_table est_unhold est_unholdok estat estat_default estat_summ estat_vce_only esti estimates etodow etof etomdy ex exi exit expand expandcl fac fact facto factor factor_estat factor_p factor_pca_rotated factor_rotate factormat fcast fcast_compute fcast_graph fdades fdadesc fdadescr fdadescri fdadescrib fdadescribe fdasav fdasave fdause fh_st file open file read file close file filefilter fillin find_hlp_file findfile findit findit_7 fit fl fli flis flist for5_0 forest forestplot form forma format fpredict frac_154 frac_adj frac_chk frac_cox frac_ddp frac_dis frac_dv frac_in frac_mun frac_pp frac_pq frac_pv frac_wgt frac_xo fracgen fracplot fracplot_7 fracpoly fracpred fron_ex fron_hn fron_p fron_tn fron_tn2 frontier ftodate ftoe ftomdy ftowdate funnel funnelplot g|0 gamhet_glf gamhet_gp gamhet_ilf gamhet_ip gamma gamma_d2 gamma_p gamma_sw gammahet gdi_hexagon gdi_spokes ge gen gene gener genera generat generate genrank genstd genvmean gettoken gl gladder gladder_7 glim_l01 glim_l02 glim_l03 glim_l04 glim_l05 glim_l06 glim_l07 glim_l08 glim_l09 glim_l10 glim_l11 glim_l12 glim_lf glim_mu glim_nw1 glim_nw2 glim_nw3 glim_p glim_v1 glim_v2 glim_v3 glim_v4 glim_v5 glim_v6 glim_v7 glm glm_6 glm_p glm_sw glmpred glo glob globa global glogit glogit_8 glogit_p gmeans gnbre_lf gnbreg gnbreg_5 gnbreg_p gomp_lf gompe_sw gomper_p gompertz gompertzhet gomphet_glf gomphet_glf_sh gomphet_gp gomphet_ilf gomphet_ilf_sh gomphet_ip gphdot gphpen gphprint gprefs gprobi_p gprobit gprobit_8 gr gr7 gr_copy gr_current gr_db gr_describe gr_dir gr_draw gr_draw_replay gr_drop gr_edit gr_editviewopts gr_example gr_example2 gr_export gr_print gr_qscheme gr_query gr_read gr_rename gr_replay gr_save gr_set gr_setscheme gr_table gr_undo gr_use graph graph7 grebar greigen greigen_7 greigen_8 grmeanby grmeanby_7 gs_fileinfo gs_filetype gs_graphinfo gs_stat gsort gwood h|0 hadimvo hareg hausman haver he heck_d2 heckma_p heckman heckp_lf heckpr_p heckprob hel help hereg hetpr_lf hetpr_p hetprob hettest hexdump hilite hist hist_7 histogram hlogit hlu hmeans hotel hotelling hprobit hreg hsearch icd9 icd9_ff icd9p iis impute imtest inbase include inf infi infil infile infix inp inpu input ins insheet insp inspe inspec inspect integ inten intreg intreg_7 intreg_p intrg2_ll intrg_ll intrg_ll2 ipolate iqreg ir irf irf_create irfm iri is_svy is_svysum isid istdize ivprob_1_lf ivprob_lf ivprobit ivprobit_p ivreg ivreg_footnote ivtob_1_lf ivtob_lf ivtobit ivtobit_p jackknife jacknife jknife jknife_6 jknife_8 jkstat joinby kalarma1 kap kap_3 kapmeier kappa kapwgt kdensity kdensity_7 keep ksm ksmirnov ktau kwallis l|0 la lab labbe labbeplot labe label labelbook ladder levels levelsof leverage lfit lfit_p li lincom line linktest lis list lloghet_glf lloghet_glf_sh lloghet_gp lloghet_ilf lloghet_ilf_sh lloghet_ip llogi_sw llogis_p llogist llogistic llogistichet lnorm_lf lnorm_sw lnorma_p lnormal lnormalhet lnormhet_glf lnormhet_glf_sh lnormhet_gp lnormhet_ilf lnormhet_ilf_sh lnormhet_ip lnskew0 loadingplot loc loca local log logi logis_lf logistic logistic_p logit logit_estat logit_p loglogs logrank loneway lookfor lookup lowess lowess_7 lpredict lrecomp lroc lroc_7 lrtest ls lsens lsens_7 lsens_x lstat ltable ltable_7 ltriang lv lvr2plot lvr2plot_7 m|0 ma mac macr macro makecns man manova manova_estat manova_p manovatest mantel mark markin markout marksample mat mat_capp mat_order mat_put_rr mat_rapp mata mata_clear mata_describe mata_drop mata_matdescribe mata_matsave mata_matuse mata_memory mata_mlib mata_mosave mata_rename mata_which matalabel matcproc matlist matname matr matri matrix matrix_input__dlg matstrik mcc mcci md0_ md1_ md1debug_ md2_ md2debug_ mds mds_estat mds_p mdsconfig mdslong mdsmat mdsshepard mdytoe mdytof me_derd mean means median memory memsize menl meqparse mer merg merge meta mfp mfx mhelp mhodds minbound mixed_ll mixed_ll_reparm mkassert mkdir mkmat mkspline ml ml_5 ml_adjs ml_bhhhs ml_c_d ml_check ml_clear ml_cnt ml_debug ml_defd ml_e0 ml_e0_bfgs ml_e0_cycle ml_e0_dfp ml_e0i ml_e1 ml_e1_bfgs ml_e1_bhhh ml_e1_cycle ml_e1_dfp ml_e2 ml_e2_cycle ml_ebfg0 ml_ebfr0 ml_ebfr1 ml_ebh0q ml_ebhh0 ml_ebhr0 ml_ebr0i ml_ecr0i ml_edfp0 ml_edfr0 ml_edfr1 ml_edr0i ml_eds ml_eer0i ml_egr0i ml_elf ml_elf_bfgs ml_elf_bhhh ml_elf_cycle ml_elf_dfp ml_elfi ml_elfs ml_enr0i ml_enrr0 ml_erdu0 ml_erdu0_bfgs ml_erdu0_bhhh ml_erdu0_bhhhq ml_erdu0_cycle ml_erdu0_dfp ml_erdu0_nrbfgs ml_exde ml_footnote ml_geqnr ml_grad0 ml_graph ml_hbhhh ml_hd0 ml_hold ml_init ml_inv ml_log ml_max ml_mlout ml_mlout_8 ml_model ml_nb0 ml_opt ml_p ml_plot ml_query ml_rdgrd ml_repor ml_s_e ml_score ml_searc ml_technique ml_unhold mleval mlf_ mlmatbysum mlmatsum mlog mlogi mlogit mlogit_footnote mlogit_p mlopts mlsum mlvecsum mnl0_ mor more mov move mprobit mprobit_lf mprobit_p mrdu0_ mrdu1_ mvdecode mvencode mvreg mvreg_estat n|0 nbreg nbreg_al nbreg_lf nbreg_p nbreg_sw nestreg net newey newey_7 newey_p news nl nl_7 nl_9 nl_9_p nl_p nl_p_7 nlcom nlcom_p nlexp2 nlexp2_7 nlexp2a nlexp2a_7 nlexp3 nlexp3_7 nlgom3 nlgom3_7 nlgom4 nlgom4_7 nlinit nllog3 nllog3_7 nllog4 nllog4_7 nlog_rd nlogit nlogit_p nlogitgen nlogittree nlpred no nobreak noi nois noisi noisil noisily note notes notes_dlg nptrend numlabel numlist odbc old_ver olo olog ologi ologi_sw ologit ologit_p ologitp on one onew onewa oneway op_colnm op_comp op_diff op_inv op_str opr opro oprob oprob_sw oprobi oprobi_p oprobit oprobitp opts_exclusive order orthog orthpoly ou out outf outfi outfil outfile outs outsh outshe outshee outsheet ovtest pac pac_7 palette parse parse_dissim pause pca pca_8 pca_display pca_estat pca_p pca_rotate pcamat pchart pchart_7 pchi pchi_7 pcorr pctile pentium pergram pergram_7 permute permute_8 personal peto_st pkcollapse pkcross pkequiv pkexamine pkexamine_7 pkshape pksumm pksumm_7 pl plo plot plugin pnorm pnorm_7 poisgof poiss_lf poiss_sw poisso_p poisson poisson_estat post postclose postfile postutil pperron pr prais prais_e prais_e2 prais_p predict predictnl preserve print pro prob probi probit probit_estat probit_p proc_time procoverlay procrustes procrustes_estat procrustes_p profiler prog progr progra program prop proportion prtest prtesti pwcorr pwd q\\s qby qbys qchi qchi_7 qladder qladder_7 qnorm qnorm_7 qqplot qqplot_7 qreg qreg_c qreg_p qreg_sw qu quadchk quantile quantile_7 que quer query range ranksum ratio rchart rchart_7 rcof recast reclink recode reg reg3 reg3_p regdw regr regre regre_p2 regres regres_p regress regress_estat regriv_p remap ren rena renam rename renpfix repeat replace report reshape restore ret retu retur return rm rmdir robvar roccomp roccomp_7 roccomp_8 rocf_lf rocfit rocfit_8 rocgold rocplot rocplot_7 roctab roctab_7 rolling rologit rologit_p rot rota rotat rotate rotatemat rreg rreg_p ru run runtest rvfplot rvfplot_7 rvpplot rvpplot_7 sa safesum sample sampsi sav save savedresults saveold sc sca scal scala scalar scatter scm_mine sco scob_lf scob_p scobi_sw scobit scor score scoreplot scoreplot_help scree screeplot screeplot_help sdtest sdtesti se search separate seperate serrbar serrbar_7 serset set set_defaults sfrancia sh she shel shell shewhart shewhart_7 signestimationsample signrank signtest simul simul_7 simulate simulate_8 sktest sleep slogit slogit_d2 slogit_p smooth snapspan so sor sort spearman spikeplot spikeplot_7 spikeplt spline_x split sqreg sqreg_p sret sretu sretur sreturn ssc st st_ct st_hc st_hcd st_hcd_sh st_is st_issys st_note st_promo st_set st_show st_smpl st_subid stack statsby statsby_8 stbase stci stci_7 stcox stcox_estat stcox_fr stcox_fr_ll stcox_p stcox_sw stcoxkm stcoxkm_7 stcstat stcurv stcurve stcurve_7 stdes stem stepwise stereg stfill stgen stir stjoin stmc stmh stphplot stphplot_7 stphtest stphtest_7 stptime strate strate_7 streg streg_sw streset sts sts_7 stset stsplit stsum sttocc sttoct stvary stweib su suest suest_8 sum summ summa summar summari summariz summarize sunflower sureg survcurv survsum svar svar_p svmat svy svy_disp svy_dreg svy_est svy_est_7 svy_estat svy_get svy_gnbreg_p svy_head svy_header svy_heckman_p svy_heckprob_p svy_intreg_p svy_ivreg_p svy_logistic_p svy_logit_p svy_mlogit_p svy_nbreg_p svy_ologit_p svy_oprobit_p svy_poisson_p svy_probit_p svy_regress_p svy_sub svy_sub_7 svy_x svy_x_7 svy_x_p svydes svydes_8 svygen svygnbreg svyheckman svyheckprob svyintreg svyintreg_7 svyintrg svyivreg svylc svylog_p svylogit svymarkout svymarkout_8 svymean svymlog svymlogit svynbreg svyolog svyologit svyoprob svyoprobit svyopts svypois svypois_7 svypoisson svyprobit svyprobt svyprop svyprop_7 svyratio svyreg svyreg_p svyregress svyset svyset_7 svyset_8 svytab svytab_7 svytest svytotal sw sw_8 swcnreg swcox swereg swilk swlogis swlogit swologit swoprbt swpois swprobit swqreg swtobit swweib symmetry symmi symplot symplot_7 syntax sysdescribe sysdir sysuse szroeter ta tab tab1 tab2 tab_or tabd tabdi tabdis tabdisp tabi table tabodds tabodds_7 tabstat tabu tabul tabula tabulat tabulate te tempfile tempname tempvar tes test testnl testparm teststd tetrachoric time_it timer tis tob tobi tobit tobit_p tobit_sw token tokeni tokeniz tokenize tostring total translate translator transmap treat_ll treatr_p treatreg trim trimfill trnb_cons trnb_mean trpoiss_d2 trunc_ll truncr_p truncreg tsappend tset tsfill tsline tsline_ex tsreport tsrevar tsrline tsset tssmooth tsunab ttest ttesti tut_chk tut_wait tutorial tw tware_st two twoway twoway__fpfit_serset twoway__function_gen twoway__histogram_gen twoway__ipoint_serset twoway__ipoints_serset twoway__kdensity_gen twoway__lfit_serset twoway__normgen_gen twoway__pci_serset twoway__qfit_serset twoway__scatteri_serset twoway__sunflower_gen twoway_ksm_serset ty typ type typeof u|0 unab unabbrev unabcmd update us use uselabel var var_mkcompanion var_p varbasic varfcast vargranger varirf varirf_add varirf_cgraph varirf_create varirf_ctable varirf_describe varirf_dir varirf_drop varirf_erase varirf_graph varirf_ograph varirf_rename varirf_set varirf_table varlist varlmar varnorm varsoc varstable varstable_w varstable_w2 varwle vce vec vec_fevd vec_mkphi vec_p vec_p_w vecirf_create veclmar veclmar_w vecnorm vecnorm_w vecrank vecstable verinst vers versi versio version view viewsource vif vwls wdatetof webdescribe webseek webuse weib1_lf weib2_lf weib_lf weib_lf0 weibhet_glf weibhet_glf_sh weibhet_glfa weibhet_glfa_sh weibhet_gp weibhet_ilf weibhet_ilf_sh weibhet_ilfa weibhet_ilfa_sh weibhet_ip weibu_sw weibul_p weibull weibull_c weibull_s weibullhet wh whelp whi which whil while wilc_st wilcoxon win wind windo window winexec wntestb wntestb_7 wntestq xchart xchart_7 xcorr xcorr_7 xi xi_6 xmlsav xmlsave xmluse xpose xsh xshe xshel xshell xt_iis xt_tis xtab_p xtabond xtbin_p xtclog xtcloglog xtcloglog_8 xtcloglog_d2 xtcloglog_pa_p xtcloglog_re_p xtcnt_p xtcorr xtdata xtdes xtfront_p xtfrontier xtgee xtgee_elink xtgee_estat xtgee_makeivar xtgee_p xtgee_plink xtgls xtgls_p xthaus xthausman xtht_p xthtaylor xtile xtint_p xtintreg xtintreg_8 xtintreg_d2 xtintreg_p xtivp_1 xtivp_2 xtivreg xtline xtline_ex xtlogit xtlogit_8 xtlogit_d2 xtlogit_fe_p xtlogit_pa_p xtlogit_re_p xtmixed xtmixed_estat xtmixed_p xtnb_fe xtnb_lf xtnbreg xtnbreg_pa_p xtnbreg_refe_p xtpcse xtpcse_p xtpois xtpoisson xtpoisson_d2 xtpoisson_pa_p xtpoisson_refe_p xtpred xtprobit xtprobit_8 xtprobit_d2 xtprobit_re_p xtps_fe xtps_lf xtps_ren xtps_ren_8 xtrar_p xtrc xtrc_p xtrchh xtrefe_p xtreg xtreg_be xtreg_fe xtreg_ml xtreg_pa_p xtreg_re xtregar xtrere_p xtset xtsf_ll xtsf_llti xtsum xttab xttest0 xttobit xttobit_8 xttobit_p xttrans yx yxview__barlike_draw yxview_area_draw yxview_bar_draw yxview_dot_draw yxview_dropline_draw yxview_function_draw yxview_iarrow_draw yxview_ilabels_draw yxview_normal_draw yxview_pcarrow_draw yxview_pcbarrow_draw yxview_pccapsym_draw yxview_pcscatter_draw yxview_pcspike_draw yxview_rarea_draw yxview_rbar_draw yxview_rbarm_draw yxview_rcap_draw yxview_rcapsym_draw yxview_rconnected_draw yxview_rline_draw yxview_rscatter_draw yxview_rspike_draw yxview_spike_draw yxview_sunflower_draw zap_s zinb zinb_llf zinb_plf zip zip_llf zip_p zip_plf zt_ct_5 zt_hc_5 zt_hcd_5 zt_is_5 zt_iss_5 zt_sho_5 zt_smp_5 ztbase_5 ztcox_5 ztdes_5 ztereg_5 ztfill_5 ztgen_5 ztir_5 ztjoin_5 ztnb ztnb_p ztp ztp_p zts_5 ztset_5 ztspli_5 ztsum_5 zttoct_5 ztvary_5 ztweib_5",contains:[{className:"symbol",
-begin:/`[a-zA-Z0-9_]+'/},{className:"variable",begin:/\$\{?[a-zA-Z0-9_]+\}?/},{className:"string",variants:[{begin:'`"[^\r\n]*?"\''},{begin:'"[^\r\n"]*"'}]},{className:"built_in",variants:[{begin:"\\b(abs|acos|asin|atan|atan2|atanh|ceil|cloglog|comb|cos|digamma|exp|floor|invcloglog|invlogit|ln|lnfact|lnfactorial|lngamma|log|log10|max|min|mod|reldif|round|sign|sin|sqrt|sum|tan|tanh|trigamma|trunc|betaden|Binomial|binorm|binormal|chi2|chi2tail|dgammapda|dgammapdada|dgammapdadx|dgammapdx|dgammapdxdx|F|Fden|Ftail|gammaden|gammap|ibeta|invbinomial|invchi2|invchi2tail|invF|invFtail|invgammap|invibeta|invnchi2|invnFtail|invnibeta|invnorm|invnormal|invttail|nbetaden|nchi2|nFden|nFtail|nibeta|norm|normal|normalden|normd|npnchi2|tden|ttail|uniform|abbrev|char|index|indexnot|length|lower|ltrim|match|plural|proper|real|regexm|regexr|regexs|reverse|rtrim|string|strlen|strlower|strltrim|strmatch|strofreal|strpos|strproper|strreverse|strrtrim|strtrim|strupper|subinstr|subinword|substr|trim|upper|word|wordcount|_caller|autocode|byteorder|chop|clip|cond|e|epsdouble|epsfloat|group|inlist|inrange|irecode|matrix|maxbyte|maxdouble|maxfloat|maxint|maxlong|mi|minbyte|mindouble|minfloat|minint|minlong|missing|r|recode|replay|return|s|scalar|d|date|day|dow|doy|halfyear|mdy|month|quarter|week|year|d|daily|dofd|dofh|dofm|dofq|dofw|dofy|h|halfyearly|hofd|m|mofd|monthly|q|qofd|quarterly|tin|twithin|w|weekly|wofd|y|yearly|yh|ym|yofd|yq|yw|cholesky|colnumb|colsof|corr|det|diag|diag0cnt|el|get|hadamard|I|inv|invsym|issym|issymmetric|J|matmissing|matuniform|mreldif|nullmat|rownumb|rowsof|sweep|syminv|trace|vec|vecdiag)(?=\\()"}]},
-a.COMMENT("^[ \t]*\\*.*$",!1),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}}}());
-hljs.registerLanguage("step21",function(){return function(a){return{name:"STEP Part 21",aliases:["p21","step","stp"],case_insensitive:!0,keywords:{$pattern:"[A-Z_][A-Z0-9_.]*",keyword:"HEADER ENDSEC DATA"},contains:[{className:"meta",begin:"ISO-10303-21;",relevance:10},{className:"meta",begin:"END-ISO-10303-21;",relevance:10},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.COMMENT("/\\*\\*!","\\*/"),a.C_NUMBER_MODE,a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),
-{className:"string",begin:"'",end:"'"},{className:"symbol",variants:[{begin:"#",end:"\\d+",illegal:"\\W"}]}]}}}());
-hljs.registerLanguage("stylus",function(){return function(a){var b={className:"variable",begin:"\\$"+a.IDENT_RE},c={className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"};return{name:"Stylus",aliases:["styl"],case_insensitive:!1,keywords:"if else for in",illegal:"(\\?|(\\bReturn\\b)|(\\bEnd\\b)|(\\bend\\b)|(\\bdef\\b)|;|#\\s|\\*\\s|===\\s|\\||%)",contains:[a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,{begin:"\\.[a-zA-Z][a-zA-Z0-9_-]*(?=[\\.\\s\\n\\[\\:,])",
-className:"selector-class"},{begin:"\\#[a-zA-Z][a-zA-Z0-9_-]*(?=[\\.\\s\\n\\[\\:,])",className:"selector-id"},{begin:"\\b(a|abbr|address|article|aside|audio|b|blockquote|body|button|canvas|caption|cite|code|dd|del|details|dfn|div|dl|dt|em|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|html|i|iframe|img|input|ins|kbd|label|legend|li|mark|menu|nav|object|ol|p|q|quote|samp|section|span|strong|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|tr|ul|var|video)(?=[\\.\\s\\n\\[\\:,])",
-className:"selector-tag"},{begin:"&?:?:\\b(after|before|first-letter|first-line|active|first-child|focus|hover|lang|link|visited)(?=[\\.\\s\\n\\[\\:,])"},{begin:"@(charset|css|debug|extend|font-face|for|import|include|media|mixin|page|warn|while)\\b"},b,a.CSS_NUMBER_MODE,a.NUMBER_MODE,{className:"function",begin:"^[a-zA-Z][a-zA-Z0-9_-]*\\(.*\\)",illegal:"[\\n]",returnBegin:!0,contains:[{className:"title",begin:"\\b[a-zA-Z][a-zA-Z0-9_-]*"},{className:"params",begin:/\(/,end:/\)/,contains:[c,b,a.APOS_STRING_MODE,
-a.CSS_NUMBER_MODE,a.NUMBER_MODE,a.QUOTE_STRING_MODE]}]},{className:"attribute",begin:"\\b("+"align-content align-items align-self animation animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-function auto backface-visibility background background-attachment background-clip background-color background-image background-origin background-position background-repeat background-size border border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-collapse border-color border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-width border-left border-left-color border-left-style border-left-width border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width border-width bottom box-decoration-break box-shadow box-sizing break-after break-before break-inside caption-side clear clip clip-path color column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width column-span column-width columns content counter-increment counter-reset cursor direction display empty-cells filter flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float font font-family font-feature-settings font-kerning font-language-override font-size font-size-adjust font-stretch font-style font-variant font-variant-ligatures font-weight height hyphens icon image-orientation image-rendering image-resolution ime-mode inherit initial justify-content left letter-spacing line-height list-style list-style-image list-style-position list-style-type margin margin-bottom margin-left margin-right margin-top marks mask max-height max-width min-height min-width nav-down nav-index nav-left nav-right nav-up none normal object-fit object-position opacity order orphans outline outline-color outline-offset outline-style outline-width overflow overflow-wrap overflow-x overflow-y padding padding-bottom padding-left padding-right padding-top page-break-after page-break-before page-break-inside perspective perspective-origin pointer-events position quotes resize right tab-size table-layout text-align text-align-last text-decoration text-decoration-color text-decoration-line text-decoration-style text-indent text-overflow text-rendering text-shadow text-transform text-underline-position top transform transform-origin transform-style transition transition-delay transition-duration transition-property transition-timing-function unicode-bidi vertical-align visibility white-space widows width word-break word-spacing word-wrap z-index".split(" ").reverse().join("|")+
-")\\b",starts:{end:/;|$/,contains:[c,b,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.CSS_NUMBER_MODE,a.NUMBER_MODE,a.C_BLOCK_COMMENT_MODE],illegal:/\./,relevance:0}}]}}}());
-hljs.registerLanguage("subunit",function(){return function(a){return{name:"SubUnit",case_insensitive:!0,contains:[{className:"string",begin:"\\[\n(multipart)?",end:"\\]\n"},{className:"string",begin:"\\d{4}-\\d{2}-\\d{2}(\\s+)\\d{2}:\\d{2}:\\d{2}.\\d+Z"},{className:"string",begin:"(\\+|-)\\d+"},{className:"keyword",relevance:10,variants:[{begin:"^(test|testing|success|successful|failure|error|skip|xfail|uxsuccess)(:?)\\s+(test)?"},{begin:"^progress(:?)(\\s+)?(pop|push)?"},{begin:"^tags:"},{begin:"^time:"}]}]}}}());
-hljs.registerLanguage("swift",function(){return function(a){var b={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",
-literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},
-c=a.COMMENT("/\\*","\\*/",{contains:["self"]}),d={className:"subst",begin:/\\\(/,end:"\\)",keywords:b,contains:[]},e={className:"string",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},f={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};d.contains=[f];return{name:"Swift",keywords:b,contains:[e,a.C_LINE_COMMENT_MODE,c,{className:"type",begin:"\\b[A-Z][\\w\u00c0-\u02b8']*[!?]"},{className:"type",
-begin:"\\b[A-Z][\\w\u00c0-\u02b8']*",relevance:0},f,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:b,contains:["self",f,e,a.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:b,end:"\\{",excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,
-{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{beginKeywords:"import",end:/$/,contains:[a.C_LINE_COMMENT_MODE,c]}]}}}());
-hljs.registerLanguage("taggerscript",function(){return function(a){return{name:"Tagger Script",contains:[{className:"comment",begin:/\$noop\(/,end:/\)/,contains:[{begin:/\(/,end:/\)/,contains:["self",{begin:/\\./}]}],relevance:10},{className:"keyword",begin:/\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*/,end:/\(/,excludeEnd:!0},{className:"variable",begin:/%[_a-zA-Z0-9:]*/,end:"%"},{className:"symbol",begin:/\\./}]}}}());
-hljs.registerLanguage("yaml",function(){return function(a){var b={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[a.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},c=a.inherit(b,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),d={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:"true false yes no null",relevance:0};a=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},
-{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+![\\w#;/?:@&=+$,.~*\\'()[\\]]+"},{className:"type",begin:"!<[\\w#;/?:@&=+$,.~*\\'()[\\]]+>"},{className:"type",begin:"![\\w#;/?:@&=+$,.~*\\'()[\\]]+"},{className:"type",
-begin:"!![\\w#;/?:@&=+$,.~*\\'()[\\]]+"},{className:"meta",begin:"&"+a.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+a.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},a.HASH_COMMENT_MODE,{beginKeywords:"true false yes no null",keywords:{literal:"true false yes no null"}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:a.C_NUMBER_RE+
-"\\b"},{begin:"{",end:"}",contains:[d],illegal:"\\n",relevance:0},{begin:"\\[",end:"\\]",contains:[d],illegal:"\\n",relevance:0},b];b=[].concat($jscomp.arrayFromIterable(a));b.pop();b.push(c);d.contains=b;return{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:a}}}());
-hljs.registerLanguage("tap",function(){return function(a){return{name:"Test Anything Protocol",case_insensitive:!0,contains:[a.HASH_COMMENT_MODE,{className:"meta",variants:[{begin:"^TAP version (\\d+)$"},{begin:"^1\\.\\.(\\d+)$"}]},{begin:"(s+)?---$",end:"\\.\\.\\.$",subLanguage:"yaml",relevance:0},{className:"number",begin:" (\\d+) "},{className:"symbol",variants:[{begin:"^ok"},{begin:"^not ok"}]}]}}}());
-hljs.registerLanguage("tcl",function(){return function(a){return{name:"Tcl",aliases:["tk"],keywords:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",contains:[a.COMMENT(";[ \\t]*#",
-"$"),a.COMMENT("^[ \\t]*#","$"),{beginKeywords:"proc",end:"[\\{]",excludeEnd:!0,contains:[{className:"title",begin:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",end:"[ \\t\\n\\r]",endsWithParent:!0,excludeEnd:!0}]},{excludeEnd:!0,variants:[{begin:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)",end:"[^a-zA-Z0-9_\\}\\$]"},{begin:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",end:"(\\))?[^a-zA-Z0-9_\\}\\$]"}]},{className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[a.inherit(a.QUOTE_STRING_MODE,
-{illegal:null})]},{className:"number",variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]}]}}}());
-hljs.registerLanguage("thrift",function(){return function(a){return{name:"Thrift",keywords:{keyword:"namespace const typedef struct enum service exception void oneway set list map required optional",built_in:"bool byte i16 i32 i64 double string binary",literal:"true false"},contains:[a.QUOTE_STRING_MODE,a.NUMBER_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"class",beginKeywords:"struct enum service exception",end:/\{/,illegal:/\n/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,
-excludeEnd:!0}})]},{begin:"\\b(set|list|map)\\s*<",end:">",keywords:"bool byte i16 i32 i64 double string binary",contains:["self"]}]}}}());
-hljs.registerLanguage("tp",function(){return function(a){var b={className:"number",begin:"[1-9][0-9]*",relevance:0},c={className:"symbol",begin:":[^\\]]+"};return{name:"TP",keywords:{keyword:"ABORT ACC ADJUST AND AP_LD BREAK CALL CNT COL CONDITION CONFIG DA DB DIV DETECT ELSE END ENDFOR ERR_NUM ERROR_PROG FINE FOR GP GUARD INC IF JMP LINEAR_MAX_SPEED LOCK MOD MONITOR OFFSET Offset OR OVERRIDE PAUSE PREG PTH RT_LD RUN SELECT SKIP Skip TA TB TO TOOL_OFFSET Tool_Offset UF UT UFRAME_NUM UTOOL_NUM UNLOCK WAIT X Y Z W P R STRLEN SUBSTR FINDSTR VOFFSET PROG ATTR MN POS",literal:"ON OFF max_speed LPOS JPOS ENABLE DISABLE START STOP RESET"},
-contains:[{className:"built_in",begin:"(AR|P|PAYLOAD|PR|R|SR|RSR|LBL|VR|UALM|MESSAGE|UTOOL|UFRAME|TIMER|TIMER_OVERFLOW|JOINT_MAX_SPEED|RESUME_PROG|DIAG_REC)\\[",end:"\\]",contains:["self",b,c]},{className:"built_in",begin:"(AI|AO|DI|DO|F|RI|RO|UI|UO|GI|GO|SI|SO)\\[",end:"\\]",contains:["self",b,a.QUOTE_STRING_MODE,c]},{className:"keyword",begin:"/(PROG|ATTR|MN|POS|END)\\b"},{className:"keyword",begin:"(CALL|RUN|POINT_LOGIC|LBL)\\b"},{className:"keyword",begin:"\\b(ACC|CNT|Skip|Offset|PSPD|RT_LD|AP_LD|Tool_Offset)"},
-{className:"number",begin:"\\d+(sec|msec|mm/sec|cm/min|inch/min|deg/sec|mm|in|cm)?\\b",relevance:0},a.COMMENT("//","[;$]"),a.COMMENT("!","[;$]"),a.COMMENT("--eg:","$"),a.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"'"},a.C_NUMBER_MODE,{className:"variable",begin:"\\$[A-Za-z0-9_]+"}]}}}());
-hljs.registerLanguage("twig",function(){return function(a){var b={beginKeywords:"attribute block constant cycle date dump include max min parent random range source template_from_string",keywords:{name:"attribute block constant cycle date dump include max min parent random range source template_from_string"},relevance:0,contains:[{className:"params",begin:"\\(",end:"\\)"}]},c={begin:/\|[A-Za-z_]+:?/,keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",
-contains:[b]},d="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with";d=d+" "+d.split(" ").map(function(a){return"end"+a}).join(" ");return{name:"Twig",aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",contains:[a.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:d,starts:{endsWithParent:!0,contains:[c,b],relevance:0}}]},{className:"template-variable",
-begin:/\{\{/,end:/}}/,contains:["self",c,b]}]}}}());
-hljs.registerLanguage("typescript",function(){var a="as in of if for while finally var new function do return void else break catch instanceof with throw case default try switch continue typeof delete let yield const class debugger async await static import from export extends".split(" "),b="true false null undefined NaN Infinity".split(" "),c=[].concat("setInterval setTimeout clearInterval clearTimeout require exports eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape".split(" "),"arguments this super console window document localStorage module global".split(" "),
-"Intl DataView Number Math Date String RegExp Object Function Boolean Error Symbol Set Map WeakSet WeakMap Proxy Reflect JSON Promise Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Float32Array Array Uint8Array Uint8ClampedArray ArrayBuffer".split(" "),"EvalError InternalError RangeError ReferenceError SyntaxError TypeError URIError".split(" "));return function(d){var e={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:a.concat("type namespace typedef interface public private protected implements declare abstract".split(" ")).join(" "),
-literal:b.join(" "),built_in:c.concat("any void number boolean string object never enum".split(" ")).join(" ")},f={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},h={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:d.C_NUMBER_RE+"n?"}],relevance:0},g={className:"subst",begin:"\\$\\{",end:"\\}",keywords:e,contains:[]},k={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[d.BACKSLASH_ESCAPE,g],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",
-returnEnd:!1,contains:[d.BACKSLASH_ESCAPE,g],subLanguage:"css"}},m={className:"string",begin:"`",end:"`",contains:[d.BACKSLASH_ESCAPE,g]};g.contains=[d.APOS_STRING_MODE,d.QUOTE_STRING_MODE,k,l,m,h,d.REGEXP_MODE];g={begin:"\\(",end:/\)/,keywords:e,contains:["self",d.QUOTE_STRING_MODE,d.APOS_STRING_MODE,d.NUMBER_MODE]};var p={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:e,contains:[d.C_LINE_COMMENT_MODE,d.C_BLOCK_COMMENT_MODE,f,g]};return{name:"TypeScript",aliases:["ts"],
-keywords:e,contains:[d.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},d.APOS_STRING_MODE,d.QUOTE_STRING_MODE,k,l,m,d.C_LINE_COMMENT_MODE,d.C_BLOCK_COMMENT_MODE,h,{begin:"("+d.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[d.C_LINE_COMMENT_MODE,d.C_BLOCK_COMMENT_MODE,d.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+d.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:d.UNDERSCORE_IDENT_RE},
-{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:e,contains:g.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:e,contains:["self",d.inherit(d.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),p],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",p]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},
-{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+d.IDENT_RE,relevance:0},f,g]}}}());
-hljs.registerLanguage("vala",function(){return function(a){return{name:"Vala",keywords:{keyword:"char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double bool struct enum string void weak unowned owned async signal static abstract interface override virtual delegate if while do for foreach else switch case break default return try catch public private protected internal using new this get set const stdout stdin stderr var",built_in:"DBus GLib CCode Gee Object Gtk Posix",
-literal:"false true null"},contains:[{className:"class",beginKeywords:"class interface namespace",end:"{",excludeEnd:!0,illegal:"[^,:\\n\\s\\.]",contains:[a.UNDERSCORE_TITLE_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",begin:'"""',end:'"""',relevance:5},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,{className:"meta",begin:"^#",end:"$",relevance:2}]}}}());
-hljs.registerLanguage("vbnet",function(){return function(a){return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0,keywords:{keyword:"addhandler addressof alias and andalso aggregate ansi as async assembly auto await binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue iterator join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass nameof namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor yield",
-built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},illegal:"//|{|}|endif|gosub|variant|wend|^\\$ ",contains:[a.inherit(a.QUOTE_STRING_MODE,{contains:[{begin:'""'}]}),a.COMMENT("'","$",{returnBegin:!0,contains:[{className:"doctag",begin:"'''|\x3c!--|--\x3e",contains:[a.PHRASAL_WORDS_MODE]},
-{className:"doctag",begin:"</?",end:">",contains:[a.PHRASAL_WORDS_MODE]}]}),a.C_NUMBER_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elseif end region externalsource"}}]}}}());
-hljs.registerLanguage("vbscript",function(){return function(a){return{name:"VBScript",aliases:["vbs"],case_insensitive:!0,keywords:{keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",built_in:"lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion scriptengine split scriptengineminorversion cint sin datepart ltrim sqr scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw chrw regexp server response request cstr err",
-literal:"true false null nothing empty"},illegal:"//",contains:[a.inherit(a.QUOTE_STRING_MODE,{contains:[{begin:'""'}]}),a.COMMENT(/'/,/$/,{relevance:0}),a.C_NUMBER_MODE]}}}());hljs.registerLanguage("vbscript-html",function(){return function(a){return{name:"VBScript in HTML",subLanguage:"xml",contains:[{begin:"<%",end:"%>",subLanguage:"vbscript"}]}}}());
-hljs.registerLanguage("verilog",function(){return function(a){return{name:"Verilog",aliases:["v","sv","svh"],case_insensitive:!1,keywords:{$pattern:/[\w\$]+/,keyword:"accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind bins binsof bit break buf|0 bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin function generate|5 genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import incdir include initial inout input inside instance int integer interconnect interface intersect join join_any join_none large let liblist library local localparam logic longint macromodule matches medium modport module nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 or output package packed parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor",
-literal:"null",built_in:"$finish $stop $exit $fatal $error $warning $info $realtime $time $printtimescale $bitstoreal $bitstoshortreal $itor $signed $cast $bits $stime $timeformat $realtobits $shortrealtobits $rtoi $unsigned $asserton $assertkill $assertpasson $assertfailon $assertnonvacuouson $assertoff $assertcontrol $assertpassoff $assertfailoff $assertvacuousoff $isunbounded $sampled $fell $changed $past_gclk $fell_gclk $changed_gclk $rising_gclk $steady_gclk $coverage_control $coverage_get $coverage_save $set_coverage_db_name $rose $stable $past $rose_gclk $stable_gclk $future_gclk $falling_gclk $changing_gclk $display $coverage_get_max $coverage_merge $get_coverage $load_coverage_db $typename $unpacked_dimensions $left $low $increment $clog2 $ln $log10 $exp $sqrt $pow $floor $ceil $sin $cos $tan $countbits $onehot $isunknown $fatal $warning $dimensions $right $high $size $asin $acos $atan $atan2 $hypot $sinh $cosh $tanh $asinh $acosh $atanh $countones $onehot0 $error $info $random $dist_chi_square $dist_erlang $dist_exponential $dist_normal $dist_poisson $dist_t $dist_uniform $q_initialize $q_remove $q_exam $async$and$array $async$nand$array $async$or$array $async$nor$array $sync$and$array $sync$nand$array $sync$or$array $sync$nor$array $q_add $q_full $psprintf $async$and$plane $async$nand$plane $async$or$plane $async$nor$plane $sync$and$plane $sync$nand$plane $sync$or$plane $sync$nor$plane $system $display $displayb $displayh $displayo $strobe $strobeb $strobeh $strobeo $write $readmemb $readmemh $writememh $value$plusargs $dumpvars $dumpon $dumplimit $dumpports $dumpportson $dumpportslimit $writeb $writeh $writeo $monitor $monitorb $monitorh $monitoro $writememb $dumpfile $dumpoff $dumpall $dumpflush $dumpportsoff $dumpportsall $dumpportsflush $fclose $fdisplay $fdisplayb $fdisplayh $fdisplayo $fstrobe $fstrobeb $fstrobeh $fstrobeo $swrite $swriteb $swriteh $swriteo $fscanf $fread $fseek $fflush $feof $fopen $fwrite $fwriteb $fwriteh $fwriteo $fmonitor $fmonitorb $fmonitorh $fmonitoro $sformat $sformatf $fgetc $ungetc $fgets $sscanf $rewind $ftell $ferror"},
-contains:[a.C_BLOCK_COMMENT_MODE,a.C_LINE_COMMENT_MODE,a.QUOTE_STRING_MODE,{className:"number",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"\\b((\\d+'(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)"},{begin:"\\B(('(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)"},{begin:"\\b([0-9_])+",relevance:0}]},{className:"variable",variants:[{begin:"#\\((?!parameter).+\\)"},{begin:"\\.\\w+",relevance:0}]},{className:"meta",begin:"`",end:"$",keywords:{"meta-keyword":"define __FILE__ __LINE__ begin_keywords celldefine default_nettype define else elsif end_keywords endcelldefine endif ifdef ifndef include line nounconnected_drive pragma resetall timescale unconnected_drive undef undefineall"},
-relevance:0}]}}}());
-hljs.registerLanguage("vhdl",function(){return function(a){return{name:"VHDL",case_insensitive:!0,keywords:{keyword:"abs access after alias all and architecture array assert assume assume_guarantee attribute begin block body buffer bus case component configuration constant context cover disconnect downto default else elsif end entity exit fairness file for force function generate generic group guarded if impure in inertial inout is label library linkage literal loop map mod nand new next nor not null of on open or others out package parameter port postponed procedure process property protected pure range record register reject release rem report restrict restrict_guarantee return rol ror select sequence severity shared signal sla sll sra srl strong subtype then to transport type unaffected units until use variable view vmode vprop vunit wait when while with xnor xor",built_in:"boolean bit character integer time delay_length natural positive string bit_vector file_open_kind file_open_status std_logic std_logic_vector unsigned signed boolean_vector integer_vector std_ulogic std_ulogic_vector unresolved_unsigned u_unsigned unresolved_signed u_signed real_vector time_vector",
-literal:"false true note warning error failure line text side width"},illegal:"{",contains:[a.C_BLOCK_COMMENT_MODE,a.COMMENT("--","$"),a.QUOTE_STRING_MODE,{className:"number",begin:"\\b(\\d(_|\\d)*#\\w+(\\.\\w+)?#([eE][-+]?\\d(_|\\d)*)?|\\d(_|\\d)*(\\.\\d(_|\\d)*)?([eE][-+]?\\d(_|\\d)*)?)",relevance:0},{className:"string",begin:"'(U|X|0|1|Z|W|L|H|-)'",contains:[a.BACKSLASH_ESCAPE]},{className:"symbol",begin:"'[A-Za-z](_?[A-Za-z0-9])*",contains:[a.BACKSLASH_ESCAPE]}]}}}());
-hljs.registerLanguage("vim",function(){return function(a){return{name:"Vim Script",keywords:{$pattern:/[!#@\w]+/,keyword:"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu go gr grepa gu gv ha helpf helpg helpt hi hid his ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf quita qa rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank",
-built_in:"synIDtrans atan2 range matcharg did_filetype asin feedkeys xor argv complete_check add getwinposx getqflist getwinposy screencol clearmatches empty extend getcmdpos mzeval garbagecollect setreg ceil sqrt diff_hlID inputsecret get getfperm getpid filewritable shiftwidth max sinh isdirectory synID system inputrestore winline atan visualmode inputlist tabpagewinnr round getregtype mapcheck hasmapto histdel argidx findfile sha256 exists toupper getcmdline taglist string getmatches bufnr strftime winwidth bufexists strtrans tabpagebuflist setcmdpos remote_read printf setloclist getpos getline bufwinnr float2nr len getcmdtype diff_filler luaeval resolve libcallnr foldclosedend reverse filter has_key bufname str2float strlen setline getcharmod setbufvar index searchpos shellescape undofile foldclosed setqflist buflisted strchars str2nr virtcol floor remove undotree remote_expr winheight gettabwinvar reltime cursor tabpagenr finddir localtime acos getloclist search tanh matchend rename gettabvar strdisplaywidth type abs py3eval setwinvar tolower wildmenumode log10 spellsuggest bufloaded synconcealed nextnonblank server2client complete settabwinvar executable input wincol setmatches getftype hlID inputsave searchpair or screenrow line settabvar histadd deepcopy strpart remote_peek and eval getftime submatch screenchar winsaveview matchadd mkdir screenattr getfontname libcall reltimestr getfsize winnr invert pow getbufline byte2line soundfold repeat fnameescape tagfiles sin strwidth spellbadword trunc maparg log lispindent hostname setpos globpath remote_foreground getchar synIDattr fnamemodify cscope_connection stridx winbufnr indent min complete_add nr2char searchpairpos inputdialog values matchlist items hlexists strridx browsedir expand fmod pathshorten line2byte argc count getwinvar glob foldtextresult getreg foreground cosh matchdelete has char2nr simplify histget searchdecl iconv winrestcmd pumvisible writefile foldlevel haslocaldir keys cos matchstr foldtext histnr tan tempname getcwd byteidx getbufvar islocked escape eventhandler remote_send serverlist winrestview synstack pyeval prevnonblank readfile cindent filereadable changenr exp"},
-illegal:/;/,contains:[a.NUMBER_MODE,{className:"string",begin:"'",end:"'",illegal:"\\n"},{className:"string",begin:/"(\\"|\n\\|[^"\n])*"/},a.COMMENT('"',"$"),{className:"variable",begin:/[bwtglsav]:[\w\d_]*/},{className:"function",beginKeywords:"function function!",end:"$",relevance:0,contains:[a.TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]},{className:"symbol",begin:/<[\w-]+>/}]}}}());
-hljs.registerLanguage("x86asm",function(){return function(a){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+a.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",
+built_in:"some all not if then else true fail false try catch catch_any semidet_true semidet_false semidet_fail impure_true impure semipure"
+},contains:[{className:"built_in",variants:[{begin:"<=>"},{begin:"<=",
+relevance:0},{begin:"=>",relevance:0},{begin:"/\\\\"},{begin:"\\\\/"}]},{
+className:"built_in",variants:[{begin:":-\\|--\x3e"},{begin:"=",relevance:0}]
+},i,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"0'.\\|0[box][0-9a-fA-F]*"
+},e.NUMBER_MODE,n,r,{begin:/:-/},{begin:/\.$/}]}}})());
+hljs.registerLanguage("mipsasm",(()=>{"use strict";return e=>({
+name:"MIPS Assembly",case_insensitive:!0,aliases:["mips"],keywords:{
+$pattern:"\\.?"+e.IDENT_RE,
+meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .ltorg ",
+built_in:"$0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $30 $31 zero at v0 v1 a0 a1 a2 a3 a4 a5 a6 a7 t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 s0 s1 s2 s3 s4 s5 s6 s7 s8 k0 k1 gp sp fp ra $f0 $f1 $f2 $f2 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 Context Random EntryLo0 EntryLo1 Context PageMask Wired EntryHi HWREna BadVAddr Count Compare SR IntCtl SRSCtl SRSMap Cause EPC PRId EBase Config Config1 Config2 Config3 LLAddr Debug DEPC DESAVE CacheErr ECC ErrorEPC TagLo DataLo TagHi DataHi WatchLo WatchHi PerfCtl PerfCnt "
+},contains:[{className:"keyword",
+begin:"\\b(addi?u?|andi?|b(al)?|beql?|bgez(al)?l?|bgtzl?|blezl?|bltz(al)?l?|bnel?|cl[oz]|divu?|ext|ins|j(al)?|jalr(\\.hb)?|jr(\\.hb)?|lbu?|lhu?|ll|lui|lw[lr]?|maddu?|mfhi|mflo|movn|movz|move|msubu?|mthi|mtlo|mul|multu?|nop|nor|ori?|rotrv?|sb|sc|se[bh]|sh|sllv?|slti?u?|srav?|srlv?|subu?|sw[lr]?|xori?|wsbh|abs\\.[sd]|add\\.[sd]|alnv.ps|bc1[ft]l?|c\\.(s?f|un|u?eq|[ou]lt|[ou]le|ngle?|seq|l[et]|ng[et])\\.[sd]|(ceil|floor|round|trunc)\\.[lw]\\.[sd]|cfc1|cvt\\.d\\.[lsw]|cvt\\.l\\.[dsw]|cvt\\.ps\\.s|cvt\\.s\\.[dlw]|cvt\\.s\\.p[lu]|cvt\\.w\\.[dls]|div\\.[ds]|ldx?c1|luxc1|lwx?c1|madd\\.[sd]|mfc1|mov[fntz]?\\.[ds]|msub\\.[sd]|mth?c1|mul\\.[ds]|neg\\.[ds]|nmadd\\.[ds]|nmsub\\.[ds]|p[lu][lu]\\.ps|recip\\.fmt|r?sqrt\\.[ds]|sdx?c1|sub\\.[ds]|suxc1|swx?c1|break|cache|d?eret|[de]i|ehb|mfc0|mtc0|pause|prefx?|rdhwr|rdpgpr|sdbbp|ssnop|synci?|syscall|teqi?|tgei?u?|tlb(p|r|w[ir])|tlti?u?|tnei?|wait|wrpgpr)",
+end:"\\s"
+},e.COMMENT("[;#](?!\\s*$)","$"),e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,{
+className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",
+begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{
+begin:"0x[0-9a-f]+"},{begin:"\\b-?\\d+"}],relevance:0},{className:"symbol",
+variants:[{begin:"^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^\\s*[0-9]+:"},{
+begin:"[0-9]+[bf]"}],relevance:0}],illegal:/\//})})());
+hljs.registerLanguage("mizar",(()=>{"use strict";return e=>({name:"Mizar",
+keywords:"environ vocabularies notations constructors definitions registrations theorems schemes requirements begin end definition registration cluster existence pred func defpred deffunc theorem proof let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from be being by means equals implies iff redefine define now not or attr is mode suppose per cases set thesis contradiction scheme reserve struct correctness compatibility coherence symmetry assymetry reflexivity irreflexivity connectedness uniqueness commutativity idempotence involutiveness projectivity",
+contains:[e.COMMENT("::","$")]})})());
+hljs.registerLanguage("perl",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}function t(...n){
+return"("+n.map((n=>e(n))).join("|")+")"}return e=>{
+const r=/[dualxmsipngr]{0,12}/,s={$pattern:/[\w.]+/,
+keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0"
+},i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:s},a={begin:/->\{/,
+end:/\}/},o={variants:[{begin:/\$\d/},{
+begin:n(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])")
+},{begin:/[$%@][^\s\w{]/,relevance:0}]
+},c=[e.BACKSLASH_ESCAPE,i,o],g=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],l=(e,t,s="\\1")=>{
+const i="\\1"===s?s:n(s,t)
+;return n(n("(?:",e,")"),t,/(?:\\.|[^\\\/])*?/,i,/(?:\\.|[^\\\/])*?/,s,r)
+},d=(e,t,s)=>n(n("(?:",e,")"),t,/(?:\\.|[^\\\/])*?/,s,r),p=[o,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{
+endsWithParent:!0}),a,{className:"string",contains:c,variants:[{
+begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",
+end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{
+begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">",
+relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",
+contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",
+contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{
+begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number",
+begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",
+relevance:0},{
+begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",
+keywords:"split return print reverse grep",relevance:0,
+contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{
+begin:l("s|tr|y",t(...g))},{begin:l("s|tr|y","\\(","\\)")},{
+begin:l("s|tr|y","\\[","\\]")},{begin:l("s|tr|y","\\{","\\}")}],relevance:2},{
+className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{
+begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",t(...g),/\1/)},{
+begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{
+begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub",
+end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{
+begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",
+subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]
+}];return i.contains=p,a.contains=p,{name:"Perl",aliases:["pl","pm"],keywords:s,
+contains:p}}})());
+hljs.registerLanguage("mojolicious",(()=>{"use strict";return e=>({
+name:"Mojolicious",subLanguage:"xml",contains:[{className:"meta",
+begin:"^__(END|DATA)__$"},{begin:"^\\s*%{1,2}={0,2}",end:"$",subLanguage:"perl"
+},{begin:"<%{1,2}={0,2}",end:"={0,1}%>",subLanguage:"perl",excludeBegin:!0,
+excludeEnd:!0}]})})());
+hljs.registerLanguage("monkey",(()=>{"use strict";return e=>{const n={
+className:"number",relevance:0,variants:[{begin:"[$][a-fA-F0-9]+"
+},e.NUMBER_MODE]};return{name:"Monkey",case_insensitive:!0,keywords:{
+keyword:"public private property continue exit extern new try catch eachin not abstract final select case default const local global field end if then else elseif endif while wend repeat until forever for to step next return module inline throw import",
+built_in:"DebugLog DebugStop Error Print ACos ACosr ASin ASinr ATan ATan2 ATan2r ATanr Abs Abs Ceil Clamp Clamp Cos Cosr Exp Floor Log Max Max Min Min Pow Sgn Sgn Sin Sinr Sqrt Tan Tanr Seed PI HALFPI TWOPI",
+literal:"true false null and or shl shr mod"},illegal:/\/\*/,
+contains:[e.COMMENT("#rem","#end"),e.COMMENT("'","$",{relevance:0}),{
+className:"function",beginKeywords:"function method",end:"[(=:]|$",illegal:/\n/,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"class",
+beginKeywords:"class interface",end:"$",contains:[{
+beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{
+className:"built_in",begin:"\\b(self|super)\\b"},{className:"meta",
+begin:"\\s*#",end:"$",keywords:{"meta-keyword":"if else elseif endif end then"}
+},{className:"meta",begin:"^\\s*strict\\b"},{beginKeywords:"alias",end:"=",
+contains:[e.UNDERSCORE_TITLE_MODE]},e.QUOTE_STRING_MODE,n]}}})());
+hljs.registerLanguage("moonscript",(()=>{"use strict";return e=>{const n={
+keyword:"if then not for in while do return else elseif break continue switch and or unless when class extends super local import export from using",
+literal:"true false nil",
+built_in:"_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug io math os package string table"
+},s="[A-Za-z$_][0-9A-Za-z$_]*",a={className:"subst",begin:/#\{/,end:/\}/,
+keywords:n},t=[e.inherit(e.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}
+}),{className:"string",variants:[{begin:/'/,end:/'/,
+contains:[e.BACKSLASH_ESCAPE]},{begin:/"/,end:/"/,
+contains:[e.BACKSLASH_ESCAPE,a]}]},{className:"built_in",begin:"@__"+e.IDENT_RE
+},{begin:"@"+e.IDENT_RE},{begin:e.IDENT_RE+"\\\\"+e.IDENT_RE}];a.contains=t
+;const i=e.inherit(e.TITLE_MODE,{begin:s}),r="(\\(.*\\)\\s*)?\\B[-=]>",l={
+className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,
+end:/\)/,keywords:n,contains:["self"].concat(t)}]};return{name:"MoonScript",
+aliases:["moon"],keywords:n,illegal:/\/\*/,
+contains:t.concat([e.COMMENT("--","$"),{className:"function",
+begin:"^\\s*"+s+"\\s*=\\s*"+r,end:"[-=]>",returnBegin:!0,contains:[i,l]},{
+begin:/[\(,:=]\s*/,relevance:0,contains:[{className:"function",begin:r,
+end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",
+beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{
+beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[i]},i]
+},{className:"name",begin:s+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0
+}])}}})());
+hljs.registerLanguage("n1ql",(()=>{"use strict";return e=>({name:"N1QL",
+case_insensitive:!0,contains:[{
+beginKeywords:"build create index delete drop explain infer|10 insert merge prepare select update upsert|10",
+end:/;/,endsWithParent:!0,keywords:{
+keyword:"all alter analyze and any array as asc begin between binary boolean break bucket build by call case cast cluster collate collection commit connect continue correlate cover create database dataset datastore declare decrement delete derived desc describe distinct do drop each element else end every except exclude execute exists explain fetch first flatten for force from function grant group gsi having if ignore ilike in include increment index infer inline inner insert intersect into is join key keys keyspace known last left let letting like limit lsm map mapping matched materialized merge minus namespace nest not number object offset on option or order outer over parse partition password path pool prepare primary private privilege procedure public raw realm reduce rename return returning revoke right role rollback satisfies schema select self semi set show some start statistics string system then to transaction trigger truncate under union unique unknown unnest unset update upsert use user using validate value valued values via view when where while with within work xor",
+literal:"true false null missing|5",
+built_in:"array_agg array_append array_concat array_contains array_count array_distinct array_ifnull array_length array_max array_min array_position array_prepend array_put array_range array_remove array_repeat array_replace array_reverse array_sort array_sum avg count max min sum greatest least ifmissing ifmissingornull ifnull missingif nullif ifinf ifnan ifnanorinf naninf neginfif posinfif clock_millis clock_str date_add_millis date_add_str date_diff_millis date_diff_str date_part_millis date_part_str date_trunc_millis date_trunc_str duration_to_str millis str_to_millis millis_to_str millis_to_utc millis_to_zone_name now_millis now_str str_to_duration str_to_utc str_to_zone_name decode_json encode_json encoded_size poly_length base64 base64_encode base64_decode meta uuid abs acos asin atan atan2 ceil cos degrees e exp ln log floor pi power radians random round sign sin sqrt tan trunc object_length object_names object_pairs object_inner_pairs object_values object_inner_values object_add object_put object_remove object_unwrap regexp_contains regexp_like regexp_position regexp_replace contains initcap length lower ltrim position repeat replace rtrim split substr title trim upper isarray isatom isboolean isnumber isobject isstring type toarray toatom toboolean tonumber toobject tostring"
+},contains:[{className:"string",begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]
+},{className:"string",begin:'"',end:'"',contains:[e.BACKSLASH_ESCAPE]},{
+className:"symbol",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE],relevance:2
+},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE]})})());
+hljs.registerLanguage("nginx",(()=>{"use strict";return e=>{const n={
+className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/\}/},{
+begin:/[$@]/+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{
+$pattern:"[a-z/_]+",
+literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"
+},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",
+contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/
+}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]
+},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",
+end:"\\s|\\{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|\\{|;",returnEnd:!0},{
+begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",
+begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{
+className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{
+name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{
+begin:e.UNDERSCORE_IDENT_RE+"\\s+\\{",returnBegin:!0,end:/\{/,contains:[{
+className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{
+begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|\\{",returnBegin:!0,contains:[{
+className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],
+illegal:"[^\\s\\}]"}}})());
+hljs.registerLanguage("nim",(()=>{"use strict";return e=>({name:"Nim",keywords:{
+keyword:"addr and as asm bind block break case cast const continue converter discard distinct div do elif else end enum except export finally for from func generic if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while with without xor yield",
+literal:"shared guarded stdin stdout stderr result true false",
+built_in:"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 bool char string cstring pointer expr stmt void auto any range array openarray varargs seq set clong culong cchar cschar cshort cint csize clonglong cfloat cdouble clongdouble cuchar cushort cuint culonglong cstringarray semistatic"
+},contains:[{className:"meta",begin:/\{\./,end:/\.\}/,relevance:10},{
+className:"string",begin:/[a-zA-Z]\w*"/,end:/"/,contains:[{begin:/""/}]},{
+className:"string",begin:/([a-zA-Z]\w*)?"""/,end:/"""/},e.QUOTE_STRING_MODE,{
+className:"type",begin:/\b[A-Z]\w+\b/,relevance:0},{className:"number",
+relevance:0,variants:[{
+begin:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/},{
+begin:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/},{
+begin:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/},{
+begin:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/}]},e.HASH_COMMENT_MODE]})})());
+hljs.registerLanguage("nix",(()=>{"use strict";return e=>{const n={
+keyword:"rec with let in inherit assert if else then",
+literal:"true false or and null",
+built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"
+},i={className:"subst",begin:/\$\{/,end:/\}/,keywords:n},s={className:"string",
+contains:[i],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]
+},t=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{
+begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{
+className:"attr",begin:/\S+/}]}];return i.contains=t,{name:"Nix",
+aliases:["nixos"],keywords:n,contains:t}}})());
+hljs.registerLanguage("node-repl",(()=>{"use strict";return e=>({
+name:"Node REPL",contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",
+subLanguage:"javascript"}},variants:[{begin:/^>(?=[ ]|$)/},{
+begin:/^\.\.\.(?=[ ]|$)/}]}]})})());
+hljs.registerLanguage("nsis",(()=>{"use strict";return e=>{const t={
+className:"variable",begin:/\$+\{[\w.:-]+\}/},n={className:"variable",
+begin:/\$+\w+/,illegal:/\(\)\{\}/},i={className:"variable",
+begin:/\$+\([\w^.:-]+\)/},r={className:"string",variants:[{begin:'"',end:'"'},{
+begin:"'",end:"'"},{begin:"`",end:"`"}],illegal:/\n/,contains:[{
+className:"meta",begin:/\$(\\[nrt]|\$)/},{className:"variable",
+begin:/\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)/
+},t,n,i]};return{name:"NSIS",case_insensitive:!1,keywords:{
+keyword:"Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecShellWait ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileWriteUTF16LE FileSeek FileWrite FileWriteByte FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetKnownFolderPath GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfRtlLanguage IfShellVarContextAll IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText Int64Cmp Int64CmpU Int64Fmt IntCmp IntCmpU IntFmt IntOp IntPtrCmp IntPtrCmpU IntPtrOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadAndSetImage LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestLongPathAware ManifestMaxVersionTested ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PEAddResource PEDllCharacteristics PERemoveResource PESubsysVer Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegMultiStr WriteRegNone WriteRegStr WriteUninstaller XPStyle",
+literal:"admin all auto both bottom bzip2 colored components current custom directory false force hide highest ifdiff ifnewer instfiles lastused leave left license listonly lzma nevershow none normal notset off on open print right show silent silentlog smooth textonly top true try un.components un.custom un.directory un.instfiles un.license uninstConfirm user Win10 Win7 Win8 WinVista zlib"
+},contains:[e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.COMMENT(";","$",{
+relevance:0}),{className:"function",
+beginKeywords:"Function PageEx Section SectionGroup",end:"$"},r,{
+className:"keyword",
+begin:/!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversion|gettlbversion|if|ifdef|ifmacrodef|ifmacrondef|ifndef|include|insertmacro|macro|macroend|makensis|packhdr|searchparse|searchreplace|system|tempfile|undef|verbose|warning)/
+},t,n,i,{className:"params",
+begin:"(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)"
+},{className:"class",begin:/\w+::\w+/},e.NUMBER_MODE]}}})());
+hljs.registerLanguage("objectivec",(()=>{"use strict";return e=>{
+const n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,
+keyword:"@interface @class @protocol @implementation"};return{
+name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"],
+keywords:{$pattern:n,
+keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",
+literal:"false true FALSE TRUE nil YES NO NULL",
+built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"
+},illegal:"</",contains:[{className:"built_in",
+begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{
+className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,
+keywords:{
+"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{
+className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,
+illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{
+className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:/(\{|$)/,
+excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{
+begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}})());
+hljs.registerLanguage("ocaml",(()=>{"use strict";return e=>({name:"OCaml",
+aliases:["ml"],keywords:{$pattern:"[a-z_]\\w*!?",
+keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",
+built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",
+literal:"true false"},illegal:/\/\/|>>/,contains:[{className:"literal",
+begin:"\\[(\\|\\|)?\\]|\\(\\)",relevance:0},e.COMMENT("\\(\\*","\\*\\)",{
+contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{
+className:"type",begin:"`[A-Z][\\w']*"},{className:"type",
+begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*",relevance:0
+},e.inherit(e.APOS_STRING_MODE,{className:"string",relevance:0
+}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{className:"number",
+begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",
+relevance:0},{begin:/->/}]})})());
+hljs.registerLanguage("openscad",(()=>{"use strict";return e=>{const n={
+className:"keyword",begin:"\\$(f[asn]|t|vp[rtd]|children)"},r={
+className:"number",begin:"\\b\\d+(\\.\\d+)?(e-?\\d+)?",relevance:0
+},s=e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),a={className:"function",
+beginKeywords:"module function",end:/=|\{/,contains:[{className:"params",
+begin:"\\(",end:"\\)",contains:["self",r,s,n,{className:"literal",
+begin:"false|true|PI|undef"}]},e.UNDERSCORE_TITLE_MODE]};return{name:"OpenSCAD",
+aliases:["scad"],keywords:{
+keyword:"function module include use for intersection_for if else \\%",
+literal:"false true PI undef",
+built_in:"circle square polygon text sphere cube cylinder polyhedron translate rotate scale resize mirror multmatrix color offset hull minkowski union difference intersection abs sign sin cos tan acos asin atan atan2 floor round ceil ln log pow sqrt exp rands min max concat lookup str chr search version version_num norm cross parent_module echo import import_dxf dxf_linear_extrude linear_extrude rotate_extrude surface projection render children dxf_cross dxf_dim let assign"
+},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,{className:"meta",
+keywords:{"meta-keyword":"include use"},begin:"include|use <",end:">"},s,n,{
+begin:"[*!#%]",relevance:0},a]}}})());
+hljs.registerLanguage("oxygene",(()=>{"use strict";return e=>{const n={
+$pattern:/\.?\w+/,
+keyword:"abstract add and array as asc aspect assembly async begin break block by case class concat const copy constructor continue create default delegate desc distinct div do downto dynamic each else empty end ensure enum equals event except exit extension external false final finalize finalizer finally flags for forward from function future global group has if implementation implements implies in index inherited inline interface into invariants is iterator join locked locking loop matching method mod module namespace nested new nil not notify nullable of old on operator or order out override parallel params partial pinned private procedure property protected public queryable raise read readonly record reintroduce remove repeat require result reverse sealed select self sequence set shl shr skip static step soft take then to true try tuple type union unit unsafe until uses using var virtual raises volatile where while with write xor yield await mapped deprecated stdcall cdecl pascal register safecall overload library platform reference packed strict published autoreleasepool selector strong weak unretained"
+},r=e.COMMENT(/\{/,/\}/,{relevance:0}),a=e.COMMENT("\\(\\*","\\*\\)",{
+relevance:10}),t={className:"string",begin:"'",end:"'",contains:[{begin:"''"}]
+},s={className:"string",begin:"(#\\d+)+"},i={className:"function",
+beginKeywords:"function constructor destructor procedure method",end:"[:;]",
+keywords:"function constructor|10 destructor|10 procedure|10 method|10",
+contains:[e.TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",keywords:n,
+contains:[t,s]},r,a]};return{name:"Oxygene",case_insensitive:!0,keywords:n,
+illegal:'("|\\$[G-Zg-z]|\\/\\*|</|=>|->)',
+contains:[r,a,e.C_LINE_COMMENT_MODE,t,s,e.NUMBER_MODE,i,{className:"class",
+begin:"=\\bclass\\b",end:"end;",keywords:n,
+contains:[t,s,r,a,e.C_LINE_COMMENT_MODE,i]}]}}})());
+hljs.registerLanguage("parser3",(()=>{"use strict";return e=>{
+const a=e.COMMENT(/\{/,/\}/,{contains:["self"]});return{name:"Parser3",
+subLanguage:"xml",relevance:0,
+contains:[e.COMMENT("^#","$"),e.COMMENT(/\^rem\{/,/\}/,{relevance:10,
+contains:[a]}),{className:"meta",begin:"^@(?:BASE|USE|CLASS|OPTIONS)$",
+relevance:10},{className:"title",
+begin:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{
+className:"variable",begin:/\$\{?[\w\-.:]+\}?/},{className:"keyword",
+begin:/\^[\w\-.:]+/},{className:"number",begin:"\\^#[0-9a-fA-F]+"
+},e.C_NUMBER_MODE]}}})());
+hljs.registerLanguage("pf",(()=>{"use strict";return t=>({
+name:"Packet Filter config",aliases:["pf.conf"],keywords:{
+$pattern:/[a-z0-9_<>-]+/,
+built_in:"block match pass load anchor|5 antispoof|10 set table",
+keyword:"in out log quick on rdomain inet inet6 proto from port os to route allow-opts divert-packet divert-reply divert-to flags group icmp-type icmp6-type label once probability recieved-on rtable prio queue tos tag tagged user keep fragment for os drop af-to|10 binat-to|10 nat-to|10 rdr-to|10 bitmask least-stats random round-robin source-hash static-port dup-to reply-to route-to parent bandwidth default min max qlimit block-policy debug fingerprints hostid limit loginterface optimization reassemble ruleset-optimization basic none profile skip state-defaults state-policy timeout const counters persist no modulate synproxy state|5 floating if-bound no-sync pflow|10 sloppy source-track global rule max-src-nodes max-src-states max-src-conn max-src-conn-rate overload flush scrub|5 max-mss min-ttl no-df|10 random-id",
+literal:"all any no-route self urpf-failed egress|5 unknown"},
+contains:[t.HASH_COMMENT_MODE,t.NUMBER_MODE,t.QUOTE_STRING_MODE,{
+className:"variable",begin:/\$[\w\d#@][\w\d_]*/},{className:"variable",
+begin:/<(?!\/)/,end:/>/}]})})());
+hljs.registerLanguage("pgsql",(()=>{"use strict";return E=>{
+const T=E.COMMENT("--","$"),N="\\$([a-zA-Z_]?|[a-zA-Z_][a-zA-Z_0-9]*)\\$",A="BIGINT INT8 BIGSERIAL SERIAL8 BIT VARYING VARBIT BOOLEAN BOOL BOX BYTEA CHARACTER CHAR VARCHAR CIDR CIRCLE DATE DOUBLE PRECISION FLOAT8 FLOAT INET INTEGER INT INT4 INTERVAL JSON JSONB LINE LSEG|10 MACADDR MACADDR8 MONEY NUMERIC DEC DECIMAL PATH POINT POLYGON REAL FLOAT4 SMALLINT INT2 SMALLSERIAL|10 SERIAL2|10 SERIAL|10 SERIAL4|10 TEXT TIME ZONE TIMETZ|10 TIMESTAMP TIMESTAMPTZ|10 TSQUERY|10 TSVECTOR|10 TXID_SNAPSHOT|10 UUID XML NATIONAL NCHAR INT4RANGE|10 INT8RANGE|10 NUMRANGE|10 TSRANGE|10 TSTZRANGE|10 DATERANGE|10 ANYELEMENT ANYARRAY ANYNONARRAY ANYENUM ANYRANGE CSTRING INTERNAL RECORD PG_DDL_COMMAND VOID UNKNOWN OPAQUE REFCURSOR NAME OID REGPROC|10 REGPROCEDURE|10 REGOPER|10 REGOPERATOR|10 REGCLASS|10 REGTYPE|10 REGROLE|10 REGNAMESPACE|10 REGCONFIG|10 REGDICTIONARY|10 ",R=A.trim().split(" ").map((E=>E.split("|")[0])).join("|"),I="ARRAY_AGG AVG BIT_AND BIT_OR BOOL_AND BOOL_OR COUNT EVERY JSON_AGG JSONB_AGG JSON_OBJECT_AGG JSONB_OBJECT_AGG MAX MIN MODE STRING_AGG SUM XMLAGG CORR COVAR_POP COVAR_SAMP REGR_AVGX REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY REGR_SYY STDDEV STDDEV_POP STDDEV_SAMP VARIANCE VAR_POP VAR_SAMP PERCENTILE_CONT PERCENTILE_DISC ROW_NUMBER RANK DENSE_RANK PERCENT_RANK CUME_DIST NTILE LAG LEAD FIRST_VALUE LAST_VALUE NTH_VALUE NUM_NONNULLS NUM_NULLS ABS CBRT CEIL CEILING DEGREES DIV EXP FLOOR LN LOG MOD PI POWER RADIANS ROUND SCALE SIGN SQRT TRUNC WIDTH_BUCKET RANDOM SETSEED ACOS ACOSD ASIN ASIND ATAN ATAND ATAN2 ATAN2D COS COSD COT COTD SIN SIND TAN TAND BIT_LENGTH CHAR_LENGTH CHARACTER_LENGTH LOWER OCTET_LENGTH OVERLAY POSITION SUBSTRING TREAT TRIM UPPER ASCII BTRIM CHR CONCAT CONCAT_WS CONVERT CONVERT_FROM CONVERT_TO DECODE ENCODE INITCAP LEFT LENGTH LPAD LTRIM MD5 PARSE_IDENT PG_CLIENT_ENCODING QUOTE_IDENT|10 QUOTE_LITERAL|10 QUOTE_NULLABLE|10 REGEXP_MATCH REGEXP_MATCHES REGEXP_REPLACE REGEXP_SPLIT_TO_ARRAY REGEXP_SPLIT_TO_TABLE REPEAT REPLACE REVERSE RIGHT RPAD RTRIM SPLIT_PART STRPOS SUBSTR TO_ASCII TO_HEX TRANSLATE OCTET_LENGTH GET_BIT GET_BYTE SET_BIT SET_BYTE TO_CHAR TO_DATE TO_NUMBER TO_TIMESTAMP AGE CLOCK_TIMESTAMP|10 DATE_PART DATE_TRUNC ISFINITE JUSTIFY_DAYS JUSTIFY_HOURS JUSTIFY_INTERVAL MAKE_DATE MAKE_INTERVAL|10 MAKE_TIME MAKE_TIMESTAMP|10 MAKE_TIMESTAMPTZ|10 NOW STATEMENT_TIMESTAMP|10 TIMEOFDAY TRANSACTION_TIMESTAMP|10 ENUM_FIRST ENUM_LAST ENUM_RANGE AREA CENTER DIAMETER HEIGHT ISCLOSED ISOPEN NPOINTS PCLOSE POPEN RADIUS WIDTH BOX BOUND_BOX CIRCLE LINE LSEG PATH POLYGON ABBREV BROADCAST HOST HOSTMASK MASKLEN NETMASK NETWORK SET_MASKLEN TEXT INET_SAME_FAMILY INET_MERGE MACADDR8_SET7BIT ARRAY_TO_TSVECTOR GET_CURRENT_TS_CONFIG NUMNODE PLAINTO_TSQUERY PHRASETO_TSQUERY WEBSEARCH_TO_TSQUERY QUERYTREE SETWEIGHT STRIP TO_TSQUERY TO_TSVECTOR JSON_TO_TSVECTOR JSONB_TO_TSVECTOR TS_DELETE TS_FILTER TS_HEADLINE TS_RANK TS_RANK_CD TS_REWRITE TSQUERY_PHRASE TSVECTOR_TO_ARRAY TSVECTOR_UPDATE_TRIGGER TSVECTOR_UPDATE_TRIGGER_COLUMN XMLCOMMENT XMLCONCAT XMLELEMENT XMLFOREST XMLPI XMLROOT XMLEXISTS XML_IS_WELL_FORMED XML_IS_WELL_FORMED_DOCUMENT XML_IS_WELL_FORMED_CONTENT XPATH XPATH_EXISTS XMLTABLE XMLNAMESPACES TABLE_TO_XML TABLE_TO_XMLSCHEMA TABLE_TO_XML_AND_XMLSCHEMA QUERY_TO_XML QUERY_TO_XMLSCHEMA QUERY_TO_XML_AND_XMLSCHEMA CURSOR_TO_XML CURSOR_TO_XMLSCHEMA SCHEMA_TO_XML SCHEMA_TO_XMLSCHEMA SCHEMA_TO_XML_AND_XMLSCHEMA DATABASE_TO_XML DATABASE_TO_XMLSCHEMA DATABASE_TO_XML_AND_XMLSCHEMA XMLATTRIBUTES TO_JSON TO_JSONB ARRAY_TO_JSON ROW_TO_JSON JSON_BUILD_ARRAY JSONB_BUILD_ARRAY JSON_BUILD_OBJECT JSONB_BUILD_OBJECT JSON_OBJECT JSONB_OBJECT JSON_ARRAY_LENGTH JSONB_ARRAY_LENGTH JSON_EACH JSONB_EACH JSON_EACH_TEXT JSONB_EACH_TEXT JSON_EXTRACT_PATH JSONB_EXTRACT_PATH JSON_OBJECT_KEYS JSONB_OBJECT_KEYS JSON_POPULATE_RECORD JSONB_POPULATE_RECORD JSON_POPULATE_RECORDSET JSONB_POPULATE_RECORDSET JSON_ARRAY_ELEMENTS JSONB_ARRAY_ELEMENTS JSON_ARRAY_ELEMENTS_TEXT JSONB_ARRAY_ELEMENTS_TEXT JSON_TYPEOF JSONB_TYPEOF JSON_TO_RECORD JSONB_TO_RECORD JSON_TO_RECORDSET JSONB_TO_RECORDSET JSON_STRIP_NULLS JSONB_STRIP_NULLS JSONB_SET JSONB_INSERT JSONB_PRETTY CURRVAL LASTVAL NEXTVAL SETVAL COALESCE NULLIF GREATEST LEAST ARRAY_APPEND ARRAY_CAT ARRAY_NDIMS ARRAY_DIMS ARRAY_FILL ARRAY_LENGTH ARRAY_LOWER ARRAY_POSITION ARRAY_POSITIONS ARRAY_PREPEND ARRAY_REMOVE ARRAY_REPLACE ARRAY_TO_STRING ARRAY_UPPER CARDINALITY STRING_TO_ARRAY UNNEST ISEMPTY LOWER_INC UPPER_INC LOWER_INF UPPER_INF RANGE_MERGE GENERATE_SERIES GENERATE_SUBSCRIPTS CURRENT_DATABASE CURRENT_QUERY CURRENT_SCHEMA|10 CURRENT_SCHEMAS|10 INET_CLIENT_ADDR INET_CLIENT_PORT INET_SERVER_ADDR INET_SERVER_PORT ROW_SECURITY_ACTIVE FORMAT_TYPE TO_REGCLASS TO_REGPROC TO_REGPROCEDURE TO_REGOPER TO_REGOPERATOR TO_REGTYPE TO_REGNAMESPACE TO_REGROLE COL_DESCRIPTION OBJ_DESCRIPTION SHOBJ_DESCRIPTION TXID_CURRENT TXID_CURRENT_IF_ASSIGNED TXID_CURRENT_SNAPSHOT TXID_SNAPSHOT_XIP TXID_SNAPSHOT_XMAX TXID_SNAPSHOT_XMIN TXID_VISIBLE_IN_SNAPSHOT TXID_STATUS CURRENT_SETTING SET_CONFIG BRIN_SUMMARIZE_NEW_VALUES BRIN_SUMMARIZE_RANGE BRIN_DESUMMARIZE_RANGE GIN_CLEAN_PENDING_LIST SUPPRESS_REDUNDANT_UPDATES_TRIGGER LO_FROM_BYTEA LO_PUT LO_GET LO_CREAT LO_CREATE LO_UNLINK LO_IMPORT LO_EXPORT LOREAD LOWRITE GROUPING CAST".split(" ").map((E=>E.split("|")[0])).join("|")
+;return{name:"PostgreSQL",aliases:["postgres","postgresql"],case_insensitive:!0,
+keywords:{
+keyword:"ABORT ALTER ANALYZE BEGIN CALL CHECKPOINT|10 CLOSE CLUSTER COMMENT COMMIT COPY CREATE DEALLOCATE DECLARE DELETE DISCARD DO DROP END EXECUTE EXPLAIN FETCH GRANT IMPORT INSERT LISTEN LOAD LOCK MOVE NOTIFY PREPARE REASSIGN|10 REFRESH REINDEX RELEASE RESET REVOKE ROLLBACK SAVEPOINT SECURITY SELECT SET SHOW START TRUNCATE UNLISTEN|10 UPDATE VACUUM|10 VALUES AGGREGATE COLLATION CONVERSION|10 DATABASE DEFAULT PRIVILEGES DOMAIN TRIGGER EXTENSION FOREIGN WRAPPER|10 TABLE FUNCTION GROUP LANGUAGE LARGE OBJECT MATERIALIZED VIEW OPERATOR CLASS FAMILY POLICY PUBLICATION|10 ROLE RULE SCHEMA SEQUENCE SERVER STATISTICS SUBSCRIPTION SYSTEM TABLESPACE CONFIGURATION DICTIONARY PARSER TEMPLATE TYPE USER MAPPING PREPARED ACCESS METHOD CAST AS TRANSFORM TRANSACTION OWNED TO INTO SESSION AUTHORIZATION INDEX PROCEDURE ASSERTION ALL ANALYSE AND ANY ARRAY ASC ASYMMETRIC|10 BOTH CASE CHECK COLLATE COLUMN CONCURRENTLY|10 CONSTRAINT CROSS DEFERRABLE RANGE DESC DISTINCT ELSE EXCEPT FOR FREEZE|10 FROM FULL HAVING ILIKE IN INITIALLY INNER INTERSECT IS ISNULL JOIN LATERAL LEADING LIKE LIMIT NATURAL NOT NOTNULL NULL OFFSET ON ONLY OR ORDER OUTER OVERLAPS PLACING PRIMARY REFERENCES RETURNING SIMILAR SOME SYMMETRIC TABLESAMPLE THEN TRAILING UNION UNIQUE USING VARIADIC|10 VERBOSE WHEN WHERE WINDOW WITH BY RETURNS INOUT OUT SETOF|10 IF STRICT CURRENT CONTINUE OWNER LOCATION OVER PARTITION WITHIN BETWEEN ESCAPE EXTERNAL INVOKER DEFINER WORK RENAME VERSION CONNECTION CONNECT TABLES TEMP TEMPORARY FUNCTIONS SEQUENCES TYPES SCHEMAS OPTION CASCADE RESTRICT ADD ADMIN EXISTS VALID VALIDATE ENABLE DISABLE REPLICA|10 ALWAYS PASSING COLUMNS PATH REF VALUE OVERRIDING IMMUTABLE STABLE VOLATILE BEFORE AFTER EACH ROW PROCEDURAL ROUTINE NO HANDLER VALIDATOR OPTIONS STORAGE OIDS|10 WITHOUT INHERIT DEPENDS CALLED INPUT LEAKPROOF|10 COST ROWS NOWAIT SEARCH UNTIL ENCRYPTED|10 PASSWORD CONFLICT|10 INSTEAD INHERITS CHARACTERISTICS WRITE CURSOR ALSO STATEMENT SHARE EXCLUSIVE INLINE ISOLATION REPEATABLE READ COMMITTED SERIALIZABLE UNCOMMITTED LOCAL GLOBAL SQL PROCEDURES RECURSIVE SNAPSHOT ROLLUP CUBE TRUSTED|10 INCLUDE FOLLOWING PRECEDING UNBOUNDED RANGE GROUPS UNENCRYPTED|10 SYSID FORMAT DELIMITER HEADER QUOTE ENCODING FILTER OFF FORCE_QUOTE FORCE_NOT_NULL FORCE_NULL COSTS BUFFERS TIMING SUMMARY DISABLE_PAGE_SKIPPING RESTART CYCLE GENERATED IDENTITY DEFERRED IMMEDIATE LEVEL LOGGED UNLOGGED OF NOTHING NONE EXCLUDE ATTRIBUTE USAGE ROUTINES TRUE FALSE NAN INFINITY ALIAS BEGIN CONSTANT DECLARE END EXCEPTION RETURN PERFORM|10 RAISE GET DIAGNOSTICS STACKED|10 FOREACH LOOP ELSIF EXIT WHILE REVERSE SLICE DEBUG LOG INFO NOTICE WARNING ASSERT OPEN SUPERUSER NOSUPERUSER CREATEDB NOCREATEDB CREATEROLE NOCREATEROLE INHERIT NOINHERIT LOGIN NOLOGIN REPLICATION NOREPLICATION BYPASSRLS NOBYPASSRLS ",
+built_in:"CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURRENT_CATALOG|10 CURRENT_DATE LOCALTIME LOCALTIMESTAMP CURRENT_ROLE|10 CURRENT_SCHEMA|10 SESSION_USER PUBLIC FOUND NEW OLD TG_NAME|10 TG_WHEN|10 TG_LEVEL|10 TG_OP|10 TG_RELID|10 TG_RELNAME|10 TG_TABLE_NAME|10 TG_TABLE_SCHEMA|10 TG_NARGS|10 TG_ARGV|10 TG_EVENT|10 TG_TAG|10 ROW_COUNT RESULT_OID|10 PG_CONTEXT|10 RETURNED_SQLSTATE COLUMN_NAME CONSTRAINT_NAME PG_DATATYPE_NAME|10 MESSAGE_TEXT TABLE_NAME SCHEMA_NAME PG_EXCEPTION_DETAIL|10 PG_EXCEPTION_HINT|10 PG_EXCEPTION_CONTEXT|10 SQLSTATE SQLERRM|10 SUCCESSFUL_COMPLETION WARNING DYNAMIC_RESULT_SETS_RETURNED IMPLICIT_ZERO_BIT_PADDING NULL_VALUE_ELIMINATED_IN_SET_FUNCTION PRIVILEGE_NOT_GRANTED PRIVILEGE_NOT_REVOKED STRING_DATA_RIGHT_TRUNCATION DEPRECATED_FEATURE NO_DATA NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED SQL_STATEMENT_NOT_YET_COMPLETE CONNECTION_EXCEPTION CONNECTION_DOES_NOT_EXIST CONNECTION_FAILURE SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION TRANSACTION_RESOLUTION_UNKNOWN PROTOCOL_VIOLATION TRIGGERED_ACTION_EXCEPTION FEATURE_NOT_SUPPORTED INVALID_TRANSACTION_INITIATION LOCATOR_EXCEPTION INVALID_LOCATOR_SPECIFICATION INVALID_GRANTOR INVALID_GRANT_OPERATION INVALID_ROLE_SPECIFICATION DIAGNOSTICS_EXCEPTION STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER CASE_NOT_FOUND CARDINALITY_VIOLATION DATA_EXCEPTION ARRAY_SUBSCRIPT_ERROR CHARACTER_NOT_IN_REPERTOIRE DATETIME_FIELD_OVERFLOW DIVISION_BY_ZERO ERROR_IN_ASSIGNMENT ESCAPE_CHARACTER_CONFLICT INDICATOR_OVERFLOW INTERVAL_FIELD_OVERFLOW INVALID_ARGUMENT_FOR_LOGARITHM INVALID_ARGUMENT_FOR_NTILE_FUNCTION INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION INVALID_ARGUMENT_FOR_POWER_FUNCTION INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION INVALID_CHARACTER_VALUE_FOR_CAST INVALID_DATETIME_FORMAT INVALID_ESCAPE_CHARACTER INVALID_ESCAPE_OCTET INVALID_ESCAPE_SEQUENCE NONSTANDARD_USE_OF_ESCAPE_CHARACTER INVALID_INDICATOR_PARAMETER_VALUE INVALID_PARAMETER_VALUE INVALID_REGULAR_EXPRESSION INVALID_ROW_COUNT_IN_LIMIT_CLAUSE INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE INVALID_TABLESAMPLE_ARGUMENT INVALID_TABLESAMPLE_REPEAT INVALID_TIME_ZONE_DISPLACEMENT_VALUE INVALID_USE_OF_ESCAPE_CHARACTER MOST_SPECIFIC_TYPE_MISMATCH NULL_VALUE_NOT_ALLOWED NULL_VALUE_NO_INDICATOR_PARAMETER NUMERIC_VALUE_OUT_OF_RANGE SEQUENCE_GENERATOR_LIMIT_EXCEEDED STRING_DATA_LENGTH_MISMATCH STRING_DATA_RIGHT_TRUNCATION SUBSTRING_ERROR TRIM_ERROR UNTERMINATED_C_STRING ZERO_LENGTH_CHARACTER_STRING FLOATING_POINT_EXCEPTION INVALID_TEXT_REPRESENTATION INVALID_BINARY_REPRESENTATION BAD_COPY_FILE_FORMAT UNTRANSLATABLE_CHARACTER NOT_AN_XML_DOCUMENT INVALID_XML_DOCUMENT INVALID_XML_CONTENT INVALID_XML_COMMENT INVALID_XML_PROCESSING_INSTRUCTION INTEGRITY_CONSTRAINT_VIOLATION RESTRICT_VIOLATION NOT_NULL_VIOLATION FOREIGN_KEY_VIOLATION UNIQUE_VIOLATION CHECK_VIOLATION EXCLUSION_VIOLATION INVALID_CURSOR_STATE INVALID_TRANSACTION_STATE ACTIVE_SQL_TRANSACTION BRANCH_TRANSACTION_ALREADY_ACTIVE HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION READ_ONLY_SQL_TRANSACTION SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED NO_ACTIVE_SQL_TRANSACTION IN_FAILED_SQL_TRANSACTION IDLE_IN_TRANSACTION_SESSION_TIMEOUT INVALID_SQL_STATEMENT_NAME TRIGGERED_DATA_CHANGE_VIOLATION INVALID_AUTHORIZATION_SPECIFICATION INVALID_PASSWORD DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST DEPENDENT_OBJECTS_STILL_EXIST INVALID_TRANSACTION_TERMINATION SQL_ROUTINE_EXCEPTION FUNCTION_EXECUTED_NO_RETURN_STATEMENT MODIFYING_SQL_DATA_NOT_PERMITTED PROHIBITED_SQL_STATEMENT_ATTEMPTED READING_SQL_DATA_NOT_PERMITTED INVALID_CURSOR_NAME EXTERNAL_ROUTINE_EXCEPTION CONTAINING_SQL_NOT_PERMITTED MODIFYING_SQL_DATA_NOT_PERMITTED PROHIBITED_SQL_STATEMENT_ATTEMPTED READING_SQL_DATA_NOT_PERMITTED EXTERNAL_ROUTINE_INVOCATION_EXCEPTION INVALID_SQLSTATE_RETURNED NULL_VALUE_NOT_ALLOWED TRIGGER_PROTOCOL_VIOLATED SRF_PROTOCOL_VIOLATED EVENT_TRIGGER_PROTOCOL_VIOLATED SAVEPOINT_EXCEPTION INVALID_SAVEPOINT_SPECIFICATION INVALID_CATALOG_NAME INVALID_SCHEMA_NAME TRANSACTION_ROLLBACK TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION SERIALIZATION_FAILURE STATEMENT_COMPLETION_UNKNOWN DEADLOCK_DETECTED SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION SYNTAX_ERROR INSUFFICIENT_PRIVILEGE CANNOT_COERCE GROUPING_ERROR WINDOWING_ERROR INVALID_RECURSION INVALID_FOREIGN_KEY INVALID_NAME NAME_TOO_LONG RESERVED_NAME DATATYPE_MISMATCH INDETERMINATE_DATATYPE COLLATION_MISMATCH INDETERMINATE_COLLATION WRONG_OBJECT_TYPE GENERATED_ALWAYS UNDEFINED_COLUMN UNDEFINED_FUNCTION UNDEFINED_TABLE UNDEFINED_PARAMETER UNDEFINED_OBJECT DUPLICATE_COLUMN DUPLICATE_CURSOR DUPLICATE_DATABASE DUPLICATE_FUNCTION DUPLICATE_PREPARED_STATEMENT DUPLICATE_SCHEMA DUPLICATE_TABLE DUPLICATE_ALIAS DUPLICATE_OBJECT AMBIGUOUS_COLUMN AMBIGUOUS_FUNCTION AMBIGUOUS_PARAMETER AMBIGUOUS_ALIAS INVALID_COLUMN_REFERENCE INVALID_COLUMN_DEFINITION INVALID_CURSOR_DEFINITION INVALID_DATABASE_DEFINITION INVALID_FUNCTION_DEFINITION INVALID_PREPARED_STATEMENT_DEFINITION INVALID_SCHEMA_DEFINITION INVALID_TABLE_DEFINITION INVALID_OBJECT_DEFINITION WITH_CHECK_OPTION_VIOLATION INSUFFICIENT_RESOURCES DISK_FULL OUT_OF_MEMORY TOO_MANY_CONNECTIONS CONFIGURATION_LIMIT_EXCEEDED PROGRAM_LIMIT_EXCEEDED STATEMENT_TOO_COMPLEX TOO_MANY_COLUMNS TOO_MANY_ARGUMENTS OBJECT_NOT_IN_PREREQUISITE_STATE OBJECT_IN_USE CANT_CHANGE_RUNTIME_PARAM LOCK_NOT_AVAILABLE OPERATOR_INTERVENTION QUERY_CANCELED ADMIN_SHUTDOWN CRASH_SHUTDOWN CANNOT_CONNECT_NOW DATABASE_DROPPED SYSTEM_ERROR IO_ERROR UNDEFINED_FILE DUPLICATE_FILE SNAPSHOT_TOO_OLD CONFIG_FILE_ERROR LOCK_FILE_EXISTS FDW_ERROR FDW_COLUMN_NAME_NOT_FOUND FDW_DYNAMIC_PARAMETER_VALUE_NEEDED FDW_FUNCTION_SEQUENCE_ERROR FDW_INCONSISTENT_DESCRIPTOR_INFORMATION FDW_INVALID_ATTRIBUTE_VALUE FDW_INVALID_COLUMN_NAME FDW_INVALID_COLUMN_NUMBER FDW_INVALID_DATA_TYPE FDW_INVALID_DATA_TYPE_DESCRIPTORS FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER FDW_INVALID_HANDLE FDW_INVALID_OPTION_INDEX FDW_INVALID_OPTION_NAME FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH FDW_INVALID_STRING_FORMAT FDW_INVALID_USE_OF_NULL_POINTER FDW_TOO_MANY_HANDLES FDW_OUT_OF_MEMORY FDW_NO_SCHEMAS FDW_OPTION_NAME_NOT_FOUND FDW_REPLY_HANDLE FDW_SCHEMA_NOT_FOUND FDW_TABLE_NOT_FOUND FDW_UNABLE_TO_CREATE_EXECUTION FDW_UNABLE_TO_CREATE_REPLY FDW_UNABLE_TO_ESTABLISH_CONNECTION PLPGSQL_ERROR RAISE_EXCEPTION NO_DATA_FOUND TOO_MANY_ROWS ASSERT_FAILURE INTERNAL_ERROR DATA_CORRUPTED INDEX_CORRUPTED "
+},illegal:/:==|\W\s*\(\*|(^|\s)\$[a-z]|\{\{|[a-z]:\s*$|\.\.\.|TO:|DO:/,
+contains:[{className:"keyword",variants:[{begin:/\bTEXT\s*SEARCH\b/},{
+begin:/\b(PRIMARY|FOREIGN|FOR(\s+NO)?)\s+KEY\b/},{
+begin:/\bPARALLEL\s+(UNSAFE|RESTRICTED|SAFE)\b/},{
+begin:/\bSTORAGE\s+(PLAIN|EXTERNAL|EXTENDED|MAIN)\b/},{
+begin:/\bMATCH\s+(FULL|PARTIAL|SIMPLE)\b/},{begin:/\bNULLS\s+(FIRST|LAST)\b/},{
+begin:/\bEVENT\s+TRIGGER\b/},{begin:/\b(MAPPING|OR)\s+REPLACE\b/},{
+begin:/\b(FROM|TO)\s+(PROGRAM|STDIN|STDOUT)\b/},{
+begin:/\b(SHARE|EXCLUSIVE)\s+MODE\b/},{
+begin:/\b(LEFT|RIGHT)\s+(OUTER\s+)?JOIN\b/},{
+begin:/\b(FETCH|MOVE)\s+(NEXT|PRIOR|FIRST|LAST|ABSOLUTE|RELATIVE|FORWARD|BACKWARD)\b/
+},{begin:/\bPRESERVE\s+ROWS\b/},{begin:/\bDISCARD\s+PLANS\b/},{
+begin:/\bREFERENCING\s+(OLD|NEW)\b/},{begin:/\bSKIP\s+LOCKED\b/},{
+begin:/\bGROUPING\s+SETS\b/},{
+begin:/\b(BINARY|INSENSITIVE|SCROLL|NO\s+SCROLL)\s+(CURSOR|FOR)\b/},{
+begin:/\b(WITH|WITHOUT)\s+HOLD\b/},{
+begin:/\bWITH\s+(CASCADED|LOCAL)\s+CHECK\s+OPTION\b/},{
+begin:/\bEXCLUDE\s+(TIES|NO\s+OTHERS)\b/},{
+begin:/\bFORMAT\s+(TEXT|XML|JSON|YAML)\b/},{
+begin:/\bSET\s+((SESSION|LOCAL)\s+)?NAMES\b/},{begin:/\bIS\s+(NOT\s+)?UNKNOWN\b/
+},{begin:/\bSECURITY\s+LABEL\b/},{begin:/\bSTANDALONE\s+(YES|NO|NO\s+VALUE)\b/
+},{begin:/\bWITH\s+(NO\s+)?DATA\b/},{begin:/\b(FOREIGN|SET)\s+DATA\b/},{
+begin:/\bSET\s+(CATALOG|CONSTRAINTS)\b/},{begin:/\b(WITH|FOR)\s+ORDINALITY\b/},{
+begin:/\bIS\s+(NOT\s+)?DOCUMENT\b/},{
+begin:/\bXML\s+OPTION\s+(DOCUMENT|CONTENT)\b/},{
+begin:/\b(STRIP|PRESERVE)\s+WHITESPACE\b/},{
+begin:/\bNO\s+(ACTION|MAXVALUE|MINVALUE)\b/},{
+begin:/\bPARTITION\s+BY\s+(RANGE|LIST|HASH)\b/},{begin:/\bAT\s+TIME\s+ZONE\b/},{
+begin:/\bGRANTED\s+BY\b/},{begin:/\bRETURN\s+(QUERY|NEXT)\b/},{
+begin:/\b(ATTACH|DETACH)\s+PARTITION\b/},{
+begin:/\bFORCE\s+ROW\s+LEVEL\s+SECURITY\b/},{
+begin:/\b(INCLUDING|EXCLUDING)\s+(COMMENTS|CONSTRAINTS|DEFAULTS|IDENTITY|INDEXES|STATISTICS|STORAGE|ALL)\b/
+},{begin:/\bAS\s+(ASSIGNMENT|IMPLICIT|PERMISSIVE|RESTRICTIVE|ENUM|RANGE)\b/}]},{
+begin:/\b(FORMAT|FAMILY|VERSION)\s*\(/},{begin:/\bINCLUDE\s*\(/,
+keywords:"INCLUDE"},{begin:/\bRANGE(?!\s*(BETWEEN|UNBOUNDED|CURRENT|[-0-9]+))/
+},{
+begin:/\b(VERSION|OWNER|TEMPLATE|TABLESPACE|CONNECTION\s+LIMIT|PROCEDURE|RESTRICT|JOIN|PARSER|COPY|START|END|COLLATION|INPUT|ANALYZE|STORAGE|LIKE|DEFAULT|DELIMITER|ENCODING|COLUMN|CONSTRAINT|TABLE|SCHEMA)\s*=/
+},{begin:/\b(PG_\w+?|HAS_[A-Z_]+_PRIVILEGE)\b/,relevance:10},{
+begin:/\bEXTRACT\s*\(/,end:/\bFROM\b/,returnEnd:!0,keywords:{
+type:"CENTURY DAY DECADE DOW DOY EPOCH HOUR ISODOW ISOYEAR MICROSECONDS MILLENNIUM MILLISECONDS MINUTE MONTH QUARTER SECOND TIMEZONE TIMEZONE_HOUR TIMEZONE_MINUTE WEEK YEAR"
+}},{begin:/\b(XMLELEMENT|XMLPI)\s*\(\s*NAME/,keywords:{keyword:"NAME"}},{
+begin:/\b(XMLPARSE|XMLSERIALIZE)\s*\(\s*(DOCUMENT|CONTENT)/,keywords:{
+keyword:"DOCUMENT CONTENT"}},{beginKeywords:"CACHE INCREMENT MAXVALUE MINVALUE",
+end:E.C_NUMBER_RE,returnEnd:!0,keywords:"BY CACHE INCREMENT MAXVALUE MINVALUE"
+},{className:"type",begin:/\b(WITH|WITHOUT)\s+TIME\s+ZONE\b/},{className:"type",
+begin:/\bINTERVAL\s+(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND)(\s+TO\s+(MONTH|HOUR|MINUTE|SECOND))?\b/
+},{
+begin:/\bRETURNS\s+(LANGUAGE_HANDLER|TRIGGER|EVENT_TRIGGER|FDW_HANDLER|INDEX_AM_HANDLER|TSM_HANDLER)\b/,
+keywords:{keyword:"RETURNS",
+type:"LANGUAGE_HANDLER TRIGGER EVENT_TRIGGER FDW_HANDLER INDEX_AM_HANDLER TSM_HANDLER"
+}},{begin:"\\b("+I+")\\s*\\("},{begin:"\\.("+R+")\\b"},{
+begin:"\\b("+R+")\\s+PATH\\b",keywords:{keyword:"PATH",
+type:A.replace("PATH ","")}},{className:"type",begin:"\\b("+R+")\\b"},{
+className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{
+className:"string",begin:"(e|E|u&|U&)'",end:"'",contains:[{begin:"\\\\."}],
+relevance:10},E.END_SAME_AS_BEGIN({begin:N,end:N,contains:[{
+subLanguage:["pgsql","perl","python","tcl","r","lua","java","php","ruby","bash","scheme","xml","json"],
+endsWithParent:!0}]}),{begin:'"',end:'"',contains:[{begin:'""'}]
+},E.C_NUMBER_MODE,E.C_BLOCK_COMMENT_MODE,T,{className:"meta",variants:[{
+begin:"%(ROW)?TYPE",relevance:10},{begin:"\\$\\d+"},{begin:"^#\\w",end:"$"}]},{
+className:"symbol",begin:"<<\\s*[a-zA-Z_][a-zA-Z_0-9$]*\\s*>>",relevance:10}]}}
+})());
+hljs.registerLanguage("php",(()=>{"use strict";return e=>{const r={
+className:"variable",
+begin:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?![A-Za-z0-9])(?![$])"},t={
+className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{
+begin:/\?>/}]},a={className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,
+end:/\}/}]},n=e.inherit(e.APOS_STRING_MODE,{illegal:null
+}),i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,
+contains:e.QUOTE_STRING_MODE.contains.concat(a)}),o=e.END_SAME_AS_BEGIN({
+begin:/<<<[ \t]*(\w+)\n/,end:/[ \t]*(\w+)\b/,
+contains:e.QUOTE_STRING_MODE.contains.concat(a)}),l={className:"string",
+contains:[e.BACKSLASH_ESCAPE,t],variants:[e.inherit(n,{begin:"b'",end:"'"
+}),e.inherit(i,{begin:'b"',end:'"'}),i,n,o]},s={className:"number",variants:[{
+begin:"\\b0b[01]+(?:_[01]+)*\\b"},{begin:"\\b0o[0-7]+(?:_[0-7]+)*\\b"},{
+begin:"\\b0x[\\da-f]+(?:_[\\da-f]+)*\\b"},{
+begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:e[+-]?\\d+)?"
+}],relevance:0},c={
+keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile enum eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list match|0 mixed new object or private protected public real return string switch throw trait try unset use var void while xor yield",
+literal:"false null true",
+built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException UnhandledMatchError ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Stringable Throwable Traversable WeakReference WeakMap Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"
+};return{aliases:["php3","php4","php5","php6","php7","php8"],
+case_insensitive:!0,keywords:c,
+contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]
+}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]
+}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,
+keywords:"__halt_compiler"}),t,{className:"keyword",begin:/\$this\b/},r,{
+begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",
+relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,
+illegal:"[$%\\[]",contains:[{beginKeywords:"use"},e.UNDERSCORE_TITLE_MODE,{
+begin:"=>",endsParent:!0},{className:"params",begin:"\\(",end:"\\)",
+excludeBegin:!0,excludeEnd:!0,keywords:c,
+contains:["self",r,e.C_BLOCK_COMMENT_MODE,l,s]}]},{className:"class",variants:[{
+beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait",
+illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{
+beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{
+beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/,
+contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",relevance:0,end:";",
+contains:[e.UNDERSCORE_TITLE_MODE]},l,s]}}})());
+hljs.registerLanguage("php-template",(()=>{"use strict";return n=>({
+name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,
+subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',
+end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{
+illegal:null,className:null,contains:null,skip:!0
+}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,
+skip:!0})]}]})})());
+hljs.registerLanguage("plaintext",(()=>{"use strict";return t=>({
+name:"Plain text",aliases:["text","txt"],disableAutodetect:!0})})());
+hljs.registerLanguage("pony",(()=>{"use strict";return e=>({name:"Pony",
+keywords:{
+keyword:"actor addressof and as be break class compile_error compile_intrinsic consume continue delegate digestof do else elseif embed end error for fun if ifdef in interface is isnt lambda let match new not object or primitive recover repeat return struct then trait try type until use var where while with xor",
+meta:"iso val tag trn box ref",literal:"this false true"},contains:[{
+className:"type",begin:"\\b_?[A-Z][\\w]*",relevance:0},{className:"string",
+begin:'"""',end:'"""',relevance:10},{className:"string",begin:'"',end:'"',
+contains:[e.BACKSLASH_ESCAPE]},{className:"string",begin:"'",end:"'",
+contains:[e.BACKSLASH_ESCAPE],relevance:0},{begin:e.IDENT_RE+"'",relevance:0},{
+className:"number",
+begin:"(-?)(\\b0[xX][a-fA-F0-9]+|\\b0[bB][01]+|(\\b\\d+(_\\d+)?(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",
+relevance:0},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]})})());
+hljs.registerLanguage("powershell",(()=>{"use strict";return e=>{const n={
+$pattern:/-?[A-z\.\-]+\b/,
+keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",
+built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"
+},s={begin:"`[\\s\\S]",relevance:0},i={className:"variable",variants:[{
+begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]
+},a={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],
+contains:[s,i,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},t={
+className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]
+},r=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,
+end:/#>/}],contains:[{className:"doctag",variants:[{
+begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/
+},{
+begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/
+}]}]}),c={className:"class",beginKeywords:"class enum",end:/\s*[{]/,
+excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},l={className:"function",
+begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,
+contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",
+begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,
+className:"params",relevance:0,contains:[i]}]},o={begin:/using\s/,end:/$/,
+returnBegin:!0,contains:[a,t,{className:"keyword",
+begin:/(using|assembly|command|module|namespace|type)/}]},p={
+className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,
+relevance:0,contains:[{className:"keyword",
+begin:"(".concat(n.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,
+relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]
+},g=[p,r,s,e.NUMBER_MODE,a,t,{className:"built_in",variants:[{
+begin:"(Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where)+(-)[\\w\\d]+"
+}]},i,{className:"literal",begin:/\$(null|true|false)\b/},{
+className:"selector-tag",begin:/@\B/,relevance:0}],m={begin:/\[/,end:/\]/,
+excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",g,{
+begin:"(string|char|byte|int|long|bool|decimal|single|double|DateTime|xml|array|hashtable|void)",
+className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,
+relevance:0})};return p.contains.unshift(m),{name:"PowerShell",
+aliases:["ps","ps1"],case_insensitive:!0,keywords:n,contains:g.concat(c,l,o,{
+variants:[{className:"operator",
+begin:"(-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor)\\b"
+},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},m)}}})());
+hljs.registerLanguage("processing",(()=>{"use strict";return e=>({
+name:"Processing",keywords:{
+keyword:"BufferedReader PVector PFont PImage PGraphics HashMap boolean byte char color double float int long String Array FloatDict FloatList IntDict IntList JSONArray JSONObject Object StringDict StringList Table TableRow XML false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",
+literal:"P2D P3D HALF_PI PI QUARTER_PI TAU TWO_PI",title:"setup draw",
+built_in:"displayHeight displayWidth mouseY mouseX mousePressed pmouseX pmouseY key keyCode pixels focused frameCount frameRate height width size createGraphics beginDraw createShape loadShape PShape arc ellipse line point quad rect triangle bezier bezierDetail bezierPoint bezierTangent curve curveDetail curvePoint curveTangent curveTightness shape shapeMode beginContour beginShape bezierVertex curveVertex endContour endShape quadraticVertex vertex ellipseMode noSmooth rectMode smooth strokeCap strokeJoin strokeWeight mouseClicked mouseDragged mouseMoved mousePressed mouseReleased mouseWheel keyPressed keyPressedkeyReleased keyTyped print println save saveFrame day hour millis minute month second year background clear colorMode fill noFill noStroke stroke alpha blue brightness color green hue lerpColor red saturation modelX modelY modelZ screenX screenY screenZ ambient emissive shininess specular add createImage beginCamera camera endCamera frustum ortho perspective printCamera printProjection cursor frameRate noCursor exit loop noLoop popStyle pushStyle redraw binary boolean byte char float hex int str unbinary unhex join match matchAll nf nfc nfp nfs split splitTokens trim append arrayCopy concat expand reverse shorten sort splice subset box sphere sphereDetail createInput createReader loadBytes loadJSONArray loadJSONObject loadStrings loadTable loadXML open parseXML saveTable selectFolder selectInput beginRaw beginRecord createOutput createWriter endRaw endRecord PrintWritersaveBytes saveJSONArray saveJSONObject saveStream saveStrings saveXML selectOutput popMatrix printMatrix pushMatrix resetMatrix rotate rotateX rotateY rotateZ scale shearX shearY translate ambientLight directionalLight lightFalloff lights lightSpecular noLights normal pointLight spotLight image imageMode loadImage noTint requestImage tint texture textureMode textureWrap blend copy filter get loadPixels set updatePixels blendMode loadShader PShaderresetShader shader createFont loadFont text textFont textAlign textLeading textMode textSize textWidth textAscent textDescent abs ceil constrain dist exp floor lerp log mag map max min norm pow round sq sqrt acos asin atan atan2 cos degrees radians sin tan noise noiseDetail noiseSeed random randomGaussian randomSeed"
+},
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE]
+})})());
+hljs.registerLanguage("profile",(()=>{"use strict";return e=>({
+name:"Python profiler",contains:[e.C_NUMBER_MODE,{
+begin:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",end:":",excludeEnd:!0},{
+begin:"(ncalls|tottime|cumtime)",end:"$",
+keywords:"ncalls tottime|10 cumtime|10 filename",relevance:10},{
+begin:"function calls",end:"$",contains:[e.C_NUMBER_MODE],relevance:10
+},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\(",
+end:"\\)$",excludeBegin:!0,excludeEnd:!0,relevance:0}]})})());
+hljs.registerLanguage("prolog",(()=>{"use strict";return n=>{const e={
+begin:/\(/,end:/\)/,relevance:0},a={begin:/\[/,end:/\]/},s={className:"comment",
+begin:/%/,end:/$/,contains:[n.PHRASAL_WORDS_MODE]},i={className:"string",
+begin:/`/,end:/`/,contains:[n.BACKSLASH_ESCAPE]},g=[{begin:/[a-z][A-Za-z0-9_]*/,
+relevance:0},{className:"symbol",variants:[{begin:/[A-Z][a-zA-Z0-9_]*/},{
+begin:/_[A-Za-z0-9_]*/}],relevance:0},e,{begin:/:-/
+},a,s,n.C_BLOCK_COMMENT_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,i,{
+className:"string",begin:/0'(\\'|.)/},{className:"string",begin:/0'\\s/
+},n.C_NUMBER_MODE];return e.contains=g,a.contains=g,{name:"Prolog",
+contains:g.concat([{begin:/\.$/}])}}})());
+hljs.registerLanguage("properties",(()=>{"use strict";return e=>{
+var n="[ \\t\\f]*",a=n+"[:=]"+n,t="("+a+"|[ \\t\\f]+)",r="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",s="([^\\\\:= \\t\\f\\n]|\\\\.)+",i={
+end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{
+begin:"\\\\\\\\"},{begin:"\\\\\\n"}]}};return{name:".properties",
+case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{
+returnBegin:!0,variants:[{begin:r+a,relevance:1},{begin:r+"[ \\t\\f]+",
+relevance:0}],contains:[{className:"attr",begin:r,endsParent:!0,relevance:0}],
+starts:i},{begin:s+t,returnBegin:!0,relevance:0,contains:[{className:"meta",
+begin:s,endsParent:!0,relevance:0}],starts:i},{className:"attr",relevance:0,
+begin:s+n+"$"}]}}})());
+hljs.registerLanguage("protobuf",(()=>{"use strict";return e=>({
+name:"Protocol Buffers",keywords:{
+keyword:"package import option optional required repeated group oneof",
+built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",
+literal:"true false"},
+contains:[e.QUOTE_STRING_MODE,e.NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+className:"class",beginKeywords:"message enum service",end:/\{/,illegal:/\n/,
+contains:[e.inherit(e.TITLE_MODE,{starts:{endsWithParent:!0,excludeEnd:!0}})]},{
+className:"function",beginKeywords:"rpc",end:/[{;]/,excludeEnd:!0,
+keywords:"rpc returns"},{begin:/^\s*[A-Z_]+(?=\s*=[^\n]+;$)/}]})})());
+hljs.registerLanguage("puppet",(()=>{"use strict";return e=>{
+const s=e.COMMENT("#","$"),r="([A-Za-z_]|::)(\\w|::)*",a=e.inherit(e.TITLE_MODE,{
+begin:r}),n={className:"variable",begin:"\\$"+r},i={className:"string",
+contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/
+}]};return{name:"Puppet",aliases:["pp"],contains:[s,n,i,{beginKeywords:"class",
+end:"\\{|;",illegal:/=/,contains:[a,s]},{beginKeywords:"define",end:/\{/,
+contains:[{className:"section",begin:e.IDENT_RE,endsParent:!0}]},{
+begin:e.IDENT_RE+"\\s+\\{",returnBegin:!0,end:/\S/,contains:[{
+className:"keyword",begin:e.IDENT_RE},{begin:/\{/,end:/\}/,keywords:{
+keyword:"and case default else elsif false if in import enherits node or true undef unless main settings $string ",
+literal:"alias audit before loglevel noop require subscribe tag owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check en_address ip_address realname command environment hour monute month monthday special target weekday creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey sslverify mounted",
+built_in:"architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version"
+},relevance:0,contains:[i,s,{begin:"[a-zA-Z_]+\\s*=>",returnBegin:!0,end:"=>",
+contains:[{className:"attr",begin:e.IDENT_RE}]},{className:"number",
+begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",
+relevance:0},n]}],relevance:0}]}}})());
+hljs.registerLanguage("purebasic",(()=>{"use strict";return e=>({
+name:"PureBASIC",aliases:["pb","pbi"],
+keywords:"Align And Array As Break CallDebugger Case CompilerCase CompilerDefault CompilerElse CompilerElseIf CompilerEndIf CompilerEndSelect CompilerError CompilerIf CompilerSelect CompilerWarning Continue Data DataSection Debug DebugLevel Declare DeclareC DeclareCDLL DeclareDLL DeclareModule Default Define Dim DisableASM DisableDebugger DisableExplicit Else ElseIf EnableASM EnableDebugger EnableExplicit End EndDataSection EndDeclareModule EndEnumeration EndIf EndImport EndInterface EndMacro EndModule EndProcedure EndSelect EndStructure EndStructureUnion EndWith Enumeration EnumerationBinary Extends FakeReturn For ForEach ForEver Global Gosub Goto If Import ImportC IncludeBinary IncludeFile IncludePath Interface List Macro MacroExpandedCount Map Module NewList NewMap Next Not Or Procedure ProcedureC ProcedureCDLL ProcedureDLL ProcedureReturn Protected Prototype PrototypeC ReDim Read Repeat Restore Return Runtime Select Shared Static Step Structure StructureUnion Swap Threaded To UndefineMacro Until Until UnuseModule UseModule Wend While With XIncludeFile XOr",
+contains:[e.COMMENT(";","$",{relevance:0}),{className:"function",
+begin:"\\b(Procedure|Declare)(C|CDLL|DLL)?\\b",end:"\\(",excludeEnd:!0,
+returnBegin:!0,contains:[{className:"keyword",
+begin:"(Procedure|Declare)(C|CDLL|DLL)?",excludeEnd:!0},{className:"type",
+begin:"\\.\\w*"},e.UNDERSCORE_TITLE_MODE]},{className:"string",begin:'(~)?"',
+end:'"',illegal:"\\n"},{className:"symbol",begin:"#[a-zA-Z_]\\w*\\$?"}]})})());
+hljs.registerLanguage("python",(()=>{"use strict";return e=>{const n={
+keyword:["and","as","assert","async","await","break","class","continue","def","del","elif","else","except","finally","for","","from","global","if","import","in","is","lambda","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],
+built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"],
+literal:["__debug__","Ellipsis","False","None","NotImplemented","True"]},a={
+className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/,
+end:/\}/,keywords:n,illegal:/#/},i={begin:/\{\{/,relevance:0},r={
+className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{
+begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,
+contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{
+begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,
+contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{
+begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,
+contains:[e.BACKSLASH_ESCAPE,a,i,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,
+end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,i,s]},{begin:/([uU]|[rR])'/,end:/'/,
+relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{
+begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,
+end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,
+contains:[e.BACKSLASH_ESCAPE,i,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,
+contains:[e.BACKSLASH_ESCAPE,i,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},t="[0-9](_?[0-9])*",l=`(\\b(${t}))?\\.(${t})|\\b(${t})\\.`,b={
+className:"number",relevance:0,variants:[{
+begin:`(\\b(${t})|(${l}))[eE][+-]?(${t})[jJ]?\\b`},{begin:`(${l})[jJ]?`},{
+begin:"\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?\\b"},{
+begin:"\\b0[bB](_?[01])+[lL]?\\b"},{begin:"\\b0[oO](_?[0-7])+[lL]?\\b"},{
+begin:"\\b0[xX](_?[0-9a-fA-F])+[lL]?\\b"},{begin:`\\b(${t})[jJ]\\b`}]},o={
+className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{
+begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,
+contains:["self",a,b,r,e.HASH_COMMENT_MODE]}]};return s.contains=[r,b,a],{
+name:"Python",aliases:["py","gyp","ipython"],keywords:n,
+illegal:/(<\/|->|\?)|=>/,contains:[a,b,{begin:/\bself\b/},{beginKeywords:"if",
+relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",
+beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,
+illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,o,{begin:/->/,
+endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,
+end:/(?=#)|$/,contains:[b,o,r]},{begin:/\b(print|exec)\(/}]}}})());
+hljs.registerLanguage("python-repl",(()=>{"use strict";return s=>({
+aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",
+subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{
+begin:/^\.\.\.(?=[ ]|$)/}]}]})})());
+hljs.registerLanguage("q",(()=>{"use strict";return e=>({name:"Q",
+aliases:["k","kdb"],keywords:{$pattern:/(`?)[A-Za-z0-9_]+\b/,
+keyword:"do while select delete by update from",literal:"0b 1b",
+built_in:"neg not null string reciprocal floor ceiling signum mod xbar xlog and or each scan over prior mmu lsq inv md5 ltime gtime count first var dev med cov cor all any rand sums prds mins maxs fills deltas ratios avgs differ prev next rank reverse iasc idesc asc desc msum mcount mavg mdev xrank mmin mmax xprev rotate distinct group where flip type key til get value attr cut set upsert raze union inter except cross sv vs sublist enlist read0 read1 hopen hclose hdel hsym hcount peach system ltrim rtrim trim lower upper ssr view tables views cols xcols keys xkey xcol xasc xdesc fkeys meta lj aj aj0 ij pj asof uj ww wj wj1 fby xgroup ungroup ej save load rsave rload show csv parse eval min max avg wavg wsum sin cos tan sum",
+type:"`float `double int `timestamp `timespan `datetime `time `boolean `symbol `char `byte `short `long `real `month `date `minute `second `guid"
+},contains:[e.C_LINE_COMMENT_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE]})})());
+hljs.registerLanguage("qml",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n
+})).join("")}return n=>{const r="[a-zA-Z_][a-zA-Z0-9\\._]*",a={
+className:"attribute",begin:"\\bid\\s*:",starts:{className:"string",end:r,
+returnEnd:!1}},t={begin:r+"\\s*:",returnBegin:!0,contains:[{
+className:"attribute",begin:r,end:"\\s*:",excludeEnd:!0,relevance:0}],
+relevance:0},i={begin:e(r,/\s*\{/),end:/\{/,returnBegin:!0,relevance:0,
+contains:[n.inherit(n.TITLE_MODE,{begin:r})]};return{name:"QML",aliases:["qt"],
+case_insensitive:!1,keywords:{
+keyword:"in of on if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import",
+literal:"true false null undefined NaN Infinity",
+built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Behavior bool color coordinate date double enumeration font geocircle georectangle geoshape int list matrix4x4 parent point quaternion real rect size string url variant vector2d vector3d vector4d Promise"
+},contains:[{className:"meta",begin:/^\s*['"]use (strict|asm)['"]/
+},n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",
+contains:[n.BACKSLASH_ESCAPE,{className:"subst",begin:"\\$\\{",end:"\\}"}]
+},n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,{className:"number",variants:[{
+begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:n.C_NUMBER_RE}],
+relevance:0},{begin:"("+n.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",
+contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,n.REGEXP_MODE,{begin:/</,
+end:/>\s*[);\]]/,relevance:0,subLanguage:"xml"}],relevance:0},{
+className:"keyword",begin:"\\bsignal\\b",starts:{className:"string",
+end:"(\\(|:|=|;|,|//|/\\*|$)",returnEnd:!0}},{className:"keyword",
+begin:"\\bproperty\\b",starts:{className:"string",end:"(:|=|;|,|//|/\\*|$)",
+returnEnd:!0}},{className:"function",beginKeywords:"function",end:/\{/,
+excludeEnd:!0,contains:[n.inherit(n.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/
+}),{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,
+contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE]}],illegal:/\[|%/},{
+begin:"\\."+n.IDENT_RE,relevance:0},a,t,i],illegal:/#/}}})());
+hljs.registerLanguage("r",(()=>{"use strict";function e(...e){return e.map((e=>{
+return(a=e)?"string"==typeof a?a:a.source:null;var a})).join("")}return a=>{
+const n=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/;return{name:"R",
+illegal:/->/,keywords:{$pattern:n,
+keyword:"function if in break next repeat else for while",
+literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10",
+built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm"
+},compilerExtensions:[(a,n)=>{if(!a.beforeMatch)return
+;if(a.starts)throw Error("beforeMatch cannot be used with starts")
+;const i=Object.assign({},a);Object.keys(a).forEach((e=>{delete a[e]
+})),a.begin=e(i.beforeMatch,e("(?=",i.begin,")")),a.starts={relevance:0,
+contains:[Object.assign(i,{endsParent:!0})]},a.relevance=0,delete i.beforeMatch
+}],contains:[a.COMMENT(/#'/,/$/,{contains:[{className:"doctag",
+begin:"@examples",starts:{contains:[{begin:/\n/},{begin:/#'\s*(?=@[a-zA-Z]+)/,
+endsParent:!0},{begin:/#'/,end:/$/,excludeBegin:!0}]}},{className:"doctag",
+begin:"@param",end:/$/,contains:[{className:"variable",variants:[{begin:n},{
+begin:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{className:"doctag",
+begin:/@[a-zA-Z]+/},{className:"meta-keyword",begin:/\\[a-zA-Z]+/}]
+}),a.HASH_COMMENT_MODE,{className:"string",contains:[a.BACKSLASH_ESCAPE],
+variants:[a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/
+}),a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/
+}),a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/
+}),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/
+}),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/
+}),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"',
+relevance:0},{begin:"'",end:"'",relevance:0}]},{className:"number",relevance:0,
+beforeMatch:/([^a-zA-Z0-9._])/,variants:[{
+match:/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/},{
+match:/0[xX][0-9a-fA-F]+([pP][+-]?\d+)?[Li]?/},{
+match:/(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?[Li]?/}]},{begin:"%",end:"%"},{
+begin:e(/[a-zA-Z][a-zA-Z_0-9]*/,"\\s+<-\\s+")},{begin:"`",end:"`",contains:[{
+begin:/\\./}]}]}}})());
+hljs.registerLanguage("reasonml",(()=>{"use strict";return e=>{
+const n="~?[a-z$_][0-9a-zA-Z$_]*",a="`?[A-Z$_][0-9a-zA-Z$_]*",s="("+["||","++","**","+.","*","/","*.","/.","..."].map((e=>e.split("").map((e=>"\\"+e)).join(""))).join("|")+"|\\|>|&&|==|===)",i="\\s+"+s+"\\s+",r={
+keyword:"and as asr assert begin class constraint do done downto else end exception external for fun function functor if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new nonrec object of open or private rec sig struct then to try type val virtual when while with",
+built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 ref string unit ",
+literal:"true false"
+},l="\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",t={
+className:"number",relevance:0,variants:[{begin:l},{begin:"\\(-"+l+"\\)"}]},c={
+className:"operator",relevance:0,begin:s},o=[{className:"identifier",
+relevance:0,begin:n},c,t],g=[e.QUOTE_STRING_MODE,c,{className:"module",
+begin:"\\b"+a,returnBegin:!0,end:".",contains:[{className:"identifier",begin:a,
+relevance:0}]}],b=[{className:"module",begin:"\\b"+a,returnBegin:!0,end:".",
+relevance:0,contains:[{className:"identifier",begin:a,relevance:0}]}],m={
+className:"function",relevance:0,keywords:r,variants:[{
+begin:"\\s(\\(\\.?.*?\\)|"+n+")\\s*=>",end:"\\s*=>",returnBegin:!0,relevance:0,
+contains:[{className:"params",variants:[{begin:n},{
+begin:"~?[a-z$_][0-9a-zA-Z$_]*(\\s*:\\s*[a-z$_][0-9a-z$_]*(\\(\\s*('?[a-z$_][0-9a-z$_]*\\s*(,'?[a-z$_][0-9a-z$_]*\\s*)*)?\\))?){0,2}"
+},{begin:/\(\s*\)/}]}]},{begin:"\\s\\(\\.?[^;\\|]*\\)\\s*=>",end:"\\s=>",
+returnBegin:!0,relevance:0,contains:[{className:"params",relevance:0,variants:[{
+begin:n,end:"(,|\\n|\\))",relevance:0,contains:[c,{className:"typing",begin:":",
+end:"(,|\\n)",returnBegin:!0,relevance:0,contains:b}]}]}]},{
+begin:"\\(\\.\\s"+n+"\\)\\s*=>"}]};g.push(m);const d={className:"constructor",
+begin:a+"\\(",end:"\\)",illegal:"\\n",keywords:r,
+contains:[e.QUOTE_STRING_MODE,c,{className:"params",begin:"\\b"+n}]},u={
+className:"pattern-match",begin:"\\|",returnBegin:!0,keywords:r,end:"=>",
+relevance:0,contains:[d,c,{relevance:0,className:"constructor",begin:a}]},v={
+className:"module-access",keywords:r,returnBegin:!0,variants:[{
+begin:"\\b("+a+"\\.)+"+n},{begin:"\\b("+a+"\\.)+\\(",end:"\\)",returnBegin:!0,
+contains:[m,{begin:"\\(",end:"\\)",skip:!0}].concat(g)},{
+begin:"\\b("+a+"\\.)+\\{",end:/\}/}],contains:g};return b.push(v),{
+name:"ReasonML",aliases:["re"],keywords:r,illegal:"(:-|:=|\\$\\{|\\+=)",
+contains:[e.COMMENT("/\\*","\\*/",{illegal:"^(#,\\/\\/)"}),{
+className:"character",begin:"'(\\\\[^']+|[^'])'",illegal:"\\n",relevance:0
+},e.QUOTE_STRING_MODE,{className:"literal",begin:"\\(\\)",relevance:0},{
+className:"literal",begin:"\\[\\|",end:"\\|\\]",relevance:0,contains:o},{
+className:"literal",begin:"\\[",end:"\\]",relevance:0,contains:o},d,{
+className:"operator",begin:i,illegal:"--\x3e",relevance:0
+},t,e.C_LINE_COMMENT_MODE,u,m,{className:"module-def",
+begin:"\\bmodule\\s+"+n+"\\s+"+a+"\\s+=\\s+\\{",end:/\}/,returnBegin:!0,
+keywords:r,relevance:0,contains:[{className:"module",relevance:0,begin:a},{
+begin:/\{/,end:/\}/,skip:!0}].concat(g)},v]}}})());
+hljs.registerLanguage("rib",(()=>{"use strict";return e=>({name:"RenderMan RIB",
+keywords:"ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry Hider Hyperboloid Identity Illuminate Imager Interior LightSource MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd TransformPoints Translate TrimCurve WorldBegin WorldEnd",
+illegal:"</",
+contains:[e.HASH_COMMENT_MODE,e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+})})());
+hljs.registerLanguage("roboconf",(()=>{"use strict";return e=>{
+const n="[a-zA-Z-_][^\\n{]+\\{",a={className:"attribute",begin:/[a-zA-Z-_]+/,
+end:/\s*:/,excludeEnd:!0,starts:{end:";",relevance:0,contains:[{
+className:"variable",begin:/\.[a-zA-Z-_]+/},{className:"keyword",
+begin:/\(optional\)/}]}};return{name:"Roboconf",aliases:["graph","instances"],
+case_insensitive:!0,keywords:"import",contains:[{begin:"^facet "+n,end:/\}/,
+keywords:"facet",contains:[a,e.HASH_COMMENT_MODE]},{begin:"^\\s*instance of "+n,
+end:/\}/,
+keywords:"name count channels instance-data instance-state instance of",
+illegal:/\S/,contains:["self",a,e.HASH_COMMENT_MODE]},{begin:"^"+n,end:/\}/,
+contains:[a,e.HASH_COMMENT_MODE]},e.HASH_COMMENT_MODE]}}})());
+hljs.registerLanguage("routeros",(()=>{"use strict";return e=>{
+const r="foreach do while for if from to step else on-error and or not in",n="true false yes no nothing nil null",i={
+className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)\}/
+}]},s={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i,{
+className:"variable",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]}]},t={
+className:"string",begin:/'/,end:/'/};return{name:"Microtik RouterOS script",
+aliases:["mikrotik"],case_insensitive:!0,keywords:{$pattern:/:?[\w-]+/,
+literal:n,
+keyword:r+" :"+r.split(" ").join(" :")+" :"+"global local beep delay put len typeof pick log time set find environment terminal error execute parse resolve toarray tobool toid toip toip6 tonum tostr totime".split(" ").join(" :")
+},contains:[{variants:[{begin:/\/\*/,end:/\*\//},{begin:/\/\//,end:/$/},{
+begin:/<\//,end:/>/}],illegal:/./},e.COMMENT("^#","$"),s,t,i,{
+begin:/[\w-]+=([^\s{}[\]()>]+)/,relevance:0,returnBegin:!0,contains:[{
+className:"attribute",begin:/[^=]+/},{begin:/=/,endsWithParent:!0,relevance:0,
+contains:[s,t,i,{className:"literal",begin:"\\b("+n.split(" ").join("|")+")\\b"
+},{begin:/("[^"]*"|[^\s{}[\]]+)/}]}]},{className:"number",begin:/\*[0-9a-fA-F]+/
+},{
+begin:"\\b(add|remove|enable|disable|set|get|print|export|edit|find|run|debug|error|info|warning)([\\s[(\\]|])",
+returnBegin:!0,contains:[{className:"builtin-name",begin:/\w+/}]},{
+className:"built_in",variants:[{
+begin:"(\\.\\./|/|\\s)((traffic-flow|traffic-generator|firewall|scheduler|aaa|accounting|address-list|address|align|area|bandwidth-server|bfd|bgp|bridge|client|clock|community|config|connection|console|customer|default|dhcp-client|dhcp-server|discovery|dns|e-mail|ethernet|filter|firmware|gps|graphing|group|hardware|health|hotspot|identity|igmp-proxy|incoming|instance|interface|ip|ipsec|ipv6|irq|l2tp-server|lcd|ldp|logging|mac-server|mac-winbox|mangle|manual|mirror|mme|mpls|nat|nd|neighbor|network|note|ntp|ospf|ospf-v3|ovpn-server|page|peer|pim|ping|policy|pool|port|ppp|pppoe-client|pptp-server|prefix|profile|proposal|proxy|queue|radius|resource|rip|ripng|route|routing|screen|script|security-profiles|server|service|service-port|settings|shares|smb|sms|sniffer|snmp|snooper|socks|sstp-server|system|tool|tracking|type|upgrade|upnp|user-manager|users|user|vlan|secret|vrrp|watchdog|web-access|wireless|pptp|pppoe|lan|wan|layer7-protocol|lease|simple|raw);?\\s)+"
+},{begin:/\.\./,relevance:0}]}]}}})());
+hljs.registerLanguage("rsl",(()=>{"use strict";return e=>({name:"RenderMan RSL",
+keywords:{
+keyword:"float color point normal vector matrix while for if do return else break extern continue",
+built_in:"abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp faceforward filterstep floor format fresnel incident length lightsource log match max min mod noise normalize ntransform opposite option phong pnoise pow printf ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan texture textureinfo trace transform vtransform xcomp ycomp zcomp"
+},illegal:"</",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"#",end:"$"},{className:"class",
+beginKeywords:"surface displacement light volume imager",end:"\\("},{
+beginKeywords:"illuminate illuminance gather",end:"\\("}]})})());
+hljs.registerLanguage("ruleslanguage",(()=>{"use strict";return T=>({
+name:"Oracle Rules Language",keywords:{
+keyword:"BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM NUMDAYS READ_DATE STAGING",
+built_in:"IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME"
+},
+contains:[T.C_LINE_COMMENT_MODE,T.C_BLOCK_COMMENT_MODE,T.APOS_STRING_MODE,T.QUOTE_STRING_MODE,T.C_NUMBER_MODE,{
+className:"literal",variants:[{begin:"#\\s+",relevance:0},{begin:"#[a-zA-Z .]+"
+}]}]})})());
+hljs.registerLanguage("rust",(()=>{"use strict";return e=>{
+const n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"
+;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",
+keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",
+literal:"true false Some None Ok Err",built_in:t},illegal:"</",
+contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]
+}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{
+className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{
+begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",
+begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{
+begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{
+begin:"\\b0x([A-Fa-f0-9_]+)"+n},{
+begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{
+className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#!?\\[",end:"\\]",
+contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",
+beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{
+endsParent:!0})],illegal:"\\S"},{className:"class",
+beginKeywords:"trait enum struct union",end:/\{/,
+contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"
+},{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}})());
+hljs.registerLanguage("sas",(()=>{"use strict";return e=>({name:"SAS",
+aliases:["SAS"],case_insensitive:!0,keywords:{
+literal:"null missing _all_ _automatic_ _character_ _infile_ _n_ _name_ _null_ _numeric_ _user_ _webout_",
+meta:"do if then else end until while abort array attrib by call cards cards4 catname continue datalines datalines4 delete delim delimiter display dm drop endsas error file filename footnote format goto in infile informat input keep label leave length libname link list lostcard merge missing modify options output out page put redirect remove rename replace retain return select set skip startsas stop title update waitsas where window x systask add and alter as cascade check create delete describe distinct drop foreign from group having index insert into in key like message modify msgtype not null on or order primary references reset restrict select set table unique update validate view where"
+},contains:[{className:"keyword",begin:/^\s*(proc [\w\d_]+|data|run|quit)[\s;]/
+},{className:"variable",begin:/&[a-zA-Z_&][a-zA-Z0-9_]*\.?/},{
+className:"emphasis",begin:/^\s*datalines|cards.*;/,end:/^\s*;\s*$/},{
+className:"built_in",
+begin:"%(bquote|nrbquote|cmpres|qcmpres|compstor|datatyp|display|do|else|end|eval|global|goto|if|index|input|keydef|label|left|length|let|local|lowcase|macro|mend|nrbquote|nrquote|nrstr|put|qcmpres|qleft|qlowcase|qscan|qsubstr|qsysfunc|qtrim|quote|qupcase|scan|str|substr|superq|syscall|sysevalf|sysexec|sysfunc|sysget|syslput|sysprod|sysrc|sysrput|then|to|trim|unquote|until|upcase|verify|while|window)"
+},{className:"name",begin:/%[a-zA-Z_][a-zA-Z_0-9]*/},{className:"meta",
+begin:"[^%](abs|addr|airy|arcos|arsin|atan|attrc|attrn|band|betainv|blshift|bnot|bor|brshift|bxor|byte|cdf|ceil|cexist|cinv|close|cnonct|collate|compbl|compound|compress|cos|cosh|css|curobs|cv|daccdb|daccdbsl|daccsl|daccsyd|dacctab|dairy|date|datejul|datepart|datetime|day|dclose|depdb|depdbsl|depdbsl|depsl|depsl|depsyd|depsyd|deptab|deptab|dequote|dhms|dif|digamma|dim|dinfo|dnum|dopen|doptname|doptnum|dread|dropnote|dsname|erf|erfc|exist|exp|fappend|fclose|fcol|fdelete|fetch|fetchobs|fexist|fget|fileexist|filename|fileref|finfo|finv|fipname|fipnamel|fipstate|floor|fnonct|fnote|fopen|foptname|foptnum|fpoint|fpos|fput|fread|frewind|frlen|fsep|fuzz|fwrite|gaminv|gamma|getoption|getvarc|getvarn|hbound|hms|hosthelp|hour|ibessel|index|indexc|indexw|input|inputc|inputn|int|intck|intnx|intrr|irr|jbessel|juldate|kurtosis|lag|lbound|left|length|lgamma|libname|libref|log|log10|log2|logpdf|logpmf|logsdf|lowcase|max|mdy|mean|min|minute|mod|month|mopen|mort|n|netpv|nmiss|normal|note|npv|open|ordinal|pathname|pdf|peek|peekc|pmf|point|poisson|poke|probbeta|probbnml|probchi|probf|probgam|probhypr|probit|probnegb|probnorm|probt|put|putc|putn|qtr|quote|ranbin|rancau|ranexp|rangam|range|rank|rannor|ranpoi|rantbl|rantri|ranuni|repeat|resolve|reverse|rewind|right|round|saving|scan|sdf|second|sign|sin|sinh|skewness|soundex|spedis|sqrt|std|stderr|stfips|stname|stnamel|substr|sum|symget|sysget|sysmsg|sysprod|sysrc|system|tan|tanh|time|timepart|tinv|tnonct|today|translate|tranwrd|trigamma|trim|trimn|trunc|uniform|upcase|uss|var|varfmt|varinfmt|varlabel|varlen|varname|varnum|varray|varrayx|vartype|verify|vformat|vformatd|vformatdx|vformatn|vformatnx|vformatw|vformatwx|vformatx|vinarray|vinarrayx|vinformat|vinformatd|vinformatdx|vinformatn|vinformatnx|vinformatw|vinformatwx|vinformatx|vlabel|vlabelx|vlength|vlengthx|vname|vnamex|vtype|vtypex|weekday|year|yyq|zipfips|zipname|zipnamel|zipstate)[(]"
+},{className:"string",variants:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},e.COMMENT("\\*",";"),e.C_BLOCK_COMMENT_MODE]})})());
+hljs.registerLanguage("scala",(()=>{"use strict";return e=>{const n={
+className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:/\$\{/,end:/\}/}]
+},a={className:"string",variants:[{begin:'"""',end:'"""'},{begin:'"',end:'"',
+illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'[a-z]+"',end:'"',
+illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",
+begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",
+begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",
+begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,
+relevance:0},i={className:"class",beginKeywords:"class object trait type",
+end:/[:={\[\n;]/,excludeEnd:!0,
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,
+excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,
+excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={
+className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,
+contains:[t]};return{name:"Scala",keywords:{literal:"true false null",
+keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"
+},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",
+begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",
+begin:"@[A-Za-z]+"}]}}})());
+hljs.registerLanguage("scheme",(()=>{"use strict";return e=>{
+const t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",n={$pattern:t,
+"builtin-name":"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"
+},r={className:"literal",begin:"(#t|#f|#\\\\"+t+"|#\\\\.)"},a={
+className:"number",variants:[{begin:"(-|\\+)?\\d+([./]\\d+)?",relevance:0},{
+begin:"(-|\\+)?\\d+([./]\\d+)?[+\\-](-|\\+)?\\d+([./]\\d+)?i",relevance:0},{
+begin:"#b[0-1]+(/[0-1]+)?"},{begin:"#o[0-7]+(/[0-7]+)?"},{
+begin:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},i=e.QUOTE_STRING_MODE,c=[e.COMMENT(";","$",{
+relevance:0}),e.COMMENT("#\\|","\\|#")],s={begin:t,relevance:0},l={
+className:"symbol",begin:"'"+t},o={endsWithParent:!0,relevance:0},g={variants:[{
+begin:/'/},{begin:"`"}],contains:[{begin:"\\(",end:"\\)",
+contains:["self",r,i,a,s,l]}]},u={className:"name",relevance:0,begin:t,
+keywords:n},d={variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}],
+contains:[{begin:/lambda/,endsWithParent:!0,returnBegin:!0,contains:[u,{
+endsParent:!0,variants:[{begin:/\(/,end:/\)/},{begin:/\[/,end:/\]/}],
+contains:[s]}]},u,o]};return o.contains=[r,a,i,s,l,g,d].concat(c),{
+name:"Scheme",illegal:/\S/,contains:[e.SHEBANG(),a,i,l,g,d].concat(c)}}})());
+hljs.registerLanguage("scilab",(()=>{"use strict";return e=>{
+const n=[e.C_NUMBER_MODE,{className:"string",begin:"'|\"",end:"'|\"",
+contains:[e.BACKSLASH_ESCAPE,{begin:"''"}]}];return{name:"Scilab",
+aliases:["sci"],keywords:{$pattern:/%?\w+/,
+keyword:"abort break case clear catch continue do elseif else endfunction end for function global if pause return resume select try then while",
+literal:"%f %F %t %T %pi %eps %inf %nan %e %i %z %s",
+built_in:"abs and acos asin atan ceil cd chdir clearglobal cosh cos cumprod deff disp error exec execstr exists exp eye gettext floor fprintf fread fsolve imag isdef isempty isinfisnan isvector lasterror length load linspace list listfiles log10 log2 log max min msprintf mclose mopen ones or pathconvert poly printf prod pwd rand real round sinh sin size gsort sprintf sqrt strcat strcmps tring sum system tanh tan type typename warning zeros matrix"
+},illegal:'("|#|/\\*|\\s+/\\w+)',contains:[{className:"function",
+beginKeywords:"function",end:"$",contains:[e.UNDERSCORE_TITLE_MODE,{
+className:"params",begin:"\\(",end:"\\)"}]},{
+begin:"[a-zA-Z_][a-zA-Z_0-9]*[\\.']+",relevance:0},{begin:"\\[",
+end:"\\][\\.']*",relevance:0,contains:n},e.COMMENT("//","$")].concat(n)}}})());
+hljs.registerLanguage("scss",(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],r=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse()
+;return a=>{const n=(e=>({IMPORTANT:{className:"meta",begin:"!important"},
+HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"},
+ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/,
+illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}
+}))(a),l=r,s=i,d="@[a-z-]+",c={className:"variable",
+begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"};return{name:"SCSS",case_insensitive:!0,
+illegal:"[=/|']",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{
+className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{
+className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0
+},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag",
+begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo",
+begin:":("+s.join("|")+")"},{className:"selector-pseudo",
+begin:"::("+l.join("|")+")"},c,{begin:/\(/,end:/\)/,contains:[a.CSS_NUMBER_MODE]
+},{className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{
+begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"
+},{begin:":",end:";",
+contains:[c,n.HEXCOLOR,a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.IMPORTANT]
+},{begin:"@(page|font-face)",lexemes:d,keywords:"@page @font-face"},{begin:"@",
+end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,
+keyword:"and or not only",attribute:t.join(" ")},contains:[{begin:d,
+className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute"
+},c,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.HEXCOLOR,a.CSS_NUMBER_MODE]}]}}
+})());
+hljs.registerLanguage("shell",(()=>{"use strict";return s=>({
+name:"Shell Session",aliases:["console"],contains:[{className:"meta",
+begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#]/,starts:{end:/[^\\](?=\s*$)/,
+subLanguage:"bash"}}]})})());
+hljs.registerLanguage("smali",(()=>{"use strict";return e=>{
+const n=["add","and","cmp","cmpg","cmpl","const","div","double","float","goto","if","int","long","move","mul","neg","new","nop","not","or","rem","return","shl","shr","sput","sub","throw","ushr","xor"]
+;return{name:"Smali",contains:[{className:"string",begin:'"',end:'"',relevance:0
+},e.COMMENT("#","$",{relevance:0}),{className:"keyword",variants:[{
+begin:"\\s*\\.end\\s[a-zA-Z0-9]*"},{begin:"^[ ]*\\.[a-zA-Z]*",relevance:0},{
+begin:"\\s:[a-zA-Z_0-9]*",relevance:0},{
+begin:"\\s(transient|constructor|abstract|final|synthetic|public|private|protected|static|bridge|system)"
+}]},{className:"built_in",variants:[{begin:"\\s("+n.join("|")+")\\s"},{
+begin:"\\s("+n.join("|")+")((-|/)[a-zA-Z0-9]+)+\\s",relevance:10},{
+begin:"\\s(aget|aput|array|check|execute|fill|filled|goto/16|goto/32|iget|instance|invoke|iput|monitor|packed|sget|sparse)((-|/)[a-zA-Z0-9]+)*\\s",
+relevance:10}]},{className:"class",begin:"L[^(;:\n]*;",relevance:0},{
+begin:"[vp][0-9]+"}]}}})());
+hljs.registerLanguage("smalltalk",(()=>{"use strict";return e=>{
+const n="[a-z][a-zA-Z0-9_]*",a={className:"string",begin:"\\$.{1}"},s={
+className:"symbol",begin:"#"+e.UNDERSCORE_IDENT_RE};return{name:"Smalltalk",
+aliases:["st"],keywords:"self super nil true false thisContext",
+contains:[e.COMMENT('"','"'),e.APOS_STRING_MODE,{className:"type",
+begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},{begin:n+":",relevance:0
+},e.C_NUMBER_MODE,s,a,{begin:"\\|[ ]*"+n+"([ ]+"+n+")*[ ]*\\|",returnBegin:!0,
+end:/\|/,illegal:/\S/,contains:[{begin:"(\\|[ ]*)?"+n}]},{begin:"#\\(",
+end:"\\)",contains:[e.APOS_STRING_MODE,a,e.C_NUMBER_MODE,s]}]}}})());
+hljs.registerLanguage("sml",(()=>{"use strict";return e=>({
+name:"SML (Standard ML)",aliases:["ml"],keywords:{$pattern:"[a-z_]\\w*!?",
+keyword:"abstype and andalso as case datatype do else end eqtype exception fn fun functor handle if in include infix infixr let local nonfix of op open orelse raise rec sharing sig signature struct structure then type val with withtype where while",
+built_in:"array bool char exn int list option order real ref string substring vector unit word",
+literal:"true false NONE SOME LESS EQUAL GREATER nil"},illegal:/\/\/|>>/,
+contains:[{className:"literal",begin:/\[(\|\|)?\]|\(\)/,relevance:0
+},e.COMMENT("\\(\\*","\\*\\)",{contains:["self"]}),{className:"symbol",
+begin:"'[A-Za-z_](?!')[\\w']*"},{className:"type",begin:"`[A-Z][\\w']*"},{
+className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},{
+begin:"[a-z_]\\w*'[\\w']*"},e.inherit(e.APOS_STRING_MODE,{className:"string",
+relevance:0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{className:"number",
+begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",
+relevance:0},{begin:/[-=]>/}]})})());
+hljs.registerLanguage("soy",(()=>{"use strict";return e=>({
+contains:[e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE,{begin:/\{delpackage/,
+ends:/\}/,relevance:10,keywords:"delpackage"},{begin:/\{namespace/,end:/\}/,
+relevance:10,keywords:"namespace autoescape",contains:[e.QUOTE_STRING_MODE]},{
+className:"template-tag",begin:/\{template/,end:/\{\/template\}/,relevance:10,
+keywords:"alias as autoescape call case default delcall else elseif fallbackmsg foreach if ifempty let msg namespace param print switch template",
+contains:[e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE,e.QUOTE_STRING_MODE,{
+className:"template-variable",relevance:0,begin:/\$[^}\s]+/},{className:"tag",
+begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0
+},{endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",
+begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{
+className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/},{begin:/'/,
+end:/'/},{begin:/[^\s"'=<>`]+/}]}]}]}]}]}]})})());
+hljs.registerLanguage("sqf",(()=>{"use strict";return e=>{const t={
+className:"string",variants:[{begin:'"',end:'"',contains:[{begin:'""',
+relevance:0}]},{begin:"'",end:"'",contains:[{begin:"''",relevance:0}]}]},a={
+className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+"meta-keyword":"define undef ifdef ifndef else endif include"},contains:[{
+begin:/\\\n/,relevance:0},e.inherit(t,{className:"meta-string"}),{
+className:"meta-string",begin:/<[^\n>]*>/,end:/$/,illegal:"\\n"
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]};return{name:"SQF",
+aliases:["sqf"],case_insensitive:!0,keywords:{
+keyword:"case catch default do else exit exitWith for forEach from if private switch then throw to try waitUntil while with",
+built_in:"abs accTime acos action actionIDs actionKeys actionKeysImages actionKeysNames actionKeysNamesArray actionName actionParams activateAddons activatedAddons activateKey add3DENConnection add3DENEventHandler add3DENLayer addAction addBackpack addBackpackCargo addBackpackCargoGlobal addBackpackGlobal addCamShake addCuratorAddons addCuratorCameraArea addCuratorEditableObjects addCuratorEditingArea addCuratorPoints addEditorObject addEventHandler addForce addGoggles addGroupIcon addHandgunItem addHeadgear addItem addItemCargo addItemCargoGlobal addItemPool addItemToBackpack addItemToUniform addItemToVest addLiveStats addMagazine addMagazineAmmoCargo addMagazineCargo addMagazineCargoGlobal addMagazineGlobal addMagazinePool addMagazines addMagazineTurret addMenu addMenuItem addMissionEventHandler addMPEventHandler addMusicEventHandler addOwnedMine addPlayerScores addPrimaryWeaponItem addPublicVariableEventHandler addRating addResources addScore addScoreSide addSecondaryWeaponItem addSwitchableUnit addTeamMember addToRemainsCollector addTorque addUniform addVehicle addVest addWaypoint addWeapon addWeaponCargo addWeaponCargoGlobal addWeaponGlobal addWeaponItem addWeaponPool addWeaponTurret admin agent agents AGLToASL aimedAtTarget aimPos airDensityRTD airplaneThrottle airportSide AISFinishHeal alive all3DENEntities allAirports allControls allCurators allCutLayers allDead allDeadMen allDisplays allGroups allMapMarkers allMines allMissionObjects allow3DMode allowCrewInImmobile allowCuratorLogicIgnoreAreas allowDamage allowDammage allowFileOperations allowFleeing allowGetIn allowSprint allPlayers allSimpleObjects allSites allTurrets allUnits allUnitsUAV allVariables ammo ammoOnPylon and animate animateBay animateDoor animatePylon animateSource animationNames animationPhase animationSourcePhase animationState append apply armoryPoints arrayIntersect asin ASLToAGL ASLToATL assert assignAsCargo assignAsCargoIndex assignAsCommander assignAsDriver assignAsGunner assignAsTurret assignCurator assignedCargo assignedCommander assignedDriver assignedGunner assignedItems assignedTarget assignedTeam assignedVehicle assignedVehicleRole assignItem assignTeam assignToAirport atan atan2 atg ATLToASL attachedObject attachedObjects attachedTo attachObject attachTo attackEnabled backpack backpackCargo backpackContainer backpackItems backpackMagazines backpackSpaceFor behaviour benchmark binocular boundingBox boundingBoxReal boundingCenter breakOut breakTo briefingName buildingExit buildingPos buttonAction buttonSetAction cadetMode call callExtension camCommand camCommit camCommitPrepared camCommitted camConstuctionSetParams camCreate camDestroy cameraEffect cameraEffectEnableHUD cameraInterest cameraOn cameraView campaignConfigFile camPreload camPreloaded camPrepareBank camPrepareDir camPrepareDive camPrepareFocus camPrepareFov camPrepareFovRange camPreparePos camPrepareRelPos camPrepareTarget camSetBank camSetDir camSetDive camSetFocus camSetFov camSetFovRange camSetPos camSetRelPos camSetTarget camTarget camUseNVG canAdd canAddItemToBackpack canAddItemToUniform canAddItemToVest cancelSimpleTaskDestination canFire canMove canSlingLoad canStand canSuspend canTriggerDynamicSimulation canUnloadInCombat canVehicleCargo captive captiveNum cbChecked cbSetChecked ceil channelEnabled cheatsEnabled checkAIFeature checkVisibility className clearAllItemsFromBackpack clearBackpackCargo clearBackpackCargoGlobal clearGroupIcons clearItemCargo clearItemCargoGlobal clearItemPool clearMagazineCargo clearMagazineCargoGlobal clearMagazinePool clearOverlay clearRadio clearWeaponCargo clearWeaponCargoGlobal clearWeaponPool clientOwner closeDialog closeDisplay closeOverlay collapseObjectTree collect3DENHistory collectiveRTD combatMode commandArtilleryFire commandChat commander commandFire commandFollow commandFSM commandGetOut commandingMenu commandMove commandRadio commandStop commandSuppressiveFire commandTarget commandWatch comment commitOverlay compile compileFinal completedFSM composeText configClasses configFile configHierarchy configName configProperties configSourceAddonList configSourceMod configSourceModList confirmSensorTarget connectTerminalToUAV controlsGroupCtrl copyFromClipboard copyToClipboard copyWaypoints cos count countEnemy countFriendly countSide countType countUnknown create3DENComposition create3DENEntity createAgent createCenter createDialog createDiaryLink createDiaryRecord createDiarySubject createDisplay createGearDialog createGroup createGuardedPoint createLocation createMarker createMarkerLocal createMenu createMine createMissionDisplay createMPCampaignDisplay createSimpleObject createSimpleTask createSite createSoundSource createTask createTeam createTrigger createUnit createVehicle createVehicleCrew createVehicleLocal crew ctAddHeader ctAddRow ctClear ctCurSel ctData ctFindHeaderRows ctFindRowHeader ctHeaderControls ctHeaderCount ctRemoveHeaders ctRemoveRows ctrlActivate ctrlAddEventHandler ctrlAngle ctrlAutoScrollDelay ctrlAutoScrollRewind ctrlAutoScrollSpeed ctrlChecked ctrlClassName ctrlCommit ctrlCommitted ctrlCreate ctrlDelete ctrlEnable ctrlEnabled ctrlFade ctrlHTMLLoaded ctrlIDC ctrlIDD ctrlMapAnimAdd ctrlMapAnimClear ctrlMapAnimCommit ctrlMapAnimDone ctrlMapCursor ctrlMapMouseOver ctrlMapScale ctrlMapScreenToWorld ctrlMapWorldToScreen ctrlModel ctrlModelDirAndUp ctrlModelScale ctrlParent ctrlParentControlsGroup ctrlPosition ctrlRemoveAllEventHandlers ctrlRemoveEventHandler ctrlScale ctrlSetActiveColor ctrlSetAngle ctrlSetAutoScrollDelay ctrlSetAutoScrollRewind ctrlSetAutoScrollSpeed ctrlSetBackgroundColor ctrlSetChecked ctrlSetEventHandler ctrlSetFade ctrlSetFocus ctrlSetFont ctrlSetFontH1 ctrlSetFontH1B ctrlSetFontH2 ctrlSetFontH2B ctrlSetFontH3 ctrlSetFontH3B ctrlSetFontH4 ctrlSetFontH4B ctrlSetFontH5 ctrlSetFontH5B ctrlSetFontH6 ctrlSetFontH6B ctrlSetFontHeight ctrlSetFontHeightH1 ctrlSetFontHeightH2 ctrlSetFontHeightH3 ctrlSetFontHeightH4 ctrlSetFontHeightH5 ctrlSetFontHeightH6 ctrlSetFontHeightSecondary ctrlSetFontP ctrlSetFontPB ctrlSetFontSecondary ctrlSetForegroundColor ctrlSetModel ctrlSetModelDirAndUp ctrlSetModelScale ctrlSetPixelPrecision ctrlSetPosition ctrlSetScale ctrlSetStructuredText ctrlSetText ctrlSetTextColor ctrlSetTooltip ctrlSetTooltipColorBox ctrlSetTooltipColorShade ctrlSetTooltipColorText ctrlShow ctrlShown ctrlText ctrlTextHeight ctrlTextWidth ctrlType ctrlVisible ctRowControls ctRowCount ctSetCurSel ctSetData ctSetHeaderTemplate ctSetRowTemplate ctSetValue ctValue curatorAddons curatorCamera curatorCameraArea curatorCameraAreaCeiling curatorCoef curatorEditableObjects curatorEditingArea curatorEditingAreaType curatorMouseOver curatorPoints curatorRegisteredObjects curatorSelected curatorWaypointCost current3DENOperation currentChannel currentCommand currentMagazine currentMagazineDetail currentMagazineDetailTurret currentMagazineTurret currentMuzzle currentNamespace currentTask currentTasks currentThrowable currentVisionMode currentWaypoint currentWeapon currentWeaponMode currentWeaponTurret currentZeroing cursorObject cursorTarget customChat customRadio cutFadeOut cutObj cutRsc cutText damage date dateToNumber daytime deActivateKey debriefingText debugFSM debugLog deg delete3DENEntities deleteAt deleteCenter deleteCollection deleteEditorObject deleteGroup deleteGroupWhenEmpty deleteIdentity deleteLocation deleteMarker deleteMarkerLocal deleteRange deleteResources deleteSite deleteStatus deleteTeam deleteVehicle deleteVehicleCrew deleteWaypoint detach detectedMines diag_activeMissionFSMs diag_activeScripts diag_activeSQFScripts diag_activeSQSScripts diag_captureFrame diag_captureFrameToFile diag_captureSlowFrame diag_codePerformance diag_drawMode diag_enable diag_enabled diag_fps diag_fpsMin diag_frameNo diag_lightNewLoad diag_list diag_log diag_logSlowFrame diag_mergeConfigFile diag_recordTurretLimits diag_setLightNew diag_tickTime diag_toggle dialog diarySubjectExists didJIP didJIPOwner difficulty difficultyEnabled difficultyEnabledRTD difficultyOption direction directSay disableAI disableCollisionWith disableConversation disableDebriefingStats disableMapIndicators disableNVGEquipment disableRemoteSensors disableSerialization disableTIEquipment disableUAVConnectability disableUserInput displayAddEventHandler displayCtrl displayParent displayRemoveAllEventHandlers displayRemoveEventHandler displaySetEventHandler dissolveTeam distance distance2D distanceSqr distributionRegion do3DENAction doArtilleryFire doFire doFollow doFSM doGetOut doMove doorPhase doStop doSuppressiveFire doTarget doWatch drawArrow drawEllipse drawIcon drawIcon3D drawLine drawLine3D drawLink drawLocation drawPolygon drawRectangle drawTriangle driver drop dynamicSimulationDistance dynamicSimulationDistanceCoef dynamicSimulationEnabled dynamicSimulationSystemEnabled echo edit3DENMissionAttributes editObject editorSetEventHandler effectiveCommander emptyPositions enableAI enableAIFeature enableAimPrecision enableAttack enableAudioFeature enableAutoStartUpRTD enableAutoTrimRTD enableCamShake enableCaustics enableChannel enableCollisionWith enableCopilot enableDebriefingStats enableDiagLegend enableDynamicSimulation enableDynamicSimulationSystem enableEndDialog enableEngineArtillery enableEnvironment enableFatigue enableGunLights enableInfoPanelComponent enableIRLasers enableMimics enablePersonTurret enableRadio enableReload enableRopeAttach enableSatNormalOnDetail enableSaving enableSentences enableSimulation enableSimulationGlobal enableStamina enableTeamSwitch enableTraffic enableUAVConnectability enableUAVWaypoints enableVehicleCargo enableVehicleSensor enableWeaponDisassembly endLoadingScreen endMission engineOn enginesIsOnRTD enginesRpmRTD enginesTorqueRTD entities environmentEnabled estimatedEndServerTime estimatedTimeLeft evalObjectArgument everyBackpack everyContainer exec execEditorScript execFSM execVM exp expectedDestination exportJIPMessages eyeDirection eyePos face faction fadeMusic fadeRadio fadeSound fadeSpeech failMission fillWeaponsFromPool find findCover findDisplay findEditorObject findEmptyPosition findEmptyPositionReady findIf findNearestEnemy finishMissionInit finite fire fireAtTarget firstBackpack flag flagAnimationPhase flagOwner flagSide flagTexture fleeing floor flyInHeight flyInHeightASL fog fogForecast fogParams forceAddUniform forcedMap forceEnd forceFlagTexture forceFollowRoad forceMap forceRespawn forceSpeed forceWalk forceWeaponFire forceWeatherChange forEachMember forEachMemberAgent forEachMemberTeam forgetTarget format formation formationDirection formationLeader formationMembers formationPosition formationTask formatText formLeader freeLook fromEditor fuel fullCrew gearIDCAmmoCount gearSlotAmmoCount gearSlotData get3DENActionState get3DENAttribute get3DENCamera get3DENConnections get3DENEntity get3DENEntityID get3DENGrid get3DENIconsVisible get3DENLayerEntities get3DENLinesVisible get3DENMissionAttribute get3DENMouseOver get3DENSelected getAimingCoef getAllEnvSoundControllers getAllHitPointsDamage getAllOwnedMines getAllSoundControllers getAmmoCargo getAnimAimPrecision getAnimSpeedCoef getArray getArtilleryAmmo getArtilleryComputerSettings getArtilleryETA getAssignedCuratorLogic getAssignedCuratorUnit getBackpackCargo getBleedingRemaining getBurningValue getCameraViewDirection getCargoIndex getCenterOfMass getClientState getClientStateNumber getCompatiblePylonMagazines getConnectedUAV getContainerMaxLoad getCursorObjectParams getCustomAimCoef getDammage getDescription getDir getDirVisual getDLCAssetsUsage getDLCAssetsUsageByName getDLCs getEditorCamera getEditorMode getEditorObjectScope getElevationOffset getEnvSoundController getFatigue getForcedFlagTexture getFriend getFSMVariable getFuelCargo getGroupIcon getGroupIconParams getGroupIcons getHideFrom getHit getHitIndex getHitPointDamage getItemCargo getMagazineCargo getMarkerColor getMarkerPos getMarkerSize getMarkerType getMass getMissionConfig getMissionConfigValue getMissionDLCs getMissionLayerEntities getModelInfo getMousePosition getMusicPlayedTime getNumber getObjectArgument getObjectChildren getObjectDLC getObjectMaterials getObjectProxy getObjectTextures getObjectType getObjectViewDistance getOxygenRemaining getPersonUsedDLCs getPilotCameraDirection getPilotCameraPosition getPilotCameraRotation getPilotCameraTarget getPlateNumber getPlayerChannel getPlayerScores getPlayerUID getPos getPosASL getPosASLVisual getPosASLW getPosATL getPosATLVisual getPosVisual getPosWorld getPylonMagazines getRelDir getRelPos getRemoteSensorsDisabled getRepairCargo getResolution getShadowDistance getShotParents getSlingLoad getSoundController getSoundControllerResult getSpeed getStamina getStatValue getSuppression getTerrainGrid getTerrainHeightASL getText getTotalDLCUsageTime getUnitLoadout getUnitTrait getUserMFDText getUserMFDvalue getVariable getVehicleCargo getWeaponCargo getWeaponSway getWingsOrientationRTD getWingsPositionRTD getWPPos glanceAt globalChat globalRadio goggles goto group groupChat groupFromNetId groupIconSelectable groupIconsVisible groupId groupOwner groupRadio groupSelectedUnits groupSelectUnit gunner gusts halt handgunItems handgunMagazine handgunWeapon handsHit hasInterface hasPilotCamera hasWeapon hcAllGroups hcGroupParams hcLeader hcRemoveAllGroups hcRemoveGroup hcSelected hcSelectGroup hcSetGroup hcShowBar hcShownBar headgear hideBody hideObject hideObjectGlobal hideSelection hint hintC hintCadet hintSilent hmd hostMission htmlLoad HUDMovementLevels humidity image importAllGroups importance in inArea inAreaArray incapacitatedState inflame inflamed infoPanel infoPanelComponentEnabled infoPanelComponents infoPanels inGameUISetEventHandler inheritsFrom initAmbientLife inPolygon inputAction inRangeOfArtillery insertEditorObject intersect is3DEN is3DENMultiplayer isAbleToBreathe isAgent isArray isAutoHoverOn isAutonomous isAutotest isBleeding isBurning isClass isCollisionLightOn isCopilotEnabled isDamageAllowed isDedicated isDLCAvailable isEngineOn isEqualTo isEqualType isEqualTypeAll isEqualTypeAny isEqualTypeArray isEqualTypeParams isFilePatchingEnabled isFlashlightOn isFlatEmpty isForcedWalk isFormationLeader isGroupDeletedWhenEmpty isHidden isInRemainsCollector isInstructorFigureEnabled isIRLaserOn isKeyActive isKindOf isLaserOn isLightOn isLocalized isManualFire isMarkedForCollection isMultiplayer isMultiplayerSolo isNil isNull isNumber isObjectHidden isObjectRTD isOnRoad isPipEnabled isPlayer isRealTime isRemoteExecuted isRemoteExecutedJIP isServer isShowing3DIcons isSimpleObject isSprintAllowed isStaminaEnabled isSteamMission isStreamFriendlyUIEnabled isText isTouchingGround isTurnedOut isTutHintsEnabled isUAVConnectable isUAVConnected isUIContext isUniformAllowed isVehicleCargo isVehicleRadarOn isVehicleSensorEnabled isWalking isWeaponDeployed isWeaponRested itemCargo items itemsWithMagazines join joinAs joinAsSilent joinSilent joinString kbAddDatabase kbAddDatabaseTargets kbAddTopic kbHasTopic kbReact kbRemoveTopic kbTell kbWasSaid keyImage keyName knowsAbout land landAt landResult language laserTarget lbAdd lbClear lbColor lbColorRight lbCurSel lbData lbDelete lbIsSelected lbPicture lbPictureRight lbSelection lbSetColor lbSetColorRight lbSetCurSel lbSetData lbSetPicture lbSetPictureColor lbSetPictureColorDisabled lbSetPictureColorSelected lbSetPictureRight lbSetPictureRightColor lbSetPictureRightColorDisabled lbSetPictureRightColorSelected lbSetSelectColor lbSetSelectColorRight lbSetSelected lbSetText lbSetTextRight lbSetTooltip lbSetValue lbSize lbSort lbSortByValue lbText lbTextRight lbValue leader leaderboardDeInit leaderboardGetRows leaderboardInit leaderboardRequestRowsFriends leaderboardsRequestUploadScore leaderboardsRequestUploadScoreKeepBest leaderboardState leaveVehicle libraryCredits libraryDisclaimers lifeState lightAttachObject lightDetachObject lightIsOn lightnings limitSpeed linearConversion lineIntersects lineIntersectsObjs lineIntersectsSurfaces lineIntersectsWith linkItem list listObjects listRemoteTargets listVehicleSensors ln lnbAddArray lnbAddColumn lnbAddRow lnbClear lnbColor lnbCurSelRow lnbData lnbDeleteColumn lnbDeleteRow lnbGetColumnsPosition lnbPicture lnbSetColor lnbSetColumnsPos lnbSetCurSelRow lnbSetData lnbSetPicture lnbSetText lnbSetValue lnbSize lnbSort lnbSortByValue lnbText lnbValue load loadAbs loadBackpack loadFile loadGame loadIdentity loadMagazine loadOverlay loadStatus loadUniform loadVest local localize locationPosition lock lockCameraTo lockCargo lockDriver locked lockedCargo lockedDriver lockedTurret lockIdentity lockTurret lockWP log logEntities logNetwork logNetworkTerminate lookAt lookAtPos magazineCargo magazines magazinesAllTurrets magazinesAmmo magazinesAmmoCargo magazinesAmmoFull magazinesDetail magazinesDetailBackpack magazinesDetailUniform magazinesDetailVest magazinesTurret magazineTurretAmmo mapAnimAdd mapAnimClear mapAnimCommit mapAnimDone mapCenterOnCamera mapGridPosition markAsFinishedOnSteam markerAlpha markerBrush markerColor markerDir markerPos markerShape markerSize markerText markerType max members menuAction menuAdd menuChecked menuClear menuCollapse menuData menuDelete menuEnable menuEnabled menuExpand menuHover menuPicture menuSetAction menuSetCheck menuSetData menuSetPicture menuSetValue menuShortcut menuShortcutText menuSize menuSort menuText menuURL menuValue min mineActive mineDetectedBy missionConfigFile missionDifficulty missionName missionNamespace missionStart missionVersion mod modelToWorld modelToWorldVisual modelToWorldVisualWorld modelToWorldWorld modParams moonIntensity moonPhase morale move move3DENCamera moveInAny moveInCargo moveInCommander moveInDriver moveInGunner moveInTurret moveObjectToEnd moveOut moveTime moveTo moveToCompleted moveToFailed musicVolume name nameSound nearEntities nearestBuilding nearestLocation nearestLocations nearestLocationWithDubbing nearestObject nearestObjects nearestTerrainObjects nearObjects nearObjectsReady nearRoads nearSupplies nearTargets needReload netId netObjNull newOverlay nextMenuItemIndex nextWeatherChange nMenuItems not numberOfEnginesRTD numberToDate objectCurators objectFromNetId objectParent objStatus onBriefingGroup onBriefingNotes onBriefingPlan onBriefingTeamSwitch onCommandModeChanged onDoubleClick onEachFrame onGroupIconClick onGroupIconOverEnter onGroupIconOverLeave onHCGroupSelectionChanged onMapSingleClick onPlayerConnected onPlayerDisconnected onPreloadFinished onPreloadStarted onShowNewObject onTeamSwitch openCuratorInterface openDLCPage openMap openSteamApp openYoutubeVideo or orderGetIn overcast overcastForecast owner param params parseNumber parseSimpleArray parseText parsingNamespace particlesQuality pickWeaponPool pitch pixelGrid pixelGridBase pixelGridNoUIScale pixelH pixelW playableSlotsNumber playableUnits playAction playActionNow player playerRespawnTime playerSide playersNumber playGesture playMission playMove playMoveNow playMusic playScriptedMission playSound playSound3D position positionCameraToWorld posScreenToWorld posWorldToScreen ppEffectAdjust ppEffectCommit ppEffectCommitted ppEffectCreate ppEffectDestroy ppEffectEnable ppEffectEnabled ppEffectForceInNVG precision preloadCamera preloadObject preloadSound preloadTitleObj preloadTitleRsc preprocessFile preprocessFileLineNumbers primaryWeapon primaryWeaponItems primaryWeaponMagazine priority processDiaryLink productVersion profileName profileNamespace profileNameSteam progressLoadingScreen progressPosition progressSetPosition publicVariable publicVariableClient publicVariableServer pushBack pushBackUnique putWeaponPool queryItemsPool queryMagazinePool queryWeaponPool rad radioChannelAdd radioChannelCreate radioChannelRemove radioChannelSetCallSign radioChannelSetLabel radioVolume rain rainbow random rank rankId rating rectangular registeredTasks registerTask reload reloadEnabled remoteControl remoteExec remoteExecCall remoteExecutedOwner remove3DENConnection remove3DENEventHandler remove3DENLayer removeAction removeAll3DENEventHandlers removeAllActions removeAllAssignedItems removeAllContainers removeAllCuratorAddons removeAllCuratorCameraAreas removeAllCuratorEditingAreas removeAllEventHandlers removeAllHandgunItems removeAllItems removeAllItemsWithMagazines removeAllMissionEventHandlers removeAllMPEventHandlers removeAllMusicEventHandlers removeAllOwnedMines removeAllPrimaryWeaponItems removeAllWeapons removeBackpack removeBackpackGlobal removeCuratorAddons removeCuratorCameraArea removeCuratorEditableObjects removeCuratorEditingArea removeDrawIcon removeDrawLinks removeEventHandler removeFromRemainsCollector removeGoggles removeGroupIcon removeHandgunItem removeHeadgear removeItem removeItemFromBackpack removeItemFromUniform removeItemFromVest removeItems removeMagazine removeMagazineGlobal removeMagazines removeMagazinesTurret removeMagazineTurret removeMenuItem removeMissionEventHandler removeMPEventHandler removeMusicEventHandler removeOwnedMine removePrimaryWeaponItem removeSecondaryWeaponItem removeSimpleTask removeSwitchableUnit removeTeamMember removeUniform removeVest removeWeapon removeWeaponAttachmentCargo removeWeaponCargo removeWeaponGlobal removeWeaponTurret reportRemoteTarget requiredVersion resetCamShake resetSubgroupDirection resize resources respawnVehicle restartEditorCamera reveal revealMine reverse reversedMouseY roadAt roadsConnectedTo roleDescription ropeAttachedObjects ropeAttachedTo ropeAttachEnabled ropeAttachTo ropeCreate ropeCut ropeDestroy ropeDetach ropeEndPosition ropeLength ropes ropeUnwind ropeUnwound rotorsForcesRTD rotorsRpmRTD round runInitScript safeZoneH safeZoneW safeZoneWAbs safeZoneX safeZoneXAbs safeZoneY save3DENInventory saveGame saveIdentity saveJoysticks saveOverlay saveProfileNamespace saveStatus saveVar savingEnabled say say2D say3D scopeName score scoreSide screenshot screenToWorld scriptDone scriptName scudState secondaryWeapon secondaryWeaponItems secondaryWeaponMagazine select selectBestPlaces selectDiarySubject selectedEditorObjects selectEditorObject selectionNames selectionPosition selectLeader selectMax selectMin selectNoPlayer selectPlayer selectRandom selectRandomWeighted selectWeapon selectWeaponTurret sendAUMessage sendSimpleCommand sendTask sendTaskResult sendUDPMessage serverCommand serverCommandAvailable serverCommandExecutable serverName serverTime set set3DENAttribute set3DENAttributes set3DENGrid set3DENIconsVisible set3DENLayer set3DENLinesVisible set3DENLogicType set3DENMissionAttribute set3DENMissionAttributes set3DENModelsVisible set3DENObjectType set3DENSelected setAccTime setActualCollectiveRTD setAirplaneThrottle setAirportSide setAmmo setAmmoCargo setAmmoOnPylon setAnimSpeedCoef setAperture setApertureNew setArmoryPoints setAttributes setAutonomous setBehaviour setBleedingRemaining setBrakesRTD setCameraInterest setCamShakeDefParams setCamShakeParams setCamUseTI setCaptive setCenterOfMass setCollisionLight setCombatMode setCompassOscillation setConvoySeparation setCuratorCameraAreaCeiling setCuratorCoef setCuratorEditingAreaType setCuratorWaypointCost setCurrentChannel setCurrentTask setCurrentWaypoint setCustomAimCoef setCustomWeightRTD setDamage setDammage setDate setDebriefingText setDefaultCamera setDestination setDetailMapBlendPars setDir setDirection setDrawIcon setDriveOnPath setDropInterval setDynamicSimulationDistance setDynamicSimulationDistanceCoef setEditorMode setEditorObjectScope setEffectCondition setEngineRPMRTD setFace setFaceAnimation setFatigue setFeatureType setFlagAnimationPhase setFlagOwner setFlagSide setFlagTexture setFog setFormation setFormationTask setFormDir setFriend setFromEditor setFSMVariable setFuel setFuelCargo setGroupIcon setGroupIconParams setGroupIconsSelectable setGroupIconsVisible setGroupId setGroupIdGlobal setGroupOwner setGusts setHideBehind setHit setHitIndex setHitPointDamage setHorizonParallaxCoef setHUDMovementLevels setIdentity setImportance setInfoPanel setLeader setLightAmbient setLightAttenuation setLightBrightness setLightColor setLightDayLight setLightFlareMaxDistance setLightFlareSize setLightIntensity setLightnings setLightUseFlare setLocalWindParams setMagazineTurretAmmo setMarkerAlpha setMarkerAlphaLocal setMarkerBrush setMarkerBrushLocal setMarkerColor setMarkerColorLocal setMarkerDir setMarkerDirLocal setMarkerPos setMarkerPosLocal setMarkerShape setMarkerShapeLocal setMarkerSize setMarkerSizeLocal setMarkerText setMarkerTextLocal setMarkerType setMarkerTypeLocal setMass setMimic setMousePosition setMusicEffect setMusicEventHandler setName setNameSound setObjectArguments setObjectMaterial setObjectMaterialGlobal setObjectProxy setObjectTexture setObjectTextureGlobal setObjectViewDistance setOvercast setOwner setOxygenRemaining setParticleCircle setParticleClass setParticleFire setParticleParams setParticleRandom setPilotCameraDirection setPilotCameraRotation setPilotCameraTarget setPilotLight setPiPEffect setPitch setPlateNumber setPlayable setPlayerRespawnTime setPos setPosASL setPosASL2 setPosASLW setPosATL setPosition setPosWorld setPylonLoadOut setPylonsPriority setRadioMsg setRain setRainbow setRandomLip setRank setRectangular setRepairCargo setRotorBrakeRTD setShadowDistance setShotParents setSide setSimpleTaskAlwaysVisible setSimpleTaskCustomData setSimpleTaskDescription setSimpleTaskDestination setSimpleTaskTarget setSimpleTaskType setSimulWeatherLayers setSize setSkill setSlingLoad setSoundEffect setSpeaker setSpeech setSpeedMode setStamina setStaminaScheme setStatValue setSuppression setSystemOfUnits setTargetAge setTaskMarkerOffset setTaskResult setTaskState setTerrainGrid setText setTimeMultiplier setTitleEffect setTrafficDensity setTrafficDistance setTrafficGap setTrafficSpeed setTriggerActivation setTriggerArea setTriggerStatements setTriggerText setTriggerTimeout setTriggerType setType setUnconscious setUnitAbility setUnitLoadout setUnitPos setUnitPosWeak setUnitRank setUnitRecoilCoefficient setUnitTrait setUnloadInCombat setUserActionText setUserMFDText setUserMFDvalue setVariable setVectorDir setVectorDirAndUp setVectorUp setVehicleAmmo setVehicleAmmoDef setVehicleArmor setVehicleCargo setVehicleId setVehicleLock setVehiclePosition setVehicleRadar setVehicleReceiveRemoteTargets setVehicleReportOwnPosition setVehicleReportRemoteTargets setVehicleTIPars setVehicleVarName setVelocity setVelocityModelSpace setVelocityTransformation setViewDistance setVisibleIfTreeCollapsed setWantedRPMRTD setWaves setWaypointBehaviour setWaypointCombatMode setWaypointCompletionRadius setWaypointDescription setWaypointForceBehaviour setWaypointFormation setWaypointHousePosition setWaypointLoiterRadius setWaypointLoiterType setWaypointName setWaypointPosition setWaypointScript setWaypointSpeed setWaypointStatements setWaypointTimeout setWaypointType setWaypointVisible setWeaponReloadingTime setWind setWindDir setWindForce setWindStr setWingForceScaleRTD setWPPos show3DIcons showChat showCinemaBorder showCommandingMenu showCompass showCuratorCompass showGPS showHUD showLegend showMap shownArtilleryComputer shownChat shownCompass shownCuratorCompass showNewEditorObject shownGPS shownHUD shownMap shownPad shownRadio shownScoretable shownUAVFeed shownWarrant shownWatch showPad showRadio showScoretable showSubtitles showUAVFeed showWarrant showWatch showWaypoint showWaypoints side sideChat sideEnemy sideFriendly sideRadio simpleTasks simulationEnabled simulCloudDensity simulCloudOcclusion simulInClouds simulWeatherSync sin size sizeOf skill skillFinal skipTime sleep sliderPosition sliderRange sliderSetPosition sliderSetRange sliderSetSpeed sliderSpeed slingLoadAssistantShown soldierMagazines someAmmo sort soundVolume spawn speaker speed speedMode splitString sqrt squadParams stance startLoadingScreen step stop stopEngineRTD stopped str sunOrMoon supportInfo suppressFor surfaceIsWater surfaceNormal surfaceType swimInDepth switchableUnits switchAction switchCamera switchGesture switchLight switchMove synchronizedObjects synchronizedTriggers synchronizedWaypoints synchronizeObjectsAdd synchronizeObjectsRemove synchronizeTrigger synchronizeWaypoint systemChat systemOfUnits tan targetKnowledge targets targetsAggregate targetsQuery taskAlwaysVisible taskChildren taskCompleted taskCustomData taskDescription taskDestination taskHint taskMarkerOffset taskParent taskResult taskState taskType teamMember teamName teams teamSwitch teamSwitchEnabled teamType terminate terrainIntersect terrainIntersectASL terrainIntersectAtASL text textLog textLogFormat tg time timeMultiplier titleCut titleFadeOut titleObj titleRsc titleText toArray toFixed toLower toString toUpper triggerActivated triggerActivation triggerArea triggerAttachedVehicle triggerAttachObject triggerAttachVehicle triggerDynamicSimulation triggerStatements triggerText triggerTimeout triggerTimeoutCurrent triggerType turretLocal turretOwner turretUnit tvAdd tvClear tvCollapse tvCollapseAll tvCount tvCurSel tvData tvDelete tvExpand tvExpandAll tvPicture tvSetColor tvSetCurSel tvSetData tvSetPicture tvSetPictureColor tvSetPictureColorDisabled tvSetPictureColorSelected tvSetPictureRight tvSetPictureRightColor tvSetPictureRightColorDisabled tvSetPictureRightColorSelected tvSetText tvSetTooltip tvSetValue tvSort tvSortByValue tvText tvTooltip tvValue type typeName typeOf UAVControl uiNamespace uiSleep unassignCurator unassignItem unassignTeam unassignVehicle underwater uniform uniformContainer uniformItems uniformMagazines unitAddons unitAimPosition unitAimPositionVisual unitBackpack unitIsUAV unitPos unitReady unitRecoilCoefficient units unitsBelowHeight unlinkItem unlockAchievement unregisterTask updateDrawIcon updateMenuItem updateObjectTree useAISteeringComponent useAudioTimeForMoves userInputDisabled vectorAdd vectorCos vectorCrossProduct vectorDiff vectorDir vectorDirVisual vectorDistance vectorDistanceSqr vectorDotProduct vectorFromTo vectorMagnitude vectorMagnitudeSqr vectorModelToWorld vectorModelToWorldVisual vectorMultiply vectorNormalized vectorUp vectorUpVisual vectorWorldToModel vectorWorldToModelVisual vehicle vehicleCargoEnabled vehicleChat vehicleRadio vehicleReceiveRemoteTargets vehicleReportOwnPosition vehicleReportRemoteTargets vehicles vehicleVarName velocity velocityModelSpace verifySignature vest vestContainer vestItems vestMagazines viewDistance visibleCompass visibleGPS visibleMap visiblePosition visiblePositionASL visibleScoretable visibleWatch waves waypointAttachedObject waypointAttachedVehicle waypointAttachObject waypointAttachVehicle waypointBehaviour waypointCombatMode waypointCompletionRadius waypointDescription waypointForceBehaviour waypointFormation waypointHousePosition waypointLoiterRadius waypointLoiterType waypointName waypointPosition waypoints waypointScript waypointsEnabledUAV waypointShow waypointSpeed waypointStatements waypointTimeout waypointTimeoutCurrent waypointType waypointVisible weaponAccessories weaponAccessoriesCargo weaponCargo weaponDirection weaponInertia weaponLowered weapons weaponsItems weaponsItemsCargo weaponState weaponsTurret weightRTD WFSideText wind ",
+literal:"blufor civilian configNull controlNull displayNull east endl false grpNull independent lineBreak locationNull nil objNull opfor pi resistance scriptNull sideAmbientLife sideEmpty sideLogic sideUnknown taskNull teamMemberNull true west"
+},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.NUMBER_MODE,{
+className:"variable",begin:/\b_+[a-zA-Z]\w*/},{className:"title",
+begin:/[a-zA-Z][a-zA-Z0-9]+_fnc_\w*/},t,a],illegal:/#|^\$ /}}})());
+hljs.registerLanguage("sql_more",(()=>{"use strict";return e=>{
+var t=e.COMMENT("--","$");return{name:"SQL (more)",aliases:["mysql","oracle"],
+disableAutodetect:!0,case_insensitive:!0,illegal:/[<>{}*]/,contains:[{
+beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",
+end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,
+keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",
+literal:"true false null unknown",
+built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"
+},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{
+className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{
+className:"string",begin:"`",end:"`"
+},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]
+},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}})());
+hljs.registerLanguage("sql",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function r(...r){
+return r.map((r=>e(r))).join("")}function t(...r){
+return"("+r.map((r=>e(r))).join("|")+")"}return e=>{
+const n=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],s=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],o=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],c=s,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update ","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!s.includes(e))),u={
+begin:r(/\b/,t(...c),/\s*\(/),keywords:{built_in:c}};return{name:"SQL",
+case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,
+keyword:((e,{exceptions:r,when:t}={})=>{const n=t
+;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e))
+})(l,{when:e=>e.length<3}),literal:a,type:i,
+built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"]
+},contains:[{begin:t(...o),keywords:{$pattern:/[\w\.]+/,keyword:l.concat(o),
+literal:a,type:i}},{className:"type",
+begin:t("double precision","large object","with timezone","without timezone")
+},u,{className:"variable",begin:/@[a-z0-9]+/},{className:"string",variants:[{
+begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,contains:[{
+begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,{className:"operator",
+begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}}})());
+hljs.registerLanguage("stan",(()=>{"use strict";return _=>({name:"Stan",
+aliases:["stanfuncs"],keywords:{$pattern:_.IDENT_RE,
+title:["functions","model","data","parameters","quantities","transformed","generated"],
+keyword:["for","in","if","else","while","break","continue","return"].concat(["int","real","vector","ordered","positive_ordered","simplex","unit_vector","row_vector","matrix","cholesky_factor_corr|10","cholesky_factor_cov|10","corr_matrix|10","cov_matrix|10","void"]).concat(["print","reject","increment_log_prob|10","integrate_ode|10","integrate_ode_rk45|10","integrate_ode_bdf|10","algebra_solver"]),
+built_in:["Phi","Phi_approx","abs","acos","acosh","algebra_solver","append_array","append_col","append_row","asin","asinh","atan","atan2","atanh","bernoulli_cdf","bernoulli_lccdf","bernoulli_lcdf","bernoulli_logit_lpmf","bernoulli_logit_rng","bernoulli_lpmf","bernoulli_rng","bessel_first_kind","bessel_second_kind","beta_binomial_cdf","beta_binomial_lccdf","beta_binomial_lcdf","beta_binomial_lpmf","beta_binomial_rng","beta_cdf","beta_lccdf","beta_lcdf","beta_lpdf","beta_rng","binary_log_loss","binomial_cdf","binomial_coefficient_log","binomial_lccdf","binomial_lcdf","binomial_logit_lpmf","binomial_lpmf","binomial_rng","block","categorical_logit_lpmf","categorical_logit_rng","categorical_lpmf","categorical_rng","cauchy_cdf","cauchy_lccdf","cauchy_lcdf","cauchy_lpdf","cauchy_rng","cbrt","ceil","chi_square_cdf","chi_square_lccdf","chi_square_lcdf","chi_square_lpdf","chi_square_rng","cholesky_decompose","choose","col","cols","columns_dot_product","columns_dot_self","cos","cosh","cov_exp_quad","crossprod","csr_extract_u","csr_extract_v","csr_extract_w","csr_matrix_times_vector","csr_to_dense_matrix","cumulative_sum","determinant","diag_matrix","diag_post_multiply","diag_pre_multiply","diagonal","digamma","dims","dirichlet_lpdf","dirichlet_rng","distance","dot_product","dot_self","double_exponential_cdf","double_exponential_lccdf","double_exponential_lcdf","double_exponential_lpdf","double_exponential_rng","e","eigenvalues_sym","eigenvectors_sym","erf","erfc","exp","exp2","exp_mod_normal_cdf","exp_mod_normal_lccdf","exp_mod_normal_lcdf","exp_mod_normal_lpdf","exp_mod_normal_rng","expm1","exponential_cdf","exponential_lccdf","exponential_lcdf","exponential_lpdf","exponential_rng","fabs","falling_factorial","fdim","floor","fma","fmax","fmin","fmod","frechet_cdf","frechet_lccdf","frechet_lcdf","frechet_lpdf","frechet_rng","gamma_cdf","gamma_lccdf","gamma_lcdf","gamma_lpdf","gamma_p","gamma_q","gamma_rng","gaussian_dlm_obs_lpdf","get_lp","gumbel_cdf","gumbel_lccdf","gumbel_lcdf","gumbel_lpdf","gumbel_rng","head","hypergeometric_lpmf","hypergeometric_rng","hypot","inc_beta","int_step","integrate_ode","integrate_ode_bdf","integrate_ode_rk45","inv","inv_Phi","inv_chi_square_cdf","inv_chi_square_lccdf","inv_chi_square_lcdf","inv_chi_square_lpdf","inv_chi_square_rng","inv_cloglog","inv_gamma_cdf","inv_gamma_lccdf","inv_gamma_lcdf","inv_gamma_lpdf","inv_gamma_rng","inv_logit","inv_sqrt","inv_square","inv_wishart_lpdf","inv_wishart_rng","inverse","inverse_spd","is_inf","is_nan","lbeta","lchoose","lgamma","lkj_corr_cholesky_lpdf","lkj_corr_cholesky_rng","lkj_corr_lpdf","lkj_corr_rng","lmgamma","lmultiply","log","log10","log1m","log1m_exp","log1m_inv_logit","log1p","log1p_exp","log2","log_determinant","log_diff_exp","log_falling_factorial","log_inv_logit","log_mix","log_rising_factorial","log_softmax","log_sum_exp","logistic_cdf","logistic_lccdf","logistic_lcdf","logistic_lpdf","logistic_rng","logit","lognormal_cdf","lognormal_lccdf","lognormal_lcdf","lognormal_lpdf","lognormal_rng","machine_precision","matrix_exp","max","mdivide_left_spd","mdivide_left_tri_low","mdivide_right_spd","mdivide_right_tri_low","mean","min","modified_bessel_first_kind","modified_bessel_second_kind","multi_gp_cholesky_lpdf","multi_gp_lpdf","multi_normal_cholesky_lpdf","multi_normal_cholesky_rng","multi_normal_lpdf","multi_normal_prec_lpdf","multi_normal_rng","multi_student_t_lpdf","multi_student_t_rng","multinomial_lpmf","multinomial_rng","multiply_log","multiply_lower_tri_self_transpose","neg_binomial_2_cdf","neg_binomial_2_lccdf","neg_binomial_2_lcdf","neg_binomial_2_log_lpmf","neg_binomial_2_log_rng","neg_binomial_2_lpmf","neg_binomial_2_rng","neg_binomial_cdf","neg_binomial_lccdf","neg_binomial_lcdf","neg_binomial_lpmf","neg_binomial_rng","negative_infinity","normal_cdf","normal_lccdf","normal_lcdf","normal_lpdf","normal_rng","not_a_number","num_elements","ordered_logistic_lpmf","ordered_logistic_rng","owens_t","pareto_cdf","pareto_lccdf","pareto_lcdf","pareto_lpdf","pareto_rng","pareto_type_2_cdf","pareto_type_2_lccdf","pareto_type_2_lcdf","pareto_type_2_lpdf","pareto_type_2_rng","pi","poisson_cdf","poisson_lccdf","poisson_lcdf","poisson_log_lpmf","poisson_log_rng","poisson_lpmf","poisson_rng","positive_infinity","pow","print","prod","qr_Q","qr_R","quad_form","quad_form_diag","quad_form_sym","rank","rayleigh_cdf","rayleigh_lccdf","rayleigh_lcdf","rayleigh_lpdf","rayleigh_rng","reject","rep_array","rep_matrix","rep_row_vector","rep_vector","rising_factorial","round","row","rows","rows_dot_product","rows_dot_self","scaled_inv_chi_square_cdf","scaled_inv_chi_square_lccdf","scaled_inv_chi_square_lcdf","scaled_inv_chi_square_lpdf","scaled_inv_chi_square_rng","sd","segment","sin","singular_values","sinh","size","skew_normal_cdf","skew_normal_lccdf","skew_normal_lcdf","skew_normal_lpdf","skew_normal_rng","softmax","sort_asc","sort_desc","sort_indices_asc","sort_indices_desc","sqrt","sqrt2","square","squared_distance","step","student_t_cdf","student_t_lccdf","student_t_lcdf","student_t_lpdf","student_t_rng","sub_col","sub_row","sum","tail","tan","tanh","target","tcrossprod","tgamma","to_array_1d","to_array_2d","to_matrix","to_row_vector","to_vector","trace","trace_gen_quad_form","trace_quad_form","trigamma","trunc","uniform_cdf","uniform_lccdf","uniform_lcdf","uniform_lpdf","uniform_rng","variance","von_mises_lpdf","von_mises_rng","weibull_cdf","weibull_lccdf","weibull_lcdf","weibull_lpdf","weibull_rng","wiener_lpdf","wishart_lpdf","wishart_rng"]
+},contains:[_.C_LINE_COMMENT_MODE,_.COMMENT(/#/,/$/,{relevance:0,keywords:{
+"meta-keyword":"include"}}),_.COMMENT(/\/\*/,/\*\//,{relevance:0,contains:[{
+className:"doctag",begin:/@(return|param)/}]}),{begin:/<\s*lower\s*=/,
+keywords:"lower"},{begin:/[<,]\s*upper\s*=/,keywords:"upper"},{
+className:"keyword",begin:/\btarget\s*\+=/,relevance:10},{
+begin:"~\\s*("+_.IDENT_RE+")\\s*\\(",
+keywords:["bernoulli","bernoulli_logit","beta","beta_binomial","binomial","binomial_logit","categorical","categorical_logit","cauchy","chi_square","dirichlet","double_exponential","exp_mod_normal","exponential","frechet","gamma","gaussian_dlm_obs","gumbel","hypergeometric","inv_chi_square","inv_gamma","inv_wishart","lkj_corr","lkj_corr_cholesky","logistic","lognormal","multi_gp","multi_gp_cholesky","multi_normal","multi_normal_cholesky","multi_normal_prec","multi_student_t","multinomial","neg_binomial","neg_binomial_2","neg_binomial_2_log","normal","ordered_logistic","pareto","pareto_type_2","poisson","poisson_log","rayleigh","scaled_inv_chi_square","skew_normal","student_t","uniform","von_mises","weibull","wiener","wishart"]
+},{className:"number",variants:[{begin:/\b\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/},{
+begin:/\.\d+(?:[eE][+-]?\d+)?\b/}],relevance:0},{className:"string",begin:'"',
+end:'"',relevance:0}]})})());
+hljs.registerLanguage("stata",(()=>{"use strict";return e=>({name:"Stata",
+aliases:["do","ado"],case_insensitive:!0,
+keywords:"if else in foreach for forv forva forval forvalu forvalue forvalues by bys bysort xi quietly qui capture about ac ac_7 acprplot acprplot_7 adjust ado adopath adoupdate alpha ameans an ano anov anova anova_estat anova_terms anovadef aorder ap app appe appen append arch arch_dr arch_estat arch_p archlm areg areg_p args arima arima_dr arima_estat arima_p as asmprobit asmprobit_estat asmprobit_lf asmprobit_mfx__dlg asmprobit_p ass asse asser assert avplot avplot_7 avplots avplots_7 bcskew0 bgodfrey bias binreg bip0_lf biplot bipp_lf bipr_lf bipr_p biprobit bitest bitesti bitowt blogit bmemsize boot bootsamp bootstrap bootstrap_8 boxco_l boxco_p boxcox boxcox_6 boxcox_p bprobit br break brier bro brow brows browse brr brrstat bs bs_7 bsampl_w bsample bsample_7 bsqreg bstat bstat_7 bstat_8 bstrap bstrap_7 bubble bubbleplot ca ca_estat ca_p cabiplot camat canon canon_8 canon_8_p canon_estat canon_p cap caprojection capt captu captur capture cat cc cchart cchart_7 cci cd censobs_table centile cf char chdir checkdlgfiles checkestimationsample checkhlpfiles checksum chelp ci cii cl class classutil clear cli clis clist clo clog clog_lf clog_p clogi clogi_sw clogit clogit_lf clogit_p clogitp clogl_sw cloglog clonevar clslistarray cluster cluster_measures cluster_stop cluster_tree cluster_tree_8 clustermat cmdlog cnr cnre cnreg cnreg_p cnreg_sw cnsreg codebook collaps4 collapse colormult_nb colormult_nw compare compress conf confi confir confirm conren cons const constr constra constrai constrain constraint continue contract copy copyright copysource cor corc corr corr2data corr_anti corr_kmo corr_smc corre correl correla correlat correlate corrgram cou coun count cox cox_p cox_sw coxbase coxhaz coxvar cprplot cprplot_7 crc cret cretu cretur creturn cross cs cscript cscript_log csi ct ct_is ctset ctst_5 ctst_st cttost cumsp cumsp_7 cumul cusum cusum_7 cutil d|0 datasig datasign datasigna datasignat datasignatu datasignatur datasignature datetof db dbeta de dec deco decod decode deff des desc descr descri describ describe destring dfbeta dfgls dfuller di di_g dir dirstats dis discard disp disp_res disp_s displ displa display distinct do doe doed doedi doedit dotplot dotplot_7 dprobit drawnorm drop ds ds_util dstdize duplicates durbina dwstat dydx e|0 ed edi edit egen eivreg emdef en enc enco encod encode eq erase ereg ereg_lf ereg_p ereg_sw ereghet ereghet_glf ereghet_glf_sh ereghet_gp ereghet_ilf ereghet_ilf_sh ereghet_ip eret eretu eretur ereturn err erro error esize est est_cfexist est_cfname est_clickable est_expand est_hold est_table est_unhold est_unholdok estat estat_default estat_summ estat_vce_only esti estimates etodow etof etomdy ex exi exit expand expandcl fac fact facto factor factor_estat factor_p factor_pca_rotated factor_rotate factormat fcast fcast_compute fcast_graph fdades fdadesc fdadescr fdadescri fdadescrib fdadescribe fdasav fdasave fdause fh_st file open file read file close file filefilter fillin find_hlp_file findfile findit findit_7 fit fl fli flis flist for5_0 forest forestplot form forma format fpredict frac_154 frac_adj frac_chk frac_cox frac_ddp frac_dis frac_dv frac_in frac_mun frac_pp frac_pq frac_pv frac_wgt frac_xo fracgen fracplot fracplot_7 fracpoly fracpred fron_ex fron_hn fron_p fron_tn fron_tn2 frontier ftodate ftoe ftomdy ftowdate funnel funnelplot g|0 gamhet_glf gamhet_gp gamhet_ilf gamhet_ip gamma gamma_d2 gamma_p gamma_sw gammahet gdi_hexagon gdi_spokes ge gen gene gener genera generat generate genrank genstd genvmean gettoken gl gladder gladder_7 glim_l01 glim_l02 glim_l03 glim_l04 glim_l05 glim_l06 glim_l07 glim_l08 glim_l09 glim_l10 glim_l11 glim_l12 glim_lf glim_mu glim_nw1 glim_nw2 glim_nw3 glim_p glim_v1 glim_v2 glim_v3 glim_v4 glim_v5 glim_v6 glim_v7 glm glm_6 glm_p glm_sw glmpred glo glob globa global glogit glogit_8 glogit_p gmeans gnbre_lf gnbreg gnbreg_5 gnbreg_p gomp_lf gompe_sw gomper_p gompertz gompertzhet gomphet_glf gomphet_glf_sh gomphet_gp gomphet_ilf gomphet_ilf_sh gomphet_ip gphdot gphpen gphprint gprefs gprobi_p gprobit gprobit_8 gr gr7 gr_copy gr_current gr_db gr_describe gr_dir gr_draw gr_draw_replay gr_drop gr_edit gr_editviewopts gr_example gr_example2 gr_export gr_print gr_qscheme gr_query gr_read gr_rename gr_replay gr_save gr_set gr_setscheme gr_table gr_undo gr_use graph graph7 grebar greigen greigen_7 greigen_8 grmeanby grmeanby_7 gs_fileinfo gs_filetype gs_graphinfo gs_stat gsort gwood h|0 hadimvo hareg hausman haver he heck_d2 heckma_p heckman heckp_lf heckpr_p heckprob hel help hereg hetpr_lf hetpr_p hetprob hettest hexdump hilite hist hist_7 histogram hlogit hlu hmeans hotel hotelling hprobit hreg hsearch icd9 icd9_ff icd9p iis impute imtest inbase include inf infi infil infile infix inp inpu input ins insheet insp inspe inspec inspect integ inten intreg intreg_7 intreg_p intrg2_ll intrg_ll intrg_ll2 ipolate iqreg ir irf irf_create irfm iri is_svy is_svysum isid istdize ivprob_1_lf ivprob_lf ivprobit ivprobit_p ivreg ivreg_footnote ivtob_1_lf ivtob_lf ivtobit ivtobit_p jackknife jacknife jknife jknife_6 jknife_8 jkstat joinby kalarma1 kap kap_3 kapmeier kappa kapwgt kdensity kdensity_7 keep ksm ksmirnov ktau kwallis l|0 la lab labbe labbeplot labe label labelbook ladder levels levelsof leverage lfit lfit_p li lincom line linktest lis list lloghet_glf lloghet_glf_sh lloghet_gp lloghet_ilf lloghet_ilf_sh lloghet_ip llogi_sw llogis_p llogist llogistic llogistichet lnorm_lf lnorm_sw lnorma_p lnormal lnormalhet lnormhet_glf lnormhet_glf_sh lnormhet_gp lnormhet_ilf lnormhet_ilf_sh lnormhet_ip lnskew0 loadingplot loc loca local log logi logis_lf logistic logistic_p logit logit_estat logit_p loglogs logrank loneway lookfor lookup lowess lowess_7 lpredict lrecomp lroc lroc_7 lrtest ls lsens lsens_7 lsens_x lstat ltable ltable_7 ltriang lv lvr2plot lvr2plot_7 m|0 ma mac macr macro makecns man manova manova_estat manova_p manovatest mantel mark markin markout marksample mat mat_capp mat_order mat_put_rr mat_rapp mata mata_clear mata_describe mata_drop mata_matdescribe mata_matsave mata_matuse mata_memory mata_mlib mata_mosave mata_rename mata_which matalabel matcproc matlist matname matr matri matrix matrix_input__dlg matstrik mcc mcci md0_ md1_ md1debug_ md2_ md2debug_ mds mds_estat mds_p mdsconfig mdslong mdsmat mdsshepard mdytoe mdytof me_derd mean means median memory memsize menl meqparse mer merg merge meta mfp mfx mhelp mhodds minbound mixed_ll mixed_ll_reparm mkassert mkdir mkmat mkspline ml ml_5 ml_adjs ml_bhhhs ml_c_d ml_check ml_clear ml_cnt ml_debug ml_defd ml_e0 ml_e0_bfgs ml_e0_cycle ml_e0_dfp ml_e0i ml_e1 ml_e1_bfgs ml_e1_bhhh ml_e1_cycle ml_e1_dfp ml_e2 ml_e2_cycle ml_ebfg0 ml_ebfr0 ml_ebfr1 ml_ebh0q ml_ebhh0 ml_ebhr0 ml_ebr0i ml_ecr0i ml_edfp0 ml_edfr0 ml_edfr1 ml_edr0i ml_eds ml_eer0i ml_egr0i ml_elf ml_elf_bfgs ml_elf_bhhh ml_elf_cycle ml_elf_dfp ml_elfi ml_elfs ml_enr0i ml_enrr0 ml_erdu0 ml_erdu0_bfgs ml_erdu0_bhhh ml_erdu0_bhhhq ml_erdu0_cycle ml_erdu0_dfp ml_erdu0_nrbfgs ml_exde ml_footnote ml_geqnr ml_grad0 ml_graph ml_hbhhh ml_hd0 ml_hold ml_init ml_inv ml_log ml_max ml_mlout ml_mlout_8 ml_model ml_nb0 ml_opt ml_p ml_plot ml_query ml_rdgrd ml_repor ml_s_e ml_score ml_searc ml_technique ml_unhold mleval mlf_ mlmatbysum mlmatsum mlog mlogi mlogit mlogit_footnote mlogit_p mlopts mlsum mlvecsum mnl0_ mor more mov move mprobit mprobit_lf mprobit_p mrdu0_ mrdu1_ mvdecode mvencode mvreg mvreg_estat n|0 nbreg nbreg_al nbreg_lf nbreg_p nbreg_sw nestreg net newey newey_7 newey_p news nl nl_7 nl_9 nl_9_p nl_p nl_p_7 nlcom nlcom_p nlexp2 nlexp2_7 nlexp2a nlexp2a_7 nlexp3 nlexp3_7 nlgom3 nlgom3_7 nlgom4 nlgom4_7 nlinit nllog3 nllog3_7 nllog4 nllog4_7 nlog_rd nlogit nlogit_p nlogitgen nlogittree nlpred no nobreak noi nois noisi noisil noisily note notes notes_dlg nptrend numlabel numlist odbc old_ver olo olog ologi ologi_sw ologit ologit_p ologitp on one onew onewa oneway op_colnm op_comp op_diff op_inv op_str opr opro oprob oprob_sw oprobi oprobi_p oprobit oprobitp opts_exclusive order orthog orthpoly ou out outf outfi outfil outfile outs outsh outshe outshee outsheet ovtest pac pac_7 palette parse parse_dissim pause pca pca_8 pca_display pca_estat pca_p pca_rotate pcamat pchart pchart_7 pchi pchi_7 pcorr pctile pentium pergram pergram_7 permute permute_8 personal peto_st pkcollapse pkcross pkequiv pkexamine pkexamine_7 pkshape pksumm pksumm_7 pl plo plot plugin pnorm pnorm_7 poisgof poiss_lf poiss_sw poisso_p poisson poisson_estat post postclose postfile postutil pperron pr prais prais_e prais_e2 prais_p predict predictnl preserve print pro prob probi probit probit_estat probit_p proc_time procoverlay procrustes procrustes_estat procrustes_p profiler prog progr progra program prop proportion prtest prtesti pwcorr pwd q\\s qby qbys qchi qchi_7 qladder qladder_7 qnorm qnorm_7 qqplot qqplot_7 qreg qreg_c qreg_p qreg_sw qu quadchk quantile quantile_7 que quer query range ranksum ratio rchart rchart_7 rcof recast reclink recode reg reg3 reg3_p regdw regr regre regre_p2 regres regres_p regress regress_estat regriv_p remap ren rena renam rename renpfix repeat replace report reshape restore ret retu retur return rm rmdir robvar roccomp roccomp_7 roccomp_8 rocf_lf rocfit rocfit_8 rocgold rocplot rocplot_7 roctab roctab_7 rolling rologit rologit_p rot rota rotat rotate rotatemat rreg rreg_p ru run runtest rvfplot rvfplot_7 rvpplot rvpplot_7 sa safesum sample sampsi sav save savedresults saveold sc sca scal scala scalar scatter scm_mine sco scob_lf scob_p scobi_sw scobit scor score scoreplot scoreplot_help scree screeplot screeplot_help sdtest sdtesti se search separate seperate serrbar serrbar_7 serset set set_defaults sfrancia sh she shel shell shewhart shewhart_7 signestimationsample signrank signtest simul simul_7 simulate simulate_8 sktest sleep slogit slogit_d2 slogit_p smooth snapspan so sor sort spearman spikeplot spikeplot_7 spikeplt spline_x split sqreg sqreg_p sret sretu sretur sreturn ssc st st_ct st_hc st_hcd st_hcd_sh st_is st_issys st_note st_promo st_set st_show st_smpl st_subid stack statsby statsby_8 stbase stci stci_7 stcox stcox_estat stcox_fr stcox_fr_ll stcox_p stcox_sw stcoxkm stcoxkm_7 stcstat stcurv stcurve stcurve_7 stdes stem stepwise stereg stfill stgen stir stjoin stmc stmh stphplot stphplot_7 stphtest stphtest_7 stptime strate strate_7 streg streg_sw streset sts sts_7 stset stsplit stsum sttocc sttoct stvary stweib su suest suest_8 sum summ summa summar summari summariz summarize sunflower sureg survcurv survsum svar svar_p svmat svy svy_disp svy_dreg svy_est svy_est_7 svy_estat svy_get svy_gnbreg_p svy_head svy_header svy_heckman_p svy_heckprob_p svy_intreg_p svy_ivreg_p svy_logistic_p svy_logit_p svy_mlogit_p svy_nbreg_p svy_ologit_p svy_oprobit_p svy_poisson_p svy_probit_p svy_regress_p svy_sub svy_sub_7 svy_x svy_x_7 svy_x_p svydes svydes_8 svygen svygnbreg svyheckman svyheckprob svyintreg svyintreg_7 svyintrg svyivreg svylc svylog_p svylogit svymarkout svymarkout_8 svymean svymlog svymlogit svynbreg svyolog svyologit svyoprob svyoprobit svyopts svypois svypois_7 svypoisson svyprobit svyprobt svyprop svyprop_7 svyratio svyreg svyreg_p svyregress svyset svyset_7 svyset_8 svytab svytab_7 svytest svytotal sw sw_8 swcnreg swcox swereg swilk swlogis swlogit swologit swoprbt swpois swprobit swqreg swtobit swweib symmetry symmi symplot symplot_7 syntax sysdescribe sysdir sysuse szroeter ta tab tab1 tab2 tab_or tabd tabdi tabdis tabdisp tabi table tabodds tabodds_7 tabstat tabu tabul tabula tabulat tabulate te tempfile tempname tempvar tes test testnl testparm teststd tetrachoric time_it timer tis tob tobi tobit tobit_p tobit_sw token tokeni tokeniz tokenize tostring total translate translator transmap treat_ll treatr_p treatreg trim trimfill trnb_cons trnb_mean trpoiss_d2 trunc_ll truncr_p truncreg tsappend tset tsfill tsline tsline_ex tsreport tsrevar tsrline tsset tssmooth tsunab ttest ttesti tut_chk tut_wait tutorial tw tware_st two twoway twoway__fpfit_serset twoway__function_gen twoway__histogram_gen twoway__ipoint_serset twoway__ipoints_serset twoway__kdensity_gen twoway__lfit_serset twoway__normgen_gen twoway__pci_serset twoway__qfit_serset twoway__scatteri_serset twoway__sunflower_gen twoway_ksm_serset ty typ type typeof u|0 unab unabbrev unabcmd update us use uselabel var var_mkcompanion var_p varbasic varfcast vargranger varirf varirf_add varirf_cgraph varirf_create varirf_ctable varirf_describe varirf_dir varirf_drop varirf_erase varirf_graph varirf_ograph varirf_rename varirf_set varirf_table varlist varlmar varnorm varsoc varstable varstable_w varstable_w2 varwle vce vec vec_fevd vec_mkphi vec_p vec_p_w vecirf_create veclmar veclmar_w vecnorm vecnorm_w vecrank vecstable verinst vers versi versio version view viewsource vif vwls wdatetof webdescribe webseek webuse weib1_lf weib2_lf weib_lf weib_lf0 weibhet_glf weibhet_glf_sh weibhet_glfa weibhet_glfa_sh weibhet_gp weibhet_ilf weibhet_ilf_sh weibhet_ilfa weibhet_ilfa_sh weibhet_ip weibu_sw weibul_p weibull weibull_c weibull_s weibullhet wh whelp whi which whil while wilc_st wilcoxon win wind windo window winexec wntestb wntestb_7 wntestq xchart xchart_7 xcorr xcorr_7 xi xi_6 xmlsav xmlsave xmluse xpose xsh xshe xshel xshell xt_iis xt_tis xtab_p xtabond xtbin_p xtclog xtcloglog xtcloglog_8 xtcloglog_d2 xtcloglog_pa_p xtcloglog_re_p xtcnt_p xtcorr xtdata xtdes xtfront_p xtfrontier xtgee xtgee_elink xtgee_estat xtgee_makeivar xtgee_p xtgee_plink xtgls xtgls_p xthaus xthausman xtht_p xthtaylor xtile xtint_p xtintreg xtintreg_8 xtintreg_d2 xtintreg_p xtivp_1 xtivp_2 xtivreg xtline xtline_ex xtlogit xtlogit_8 xtlogit_d2 xtlogit_fe_p xtlogit_pa_p xtlogit_re_p xtmixed xtmixed_estat xtmixed_p xtnb_fe xtnb_lf xtnbreg xtnbreg_pa_p xtnbreg_refe_p xtpcse xtpcse_p xtpois xtpoisson xtpoisson_d2 xtpoisson_pa_p xtpoisson_refe_p xtpred xtprobit xtprobit_8 xtprobit_d2 xtprobit_re_p xtps_fe xtps_lf xtps_ren xtps_ren_8 xtrar_p xtrc xtrc_p xtrchh xtrefe_p xtreg xtreg_be xtreg_fe xtreg_ml xtreg_pa_p xtreg_re xtregar xtrere_p xtset xtsf_ll xtsf_llti xtsum xttab xttest0 xttobit xttobit_8 xttobit_p xttrans yx yxview__barlike_draw yxview_area_draw yxview_bar_draw yxview_dot_draw yxview_dropline_draw yxview_function_draw yxview_iarrow_draw yxview_ilabels_draw yxview_normal_draw yxview_pcarrow_draw yxview_pcbarrow_draw yxview_pccapsym_draw yxview_pcscatter_draw yxview_pcspike_draw yxview_rarea_draw yxview_rbar_draw yxview_rbarm_draw yxview_rcap_draw yxview_rcapsym_draw yxview_rconnected_draw yxview_rline_draw yxview_rscatter_draw yxview_rspike_draw yxview_spike_draw yxview_sunflower_draw zap_s zinb zinb_llf zinb_plf zip zip_llf zip_p zip_plf zt_ct_5 zt_hc_5 zt_hcd_5 zt_is_5 zt_iss_5 zt_sho_5 zt_smp_5 ztbase_5 ztcox_5 ztdes_5 ztereg_5 ztfill_5 ztgen_5 ztir_5 ztjoin_5 ztnb ztnb_p ztp ztp_p zts_5 ztset_5 ztspli_5 ztsum_5 zttoct_5 ztvary_5 ztweib_5",
+contains:[{className:"symbol",begin:/`[a-zA-Z0-9_]+'/},{className:"variable",
+begin:/\$\{?[a-zA-Z0-9_]+\}?/},{className:"string",variants:[{
+begin:'`"[^\r\n]*?"\''},{begin:'"[^\r\n"]*"'}]},{className:"built_in",
+variants:[{
+begin:"\\b(abs|acos|asin|atan|atan2|atanh|ceil|cloglog|comb|cos|digamma|exp|floor|invcloglog|invlogit|ln|lnfact|lnfactorial|lngamma|log|log10|max|min|mod|reldif|round|sign|sin|sqrt|sum|tan|tanh|trigamma|trunc|betaden|Binomial|binorm|binormal|chi2|chi2tail|dgammapda|dgammapdada|dgammapdadx|dgammapdx|dgammapdxdx|F|Fden|Ftail|gammaden|gammap|ibeta|invbinomial|invchi2|invchi2tail|invF|invFtail|invgammap|invibeta|invnchi2|invnFtail|invnibeta|invnorm|invnormal|invttail|nbetaden|nchi2|nFden|nFtail|nibeta|norm|normal|normalden|normd|npnchi2|tden|ttail|uniform|abbrev|char|index|indexnot|length|lower|ltrim|match|plural|proper|real|regexm|regexr|regexs|reverse|rtrim|string|strlen|strlower|strltrim|strmatch|strofreal|strpos|strproper|strreverse|strrtrim|strtrim|strupper|subinstr|subinword|substr|trim|upper|word|wordcount|_caller|autocode|byteorder|chop|clip|cond|e|epsdouble|epsfloat|group|inlist|inrange|irecode|matrix|maxbyte|maxdouble|maxfloat|maxint|maxlong|mi|minbyte|mindouble|minfloat|minint|minlong|missing|r|recode|replay|return|s|scalar|d|date|day|dow|doy|halfyear|mdy|month|quarter|week|year|d|daily|dofd|dofh|dofm|dofq|dofw|dofy|h|halfyearly|hofd|m|mofd|monthly|q|qofd|quarterly|tin|twithin|w|weekly|wofd|y|yearly|yh|ym|yofd|yq|yw|cholesky|colnumb|colsof|corr|det|diag|diag0cnt|el|get|hadamard|I|inv|invsym|issym|issymmetric|J|matmissing|matuniform|mreldif|nullmat|rownumb|rowsof|sweep|syminv|trace|vec|vecdiag)(?=\\()"
+}]},e.COMMENT("^[ \t]*\\*.*$",!1),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]
+})})());
+hljs.registerLanguage("step21",(()=>{"use strict";return e=>({
+name:"STEP Part 21",aliases:["p21","step","stp"],case_insensitive:!0,keywords:{
+$pattern:"[A-Z_][A-Z0-9_.]*",keyword:"HEADER ENDSEC DATA"},contains:[{
+className:"meta",begin:"ISO-10303-21;",relevance:10},{className:"meta",
+begin:"END-ISO-10303-21;",relevance:10
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.COMMENT("/\\*\\*!","\\*/"),e.C_NUMBER_MODE,e.inherit(e.APOS_STRING_MODE,{
+illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),{
+className:"string",begin:"'",end:"'"},{className:"symbol",variants:[{begin:"#",
+end:"\\d+",illegal:"\\W"}]}]})})());
+hljs.registerLanguage("stylus",(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],r=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse()
+;return n=>{const a=(e=>({IMPORTANT:{className:"meta",begin:"!important"},
+HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"},
+ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/,
+illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}}))(n),s={
+className:"variable",begin:"\\$"+n.IDENT_RE},l="(?=[.\\s\\n[:,(])";return{
+name:"Stylus",aliases:["styl"],case_insensitive:!1,keywords:"if else for in",
+illegal:"(\\?|(\\bReturn\\b)|(\\bEnd\\b)|(\\bend\\b)|(\\bdef\\b)|;|#\\s|\\*\\s|===\\s|\\||%)",
+contains:[n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,a.HEXCOLOR,{
+begin:"\\.[a-zA-Z][a-zA-Z0-9_-]*(?=[.\\s\\n[:,(])",className:"selector-class"},{
+begin:"#[a-zA-Z][a-zA-Z0-9_-]*(?=[.\\s\\n[:,(])",className:"selector-id"},{
+begin:"\\b("+e.join("|")+")"+l,className:"selector-tag"},{
+className:"selector-pseudo",begin:"&?:("+i.join("|")+")"+l},{
+className:"selector-pseudo",begin:"&?::("+o.join("|")+")"+l
+},a.ATTRIBUTE_SELECTOR_MODE,{className:"keyword",begin:/@media/,starts:{
+end:/[{;}]/,keywords:{$pattern:/[a-z-]+/,keyword:"and or not only",
+attribute:t.join(" ")},contains:[n.CSS_NUMBER_MODE]}},{className:"keyword",
+begin:"@((-(o|moz|ms|webkit)-)?(charset|css|debug|extend|font-face|for|import|include|keyframes|media|mixin|page|warn|while))\\b"
+},s,n.CSS_NUMBER_MODE,{className:"function",
+begin:"^[a-zA-Z][a-zA-Z0-9_-]*\\(.*\\)",illegal:"[\\n]",returnBegin:!0,
+contains:[{className:"title",begin:"\\b[a-zA-Z][a-zA-Z0-9_-]*"},{
+className:"params",begin:/\(/,end:/\)/,
+contains:[a.HEXCOLOR,s,n.APOS_STRING_MODE,n.CSS_NUMBER_MODE,n.QUOTE_STRING_MODE]
+}]},{className:"attribute",begin:"\\b("+r.join("|")+")\\b",starts:{end:/;|$/,
+contains:[a.HEXCOLOR,s,n.APOS_STRING_MODE,n.QUOTE_STRING_MODE,n.CSS_NUMBER_MODE,n.C_BLOCK_COMMENT_MODE,a.IMPORTANT],
+illegal:/\./,relevance:0}}]}}})());
+hljs.registerLanguage("subunit",(()=>{"use strict";return s=>({name:"SubUnit",
+case_insensitive:!0,contains:[{className:"string",begin:"\\[\n(multipart)?",
+end:"\\]\n"},{className:"string",
+begin:"\\d{4}-\\d{2}-\\d{2}(\\s+)\\d{2}:\\d{2}:\\d{2}.\\d+Z"},{
+className:"string",begin:"(\\+|-)\\d+"},{className:"keyword",relevance:10,
+variants:[{
+begin:"^(test|testing|success|successful|failure|error|skip|xfail|uxsuccess)(:?)\\s+(test)?"
+},{begin:"^progress(:?)(\\s+)?(pop|push)?"},{begin:"^tags:"},{begin:"^time:"}]}]
+})})());
+hljs.registerLanguage("swift",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")}
+function a(...n){return n.map((n=>e(n))).join("")}function t(...n){
+return"("+n.map((n=>e(n))).join("|")+")"}
+const i=e=>a(/\b/,e,/\w$/.test(e)?/\b/:/\B/),s=["Protocol","Type"].map(i),u=["init","self"].map(i),c=["Any","Self"],r=["associatedtype",/as\?/,/as!/,"as","break","case","catch","class","continue","convenience","default","defer","deinit","didSet","do","dynamic","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","lazy","let","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],o=["false","nil","true"],l=["assignment","associativity","higherThan","left","lowerThan","none","right"],m=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warn_unqualified_access","#warning"],d=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],p=t(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),F=t(p,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),b=a(p,F,"*"),h=t(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),f=t(h,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),w=a(h,f,"*"),y=a(/[A-Z]/,f,"*"),g=["autoclosure",a(/convention\(/,t("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",a(/objc\(/,w,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","testable","UIApplicationMain","unknown","usableFromInline"],E=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"]
+;return e=>{const p={match:/\s+/,relevance:0},h=e.COMMENT("/\\*","\\*/",{
+contains:["self"]}),v=[e.C_LINE_COMMENT_MODE,h],N={className:"keyword",
+begin:a(/\./,n(t(...s,...u))),end:t(...s,...u),excludeBegin:!0},A={
+match:a(/\./,t(...r)),relevance:0
+},C=r.filter((e=>"string"==typeof e)).concat(["_|0"]),_={variants:[{
+className:"keyword",
+match:t(...r.filter((e=>"string"!=typeof e)).concat(c).map(i),...u)}]},D={
+$pattern:t(/\b\w+/,/#\w+/),keyword:C.concat(m),literal:o},B=[N,A,_],k=[{
+match:a(/\./,t(...d)),relevance:0},{className:"built_in",
+match:a(/\b/,t(...d),/(?=\()/)}],M={match:/->/,relevance:0},S=[M,{
+className:"operator",relevance:0,variants:[{match:b},{match:`\\.(\\.|${F})+`}]
+}],x="([0-9a-fA-F]_*)+",I={className:"number",relevance:0,variants:[{
+match:"\\b(([0-9]_*)+)(\\.(([0-9]_*)+))?([eE][+-]?(([0-9]_*)+))?\\b"},{
+match:`\\b0x(${x})(\\.(${x}))?([pP][+-]?(([0-9]_*)+))?\\b`},{
+match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},O=(e="")=>({
+className:"subst",variants:[{match:a(/\\/,e,/[0\\tnr"']/)},{
+match:a(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}]}),T=(e="")=>({className:"subst",
+match:a(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/)}),L=(e="")=>({className:"subst",
+label:"interpol",begin:a(/\\/,e,/\(/),end:/\)/}),P=(e="")=>({begin:a(e,/"""/),
+end:a(/"""/,e),contains:[O(e),T(e),L(e)]}),$=(e="")=>({begin:a(e,/"/),
+end:a(/"/,e),contains:[O(e),L(e)]}),K={className:"string",
+variants:[P(),P("#"),P("##"),P("###"),$(),$("#"),$("##"),$("###")]},j={
+match:a(/`/,w,/`/)},z=[j,{className:"variable",match:/\$\d+/},{
+className:"variable",match:`\\$${f}+`}],q=[{match:/(@|#)available/,
+className:"keyword",starts:{contains:[{begin:/\(/,end:/\)/,keywords:E,
+contains:[...S,I,K]}]}},{className:"keyword",match:a(/@/,t(...g))},{
+className:"meta",match:a(/@/,w)}],U={match:n(/\b[A-Z]/),relevance:0,contains:[{
+className:"type",
+match:a(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,f,"+")
+},{className:"type",match:y,relevance:0},{match:/[?!]+/,relevance:0},{
+match:/\.\.\./,relevance:0},{match:a(/\s+&\s+/,n(y)),relevance:0}]},Z={
+begin:/</,end:/>/,keywords:D,contains:[...v,...B,...q,M,U]};U.contains.push(Z)
+;const G={begin:/\(/,end:/\)/,relevance:0,keywords:D,contains:["self",{
+match:a(w,/\s*:/),keywords:"_|0",relevance:0
+},...v,...B,...k,...S,I,K,...z,...q,U]},H={beginKeywords:"func",contains:[{
+className:"title",match:t(j.match,w,b),endsParent:!0,relevance:0},p]},R={
+begin:/</,end:/>/,contains:[...v,U]},V={begin:/\(/,end:/\)/,keywords:D,
+contains:[{begin:t(n(a(w,/\s*:/)),n(a(w,/\s+/,w,/\s*:/))),end:/:/,relevance:0,
+contains:[{className:"keyword",match:/\b_\b/},{className:"params",match:w}]
+},...v,...B,...S,I,K,...q,U,G],endsParent:!0,illegal:/["']/},W={
+className:"function",match:n(/\bfunc\b/),contains:[H,R,V,p],illegal:[/\[/,/%/]
+},X={className:"function",match:/\b(subscript|init[?!]?)\s*(?=[<(])/,keywords:{
+keyword:"subscript init init? init!",$pattern:/\w+[?!]?/},contains:[R,V,p],
+illegal:/\[|%/},J={beginKeywords:"operator",end:e.MATCH_NOTHING_RE,contains:[{
+className:"title",match:b,endsParent:!0,relevance:0}]},Q={
+beginKeywords:"precedencegroup",end:e.MATCH_NOTHING_RE,contains:[{
+className:"title",match:y,relevance:0},{begin:/{/,end:/}/,relevance:0,
+endsParent:!0,keywords:[...l,...o],contains:[U]}]};for(const e of K.variants){
+const n=e.contains.find((e=>"interpol"===e.label));n.keywords=D
+;const a=[...B,...k,...S,I,K,...z];n.contains=[...a,{begin:/\(/,end:/\)/,
+contains:["self",...a]}]}return{name:"Swift",keywords:D,contains:[...v,W,X,{
+className:"class",beginKeywords:"struct protocol class extension enum",
+end:"\\{",excludeEnd:!0,keywords:D,contains:[e.inherit(e.TITLE_MODE,{
+begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...B]},J,Q,{
+beginKeywords:"import",end:/$/,contains:[...v],relevance:0
+},...B,...k,...S,I,K,...z,...q,U,G]}}})());
+hljs.registerLanguage("taggerscript",(()=>{"use strict";return e=>({
+name:"Tagger Script",contains:[{className:"comment",begin:/\$noop\(/,end:/\)/,
+contains:[{begin:/\(/,end:/\)/,contains:["self",{begin:/\\./}]}],relevance:10},{
+className:"keyword",begin:/\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*/,end:/\(/,
+excludeEnd:!0},{className:"variable",begin:/%[_a-zA-Z0-9:]*/,end:"%"},{
+className:"symbol",begin:/\\./}]})})());
+hljs.registerLanguage("yaml",(()=>{"use strict";return e=>{
+var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={
+className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/
+},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",
+variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{
+variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={
+end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/,
+end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",
+contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{
+begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{
+begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",
+relevance:10},{className:"string",
+begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{
+begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,
+relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",
+begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a
+},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",
+begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",
+relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{
+className:"number",
+begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"
+},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b]
+;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0,
+aliases:["yml","YAML"],contains:b}}})());
+hljs.registerLanguage("tap",(()=>{"use strict";return e=>({
+name:"Test Anything Protocol",case_insensitive:!0,
+contains:[e.HASH_COMMENT_MODE,{className:"meta",variants:[{
+begin:"^TAP version (\\d+)$"},{begin:"^1\\.\\.(\\d+)$"}]},{begin:/---$/,
+end:"\\.\\.\\.$",subLanguage:"yaml",relevance:0},{className:"number",
+begin:" (\\d+) "},{className:"symbol",variants:[{begin:"^ok"},{begin:"^not ok"}]
+}]})})());
+hljs.registerLanguage("tcl",(()=>{"use strict";function e(...e){
+return e.map((e=>{return(a=e)?"string"==typeof a?a:a.source:null;var a
+})).join("")}return a=>{const t=/[a-zA-Z_][a-zA-Z0-9_]*/,r={className:"number",
+variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]};return{name:"Tcl",
+aliases:["tk"],
+keywords:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",
+contains:[a.COMMENT(";[ \\t]*#","$"),a.COMMENT("^[ \\t]*#","$"),{
+beginKeywords:"proc",end:"[\\{]",excludeEnd:!0,contains:[{className:"title",
+begin:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",end:"[ \\t\\n\\r]",
+endsWithParent:!0,excludeEnd:!0}]},{className:"variable",variants:[{
+begin:e(/\$/,(n=/::/,e("(",n,")?")),t,"(::",t,")*")},{
+begin:"\\$\\{(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",end:"\\}",contains:[r]}]},{
+className:"string",contains:[a.BACKSLASH_ESCAPE],
+variants:[a.inherit(a.QUOTE_STRING_MODE,{illegal:null})]},r]};var n}})());
+hljs.registerLanguage("thrift",(()=>{"use strict";return e=>{
+const t="bool byte i16 i32 i64 double string binary";return{name:"Thrift",
+keywords:{
+keyword:"namespace const typedef struct enum service exception void oneway set list map required optional",
+built_in:t,literal:"true false"},
+contains:[e.QUOTE_STRING_MODE,e.NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{
+className:"class",beginKeywords:"struct enum service exception",end:/\{/,
+illegal:/\n/,contains:[e.inherit(e.TITLE_MODE,{starts:{endsWithParent:!0,
+excludeEnd:!0}})]},{begin:"\\b(set|list|map)\\s*<",end:">",keywords:t,
+contains:["self"]}]}}})());
+hljs.registerLanguage("tp",(()=>{"use strict";return O=>{const e={
+className:"number",begin:"[1-9][0-9]*",relevance:0},R={className:"symbol",
+begin:":[^\\]]+"};return{name:"TP",keywords:{
+keyword:"ABORT ACC ADJUST AND AP_LD BREAK CALL CNT COL CONDITION CONFIG DA DB DIV DETECT ELSE END ENDFOR ERR_NUM ERROR_PROG FINE FOR GP GUARD INC IF JMP LINEAR_MAX_SPEED LOCK MOD MONITOR OFFSET Offset OR OVERRIDE PAUSE PREG PTH RT_LD RUN SELECT SKIP Skip TA TB TO TOOL_OFFSET Tool_Offset UF UT UFRAME_NUM UTOOL_NUM UNLOCK WAIT X Y Z W P R STRLEN SUBSTR FINDSTR VOFFSET PROG ATTR MN POS",
+literal:"ON OFF max_speed LPOS JPOS ENABLE DISABLE START STOP RESET"},
+contains:[{className:"built_in",
+begin:"(AR|P|PAYLOAD|PR|R|SR|RSR|LBL|VR|UALM|MESSAGE|UTOOL|UFRAME|TIMER|TIMER_OVERFLOW|JOINT_MAX_SPEED|RESUME_PROG|DIAG_REC)\\[",
+end:"\\]",contains:["self",e,R]},{className:"built_in",
+begin:"(AI|AO|DI|DO|F|RI|RO|UI|UO|GI|GO|SI|SO)\\[",end:"\\]",
+contains:["self",e,O.QUOTE_STRING_MODE,R]},{className:"keyword",
+begin:"/(PROG|ATTR|MN|POS|END)\\b"},{className:"keyword",
+begin:"(CALL|RUN|POINT_LOGIC|LBL)\\b"},{className:"keyword",
+begin:"\\b(ACC|CNT|Skip|Offset|PSPD|RT_LD|AP_LD|Tool_Offset)"},{
+className:"number",
+begin:"\\d+(sec|msec|mm/sec|cm/min|inch/min|deg/sec|mm|in|cm)?\\b",relevance:0
+},O.COMMENT("//","[;$]"),O.COMMENT("!","[;$]"),O.COMMENT("--eg:","$"),O.QUOTE_STRING_MODE,{
+className:"string",begin:"'",end:"'"},O.C_NUMBER_MODE,{className:"variable",
+begin:"\\$[A-Za-z0-9_]+"}]}}})());
+hljs.registerLanguage("twig",(()=>{"use strict";return e=>{
+var a="attribute block constant cycle date dump include max min parent random range source template_from_string",n={
+beginKeywords:a,keywords:{name:a},relevance:0,contains:[{className:"params",
+begin:"\\(",end:"\\)"}]},t={begin:/\|[A-Za-z_]+:?/,
+keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",
+contains:[n]
+},s="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with"
+;return s=s+" "+s.split(" ").map((e=>"end"+e)).join(" "),{name:"Twig",
+aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",
+contains:[e.COMMENT(/\{#/,/#\}/),{className:"template-tag",begin:/\{%/,
+end:/%\}/,contains:[{className:"name",begin:/\w+/,keywords:s,starts:{
+endsWithParent:!0,contains:[t,n],relevance:0}}]},{className:"template-variable",
+begin:/\{\{/,end:/\}\}/,contains:["self",t,n]}]}}})());
+hljs.registerLanguage("typescript",(()=>{"use strict"
+;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"])
+;function t(e){return r("(?=",e,")")}function r(...e){return e.map((e=>{
+return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return i=>{
+const c={$pattern:e,
+keyword:n.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]),
+literal:a,
+built_in:s.concat(["any","void","number","boolean","string","object","never","enum"])
+},o={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},l=(e,n,a)=>{
+const s=e.contains.findIndex((e=>e.label===n))
+;if(-1===s)throw Error("can not find mode to replace");e.contains.splice(s,1,a)
+},b=(i=>{const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/,
+end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
+const a=e[0].length+e.index,s=e.input[a];"<"!==s?">"===s&&(((e,{after:n})=>{
+const a="</"+e[0].slice(1);return-1!==e.input.indexOf(a,n)})(e,{after:a
+})||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n,literal:a,
+built_in:s},b="\\.([0-9](_?[0-9])*)",d="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",g={
+className:"number",variants:[{
+begin:`(\\b(${d})((${b})|\\.)?|(${b}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{
+begin:`\\b(${d})\\b((${b})\\b|\\.)?|(${b})\\b`},{
+begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
+begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
+begin:"\\b0[0-7]+n?\\b"}],relevance:0},u={className:"subst",begin:"\\$\\{",
+end:"\\}",keywords:l,contains:[]},E={begin:"html`",end:"",starts:{end:"`",
+returnEnd:!1,contains:[i.BACKSLASH_ESCAPE,u],subLanguage:"xml"}},m={
+begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
+contains:[i.BACKSLASH_ESCAPE,u],subLanguage:"css"}},y={className:"string",
+begin:"`",end:"`",contains:[i.BACKSLASH_ESCAPE,u]},_={className:"comment",
+variants:[i.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
+className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",
+end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)",
+endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
+}),i.C_BLOCK_COMMENT_MODE,i.C_LINE_COMMENT_MODE]
+},p=[i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,E,m,y,g,i.REGEXP_MODE]
+;u.contains=p.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(p)
+});const N=[].concat(_,u.contains),f=N.concat([{begin:/\(/,end:/\)/,keywords:l,
+contains:["self"].concat(N)}]),A={className:"params",begin:/\(/,end:/\)/,
+excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f};return{name:"Javascript",
+aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:f},
+illegal:/#(?![$_A-z])/,contains:[i.SHEBANG({label:"shebang",binary:"node",
+relevance:5}),{label:"use_strict",className:"meta",relevance:10,
+begin:/^\s*['"]use (strict|asm)['"]/
+},i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,E,m,y,_,g,{
+begin:r(/[{,\n]\s*/,t(r(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))),
+relevance:0,contains:[{className:"attr",begin:c+t("\\s*:"),relevance:0}]},{
+begin:"("+i.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",contains:[_,i.REGEXP_MODE,{className:"function",
+begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+i.UNDERSCORE_IDENT_RE+")\\s*=>",
+returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{
+begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0
+},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f}]}]
+},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{
+variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,
+end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0,
+contains:["self"]}]}],relevance:0},{className:"function",
+beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l,
+contains:["self",i.inherit(i.TITLE_MODE,{begin:c}),A],illegal:/%/},{
+beginKeywords:"while if switch catch for"},{className:"function",
+begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
+returnBegin:!0,contains:[A,i.inherit(i.TITLE_MODE,{begin:c})]},{variants:[{
+begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class",
+beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{
+beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,
+end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:c}),"self",A]
+},{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set",
+contains:[i.inherit(i.TITLE_MODE,{begin:c}),{begin:/\(\)/},A]},{begin:/\$[(.]/}]
+}})(i)
+;return Object.assign(b.keywords,c),b.exports.PARAMS_CONTAINS.push(o),b.contains=b.contains.concat([o,{
+beginKeywords:"namespace",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",
+end:/\{/,excludeEnd:!0,keywords:"interface extends"
+}]),l(b,"shebang",i.SHEBANG()),l(b,"use_strict",{className:"meta",relevance:10,
+begin:/^\s*['"]use strict['"]/
+}),b.contains.find((e=>"function"===e.className)).relevance=0,Object.assign(b,{
+name:"TypeScript",aliases:["ts","tsx"]}),b}})());
+hljs.registerLanguage("vala",(()=>{"use strict";return e=>({name:"Vala",
+keywords:{
+keyword:"char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double bool struct enum string void weak unowned owned async signal static abstract interface override virtual delegate if while do for foreach else switch case break default return try catch public private protected internal using new this get set const stdout stdin stderr var",
+built_in:"DBus GLib CCode Gee Object Gtk Posix",literal:"false true null"},
+contains:[{className:"class",beginKeywords:"class interface namespace",end:/\{/,
+excludeEnd:!0,illegal:"[^,:\\n\\s\\.]",contains:[e.UNDERSCORE_TITLE_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",begin:'"""',
+end:'"""',relevance:5},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{
+className:"meta",begin:"^#",end:"$",relevance:2}]})})());
+hljs.registerLanguage("vbnet",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function n(...n){
+return n.map((n=>e(n))).join("")}function t(...n){
+return"("+n.map((n=>e(n))).join("|")+")"}return e=>{
+const a=/\d{1,2}\/\d{1,2}\/\d{4}/,i=/\d{4}-\d{1,2}-\d{1,2}/,s=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,o={
+className:"literal",variants:[{begin:n(/# */,t(i,a),/ *#/)},{
+begin:n(/# */,r,/ *#/)},{begin:n(/# */,s,/ *#/)},{
+begin:n(/# */,t(i,a),/ +/,t(s,r),/ *#/)}]},l=e.COMMENT(/'''/,/$/,{contains:[{
+className:"doctag",begin:/<\/?/,end:/>/}]}),c=e.COMMENT(null,/$/,{variants:[{
+begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:"Visual Basic .NET",
+aliases:["vb"],case_insensitive:!0,classNameAliases:{label:"symbol"},keywords:{
+keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield",
+built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort",
+type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort",
+literal:"true false nothing"},
+illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{
+className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/,
+end:/"/,illegal:/\n/,contains:[{begin:/""/}]},o,{className:"number",relevance:0,
+variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/
+},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{
+begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{
+className:"label",begin:/^\w+:/},l,c,{className:"meta",
+begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,
+end:/$/,keywords:{
+"meta-keyword":"const disable else elseif enable end externalsource if region then"
+},contains:[c]}]}}})());
+hljs.registerLanguage("vbscript",(()=>{"use strict";function e(e){
+return e?"string"==typeof e?e:e.source:null}function t(...t){
+return t.map((t=>e(t))).join("")}function r(...t){
+return"("+t.map((t=>e(t))).join("|")+")"}return e=>{
+const i="lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid split cint sin datepart ltrim sqr time derived eval date formatpercent exp inputbox left ascw chrw regexp cstr err".split(" ")
+;return{name:"VBScript",aliases:["vbs"],case_insensitive:!0,keywords:{
+keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",
+built_in:["server","response","request","scriptengine","scriptenginebuildversion","scriptengineminorversion","scriptenginemajorversion"],
+literal:"true false null nothing empty"},illegal:"//",contains:[{
+begin:t(r(...i),"\\s*\\("),relevance:0,keywords:{built_in:i}
+},e.inherit(e.QUOTE_STRING_MODE,{contains:[{begin:'""'}]}),e.COMMENT(/'/,/$/,{
+relevance:0}),e.C_NUMBER_MODE]}}})());
+hljs.registerLanguage("vbscript-html",(()=>{"use strict";return e=>({
+name:"VBScript in HTML",subLanguage:"xml",contains:[{begin:"<%",end:"%>",
+subLanguage:"vbscript"}]})})());
+hljs.registerLanguage("verilog",(()=>{"use strict";return e=>({name:"Verilog",
+aliases:["v","sv","svh"],case_insensitive:!1,keywords:{$pattern:/[\w\$]+/,
+keyword:"accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind bins binsof bit break buf|0 bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin function generate|5 genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import incdir include initial inout input inside instance int integer interconnect interface intersect join join_any join_none large let liblist library local localparam logic longint macromodule matches medium modport module nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 or output package packed parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor",
+literal:"null",
+built_in:"$finish $stop $exit $fatal $error $warning $info $realtime $time $printtimescale $bitstoreal $bitstoshortreal $itor $signed $cast $bits $stime $timeformat $realtobits $shortrealtobits $rtoi $unsigned $asserton $assertkill $assertpasson $assertfailon $assertnonvacuouson $assertoff $assertcontrol $assertpassoff $assertfailoff $assertvacuousoff $isunbounded $sampled $fell $changed $past_gclk $fell_gclk $changed_gclk $rising_gclk $steady_gclk $coverage_control $coverage_get $coverage_save $set_coverage_db_name $rose $stable $past $rose_gclk $stable_gclk $future_gclk $falling_gclk $changing_gclk $display $coverage_get_max $coverage_merge $get_coverage $load_coverage_db $typename $unpacked_dimensions $left $low $increment $clog2 $ln $log10 $exp $sqrt $pow $floor $ceil $sin $cos $tan $countbits $onehot $isunknown $fatal $warning $dimensions $right $high $size $asin $acos $atan $atan2 $hypot $sinh $cosh $tanh $asinh $acosh $atanh $countones $onehot0 $error $info $random $dist_chi_square $dist_erlang $dist_exponential $dist_normal $dist_poisson $dist_t $dist_uniform $q_initialize $q_remove $q_exam $async$and$array $async$nand$array $async$or$array $async$nor$array $sync$and$array $sync$nand$array $sync$or$array $sync$nor$array $q_add $q_full $psprintf $async$and$plane $async$nand$plane $async$or$plane $async$nor$plane $sync$and$plane $sync$nand$plane $sync$or$plane $sync$nor$plane $system $display $displayb $displayh $displayo $strobe $strobeb $strobeh $strobeo $write $readmemb $readmemh $writememh $value$plusargs $dumpvars $dumpon $dumplimit $dumpports $dumpportson $dumpportslimit $writeb $writeh $writeo $monitor $monitorb $monitorh $monitoro $writememb $dumpfile $dumpoff $dumpall $dumpflush $dumpportsoff $dumpportsall $dumpportsflush $fclose $fdisplay $fdisplayb $fdisplayh $fdisplayo $fstrobe $fstrobeb $fstrobeh $fstrobeo $swrite $swriteb $swriteh $swriteo $fscanf $fread $fseek $fflush $feof $fopen $fwrite $fwriteb $fwriteh $fwriteo $fmonitor $fmonitorb $fmonitorh $fmonitoro $sformat $sformatf $fgetc $ungetc $fgets $sscanf $rewind $ftell $ferror"
+},contains:[e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE,e.QUOTE_STRING_MODE,{
+className:"number",contains:[e.BACKSLASH_ESCAPE],variants:[{
+begin:"\\b((\\d+'(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)"},{
+begin:"\\B(('(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)"},{begin:"\\b([0-9_])+",
+relevance:0}]},{className:"variable",variants:[{begin:"#\\((?!parameter).+\\)"
+},{begin:"\\.\\w+",relevance:0}]},{className:"meta",begin:"`",end:"$",keywords:{
+"meta-keyword":"define __FILE__ __LINE__ begin_keywords celldefine default_nettype define else elsif end_keywords endcelldefine endif ifdef ifndef include line nounconnected_drive pragma resetall timescale unconnected_drive undef undefineall"
+},relevance:0}]})})());
+hljs.registerLanguage("vhdl",(()=>{"use strict";return e=>({name:"VHDL",
+case_insensitive:!0,keywords:{
+keyword:"abs access after alias all and architecture array assert assume assume_guarantee attribute begin block body buffer bus case component configuration constant context cover disconnect downto default else elsif end entity exit fairness file for force function generate generic group guarded if impure in inertial inout is label library linkage literal loop map mod nand new next nor not null of on open or others out package parameter port postponed procedure process property protected pure range record register reject release rem report restrict restrict_guarantee return rol ror select sequence severity shared signal sla sll sra srl strong subtype then to transport type unaffected units until use variable view vmode vprop vunit wait when while with xnor xor",
+built_in:"boolean bit character integer time delay_length natural positive string bit_vector file_open_kind file_open_status std_logic std_logic_vector unsigned signed boolean_vector integer_vector std_ulogic std_ulogic_vector unresolved_unsigned u_unsigned unresolved_signed u_signed real_vector time_vector",
+literal:"false true note warning error failure line text side width"},
+illegal:/\{/,
+contains:[e.C_BLOCK_COMMENT_MODE,e.COMMENT("--","$"),e.QUOTE_STRING_MODE,{
+className:"number",
+begin:"\\b(\\d(_|\\d)*#\\w+(\\.\\w+)?#([eE][-+]?\\d(_|\\d)*)?|\\d(_|\\d)*(\\.\\d(_|\\d)*)?([eE][-+]?\\d(_|\\d)*)?)",
+relevance:0},{className:"string",begin:"'(U|X|0|1|Z|W|L|H|-)'",
+contains:[e.BACKSLASH_ESCAPE]},{className:"symbol",
+begin:"'[A-Za-z](_?[A-Za-z0-9])*",contains:[e.BACKSLASH_ESCAPE]}]})})());
+hljs.registerLanguage("vim",(()=>{"use strict";return e=>({name:"Vim Script",
+keywords:{$pattern:/[!#@\w]+/,
+keyword:"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu go gr grepa gu gv ha helpf helpg helpt hi hid his ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf quita qa rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank",
+built_in:"synIDtrans atan2 range matcharg did_filetype asin feedkeys xor argv complete_check add getwinposx getqflist getwinposy screencol clearmatches empty extend getcmdpos mzeval garbagecollect setreg ceil sqrt diff_hlID inputsecret get getfperm getpid filewritable shiftwidth max sinh isdirectory synID system inputrestore winline atan visualmode inputlist tabpagewinnr round getregtype mapcheck hasmapto histdel argidx findfile sha256 exists toupper getcmdline taglist string getmatches bufnr strftime winwidth bufexists strtrans tabpagebuflist setcmdpos remote_read printf setloclist getpos getline bufwinnr float2nr len getcmdtype diff_filler luaeval resolve libcallnr foldclosedend reverse filter has_key bufname str2float strlen setline getcharmod setbufvar index searchpos shellescape undofile foldclosed setqflist buflisted strchars str2nr virtcol floor remove undotree remote_expr winheight gettabwinvar reltime cursor tabpagenr finddir localtime acos getloclist search tanh matchend rename gettabvar strdisplaywidth type abs py3eval setwinvar tolower wildmenumode log10 spellsuggest bufloaded synconcealed nextnonblank server2client complete settabwinvar executable input wincol setmatches getftype hlID inputsave searchpair or screenrow line settabvar histadd deepcopy strpart remote_peek and eval getftime submatch screenchar winsaveview matchadd mkdir screenattr getfontname libcall reltimestr getfsize winnr invert pow getbufline byte2line soundfold repeat fnameescape tagfiles sin strwidth spellbadword trunc maparg log lispindent hostname setpos globpath remote_foreground getchar synIDattr fnamemodify cscope_connection stridx winbufnr indent min complete_add nr2char searchpairpos inputdialog values matchlist items hlexists strridx browsedir expand fmod pathshorten line2byte argc count getwinvar glob foldtextresult getreg foreground cosh matchdelete has char2nr simplify histget searchdecl iconv winrestcmd pumvisible writefile foldlevel haslocaldir keys cos matchstr foldtext histnr tan tempname getcwd byteidx getbufvar islocked escape eventhandler remote_send serverlist winrestview synstack pyeval prevnonblank readfile cindent filereadable changenr exp"
+},illegal:/;/,contains:[e.NUMBER_MODE,{className:"string",begin:"'",end:"'",
+illegal:"\\n"},{className:"string",begin:/"(\\"|\n\\|[^"\n])*"/
+},e.COMMENT('"',"$"),{className:"variable",begin:/[bwtglsav]:[\w\d_]*/},{
+className:"function",beginKeywords:"function function!",end:"$",relevance:0,
+contains:[e.TITLE_MODE,{className:"params",begin:"\\(",end:"\\)"}]},{
+className:"symbol",begin:/<[\w-]+>/}]})})());
+hljs.registerLanguage("x86asm",(()=>{"use strict";return s=>({
+name:"Intel x86 Assembly",case_insensitive:!0,keywords:{
+$pattern:"[.%]?"+s.IDENT_RE,
+keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",
built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",
-meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},
-contains:[a.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},a.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",
-end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());
-hljs.registerLanguage("xl",function(){return function(a){var b={$pattern:/[a-zA-Z][a-zA-Z0-9_?]*/,keyword:"if then else do while until for loop import with is as where when by data constant integer real text name boolean symbol infix prefix postfix block tree",literal:"true false nil",built_in:"in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin acos atan exp expm1 log log2 log10 log1p pi at text_length text_range text_find text_replace contains page slide basic_slide title_slide title subtitle fade_in fade_out fade_at clear_color color line_color line_width texture_wrap texture_transform texture scale_?x scale_?y scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y rotate_?z? rectangle circle ellipse sphere path line_to move_to quad_to curve_to theme background contents locally time mouse_?x mouse_?y mouse_buttons ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts"},
-c={className:"string",begin:'"',end:'"',illegal:"\\n"},d={beginKeywords:"import",end:"$",keywords:b,contains:[c]},e={className:"function",begin:/[a-z][^\n]*->/,returnBegin:!0,end:/->/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,keywords:b}})]};return{name:"XL",aliases:["tao"],keywords:b,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,{className:"string",begin:"'",end:"'",illegal:"\\n"},{className:"string",begin:"<<",end:">>"},e,d,{className:"number",begin:"[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?"},
-a.NUMBER_MODE]}}}());
-hljs.registerLanguage("xquery",function(){return function(a){return{name:"XQuery",aliases:["xpath","xq"],case_insensitive:!1,illegal:/(proc)|(abstract)|(extends)|(until)|(#)/,keywords:{$pattern:/[a-zA-Z\$][a-zA-Z0-9_:\-]*/,keyword:"module schema namespace boundary-space preserve no-preserve strip default collation base-uri ordering context decimal-format decimal-separator copy-namespaces empty-sequence except exponent-separator external grouping-separator inherit no-inherit lax minus-sign per-mille percent schema-attribute schema-element strict unordered zero-digit declare import option function validate variable for at in let where order group by return if then else tumbling sliding window start when only end previous next stable ascending descending allowing empty greatest least some every satisfies switch case typeswitch try catch and or to union intersect instance of treat as castable cast map array delete insert into replace value rename copy modify update",type:"item document-node node attribute document element comment namespace namespace-node processing-instruction text construction xs:anyAtomicType xs:untypedAtomic xs:duration xs:time xs:decimal xs:float xs:double xs:gYearMonth xs:gYear xs:gMonthDay xs:gMonth xs:gDay xs:boolean xs:base64Binary xs:hexBinary xs:anyURI xs:QName xs:NOTATION xs:dateTime xs:dateTimeStamp xs:date xs:string xs:normalizedString xs:token xs:language xs:NMTOKEN xs:Name xs:NCName xs:ID xs:IDREF xs:ENTITY xs:integer xs:nonPositiveInteger xs:negativeInteger xs:long xs:int xs:short xs:byte xs:nonNegativeInteger xs:unisignedLong xs:unsignedInt xs:unsignedShort xs:unsignedByte xs:positiveInteger xs:yearMonthDuration xs:dayTimeDuration",
-literal:"eq ne lt le gt ge is self:: child:: descendant:: descendant-or-self:: attribute:: following:: following-sibling:: parent:: ancestor:: ancestor-or-self:: preceding:: preceding-sibling:: NaN"},contains:[{className:"variable",begin:/[\$][\w-:]+/},{className:"built_in",variants:[{begin:/\barray:/,end:/(?:append|filter|flatten|fold\-(?:left|right)|for-each(?:\-pair)?|get|head|insert\-before|join|put|remove|reverse|size|sort|subarray|tail)\b/},{begin:/\bmap:/,end:/(?:contains|entry|find|for\-each|get|keys|merge|put|remove|size)\b/},
-{begin:/\bmath:/,end:/(?:a(?:cos|sin|tan[2]?)|cos|exp(?:10)?|log(?:10)?|pi|pow|sin|sqrt|tan)\b/},{begin:/\bop:/,end:/\(/,excludeEnd:!0},{begin:/\bfn:/,end:/\(/,excludeEnd:!0},{begin:/[^<\/\$:'"-]\b(?:abs|accumulator\-(?:after|before)|adjust\-(?:date(?:Time)?|time)\-to\-timezone|analyze\-string|apply|available\-(?:environment\-variables|system\-properties)|avg|base\-uri|boolean|ceiling|codepoints?\-(?:equal|to\-string)|collation\-key|collection|compare|concat|contains(?:\-token)?|copy\-of|count|current(?:\-)?(?:date(?:Time)?|time|group(?:ing\-key)?|output\-uri|merge\-(?:group|key))?data|dateTime|days?\-from\-(?:date(?:Time)?|duration)|deep\-equal|default\-(?:collation|language)|distinct\-values|document(?:\-uri)?|doc(?:\-available)?|element\-(?:available|with\-id)|empty|encode\-for\-uri|ends\-with|environment\-variable|error|escape\-html\-uri|exactly\-one|exists|false|filter|floor|fold\-(?:left|right)|for\-each(?:\-pair)?|format\-(?:date(?:Time)?|time|integer|number)|function\-(?:arity|available|lookup|name)|generate\-id|has\-children|head|hours\-from\-(?:dateTime|duration|time)|id(?:ref)?|implicit\-timezone|in\-scope\-prefixes|index\-of|innermost|insert\-before|iri\-to\-uri|json\-(?:doc|to\-xml)|key|lang|last|load\-xquery\-module|local\-name(?:\-from\-QName)?|(?:lower|upper)\-case|matches|max|minutes\-from\-(?:dateTime|duration|time)|min|months?\-from\-(?:date(?:Time)?|duration)|name(?:space\-uri\-?(?:for\-prefix|from\-QName)?)?|nilled|node\-name|normalize\-(?:space|unicode)|not|number|one\-or\-more|outermost|parse\-(?:ietf\-date|json)|path|position|(?:prefix\-from\-)?QName|random\-number\-generator|regex\-group|remove|replace|resolve\-(?:QName|uri)|reverse|root|round(?:\-half\-to\-even)?|seconds\-from\-(?:dateTime|duration|time)|snapshot|sort|starts\-with|static\-base\-uri|stream\-available|string\-?(?:join|length|to\-codepoints)?|subsequence|substring\-?(?:after|before)?|sum|system\-property|tail|timezone\-from\-(?:date(?:Time)?|time)|tokenize|trace|trans(?:form|late)|true|type\-available|unordered|unparsed\-(?:entity|text)?\-?(?:public\-id|uri|available|lines)?|uri\-collection|xml\-to\-json|years?\-from\-(?:date(?:Time)?|duration)|zero\-or\-one)\b/},
-{begin:/\blocal:/,end:/\(/,excludeEnd:!0},{begin:/\bzip:/,end:/(?:zip\-file|(?:xml|html|text|binary)\-entry| (?:update\-)?entries)\b/},{begin:/\b(?:util|db|functx|app|xdmp|xmldb):/,end:/\(/,excludeEnd:!0}]},{className:"string",variants:[{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{className:"comment",begin:"\\(:",end:":\\)",
-relevance:10,contains:[{className:"doctag",begin:"@\\w+"}]},{className:"meta",begin:/%[\w-:]+/},{className:"title",begin:/\bxquery version "[13]\.[01]"\s?(?:encoding ".+")?/,end:/;/},{beginKeywords:"element attribute comment document processing-instruction",end:"{",excludeEnd:!0},{begin:/<([\w\._:\-]+)((\s*.*)=('|").*('|"))?>/,end:/(\/[\w\._:\-]+>)/,subLanguage:"xml",contains:[{begin:"{",end:"}",subLanguage:"xquery"},"self"]}]}}}());
-hljs.registerLanguage("zephir",function(){return function(a){var b={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null})]},c=a.UNDERSCORE_TITLE_MODE,d={variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]};return{name:"Zephir",aliases:["zep"],keywords:"namespace class interface use extends function return abstract final public protected private static deprecated throw try catch Exception echo empty isset instanceof unset let var new const self require if else elseif switch case default do while loop for continue break likely unlikely __LINE__ __FILE__ __DIR__ __FUNCTION__ __CLASS__ __TRAIT__ __METHOD__ __NAMESPACE__ array boolean float double integer object resource string char long unsigned bool int uint ulong uchar true false null undefined",
-contains:[a.C_LINE_COMMENT_MODE,a.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),{className:"string",begin:"<<<['\"]?\\w+['\"]?$",end:"^\\w+;",contains:[a.BACKSLASH_ESCAPE]},{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"function fn",end:/[;{]/,excludeEnd:!0,illegal:"\\$|\\[|%",contains:[c,{className:"params",begin:"\\(",end:"\\)",keywords:"namespace class interface use extends function return abstract final public protected private static deprecated throw try catch Exception echo empty isset instanceof unset let var new const self require if else elseif switch case default do while loop for continue break likely unlikely __LINE__ __FILE__ __DIR__ __FUNCTION__ __CLASS__ __TRAIT__ __METHOD__ __NAMESPACE__ array boolean float double integer object resource string char long unsigned bool int uint ulong uchar true false null undefined",
-contains:["self",a.C_BLOCK_COMMENT_MODE,b,d]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},c]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[c]},{beginKeywords:"use",end:";",contains:[c]},{begin:"=>"},b,d]}}}());
+meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"
+},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{
+begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*(\\.[0-9_]*)?(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",
+relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{
+begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"
+},{
+begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"
+}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"
+},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{
+begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{
+begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",
+begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{
+className:"meta",begin:/^\s*\.[\w_-]+/}]})})());
+hljs.registerLanguage("xl",(()=>{"use strict";return e=>{const t={
+$pattern:/[a-zA-Z][a-zA-Z0-9_?]*/,
+keyword:"if then else do while until for loop import with is as where when by data constant integer real text name boolean symbol infix prefix postfix block tree",
+literal:"true false nil",
+built_in:"in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin acos atan exp expm1 log log2 log10 log1p pi at text_length text_range text_find text_replace contains page slide basic_slide title_slide title subtitle fade_in fade_out fade_at clear_color color line_color line_width texture_wrap texture_transform texture scale_?x scale_?y scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y rotate_?z? rectangle circle ellipse sphere path line_to move_to quad_to curve_to theme background contents locally time mouse_?x mouse_?y mouse_buttons ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts"
+},a={className:"string",begin:'"',end:'"',illegal:"\\n"},n={
+beginKeywords:"import",end:"$",keywords:t,contains:[a]},o={className:"function",
+begin:/[a-z][^\n]*->/,returnBegin:!0,end:/->/,contains:[e.inherit(e.TITLE_MODE,{
+starts:{endsWithParent:!0,keywords:t}})]};return{name:"XL",aliases:["tao"],
+keywords:t,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{
+className:"string",begin:"'",end:"'",illegal:"\\n"},{className:"string",
+begin:"<<",end:">>"},o,n,{className:"number",
+begin:"[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?"},e.NUMBER_MODE]}}
+})());
+hljs.registerLanguage("xquery",(()=>{"use strict";return e=>({name:"XQuery",
+aliases:["xpath","xq"],case_insensitive:!1,
+illegal:/(proc)|(abstract)|(extends)|(until)|(#)/,keywords:{
+$pattern:/[a-zA-Z$][a-zA-Z0-9_:-]*/,
+keyword:"module schema namespace boundary-space preserve no-preserve strip default collation base-uri ordering context decimal-format decimal-separator copy-namespaces empty-sequence except exponent-separator external grouping-separator inherit no-inherit lax minus-sign per-mille percent schema-attribute schema-element strict unordered zero-digit declare import option function validate variable for at in let where order group by return if then else tumbling sliding window start when only end previous next stable ascending descending allowing empty greatest least some every satisfies switch case typeswitch try catch and or to union intersect instance of treat as castable cast map array delete insert into replace value rename copy modify update",
+type:"item document-node node attribute document element comment namespace namespace-node processing-instruction text construction xs:anyAtomicType xs:untypedAtomic xs:duration xs:time xs:decimal xs:float xs:double xs:gYearMonth xs:gYear xs:gMonthDay xs:gMonth xs:gDay xs:boolean xs:base64Binary xs:hexBinary xs:anyURI xs:QName xs:NOTATION xs:dateTime xs:dateTimeStamp xs:date xs:string xs:normalizedString xs:token xs:language xs:NMTOKEN xs:Name xs:NCName xs:ID xs:IDREF xs:ENTITY xs:integer xs:nonPositiveInteger xs:negativeInteger xs:long xs:int xs:short xs:byte xs:nonNegativeInteger xs:unisignedLong xs:unsignedInt xs:unsignedShort xs:unsignedByte xs:positiveInteger xs:yearMonthDuration xs:dayTimeDuration",
+literal:"eq ne lt le gt ge is self:: child:: descendant:: descendant-or-self:: attribute:: following:: following-sibling:: parent:: ancestor:: ancestor-or-self:: preceding:: preceding-sibling:: NaN"
+},contains:[{className:"variable",begin:/[$][\w\-:]+/},{className:"built_in",
+variants:[{begin:/\barray:/,
+end:/(?:append|filter|flatten|fold-(?:left|right)|for-each(?:-pair)?|get|head|insert-before|join|put|remove|reverse|size|sort|subarray|tail)\b/
+},{begin:/\bmap:/,
+end:/(?:contains|entry|find|for-each|get|keys|merge|put|remove|size)\b/},{
+begin:/\bmath:/,
+end:/(?:a(?:cos|sin|tan[2]?)|cos|exp(?:10)?|log(?:10)?|pi|pow|sin|sqrt|tan)\b/
+},{begin:/\bop:/,end:/\(/,excludeEnd:!0},{begin:/\bfn:/,end:/\(/,excludeEnd:!0
+},{
+begin:/[^</$:'"-]\b(?:abs|accumulator-(?:after|before)|adjust-(?:date(?:Time)?|time)-to-timezone|analyze-string|apply|available-(?:environment-variables|system-properties)|avg|base-uri|boolean|ceiling|codepoints?-(?:equal|to-string)|collation-key|collection|compare|concat|contains(?:-token)?|copy-of|count|current(?:-)?(?:date(?:Time)?|time|group(?:ing-key)?|output-uri|merge-(?:group|key))?data|dateTime|days?-from-(?:date(?:Time)?|duration)|deep-equal|default-(?:collation|language)|distinct-values|document(?:-uri)?|doc(?:-available)?|element-(?:available|with-id)|empty|encode-for-uri|ends-with|environment-variable|error|escape-html-uri|exactly-one|exists|false|filter|floor|fold-(?:left|right)|for-each(?:-pair)?|format-(?:date(?:Time)?|time|integer|number)|function-(?:arity|available|lookup|name)|generate-id|has-children|head|hours-from-(?:dateTime|duration|time)|id(?:ref)?|implicit-timezone|in-scope-prefixes|index-of|innermost|insert-before|iri-to-uri|json-(?:doc|to-xml)|key|lang|last|load-xquery-module|local-name(?:-from-QName)?|(?:lower|upper)-case|matches|max|minutes-from-(?:dateTime|duration|time)|min|months?-from-(?:date(?:Time)?|duration)|name(?:space-uri-?(?:for-prefix|from-QName)?)?|nilled|node-name|normalize-(?:space|unicode)|not|number|one-or-more|outermost|parse-(?:ietf-date|json)|path|position|(?:prefix-from-)?QName|random-number-generator|regex-group|remove|replace|resolve-(?:QName|uri)|reverse|root|round(?:-half-to-even)?|seconds-from-(?:dateTime|duration|time)|snapshot|sort|starts-with|static-base-uri|stream-available|string-?(?:join|length|to-codepoints)?|subsequence|substring-?(?:after|before)?|sum|system-property|tail|timezone-from-(?:date(?:Time)?|time)|tokenize|trace|trans(?:form|late)|true|type-available|unordered|unparsed-(?:entity|text)?-?(?:public-id|uri|available|lines)?|uri-collection|xml-to-json|years?-from-(?:date(?:Time)?|duration)|zero-or-one)\b/
+},{begin:/\blocal:/,end:/\(/,excludeEnd:!0},{begin:/\bzip:/,
+end:/(?:zip-file|(?:xml|html|text|binary)-entry| (?:update-)?entries)\b/},{
+begin:/\b(?:util|db|functx|app|xdmp|xmldb):/,end:/\(/,excludeEnd:!0}]},{
+className:"string",variants:[{begin:/"/,end:/"/,contains:[{begin:/""/,
+relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]},{
+className:"number",
+begin:/(\b0[0-7_]+)|(\b0x[0-9a-fA-F_]+)|(\b[1-9][0-9_]*(\.[0-9_]+)?)|[0_]\b/,
+relevance:0},{className:"comment",begin:/\(:/,end:/:\)/,relevance:10,contains:[{
+className:"doctag",begin:/@\w+/}]},{className:"meta",begin:/%[\w\-:]+/},{
+className:"title",begin:/\bxquery version "[13]\.[01]"\s?(?:encoding ".+")?/,
+end:/;/},{
+beginKeywords:"element attribute comment document processing-instruction",
+end:/\{/,excludeEnd:!0},{begin:/<([\w._:-]+)(\s+\S*=('|").*('|"))?>/,
+end:/(\/[\w._:-]+>)/,subLanguage:"xml",contains:[{begin:/\{/,end:/\}/,
+subLanguage:"xquery"},"self"]}]})})());
+hljs.registerLanguage("zephir",(()=>{"use strict";return e=>{const n={
+className:"string",contains:[e.BACKSLASH_ESCAPE],
+variants:[e.inherit(e.APOS_STRING_MODE,{illegal:null
+}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},s=e.UNDERSCORE_TITLE_MODE,a={
+variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]
+},i="namespace class interface use extends function return abstract final public protected private static deprecated throw try catch Exception echo empty isset instanceof unset let var new const self require if else elseif switch case default do while loop for continue break likely unlikely __LINE__ __FILE__ __DIR__ __FUNCTION__ __CLASS__ __TRAIT__ __METHOD__ __NAMESPACE__ array boolean float double integer object resource string char long unsigned bool int uint ulong uchar true false null undefined"
+;return{name:"Zephir",aliases:["zep"],keywords:i,
+contains:[e.C_LINE_COMMENT_MODE,e.COMMENT(/\/\*/,/\*\//,{contains:[{
+className:"doctag",begin:/@[A-Za-z]+/}]}),{className:"string",
+begin:/<<<['"]?\w+['"]?$/,end:/^\w+;/,contains:[e.BACKSLASH_ESCAPE]},{
+begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",
+beginKeywords:"function fn",end:/[;{]/,excludeEnd:!0,illegal:/\$|\[|%/,
+contains:[s,{className:"params",begin:/\(/,end:/\)/,keywords:i,
+contains:["self",e.C_BLOCK_COMMENT_MODE,n,a]}]},{className:"class",
+beginKeywords:"class interface",end:/\{/,excludeEnd:!0,illegal:/[:($"]/,
+contains:[{beginKeywords:"extends implements"},s]},{beginKeywords:"namespace",
+end:/;/,illegal:/[.']/,contains:[s]},{beginKeywords:"use",end:/;/,contains:[s]
+},{begin:/=>/},n,a]}}})()); \ No newline at end of file
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
index 64f0076e49..f11b96d876 100644
--- a/lib/jackson/BUILD
+++ b/lib/jackson/BUILD
@@ -12,6 +12,7 @@ java_library(
name = "jackson-core",
data = ["//lib:LICENSE-Apache2.0"],
visibility = [
+ "//java/com/google/gerrit/acceptance:__pkg__",
"//java/com/google/gerrit/elasticsearch:__pkg__",
"//plugins:__pkg__",
],
diff --git a/lib/jetty/BUILD b/lib/jetty/BUILD
index 641738597d..86d455f133 100644
--- a/lib/jetty/BUILD
+++ b/lib/jetty/BUILD
@@ -4,7 +4,10 @@ java_library(
name = "servlet",
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
- exports = ["@jetty-servlet//jar"],
+ exports = [
+ ":util-ajax",
+ "@jetty-servlet//jar",
+ ],
runtime_deps = [":security"],
)
@@ -69,3 +72,9 @@ java_library(
data = ["//lib:LICENSE-Apache2.0"],
exports = ["@jetty-util//jar"],
)
+
+java_library(
+ name = "util-ajax",
+ data = ["//lib:LICENSE-Apache2.0"],
+ exports = ["@jetty-util-ajax//jar"],
+)
diff --git a/lib/jgit/BUILD b/lib/jgit/BUILD
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/jgit/BUILD
+++ /dev/null
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
deleted file mode 100644
index 519d6edb35..0000000000
--- a/lib/jgit/jgit.bzl
+++ /dev/null
@@ -1,75 +0,0 @@
-load("//tools/bzl:maven_jar.bzl", "MAVEN_CENTRAL", "maven_jar")
-
-_JGIT_VERS = "5.3.9.202012012026-r"
-
-_DOC_VERS = _JGIT_VERS # Set to _JGIT_VERS unless using a snapshot
-
-JGIT_DOC_URL = "https://archive.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
-
-_JGIT_REPO = MAVEN_CENTRAL # Leave here even if set to MAVEN_CENTRAL.
-
-# set this to use a local version.
-# "/home/<user>/projects/jgit"
-LOCAL_JGIT_REPO = ""
-
-def jgit_repos():
- if LOCAL_JGIT_REPO:
- native.local_repository(
- name = "jgit",
- path = LOCAL_JGIT_REPO,
- )
- jgit_maven_repos_dev()
- else:
- jgit_maven_repos()
-
-def jgit_maven_repos_dev():
- # Transitive dependencies from JGit's WORKSPACE.
- maven_jar(
- name = "hamcrest-library",
- artifact = "org.hamcrest:hamcrest-library:1.3",
- sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
- )
- maven_jar(
- name = "jzlib",
- artifact = "com.jcraft:jzlib:1.1.1",
- sha1 = "a1551373315ffc2f96130a0e5704f74e151777ba",
- )
-
-def jgit_maven_repos():
- maven_jar(
- name = "jgit-lib",
- artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
- repository = _JGIT_REPO,
- sha1 = "b6d3af64d2538db1c25ee8cf9f2346fd8663321b",
- )
- maven_jar(
- name = "jgit-servlet",
- artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
- repository = _JGIT_REPO,
- sha1 = "1f68350b98cbbd9a5219ad5827b3aa6c46a15dea",
- )
- maven_jar(
- name = "jgit-archive",
- artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
- repository = _JGIT_REPO,
- sha1 = "7a4bf3ac728274129acf8c13c11b66199808eb20",
- )
- maven_jar(
- name = "jgit-junit",
- artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
- repository = _JGIT_REPO,
- sha1 = "dd8a2b2cd0b65ee00c260d1a1e7ed33aed951c69",
- )
-
-def jgit_dep(name):
- mapping = {
- "@jgit-archive//jar": "@jgit//org.eclipse.jgit.archive:jgit-archive",
- "@jgit-junit//jar": "@jgit//org.eclipse.jgit.junit:junit",
- "@jgit-lib//jar": "@jgit//org.eclipse.jgit:jgit",
- "@jgit-servlet//jar": "@jgit//org.eclipse.jgit.http.server:jgit-servlet",
- }
-
- if LOCAL_JGIT_REPO:
- return mapping[name]
- else:
- return name
diff --git a/lib/jgit/org.eclipse.jgit.archive/BUILD b/lib/jgit/org.eclipse.jgit.archive/BUILD
deleted file mode 100644
index 151cd71b6f..0000000000
--- a/lib/jgit/org.eclipse.jgit.archive/BUILD
+++ /dev/null
@@ -1,10 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-load("//lib/jgit:jgit.bzl", "jgit_dep")
-
-java_library(
- name = "jgit-archive",
- data = ["//lib:LICENSE-jgit"],
- visibility = ["//visibility:public"],
- exports = [jgit_dep("@jgit-archive//jar")],
- runtime_deps = ["//lib/jgit/org.eclipse.jgit:jgit"],
-)
diff --git a/lib/jgit/org.eclipse.jgit.http.server/BUILD b/lib/jgit/org.eclipse.jgit.http.server/BUILD
deleted file mode 100644
index fd634a5cc9..0000000000
--- a/lib/jgit/org.eclipse.jgit.http.server/BUILD
+++ /dev/null
@@ -1,10 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-load("//lib/jgit:jgit.bzl", "jgit_dep")
-
-java_library(
- name = "jgit-servlet",
- data = ["//lib:LICENSE-jgit"],
- visibility = ["//visibility:public"],
- exports = [jgit_dep("@jgit-servlet//jar")],
- runtime_deps = ["//lib/jgit/org.eclipse.jgit:jgit"],
-)
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUILD b/lib/jgit/org.eclipse.jgit.junit/BUILD
deleted file mode 100644
index abc522b8a0..0000000000
--- a/lib/jgit/org.eclipse.jgit.junit/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-load("//lib/jgit:jgit.bzl", "jgit_dep")
-
-java_library(
- name = "junit",
- testonly = True,
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [jgit_dep("@jgit-junit//jar")],
- runtime_deps = ["//lib/jgit/org.eclipse.jgit:jgit"],
-)
diff --git a/lib/jgit/org.eclipse.jgit/BUILD b/lib/jgit/org.eclipse.jgit/BUILD
deleted file mode 100644
index c1f260740f..0000000000
--- a/lib/jgit/org.eclipse.jgit/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-load("//lib/jgit:jgit.bzl", "jgit_dep")
-
-java_library(
- name = "jgit",
- data = ["//lib:LICENSE-jgit"],
- visibility = ["//visibility:public"],
- exports = [jgit_dep("@jgit-lib//jar")],
- runtime_deps = [
- ":javaewah",
- "//lib/log:api",
- ],
-)
-
-java_library(
- name = "javaewah",
- data = ["//lib:LICENSE-Apache2.0"],
- visibility = ["//visibility:public"],
- exports = ["@javaewah//jar"],
-)
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index 1597c02848..1cd8d52610 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -34,44 +34,44 @@ def load_bower_archives():
bower_archive(
name = "iron-a11y-announcer",
package = "PolymerElements/iron-a11y-announcer",
- version = "1.0.6",
- sha1 = "14aed1e1b300ea344e80362e875919ea3d104dcc",
+ version = "2.1.0",
+ sha1 = "bda12ed6fe7b98a64bf5f70f3e84384053763190",
)
bower_archive(
name = "iron-a11y-keys-behavior",
package = "PolymerElements/iron-a11y-keys-behavior",
- version = "1.1.9",
- sha1 = "f58358ee652c67e6e721364ba50fb77a2ece1465",
+ version = "2.1.1",
+ sha1 = "4c8f303479253301e81c63b8ba7bd4cfb62ddf55",
)
bower_archive(
name = "iron-behaviors",
package = "PolymerElements/iron-behaviors",
- version = "1.0.18",
- sha1 = "e231a1a02b090f5183db917639fdb96cdd0dca18",
+ version = "2.1.1",
+ sha1 = "d2418e886c3237dcbc8d74a956eec367a95cd068",
)
bower_archive(
name = "iron-checked-element-behavior",
package = "PolymerElements/iron-checked-element-behavior",
- version = "1.0.6",
- sha1 = "93ad3554cec119d8c5732d1c722ad113e1866370",
+ version = "2.1.1",
+ sha1 = "822b6c73e349cf5174e3a17aa9b3d2cb823c37ac",
)
bower_archive(
name = "iron-fit-behavior",
package = "PolymerElements/iron-fit-behavior",
- version = "1.2.7",
- sha1 = "01c485fbf898307029bbb72ac7e132db1570a842",
+ version = "2.2.1",
+ sha1 = "7b12bc96bf05f04bbb6ad78a16d6c39758263a14",
)
bower_archive(
name = "iron-flex-layout",
package = "PolymerElements/iron-flex-layout",
- version = "1.3.9",
- sha1 = "d987b924cf29fcfe4b393833e81fdc9f1e268796",
+ version = "2.0.3",
+ sha1 = "c88e9577cabb005ea6d33f35b97d9c39c68f3d9e",
)
bower_archive(
name = "iron-form-element-behavior",
package = "PolymerElements/iron-form-element-behavior",
- version = "1.0.7",
- sha1 = "7b5a79e02cc32f0918725dd26925d0df1e03ed12",
+ version = "2.1.3",
+ sha1 = "634f01cdedd7a616ae025fdcde85c6c5804f6377",
)
bower_archive(
name = "iron-menu-behavior",
@@ -82,20 +82,20 @@ def load_bower_archives():
bower_archive(
name = "iron-meta",
package = "PolymerElements/iron-meta",
- version = "1.1.3",
- sha1 = "f77eba3f6f6817f10bda33918bde8f963d450041",
+ version = "2.1.1",
+ sha1 = "7985a9f18b6c32d62f5d3870d58d73ef66613cb9",
)
bower_archive(
name = "iron-resizable-behavior",
- package = "polymerelements/iron-resizable-behavior",
- version = "1.0.6",
- sha1 = "719c2a8a1a784f8aefcdeef41fcc2e5a03518d9e",
+ package = "PolymerElements/iron-resizable-behavior",
+ version = "2.1.1",
+ sha1 = "31e32da6880a983da32da21ee3f483525b24e458",
)
bower_archive(
name = "iron-validatable-behavior",
package = "PolymerElements/iron-validatable-behavior",
- version = "1.1.2",
- sha1 = "7111f34ff32e1510131dfbdb1eaa51bfa291e8be",
+ version = "2.1.0",
+ sha1 = "b5dcf3bf4d95b074b74f8170d7122d34ab417daf",
)
bower_archive(
name = "lodash",
@@ -111,15 +111,15 @@ def load_bower_archives():
)
bower_archive(
name = "neon-animation",
- package = "polymerelements/neon-animation",
- version = "1.2.5",
- sha1 = "588d289f779d02b21ce5b676e257bbd6155649e8",
+ package = "PolymerElements/neon-animation",
+ version = "2.2.1",
+ sha1 = "865f4252c6306b91609769fefefb4f641361931f",
)
bower_archive(
name = "paper-behaviors",
package = "PolymerElements/paper-behaviors",
- version = "1.0.13",
- sha1 = "a81eab28a952e124c208430e17508d9a1aae4ee7",
+ version = "2.1.1",
+ sha1 = "af59936a9015cda4abcfb235f831090a41faa2c4",
)
bower_archive(
name = "paper-icon-button",
@@ -130,16 +130,22 @@ def load_bower_archives():
bower_archive(
name = "paper-ripple",
package = "PolymerElements/paper-ripple",
- version = "1.0.10",
- sha1 = "21199db50d02b842da54bd6f4f1d1b10b474e893",
+ version = "2.1.1",
+ sha1 = "d402c8165c6a09d17c12a2b421e69ea54e2fc8ef",
)
bower_archive(
name = "paper-styles",
package = "PolymerElements/paper-styles",
- # Basically 1.3.1 but with
- # https://github.com/PolymerElements/paper-styles/pull/164 applied
- version = "dd0b13e186b9690d5e74a93f6e51e0835ea60495",
- sha1 = "f859a8dee403fbb724e8d0cf009db79c6dd61b47",
+ # Basically 2.1.0 but with
+ # https://github.com/PolymerElements/paper-styles/pull/165 applied
+ version = "a6c207e6eee3402fd7a6550e6f9c387ca22ec4c4",
+ sha1 = "6bd17410578b5d4017ccef330393a4b41b1c716e",
+ )
+ bower_archive(
+ name = "shadycss",
+ package = "webcomponents/shadycss",
+ version = "1.9.1",
+ sha1 = "3ef3bd54280ea2d7ce90434620354a2022c8e13d",
)
bower_archive(
name = "sinon-chai",
@@ -160,14 +166,8 @@ def load_bower_archives():
sha1 = "d6c07a0112ab2e9677fe085933744466a89232fb",
)
bower_archive(
- name = "web-animations-js",
- package = "web-animations/web-animations-js",
- version = "2.3.1",
- sha1 = "2ba5548d36188fe54555eaad0a576de4b027661e",
- )
- bower_archive(
name = "webcomponentsjs",
package = "webcomponents/webcomponentsjs",
- version = "0.7.24",
- sha1 = "559227f8ee9db9bfbd81989f24510cc0c1bfc65c",
+ version = "1.3.3",
+ sha1 = "bbad90bd8301a2f2f5e014e750e0c86351579391",
)
diff --git a/lib/js/bower_components.bzl b/lib/js/bower_components.bzl
index 64ab611362..7fd61c7956 100644
--- a/lib/js/bower_components.bzl
+++ b/lib/js/bower_components.bzl
@@ -77,7 +77,6 @@ def define_bower_components():
deps = [
":iron-behaviors",
":iron-overlay-behavior",
- ":iron-resizable-behavior",
":neon-animation",
":polymer",
],
@@ -195,11 +194,9 @@ def define_bower_components():
name = "neon-animation",
license = "//lib:LICENSE-polymer",
deps = [
- ":iron-meta",
":iron-resizable-behavior",
":iron-selector",
":polymer",
- ":web-animations-js",
],
)
bower_component(
@@ -331,14 +328,15 @@ def define_bower_components():
bower_component(
name = "polymer",
license = "//lib:LICENSE-polymer",
- deps = [":webcomponentsjs"],
+ deps = [
+ ":shadycss",
+ ":webcomponentsjs",
+ ],
seed = True,
)
bower_component(
- name = "promise-polyfill",
- license = "//lib:LICENSE-promise-polyfill",
- deps = [":polymer"],
- seed = True,
+ name = "shadycss",
+ license = "//lib:LICENSE-shadycss",
)
bower_component(
name = "sinon-chai",
@@ -358,10 +356,6 @@ def define_bower_components():
seed = True,
)
bower_component(
- name = "web-animations-js",
- license = "//lib:LICENSE-Apache2.0",
- )
- bower_component(
name = "web-component-tester",
license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
deps = [
diff --git a/lib/js/npm.bzl b/lib/js/npm.bzl
index 8a9e1ee6e5..5a6a8c0fcf 100644
--- a/lib/js/npm.bzl
+++ b/lib/js/npm.bzl
@@ -1,11 +1,11 @@
NPM_VERSIONS = {
"bower": "1.8.8",
"crisper": "2.0.2",
- "polymer-bundler": "4.0.2",
+ "polymer-bundler": "4.0.9",
}
NPM_SHA1S = {
"bower": "82544be34a33aeae7efb8bdf9905247b2cffa985",
"crisper": "7183c58cea33632fb036c91cefd1b43e390d22a2",
- "polymer-bundler": "6b296b6099ab5a0e93ca914cbe93e753f2395910",
+ "polymer-bundler": "c80c9815690d76656d1fa6a231481850b4fa3874",
}
diff --git a/lib/log/BUILD b/lib/log/BUILD
index b119b9465b..fa5bc45beb 100644
--- a/lib/log/BUILD
+++ b/lib/log/BUILD
@@ -5,7 +5,7 @@ java_library(
data = ["//lib:LICENSE-slf4j"],
visibility = [
"//javatests/com/google/gerrit/elasticsearch:__pkg__",
- "//lib/jgit/org.eclipse.jgit:__pkg__",
+ "//lib:__pkg__",
"//plugins:__pkg__",
],
exports = ["@log-api//jar"],
diff --git a/lib/mina/BUILD b/lib/mina/BUILD
index 5ad47cd3f1..2f98ee3e39 100644
--- a/lib/mina/BUILD
+++ b/lib/mina/BUILD
@@ -6,6 +6,7 @@ java_library(
visibility = ["//visibility:public"],
exports = [
":eddsa",
+ "@sshd-common//jar",
"@sshd-mina//jar",
"@sshd//jar",
],
diff --git a/lib/mockito/BUILD b/lib/mockito/BUILD
index cff36cd019..2aaf56d08a 100644
--- a/lib/mockito/BUILD
+++ b/lib/mockito/BUILD
@@ -8,8 +8,7 @@ package(
java_library(
name = "mockito",
data = ["//lib:LICENSE-mockito"],
- # Only exposed for plugin tests; core tests should use Easymock
- visibility = ["//java/com/google/gerrit/acceptance:__pkg__"],
+ visibility = ["//visibility:public"],
exports = ["@mockito//jar"],
runtime_deps = [
":byte-buddy",
@@ -21,13 +20,13 @@ java_library(
java_library(
name = "byte-buddy",
data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@byte-buddy//jar"],
+ exports = ["@bytebuddy//jar"],
)
java_library(
name = "byte-buddy-agent",
data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@byte-buddy-agent//jar"],
+ exports = ["@bytebuddy-agent//jar"],
)
java_library(
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index 6341dc5337..d270853a7b 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -26,20 +26,14 @@ httpcore-nio
j2objc
jackson-annotations
jackson-core
-javassist
jna
jruby
mina-core
nekohtml
objenesis
openid-consumer
-powermock-api-easymock
-powermock-api-support
-powermock-core
-powermock-module-junit4
-powermock-module-junit4-common
-powermock-reflect
sshd
+sshd-common
sshd-mina
testcontainers
testcontainers-elasticsearch
diff --git a/lib/powermock/BUILD b/lib/powermock/BUILD
deleted file mode 100644
index 39df164aa8..0000000000
--- a/lib/powermock/BUILD
+++ /dev/null
@@ -1,69 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-
-java_library(
- name = "powermock-module-junit4",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- ":powermock-module-junit4-common",
- "//lib:junit",
- "@powermock-module-junit4//jar",
- ],
-)
-
-java_library(
- name = "powermock-module-junit4-common",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- ":powermock-reflect",
- "//lib:junit",
- "@powermock-module-junit4-common//jar",
- ],
-)
-
-java_library(
- name = "powermock-reflect",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- "//lib:junit",
- "//lib/easymock:objenesis",
- "@powermock-reflect//jar",
- ],
-)
-
-java_library(
- name = "powermock-api-easymock",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- ":powermock-api-support",
- "//lib/easymock",
- "@powermock-api-easymock//jar",
- ],
-)
-
-java_library(
- name = "powermock-api-support",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- ":powermock-core",
- ":powermock-reflect",
- "//lib:junit",
- "@powermock-api-support//jar",
- ],
-)
-
-java_library(
- name = "powermock-core",
- data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
- visibility = ["//visibility:public"],
- exports = [
- ":powermock-reflect",
- "//lib:javassist",
- "//lib:junit",
- "@powermock-core//jar",
- ],
-)
diff --git a/modules/jgit b/modules/jgit
new file mode 160000
+Subproject 00386272264f65c41e36406f7c2e9ea6e901276
diff --git a/package.json b/package.json
index db09e9c4c2..56acf61fae 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gerrit",
- "version": "3.0.7-SNAPSHOT",
+ "version": "3.1.0-SNAPSHOT",
"description": "Gerrit Code Review",
"dependencies": {},
"devDependencies": {
diff --git a/plugins/BUILD b/plugins/BUILD
index b9776ead8e..5f9c1426e2 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -44,7 +44,7 @@ EXPORTS = [
"//java/com/google/gerrit/mail",
"//java/com/google/gerrit/metrics",
"//java/com/google/gerrit/metrics/dropwizard",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/server/api",
"//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/cache/mem",
@@ -69,8 +69,8 @@ EXPORTS = [
"//lib/httpcomponents:httpclient",
"//lib/httpcomponents:httpcore",
"//lib/jackson:jackson-core",
- "//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet",
- "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib:jgit-servlet",
+ "//lib:jgit",
"//lib:jsr305",
"//lib/log:api",
"//lib/log:log4j",
@@ -94,7 +94,18 @@ EXPORTS = [
]
java_binary(
+ name = "bouncycastle-deploy-env",
+ main_class = "Dummy",
+ runtime_deps = [
+ "//lib/bouncycastle:bcpg",
+ "//lib/bouncycastle:bcpkix",
+ "//lib/bouncycastle:bcprov",
+ ],
+)
+
+java_binary(
name = "plugin-api",
+ deploy_env = ["bouncycastle-deploy-env"],
main_class = "Dummy",
visibility = ["//visibility:public"],
runtime_deps = [":plugin-lib"],
@@ -121,12 +132,12 @@ java_binary(
"//antlr3:libquery_parser-src.jar",
"//java/com/google/gerrit/common:libannotations-src.jar",
"//java/com/google/gerrit/common:libserver-src.jar",
+ "//java/com/google/gerrit/entities:libentities-src.jar",
"//java/com/google/gerrit/extensions:libapi-src.jar",
"//java/com/google/gerrit/httpd:libhttpd-src.jar",
"//java/com/google/gerrit/index:libindex-src.jar",
"//java/com/google/gerrit/index:libquery_exception-src.jar",
"//java/com/google/gerrit/pgm/init/api:libapi-src.jar",
- "//java/com/google/gerrit/reviewdb:libserver-src.jar",
"//java/com/google/gerrit/server:libserver-src.jar",
"//java/com/google/gerrit/server/restapi:librestapi-src.jar",
"//java/com/google/gerrit/sshd:libsshd-src.jar",
@@ -143,7 +154,7 @@ java_doc(
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/entities",
"//java/com/google/gerrit/util/http",
],
pkgs = ["com.google.gerrit"],
diff --git a/plugins/delete-project b/plugins/delete-project
-Subproject 542bb8137e356e8ed89856576499d185a9f5c75
+Subproject debaa2c6d66b5a3c1bc95bc09283344c88407d4
diff --git a/plugins/download-commands b/plugins/download-commands
-Subproject 41c61bf8c1869bff4e0b436f69478c2137d0ca0
+Subproject 1b98be8d371e68237182df0f04361017f2be2ea
diff --git a/plugins/gitiles b/plugins/gitiles
-Subproject 0f6ffbecc51a0571c8a77248b2655a834700d6a
+Subproject 3438c497cb1ef7711e023b89ba05ea3e973fdd9
diff --git a/plugins/hooks b/plugins/hooks
-Subproject 089687bdcc64b003d09a77f00eaa77bb79b15b9
+Subproject 5678f93fa62df1ec2baedc3a407aff71ca96556
diff --git a/plugins/plugin-manager b/plugins/plugin-manager
-Subproject d3b2a6eabcb641e952f253e61b927cd1f7f6e30
+Subproject 20bec5084c7b90029b8860cbb2fb9a12928f697
diff --git a/plugins/replication b/plugins/replication
-Subproject 16d00cdf95a0e04db345512fcd48c8ec3d86744
+Subproject 17e8c41d5e7b83c419a52a6f7da0ad3ade514f7
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
-Subproject be5037839d987a319dac2236e9c1221d4d31848
+Subproject b9e1e8d61a324ca0592bbb7c80c2802618ef5bc
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
-Subproject 8a3b6faeaebc0f9b7d1af19eb0022b32994e476
+Subproject 4508df9fe31f9fb70f1392173686ebf80a88de3
diff --git a/plugins/webhooks b/plugins/webhooks
-Subproject 8f7a232ef94f60b20c059fff7066d0d1864bec8
+Subproject 11b6fb81b5e4fb0bf3909f862c120d20d4fa9aa
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD
index 0e9b4bb3f5..1c685ab6e1 100644
--- a/polygerrit-ui/BUILD
+++ b/polygerrit-ui/BUILD
@@ -31,7 +31,7 @@ bower_component_bundle(
"//lib/js:paper-toggle-button",
"//lib/js:polymer",
"//lib/js:polymer-resin",
- "//lib/js:promise-polyfill",
+ "//lib/js:shadycss",
],
)
diff --git a/polygerrit-ui/Polymer2.md b/polygerrit-ui/Polymer2.md
new file mode 100644
index 0000000000..96bf77994b
--- /dev/null
+++ b/polygerrit-ui/Polymer2.md
@@ -0,0 +1,15 @@
+## Polymer 2 upgrade
+
+Gerrit is updating to use polymer 2 from polymer 1 by following the [Polymer 2.0 upgrade guide](https://polymer-library.polymer-project.org/2.0/docs/upgrade).
+
+Polymer 2 contains several breaking changes that may affect some of the UI features and plugins. One of the biggest change is to have the shadow DOM enabled. This will affect how you query elements inside of your component, how css style works within and across components, and several other usages.
+
+If you are owner of any plugins, please start following the [Polymer 2.0 upgrade guide](https://polymer-library.polymer-project.org/2.0/docs/upgrade) to migrate your plugins to be polymer 2 ready.
+
+If you notice any issues or need help with anything, don't hesitate to report to us [here](https://bugs.chromium.org/p/gerrit/issues/list).
+
+
+### Related resources
+
+- [Polymer 2.0 upgrade guide](https://polymer-library.polymer-project.org/2.0/docs/upgrade)
+- [Polymer Shadow DOM](https://polymer-library.polymer-project.org/2.0/docs/devguide/shadow-dom)
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 4dbe14678d..1fbf581b07 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -1,4 +1,8 @@
-# PolyGerrit
+# Gerrit Polymer Frontend
+
+Follow the
+[setup instructions for Gerrit backend developers](https://gerrit-review.googlesource.com/Documentation/dev-readme.html)
+where applicable.
## Installing [Bazel](https://bazel.build/)
@@ -20,8 +24,8 @@ brew install node
brew install npm
```
-All other platforms: [download from
-nodejs.org](https://nodejs.org/en/download/).
+All other platforms:
+[download from nodejs.org](https://nodejs.org/en/download/).
Various steps below require installing additional npm packages. The full list of
dependencies can be installed with:
@@ -33,17 +37,12 @@ npm install
It may complain about a missing `typescript@2.3.4` peer dependency, which is
harmless.
-If you're interested in the details, keep reading.
-
-## Local UI, Production Data
+## Running locally against production data
-This is a quick and easy way to test your local changes against real data.
-Unfortunately, you can't sign in, so testing certain features will require
-you to use the "test data" technique described below.
+#### Go server
-### Running the server
-
-To test the local UI against gerrit-review.googlesource.com:
+To test the local Polymer frontend against gerrit-review.googlesource.com
+simply execute:
```sh
./polygerrit-ui/run-server.sh
@@ -51,37 +50,49 @@ To test the local UI against gerrit-review.googlesource.com:
Then visit http://localhost:8081
-## Local UI, Test Data
-
-1. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_gerrit_development_war_file)
-2. Set up a local test site. Docs
- [here](https://gerrit-review.googlesource.com/Documentation/linux-quickstart.html) and
- [here](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init).
-
-When your project is set up and works using the classic UI, run a test server
-that serves PolyGerrit:
+This method is based on a
+[simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go).
+Mostly it just switches between serving files locally and proxying the real
+server based on the file name. It also does some basic response rewriting, e.g.
+it patches the `config/server/info` response with plugin information provided on
+the command line:
```sh
-bazel build gerrit &&
- $(bazel info output_base)/external/local_jdk/bin/java -DsourceRoot=/path/to/my/checkout \
- -jar bazel-bin/gerrit.war daemon --polygerrit-dev \
- -d ../gerrit_testsite --console-log --show-stack-trace
+./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html
```
-Serving plugins
+The biggest draw back of this method is that you cannot log in, so cannot test
+scenarios that require it.
-> Local dev plugins must be put inside of gerrit/plugins
+#### MITM Proxy
-Loading a single plugin file:
+[MITM Proxy](https://mitmproxy.org/) is an open source product for proxying
+https servers. The
+[contrib/mitm-ui/](https://gerrit.googlesource.com/gerrit/+/master/contrib/mitm-ui/)
+directory contains scripts (and documentation) for using this technology
+(instead of the Go server). These scripts are somewhat experimental and
+unmaintained though.
-```sh
-./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js
-```
+## Running locally against a Gerrit test site
+
+Set up a local test site once:
+
+1. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_gerrit_development_war_file)
+2. [Set up a local test site](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init).
+3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) your test site with some test data.
-Loading multiple plugin files:
+For running a locally built Gerrit war against your test instance use
+[this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon),
+and add the `--polygerrit-dev` option, if you want to serve the Polymer frontend
+directly from the sources in `polygerrit_ui/app/` instead of from the war:
```sh
-./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html
+$(bazel info output_base)/external/local_jdk/bin/java \
+ -DsourceRoot=$(bazel info workspace) \
+ -jar bazel-bin/gerrit.war daemon \
+ -d $GERRIT_SITE \
+ --console-log \
+ --polygerrit-dev
```
## Running Tests
@@ -93,7 +104,7 @@ to the `npm install` command to avoid file permission errors.
For daily development you typically only want to run and debug individual tests.
Run the local [Go proxy server](#go-server) and navigate for example to
-<http://localhost:8081/elements/change/gr-account-entry/gr-account-entry_test.html>.
+<http://localhost:8081/elements/shared/gr-account-entry/gr-account-entry_test.html>.
Check "Disable cache" in the "Network" tab of Chrome's dev tools, so code
changes are picked up on "reload".
@@ -115,11 +126,6 @@ To run Chrome tests in headless mode:
WCT_HEADLESS_MODE=1 WCT_ARGS='--verbose -l chrome' ./polygerrit-ui/app/run_test.sh
```
-Toolchain requirements for headless mode:
-
-* Chrome: 59+
-* web-component-tester: v6.5.0+
-
## Style guide
We follow the [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml)
@@ -176,11 +182,14 @@ npm run polylint
```
## Template Type Safety
-Polymer elements are not type checked against the element definition, making it trivial to break the display when refactoring or moving code. We now run additional tests to help ensure that template types are checked.
+Polymer elements are not type checked against the element definition, making it
+trivial to break the display when refactoring or moving code. We now run
+additional tests to help ensure that template types are checked.
A few notes to ensure that these tests pass
- Any functions with optional parameters will need closure annotations.
-- Any Polymer parameters that are nullable or can be multiple types (other than the one explicitly delared) will need type annotations.
+- Any Polymer parameters that are nullable or can be multiple types (other than
+ the one explicitly delared) will need type annotations.
These tests require the `typescript` and `fried-twinkie` npm packages.
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 3a3c586970..5ec0274d31 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -181,7 +181,7 @@ sh_test(
"embed/test.html",
"test/common-test-setup.html",
":embed_test_files",
- ":polygerrit_embed_ui.zip",
+ ":pg_code.zip",
":test_components.zip",
],
# Should not run sandboxed.
diff --git a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
index fec459b33f..970bfc7f61 100644
--- a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>async-foreach-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="async-foreach-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
index c21e96f357..b61b142825 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>base-url-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<script>
/** @type {string} */
@@ -52,7 +54,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.BaseUrlBehavior,
],
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
index 96d4a08538..2c513f3ded 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Polymer included for the html import polyfill. -->
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<title>docs-url-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<link rel="import" href="docs-url-behavior.html">
@@ -38,7 +40,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'docs-url-behavior-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.DocsUrlBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
index e445a7848a..8323ac6f3d 100644
--- a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>dom-util-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="dom-util-behavior.html">
@@ -46,7 +48,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.DomUtilBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html b/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html
new file mode 100644
index 0000000000..b5afab14c5
--- /dev/null
+++ b/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html
@@ -0,0 +1,55 @@
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<script>
+(function(window) {
+ 'use strict';
+
+ window.Gerrit = window.Gerrit || {};
+
+ /** @polymerBehavior Gerrit.FireBehavior */
+ Gerrit.FireBehavior = {
+ /**
+ * Dispatches a custom event with an optional detail value.
+ *
+ * @param {string} type Name of event type.
+ * @param {*=} detail Detail value containing event-specific
+ * payload.
+ * @param {{ bubbles: (boolean|undefined), cancelable: (boolean|undefined),
+ * composed: (boolean|undefined) }=}
+ * options Object specifying options. These may include:
+ * `bubbles` (boolean, defaults to `true`),
+ * `cancelable` (boolean, defaults to false), and
+ * `composed` (boolean, defaults to true).
+ * @return {!Event} The new event that was fired.
+ * @override
+ */
+ fire(type, detail, options) {
+ options = options || {};
+ detail = (detail === null || detail === undefined) ? {} : detail;
+ const event = new Event(type, {
+ bubbles: options.bubbles === undefined ? true : options.bubbles,
+ cancelable: Boolean(options.cancelable),
+ composed: options.composed === undefined ? true: options.composed,
+ });
+ event.detail = detail;
+ this.dispatchEvent(event);
+ return event;
+ },
+ };
+})(window);
+</script>
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
index 530c76c120..fedefc6328 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
@@ -88,10 +88,6 @@ limitations under the License.
id: 'owner',
name: 'Owner',
},
- publishDrafts: {
- id: 'publishDrafts',
- name: 'Publish Drafts',
- },
push: {
id: 'push',
name: 'Push',
@@ -138,6 +134,7 @@ limitations under the License.
* object.
*/
toSortedArray(obj) {
+ if (!obj) { return []; }
return Object.keys(obj).map(key => {
return {
id: key,
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
index 0b37a0d8f6..0d1ee575f1 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-access-behavior.html">
@@ -38,7 +40,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.AccessBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
index d2175779e1..0285e35fb5 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-admin-nav-behavior.html">
@@ -41,7 +43,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.AdminNavBehavior,
],
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
index b052d06737..791e2afaa2 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-table-behavior.html">
@@ -48,7 +50,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.ChangeTableBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
index 40379e48ea..3106fc8ba2 100644
--- a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
@@ -15,33 +15,28 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
+<script src="../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+
<script>
(function(window) {
'use strict';
- const ANONYMOUS_NAME = 'Anonymous';
-
window.Gerrit = window.Gerrit || {};
- /** @polymerBehavior Gerrit.AnonymousNameBehavior */
- Gerrit.AnonymousNameBehavior = {
+ /** @polymerBehavior Gerrit.DisplayNameBehavior */
+ Gerrit.DisplayNameBehavior = {
+ // TODO(dmfilippov) replace DisplayNameBehavior with GrDisplayNameUtils
+
/**
* enableEmail when true enables to fallback to using email if
* the account name is not avilable.
*/
getUserName(config, account, enableEmail) {
- if (account && account.name) {
- return account.name;
- } else if (account && account.username) {
- return account.username;
- } else if (enableEmail && account && account.email) {
- return account.email;
- } else if (config && config.user &&
- config.user.anonymous_coward_name !== 'Anonymous Coward') {
- return config.user.anonymous_coward_name;
- }
-
- return ANONYMOUS_NAME;
+ return GrDisplayNameUtils.getUserName(config, account, enableEmail);
+ },
+
+ getGroupDisplayName(group) {
+ return GrDisplayNameUtils.getGroupDisplayName(group);
},
};
})(window);
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
index 820d6bc602..3d4eca1b85 100644
--- a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
@@ -17,12 +17,14 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-anonymous-name-behavior</title>
+<title>gr-display-name-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
-<link rel="import" href="gr-anonymous-name-behavior.html">
+<link rel="import" href="gr-display-name-behavior.html">
<test-fixture id="basic">
<template>
@@ -31,7 +33,7 @@ limitations under the License.
</test-fixture>
<script>
- suite('gr-anonymous-name-behavior tests', () => {
+ suite('gr-display-name-behavior tests', () => {
let element;
// eslint-disable-next-line no-unused-vars
const config = {
@@ -44,9 +46,8 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element-anon',
- _legacyUndefinedCheck: true,
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
});
});
@@ -55,21 +56,21 @@ limitations under the License.
element = fixture('basic');
});
- test('test for it to return name', () => {
+ test('getUserName name only', () => {
const account = {
name: 'test-name',
};
assert.deepEqual(element.getUserName(config, account, true), 'test-name');
});
- test('test for it to return username', () => {
+ test('getUserName username only', () => {
const account = {
username: 'test-user',
};
assert.deepEqual(element.getUserName(config, account, true), 'test-user');
});
- test('test for it to return email', () => {
+ test('getUserName email only', () => {
const account = {
email: 'test-user@test-url.com',
};
@@ -77,11 +78,11 @@ limitations under the License.
'test-user@test-url.com');
});
- test('test for it not to Anonymous Coward as the anon name', () => {
+ test('getUserName returns not Anonymous Coward as the anon name', () => {
assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
});
- test('test for the config returning the anon name', () => {
+ test('getUserName for the config returning the anon name', () => {
const config = {
user: {
anonymous_coward_name: 'Test Anon',
@@ -89,5 +90,10 @@ limitations under the License.
};
assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
});
+
+ test('getGroupDisplayName', () => {
+ assert.equal(element.getGroupDisplayName({name: 'Some user name'}),
+ 'Some user name (group)');
+ });
});
</script>
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
index f6c765f8f7..535483d989 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-list-view-behavior.html">
@@ -40,7 +42,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.ListViewBehavior],
});
});
@@ -68,10 +69,10 @@ limitations under the License.
test('getFilterValue', () => {
let params;
- assert.equal(element.getFilterValue(params), null);
+ assert.equal(element.getFilterValue(params), '');
params = {filter: null};
- assert.equal(element.getFilterValue(params), null);
+ assert.equal(element.getFilterValue(params), '');
params = {filter: 'test'};
assert.equal(element.getFilterValue(params), 'test');
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index a4c1e86ae1..28d69900d0 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -31,7 +31,7 @@ limitations under the License.
window.Gerrit = window.Gerrit || {};
- /** @polymerBehavior this */
+ /** @polymerBehavior Gerrit.PatchSetBehavior*/
Gerrit.PatchSetBehavior = {
EDIT_NAME: 'edit',
PARENT_NAME: 'PARENT',
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index b858c51a8a..3db40847c8 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Polymer included for the html import polyfill. -->
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<title>gr-patch-set-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<link rel="import" href="gr-patch-set-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 75c24330ba..0046290e69 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Polymer included for the html import polyfill. -->
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<title>gr-path-list-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<link rel="import" href="gr-path-list-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html b/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
index 2dc070d2b1..2fa9191f93 100644
--- a/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
@@ -20,7 +20,7 @@ limitations under the License.
window.Gerrit = window.Gerrit || {};
- /** @polymerBehavior this */
+ /** @polymerBehavior Gerrit.RepoPluginConfig*/
Gerrit.RepoPluginConfig = {
// Should be kept in sync with
// gerrit/java/com/google/gerrit/extensions/api/projects/ProjectConfigEntryType.java.
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html
index 07d3484e62..0e2e99fdca 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../elements/shared/gr-tooltip/gr-tooltip.html">
<script src="../../scripts/rootElement.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
index 04d8b6e62b..0bf620f030 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
@@ -89,7 +89,7 @@
this._tooltip = tooltip;
this.listen(window, 'scroll', '_handleWindowScroll');
this.listen(this, 'mouseleave', '_handleHideTooltip');
- this.listen(this, 'tap', '_handleHideTooltip');
+ this.listen(this, 'click', '_handleHideTooltip');
},
_handleHideTooltip(e) {
@@ -101,7 +101,7 @@
this.unlisten(window, 'scroll', '_handleWindowScroll');
this.unlisten(this, 'mouseleave', '_handleHideTooltip');
- this.unlisten(this, 'tap', '_handleHideTooltip');
+ this.unlisten(this, 'click', '_handleHideTooltip');
this.setAttribute('title', this._titleText);
if (this._tooltip && this._tooltip.parentNode) {
this._tooltip.parentNode.removeChild(this._tooltip);
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
index 943e0001d5..173c8d4ac5 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<title>tooltip-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-tooltip-behavior.html">
@@ -51,7 +53,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'tooltip-behavior-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.TooltipBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
index d909e86019..73e51d3c93 100644
--- a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<title>gr-url-encoding-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="gr-url-encoding-behavior.html">
@@ -40,7 +42,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.URLEncodingBehavior],
});
});
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index df7f3cff7d..3c5a733c17 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -96,8 +96,8 @@ by gr-app, and actually implemented by gr-comment-thread.
NOTE: doc-only shortcuts will not be customizable in the same way that other
shortcuts are.
-->
-<link rel="import" href="../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<script>
(function(window) {
@@ -180,6 +180,7 @@ shortcuts are.
SEARCH: 'SEARCH',
SEND_REPLY: 'SEND_REPLY',
+ EMOJI_DROPDOWN: 'EMOJI_DROPDOWN',
};
const _help = new Map();
@@ -292,6 +293,8 @@ shortcuts are.
'Show/hide selected inline diff');
_describe(Shortcut.SEND_REPLY, ShortcutSection.REPLY_DIALOG, 'Send reply');
+ _describe(Shortcut.EMOJI_DROPDOWN, ShortcutSection.REPLY_DIALOG,
+ 'Emoji dropdown');
// Must be declared outside behavior implementation to be accessed inside
// behavior functions.
@@ -423,6 +426,9 @@ shortcuts are.
}
describeBinding(binding) {
+ if (binding.length === 1) {
+ return [binding];
+ }
return binding.split(':')[0].split('+').map(part => {
switch (part) {
case 'shift':
@@ -457,7 +463,7 @@ shortcuts are.
window.Gerrit = window.Gerrit || {};
- /** @polymerBehavior KeyboardShortcutBehavior */
+ /** @polymerBehavior Gerrit.KeyboardShortcutBehavior*/
Gerrit.KeyboardShortcutBehavior = [
Polymer.IronA11yKeysBehavior,
{
@@ -506,7 +512,7 @@ shortcuts are.
},
// Alias for getKeyboardEvent.
- /** @return {!(Event|PolymerDomApi|PolymerEventApi)} */
+ /** @return {!Event} */
getKeyboardEvent(e) {
return getKeyboardEvent(e);
},
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index b4451fecd2..3183c7eff9 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="keyboard-shortcut-behavior.html">
@@ -50,7 +52,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.KeyboardShortcutBehavior],
keyBindings: {
k: '_handleKey',
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
index 354bedc2a7..85bc6a194d 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../base-url-behavior/base-url-behavior.html">
<script>
(function(window) {
@@ -97,6 +97,12 @@ limitations under the License.
// Skip mergeability data.
SKIP_MERGEABLE: 22,
+
+ /**
+ * Skip diffstat computation that compute the insertions field (number of lines inserted) and
+ * deletions field (number of lines deleted)
+ */
+ SKIP_DIFFSTAT: 23,
},
listChangesOptionsToHex(...args) {
@@ -123,8 +129,8 @@ limitations under the License.
return this.getBaseUrl() + '/c/' + changeNum;
},
- changeIsOpen(status) {
- return status === this.ChangeStatus.NEW;
+ changeIsOpen(change) {
+ return change && change.status === this.ChangeStatus.NEW;
},
/**
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
index 6af43dc449..a77a01f303 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>keyboard-shortcut-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<script>
/** @type {string} */
@@ -54,7 +56,6 @@ limitations under the License.
// Define a Polymer element that uses this behavior.
Polymer({
is: 'test-element',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.BaseUrlBehavior,
Gerrit.RESTClientBehavior,
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
index 6e040a3b05..ab446f1a77 100644
--- a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<title>safe-types-behavior</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
-<script src="../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
<link rel="import" href="safe-types-behavior.html">
@@ -39,7 +41,6 @@ limitations under the License.
suiteSetup(() => {
Polymer({
is: 'safe-types-element',
- _legacyUndefinedCheck: true,
behaviors: [Gerrit.SafeTypes],
});
});
@@ -119,4 +120,4 @@ limitations under the License.
});
});
});
-</script> \ No newline at end of file
+</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
index 45bc5f67d8..ac65360684 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
@@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -33,7 +34,7 @@ limitations under the License.
<style include="shared-styles">
:host {
display: block;
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
fieldset {
border: 1px solid var(--border-color);
@@ -50,13 +51,13 @@ limitations under the License.
display: flex;
justify-content: space-between;
min-height: 3em;
- padding: 0 .7em;
+ padding: 0 var(--spacing-m);
}
#deletedContainer {
border-bottom: 0;
}
.sectionContent {
- padding: .7em;
+ padding: var(--spacing-m);
}
#editBtn,
.editing #editBtn.global,
@@ -65,11 +66,11 @@ limitations under the License.
#addPermission,
#deleteBtn,
.editingRef .name,
- #editRefInput {
+ .editRefInput {
display: none;
}
.editing #editBtn,
- .editingRef #editRefInput {
+ .editingRef .editRefInput {
display: flex;
}
.deleted #deletedContainer {
@@ -82,7 +83,7 @@ limitations under the License.
}
.editing #deleteBtn,
#undoRemoveBtn {
- padding-right: .7em;
+ padding-right: var(--spacing-m);
}
</style>
<style include="gr-form-styles"></style>
@@ -96,20 +97,26 @@ limitations under the License.
id="editBtn"
link
class$="[[_computeEditBtnClass(section.id)]]"
- on-tap="editReference">
+ on-click="editReference">
<iron-icon id="icon" icon="gr-icons:create"></iron-icon>
</gr-button>
</div>
- <input
- id="editRefInput"
+ <iron-input
+ class="editRefInput"
bind-value="{{section.id}}"
- is="iron-input"
type="text"
on-input="_handleValueChange">
+ <input
+ class="editRefInput"
+ bind-value="{{section.id}}"
+ is="iron-input"
+ type="text"
+ on-input="_handleValueChange">
+ </iron-input>
<gr-button
link
id="deleteBtn"
- on-tap="_handleRemoveReference">Remove</gr-button>
+ on-click="_handleRemoveReference">Remove</gr-button>
</div><!-- end header -->
<div class="sectionContent">
<template
@@ -140,7 +147,7 @@ limitations under the License.
<gr-button
link
id="addBtn"
- on-tap="_handleAddPermission">Add</gr-button>
+ on-click="_handleAddPermission">Add</gr-button>
</div>
<!-- end addPermission -->
</div><!-- end sectionContent -->
@@ -150,7 +157,7 @@ limitations under the License.
<gr-button
link
id="undoRemoveBtn"
- on-tap="_handleUndoRemove">Undo</gr-button>
+ on-click="_handleUndoRemove">Undo</gr-button>
</div><!-- end deletedContainer -->
</fieldset>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
index 71f8d26ecf..81787090ae 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
@@ -39,7 +39,6 @@
Polymer({
is: 'gr-access-section',
- _legacyUndefinedCheck: true,
properties: {
capabilities: Object,
@@ -72,6 +71,11 @@
behaviors: [
Gerrit.AccessBehavior,
+ /**
+ * Unused in this element, but called by other elements in tests
+ * e.g gr-repo-access_test.
+ */
+ Gerrit.FireBehavior,
],
listeners: {
@@ -79,7 +83,15 @@
},
_updateSection(section) {
- this._permissions = this.toSortedArray(section.value.permissions);
+ let permissions = this.toSortedArray(section.value.permissions);
+ // We do not care about permissions for global capabilities that are not
+ // currently supported by the server (f.i. capabilities provided by
+ // plugins that are no longer installed).
+ if (section.id === GLOBAL_NAME) {
+ permissions = permissions.filter(
+ p => this.capabilities.hasOwnProperty(p.id));
+ }
+ this._permissions = permissions;
this._originalId = section.id;
},
@@ -96,7 +108,8 @@
// For a new section, this is not fired because new permissions and
// rules have to be added in order to save, modifying the ref is not
// enough.
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'access-modified', {bubbles: true, composed: true}));
}
this.section.value.updatedId = this.section.id;
},
@@ -123,6 +136,9 @@
_computePermissions(name, capabilities, labels) {
let allPermissions;
+ if (!this.section || !this.section.value) {
+ return [];
+ }
if (name === GLOBAL_NAME) {
allPermissions = this.toSortedArray(capabilities);
} else {
@@ -147,6 +163,7 @@
_computeLabelOptions(labels) {
const labelOptions = [];
+ if (!labels) { return []; }
for (const labelName of Object.keys(labels)) {
labelOptions.push({
id: 'label-' + labelName,
@@ -200,12 +217,13 @@
_handleRemoveReference() {
if (this.section.value.added) {
- this.dispatchEvent(new CustomEvent('added-section-removed',
- {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'added-section-removed', {bubbles: true, composed: true}));
}
this._deleted = true;
this.section.value.deleted = true;
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_handleUndoRemove() {
@@ -213,13 +231,19 @@
delete this.section.value.deleted;
},
+ editRefInput() {
+ return Polymer.dom(this.root).querySelector(Polymer.Element ?
+ 'iron-input.editRefInput' :
+ 'input[is=iron-input].editRefInput');
+ },
+
editReference() {
this._editingRef = true;
- this.$.editRefInput.focus();
+ this.editRefInput().focus();
},
_isEditEnabled(canUpload, ownerOf, sectionId) {
- return canUpload || ownerOf.indexOf(sectionId) >= 0;
+ return canUpload || (ownerOf && ownerOf.indexOf(sectionId) >= 0);
},
_computeSectionClass(editing, canUpload, ownerOf, editingRef, deleted) {
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index 56f321b5c7..557501428f 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-access-section</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-access-section.html">
@@ -327,6 +329,24 @@ limitations under the License.
default_value: 0,
},
};
+ element.capabilities = {
+ accessDatabase: {
+ id: 'accessDatabase',
+ name: 'Access Database',
+ },
+ administrateServer: {
+ id: 'administrateServer',
+ name: 'Administrate Server',
+ },
+ batchChangesLimit: {
+ id: 'batchChangesLimit',
+ name: 'Batch Changes Limit',
+ },
+ createAccount: {
+ id: 'createAccount',
+ name: 'Create Account',
+ },
+ };
});
suite('Global section', () => {
setup(() => {
@@ -340,24 +360,6 @@ limitations under the License.
},
},
};
- element.capabilities = {
- accessDatabase: {
- id: 'accessDatabase',
- name: 'Access Database',
- },
- administrateServer: {
- id: 'administrateServer',
- name: 'Administrate Server',
- },
- batchChangesLimit: {
- id: 'batchChangesLimit',
- name: 'Batch Changes Limit',
- },
- createAccount: {
- id: 'createAccount',
- name: 'Create Account',
- },
- };
element._updateSection(element.section);
flushAsynchronousOperations();
});
@@ -385,7 +387,6 @@ limitations under the License.
},
},
};
- element.capabilities = {};
element._updateSection(element.section);
flushAsynchronousOperations();
});
@@ -455,7 +456,7 @@ limitations under the License.
1);
});
- test('edit section reference', () => {
+ test('edit section reference', done => {
element.canUpload = true;
element.ownerOf = [];
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
@@ -464,14 +465,16 @@ limitations under the License.
assert.isTrue(element.$.section.classList.contains('editing'));
assert.isFalse(element._editingRef);
MockInteractions.tap(element.$.editBtn);
- element.$.editRefInput.bindValue='new/ref';
- flushAsynchronousOperations();
- assert.equal(element.section.id, 'new/ref');
- assert.isTrue(element._editingRef);
- assert.isTrue(element.$.section.classList.contains('editingRef'));
- element.editing = false;
- assert.isFalse(element._editingRef);
- assert.equal(element.section.id, 'refs/for/bar');
+ element.editRefInput().bindValue='new/ref';
+ setTimeout(() => {
+ assert.equal(element.section.id, 'new/ref');
+ assert.isTrue(element._editingRef);
+ assert.isTrue(element.$.section.classList.contains('editingRef'));
+ element.editing = false;
+ assert.isFalse(element._editingRef);
+ assert.equal(element.section.id, 'refs/for/bar');
+ done();
+ });
});
test('_handleValueChange', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
index ea08d8943d..dd8758f332 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.html
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
@@ -54,7 +54,7 @@ limitations under the License.
<template is="dom-repeat" items="[[_shownGroups]]">
<tr class="table">
<td class="name">
- <a href$="[[_computeGroupUrl(item.group_id)]]">[[item.name]]</a>
+ <a href$="[[_computeGroupUrl(item.id)]]">[[item.name]]</a>
</td>
<td class="description">[[item.description]]</td>
<td class="visibleToAll">[[_visibleToAll(item)]]</td>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 9db2e34471..cf2100d81d 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-admin-group-list',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -68,6 +67,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.ListViewBehavior,
],
@@ -97,8 +97,13 @@
}
},
+ /**
+ * Generates groups link (/admin/groups/<uuid>)
+ *
+ * @param {string} id
+ */
_computeGroupUrl(id) {
- return Gerrit.Nav.getUrlForGroup(id);
+ return Gerrit.Nav.getUrlForGroup(decodeURIComponent(id));
},
_getCreateGroupCapability() {
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
index 065a757dc1..bd9b30a9a5 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-admin-group-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
@@ -67,6 +69,30 @@ limitations under the License.
sandbox.restore();
});
+ test('_computeGroupUrl', () => {
+ let urlStub = sandbox.stub(Gerrit.Nav, 'getUrlForGroup',
+ () => '/admin/groups/e2cd66f88a2db4d391ac068a92d987effbe872f5');
+
+ let group = {
+ id: 'e2cd66f88a2db4d391ac068a92d987effbe872f5',
+ };
+ assert.equal(element._computeGroupUrl(group),
+ '/admin/groups/e2cd66f88a2db4d391ac068a92d987effbe872f5');
+
+ urlStub.restore();
+
+ urlStub = sandbox.stub(Gerrit.Nav, 'getUrlForGroup',
+ () => '/admin/groups/user/test');
+
+ group = {
+ id: 'user%2Ftest',
+ };
+ assert.equal(element._computeGroupUrl(group),
+ '/admin/groups/user/test');
+
+ urlStub.restore();
+ });
+
suite('list with groups', () => {
setup(done => {
groups = _.times(26, groupGenerator);
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
index b6a6d271a4..c7187a943d 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html">
@@ -56,7 +56,7 @@ limitations under the License.
padding: 5px 4px;
}
iron-icon {
- margin: 0 .2em;
+ margin: 0 var(--spacing-xs);
}
.breadcrumb {
align-items: center;
@@ -74,7 +74,7 @@ limitations under the License.
display: inline-block;
}
main.breadcrumbs:not(.table) {
- margin-top: 1em;
+ margin-top: var(--spacing-l);
}
</style>
<gr-page-nav class="navStyles">
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
index 4d964d7bb5..72cca9e510 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-admin-view',
- _legacyUndefinedCheck: true,
properties: {
/** @type {?} */
@@ -198,10 +197,6 @@
this.reload();
},
- _computeSelectedTitle(params) {
- return this.getSelectedTitle(params.view);
- },
-
// TODO (beckysiegel): Update these functions after router abstraction is
// updated. They are currently copied from gr-dropdown (and should be
// updated there as well once complete).
@@ -228,6 +223,7 @@
* @param {string=} opt_detailType
*/
_computeSelectedClass(itemView, params, opt_detailType) {
+ if (!params) return '';
// Group params are structured differently from admin params. Compute
// selected differently for groups.
// TODO(wyatta): Simplify this when all routes work like group params.
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index ff25377277..984be198ef 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-admin-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-admin-view.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html
index 38730830cb..9d8ee18d8d 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
index 9c0e405292..acc76de6c9 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
@@ -25,7 +25,6 @@
Polymer({
is: 'gr-confirm-delete-item-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -44,6 +43,10 @@
itemType: String,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
_handleConfirmTap(e) {
e.preventDefault();
e.stopPropagation();
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
index d735acbc12..3292cec8f0 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-delete-item-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-delete-item-dialog.html">
@@ -49,19 +51,23 @@ limitations under the License.
test('_handleConfirmTap', () => {
const confirmHandler = sandbox.stub();
element.addEventListener('confirm', confirmHandler);
- sandbox.stub(element, '_handleConfirmTap');
+ sandbox.spy(element, '_handleConfirmTap');
element.$$('gr-dialog').fire('confirm');
assert.isTrue(confirmHandler.called);
+ assert.isTrue(confirmHandler.calledOnce);
assert.isTrue(element._handleConfirmTap.called);
+ assert.isTrue(element._handleConfirmTap.calledOnce);
});
test('_handleCancelTap', () => {
const cancelHandler = sandbox.stub();
element.addEventListener('cancel', cancelHandler);
- sandbox.stub(element, '_handleCancelTap');
+ sandbox.spy(element, '_handleCancelTap');
element.$$('gr-dialog').fire('cancel');
assert.isTrue(cancelHandler.called);
+ assert.isTrue(cancelHandler.calledOnce);
assert.isTrue(element._handleCancelTap.called);
+ assert.isTrue(element._handleCancelTap.calledOnce);
});
test('_computeItemName function for branches', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
index da1c871a9f..2a95991c5a 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
@@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -42,7 +43,7 @@ limitations under the License.
}
gr-autocomplete {
--gr-autocomplete: {
- padding: 0 .15em;
+ padding: 0 var(--spacing-xs);
}
}
.hide {
@@ -69,23 +70,33 @@ limitations under the License.
<section class$="[[_computeBranchClass(baseChange)]]">
<span class="title">Provide base commit sha1 for change</span>
<span class="value">
- <input
- is="iron-input"
- id="baseCommitInput"
+ <iron-input
maxlength="40"
placeholder="(optional)"
bind-value="{{baseCommit}}">
+ <input
+ is="iron-input"
+ id="baseCommitInput"
+ maxlength="40"
+ placeholder="(optional)"
+ bind-value="{{baseCommit}}">
+ </iron-input>
</span>
</section>
<section>
<span class="title">Enter topic for new change</span>
<span class="value">
- <input
- is="iron-input"
- id="tagNameInput"
+ <iron-input
maxlength="1024"
placeholder="(optional)"
bind-value="{{topic}}">
+ <input
+ is="iron-input"
+ id="tagNameInput"
+ maxlength="1024"
+ placeholder="(optional)"
+ bind-value="{{topic}}">
+ </iron-input>
</span>
</section>
<section id="description">
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
index 5a4c0646ad..e29e5f847e 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-create-change-dialog',
- _legacyUndefinedCheck: true,
properties: {
repoName: String,
@@ -50,6 +49,11 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ /**
+ * Unused in this element, but called by other elements in tests
+ * e.g gr-repo-commands_test.
+ */
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
index aa4da68937..3a3683f5ce 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-change-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-create-change-dialog.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.html
index d96a935e35..8a4287b68b 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -39,10 +39,12 @@ limitations under the License.
<div id="form">
<section>
<span class="title">Group name</span>
- <input
- is="iron-input"
- id="groupNameInput"
+ <iron-input
bind-value="{{_name}}">
+ <input
+ is="iron-input"
+ bind-value="{{_name}}">
+ </iron-input>
</section>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
index ec667ee8a9..01aeb4382f 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-create-group-dialog',
- _legacyUndefinedCheck: true,
properties: {
params: Object,
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
index 95ffdb19f1..ebca2893dd 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-group-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-create-group-dialog.html">
@@ -51,13 +53,16 @@ limitations under the License.
sandbox.restore();
});
- test('name is updated correctly', () => {
+ test('name is updated correctly', done => {
assert.isFalse(element.hasNewGroupName);
- element.$.groupNameInput.bindValue = GROUP_NAME;
+ ironInput(element.root).bindValue = GROUP_NAME;
- assert.isTrue(element.hasNewGroupName);
- assert.deepEqual(element._name, GROUP_NAME);
+ setTimeout(() => {
+ assert.isTrue(element.hasNewGroupName);
+ assert.deepEqual(element._name, GROUP_NAME);
+ done();
+ });
});
test('test for redirecting to group on successful creation', done => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.html
index aa9639b98d..33153eb4c0 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -45,29 +45,39 @@ limitations under the License.
</style>
<div class="gr-form-styles">
<div id="form">
- <section>
+ <section id="itemNameSection">
<span class="title">[[detailType]] name</span>
- <input
- is="iron-input"
- id="itemNameInput"
+ <iron-input
placeholder="[[detailType]] Name"
bind-value="{{_itemName}}">
+ <input
+ is="iron-input"
+ placeholder="[[detailType]] Name"
+ bind-value="{{_itemName}}">
+ </iron-input>
</section>
- <section>
+ <section id="itemRevisionSection">
<span class="title">Initial Revision</span>
- <input
- is="iron-input"
- id="itemRevisionInput"
+ <iron-input
placeholder="Revision (Branch or SHA-1)"
bind-value="{{_itemRevision}}">
+ <input
+ is="iron-input"
+ placeholder="Revision (Branch or SHA-1)"
+ bind-value="{{_itemRevision}}">
+ </iron-input>
</section>
- <section class$="[[_computeHideItemClass(itemDetail)]]">
+ <section id="itemAnnotationSection"
+ class$="[[_computeHideItemClass(itemDetail)]]">
<span class="title">Annotation</span>
- <input
- is="iron-input"
- id="itemAnnotationInput"
+ <iron-input
placeholder="Annotation (Optional)"
bind-value="{{_itemAnnotation}}">
+ <input
+ is="iron-input"
+ placeholder="Annotation (Optional)"
+ bind-value="{{_itemAnnotation}}">
+ </iron-input>
</section>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
index 65bb46d7d7..4e9da900ba 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
@@ -24,7 +24,6 @@
Polymer({
is: 'gr-create-pointer-dialog',
- _legacyUndefinedCheck: true,
properties: {
detailType: String,
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
index 39e200a72c..08e82138b3 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-pointer-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-create-pointer-dialog.html">
@@ -49,7 +51,7 @@ limitations under the License.
sandbox.restore();
});
- test('branch created', () => {
+ test('branch created', done => {
sandbox.stub(element.$.restAPI, 'createRepoBranch', () => {
return Promise.resolve({});
});
@@ -59,17 +61,18 @@ limitations under the License.
element._itemName = 'test-branch';
element.itemDetail = 'branches';
- element.$.itemNameInput.bindValue = 'test-branch2';
- element.$.itemRevisionInput.bindValue = 'HEAD';
+ ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
+ ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
- assert.isTrue(element.hasNewItemName);
-
- assert.equal(element._itemName, 'test-branch2');
-
- assert.equal(element._itemRevision, 'HEAD');
+ setTimeout(() => {
+ assert.isTrue(element.hasNewItemName);
+ assert.equal(element._itemName, 'test-branch2');
+ assert.equal(element._itemRevision, 'HEAD');
+ done();
+ });
});
- test('tag created', () => {
+ test('tag created', done => {
sandbox.stub(element.$.restAPI, 'createRepoTag', () => {
return Promise.resolve({});
});
@@ -79,17 +82,18 @@ limitations under the License.
element._itemName = 'test-tag';
element.itemDetail = 'tags';
- element.$.itemNameInput.bindValue = 'test-tag2';
- element.$.itemRevisionInput.bindValue = 'HEAD';
-
- assert.isTrue(element.hasNewItemName);
-
- assert.equal(element._itemName, 'test-tag2');
+ ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
+ ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
- assert.equal(element._itemRevision, 'HEAD');
+ setTimeout(() => {
+ assert.isTrue(element.hasNewItemName);
+ assert.equal(element._itemName, 'test-tag2');
+ assert.equal(element._itemRevision, 'HEAD');
+ done();
+ });
});
- test('tag created with annotations', () => {
+ test('tag created with annotations', done => {
sandbox.stub(element.$.restAPI, 'createRepoTag', () => {
return Promise.resolve({});
});
@@ -100,17 +104,17 @@ limitations under the License.
element._itemAnnotation = 'test-message';
element.itemDetail = 'tags';
- element.$.itemNameInput.bindValue = 'test-tag2';
- element.$.itemAnnotationInput.bindValue = 'test-message2';
- element.$.itemRevisionInput.bindValue = 'HEAD';
-
- assert.isTrue(element.hasNewItemName);
-
- assert.equal(element._itemName, 'test-tag2');
+ ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
+ ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
+ ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
- assert.equal(element._itemAnnotation, 'test-message2');
-
- assert.equal(element._itemRevision, 'HEAD');
+ setTimeout(() => {
+ assert.isTrue(element.hasNewItemName);
+ assert.equal(element._itemName, 'test-tag2');
+ assert.equal(element._itemAnnotation, 'test-message2');
+ assert.equal(element._itemRevision, 'HEAD');
+ done();
+ });
});
test('_computeHideItemClass returns hideItem if type is branches', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
index b38fab59f4..d1a2471e4a 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
@@ -41,10 +41,9 @@ limitations under the License.
border: none;
--gr-autocomplete: {
border: 1px solid var(--border-color);
- border-radius: 2px;
- font-size: var(--font-size-normal);
+ border-radius: var(--border-radius);
height: 2em;
- padding: 0 .15em;
+ padding: 0 var(--spacing-xs);
width: 20em;
}
}
@@ -54,10 +53,13 @@ limitations under the License.
<div id="form">
<section>
<span class="title">Repository name</span>
- <input is="iron-input"
- id="repoNameInput"
- autocomplete="on"
- bind-value="{{_repoConfig.name}}">
+ <iron-input autocomplete="on"
+ bind-value="{{_repoConfig.name}}">
+ <input is="iron-input"
+ id="repoNameInput"
+ autocomplete="on"
+ bind-value="{{_repoConfig.name}}">
+ </iron-input>
</section>
<section>
<span class="title">Rights inherit from</span>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
index ef7edd4d94..bb2b5f22a6 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-create-repo-dialog',
- _legacyUndefinedCheck: true,
properties: {
params: Object,
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
index 79079f5c0b..7e32c5ce42 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-repo-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-create-repo-dialog.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
index b10c98a309..c15f0916a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
@@ -16,12 +16,14 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
<dom-module id="gr-group-audit-log">
<template>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
index 966f3c9eca..8901d4ac36 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-group-audit-log',
- _legacyUndefinedCheck: true,
properties: {
groupId: String,
@@ -33,6 +32,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.ListViewBehavior,
],
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index 59a665b9b7..313d465a1a 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-group-audit-log</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-group-audit-log.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
index bcfc9e1649..86f66c43c6 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
@@ -16,10 +16,10 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-subpage-styles.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
@@ -43,7 +43,6 @@ limitations under the License.
gr-autocomplete {
width: 20em;
--gr-autocomplete: {
- font-size: var(--font-size-normal);
height: 2em;
width: 20em;
}
@@ -91,7 +90,7 @@ limitations under the License.
</span>
<gr-button
id="saveGroupMember"
- on-tap="_handleSavingGroupMember"
+ on-click="_handleSavingGroupMember"
disabled="[[!_groupMemberSearchId]]">
Add
</gr-button>
@@ -111,7 +110,7 @@ limitations under the License.
<td class="deleteColumn">
<gr-button
class="deleteMembersButton"
- on-tap="_handleDeleteMember">
+ on-click="_handleDeleteMember">
Delete
</gr-button>
</td>
@@ -133,7 +132,7 @@ limitations under the License.
</span>
<gr-button
id="saveIncludedGroups"
- on-tap="_handleSavingIncludedGroups"
+ on-click="_handleSavingIncludedGroups"
disabled="[[!_includedGroupSearchId]]">
Add
</gr-button>
@@ -163,7 +162,7 @@ limitations under the License.
<td class="deleteColumn">
<gr-button
class="deleteIncludedGroupButton"
- on-tap="_handleDeleteIncludedGroup">
+ on-click="_handleDeleteIncludedGroup">
Delete
</gr-button>
</td>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
index b2784e5a5a..7f8e9ac68c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
@@ -25,7 +25,6 @@
Polymer({
is: 'gr-group-members',
- _legacyUndefinedCheck: true,
properties: {
groupId: Number,
@@ -66,6 +65,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -205,6 +205,7 @@
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message: SAVING_ERROR_TEXT},
bubbles: true,
+ composed: true,
}));
return err;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index 45d5497c3d..bf9113b1d9 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-group-members</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-group-members.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
index b92dc4b46d..7617c19bbe 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-subpage-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -35,7 +35,7 @@ limitations under the License.
content: ' *';
}
.inputUpdateBtn {
- margin-top: .3em;
+ margin-top: var(--spacing-s);
}
</style>
<style include="gr-form-styles"></style>
@@ -51,7 +51,8 @@ limitations under the License.
<h3 id="groupUUID">Group UUID</h3>
<fieldset>
<gr-copy-clipboard
- text="[[_groupConfig.id]]"></gr-copy-clipboard>
+ id="uuid"
+ text="[[_getGroupUUID(_groupConfig.id)]]"></gr-copy-clipboard>
</fieldset>
<h3 id="groupName" class$="[[_computeHeaderClass(_rename)]]">
Group Name
@@ -66,7 +67,7 @@ limitations under the License.
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
<gr-button
id="inputUpdateNameBtn"
- on-tap="_handleSaveName"
+ on-click="_handleSaveName"
disabled="[[!_rename]]">
Rename Group</gr-button>
</span>
@@ -86,7 +87,7 @@ limitations under the License.
</span>
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
<gr-button
- on-tap="_handleSaveOwner"
+ on-click="_handleSaveOwner"
disabled="[[!_owner]]">
Change Owners</gr-button>
</span>
@@ -104,7 +105,7 @@ limitations under the License.
</div>
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
<gr-button
- on-tap="_handleSaveDescription"
+ on-click="_handleSaveDescription"
disabled="[[!_description]]">
Save Description
</gr-button>
@@ -132,7 +133,7 @@ limitations under the License.
</section>
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
<gr-button
- on-tap="_handleSaveOptions"
+ on-click="_handleSaveOptions"
disabled="[[!_options]]">
Save Group Options
</gr-button>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
index 2d7f9cbf18..09953fb74e 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
@@ -32,7 +32,6 @@
Polymer({
is: 'gr-group',
- _legacyUndefinedCheck: true,
/**
* Fired when the group name changes.
@@ -89,6 +88,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_handleConfigName(_groupConfig.name)',
'_handleConfigOwner(_groupConfig.owner, _groupConfigOwner)',
@@ -233,5 +236,11 @@
_computeGroupDisabled(owner, admin, groupIsInternal) {
return groupIsInternal && (admin || owner) ? false : true;
},
+
+ _getGroupUUID(id) {
+ if (!id) return;
+
+ return id.match(INTERNAL_GROUP_REGEX) ? id : decodeURIComponent(id);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
index 226f3ab83b..fc04c02bb9 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-group</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-group.html">
@@ -240,5 +242,19 @@ limitations under the License.
element._loadGroup();
});
+
+ test('uuid', () => {
+ element._groupConfig = {
+ id: '6a1e70e1a88782771a91808c8af9bbb7a9871389',
+ };
+
+ assert.equal(element._groupConfig.id, element.$.uuid.text);
+
+ element._groupConfig = {
+ id: 'user%2Fgroup',
+ };
+
+ assert.equal('user/group', element.$.uuid.text);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
index a5bb5fd8e8..931e2cda5b 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
@@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -31,13 +32,13 @@ limitations under the License.
<style include="shared-styles">
:host {
display: block;
- margin-bottom: .7em;
+ margin-bottom: var(--spacing-m);
}
.header {
align-items: baseline;
display: flex;
justify-content: space-between;
- margin: .3em .7em;
+ margin: var(--spacing-s) var(--spacing-m);
}
.rules {
background: var(--table-header-background-color);
@@ -48,7 +49,7 @@ limitations under the License.
border-bottom: 1px solid var(--border-color);
}
.title {
- margin-bottom: .3em;
+ margin-bottom: var(--spacing-s);
}
#addRule,
#removeBtn {
@@ -60,11 +61,11 @@ limitations under the License.
}
.editing #removeBtn {
display: block;
- margin-left: 1.5em;
+ margin-left: var(--spacing-xl);
}
.editing #addRule {
display: block;
- padding: .7em;
+ padding: var(--spacing-m);
}
#deletedContainer,
.deleted #mainContainer {
@@ -75,7 +76,7 @@ limitations under the License.
border: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
- padding: .7em;
+ padding: var(--spacing-m);
}
#mainContainer {
display: block;
@@ -100,7 +101,7 @@ limitations under the License.
<gr-button
link
id="removeBtn"
- on-tap="_handleRemovePermission">Remove</gr-button>
+ on-click="_handleRemovePermission">Remove</gr-button>
</div>
</div><!-- end header -->
<div class="rules">
@@ -136,7 +137,7 @@ limitations under the License.
<gr-button
link
id="undoRemoveBtn"
- on-tap="_handleUndoRemove">Undo</gr-button>
+ on-click="_handleUndoRemove">Undo</gr-button>
</div><!-- end deletedContainer -->
</section>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index 58a006da20..75e715b985 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -38,7 +38,6 @@
Polymer({
is: 'gr-permission',
- _legacyUndefinedCheck: true,
properties: {
labels: Object,
@@ -78,6 +77,11 @@
behaviors: [
Gerrit.AccessBehavior,
+ /**
+ * Unused in this element, but called by other elements in tests
+ * e.g gr-access-section_test.
+ */
+ Gerrit.FireBehavior,
],
observers: [
@@ -138,17 +142,19 @@
_handleValueChange() {
this.permission.value.modified = true;
// Allows overall access page to know a change has been made.
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_handleRemovePermission() {
if (this.permission.value.added) {
- this.dispatchEvent(new CustomEvent('added-permission-removed',
- {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'added-permission-removed', {bubbles: true, composed: true}));
}
this._deleted = true;
this.permission.value.deleted = true;
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_handleRulesChanged(changeRecord) {
@@ -177,7 +183,8 @@
},
_computeLabel(permission, labels) {
- if (!permission.value.label) { return; }
+ if (!labels || !permission ||
+ !permission.value || !permission.value.label) { return; }
const labelName = permission.value.label;
@@ -281,7 +288,8 @@
value.added = true;
// See comment above for why we cannot use "this.set(...)" here.
this.permission.value.rules[groupId] = value;
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_computeHasRange(name) {
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index a432ab0b20..0c70f9cfe2 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-permission</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-permission.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html
index ca98c500de..276152688a 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -33,22 +33,22 @@ limitations under the License.
.existingItems {
background: var(--table-header-background-color);
border: 1px solid var(--border-color);
- border-radius: 2px;
+ border-radius: var(--border-radius);
}
gr-button {
float: right;
- margin-left: .5em;
+ margin-left: var(--spacing-m);
width: 4.5em;
}
.row {
align-items: center;
display: flex;
justify-content: space-between;
- padding: .5em 0;
+ padding: var(--spacing-m) 0;
width: 100%;
}
.existingItems .row {
- padding: .5em;
+ padding: var(--spacing-m);
}
.existingItems .row:not(:first-of-type) {
border-top: 1px solid var(--border-color);
@@ -61,7 +61,7 @@ limitations under the License.
}
.placeholder {
color: var(--deemphasized-text-color);
- padding-top: .75em;
+ padding-top: var(--spacing-m);
}
</style>
<div class="wrapper gr-form-styles">
@@ -73,8 +73,8 @@ limitations under the License.
<gr-button
link
disabled$="[[disabled]]"
- data-item="[[item]]"
- on-tap="_handleDelete">Delete</gr-button>
+ data-item$="[[item]]"
+ on-click="_handleDelete">Delete</gr-button>
</div>
</template>
</div>
@@ -83,16 +83,20 @@ limitations under the License.
<div class="row placeholder">None configured.</div>
</template>
<div class$="row [[_computeShowInputRow(disabled)]]">
- <input
- is="iron-input"
- id="input"
+ <iron-input
on-keydown="_handleInputKeydown"
- bind-value="{{_newValue}}"/>
+ bind-value="{{_newValue}}">
+ <input
+ is="iron-input"
+ id="input"
+ on-keydown="_handleInputKeydown"
+ bind-value="{{_newValue}}">
+ </iron-input>
<gr-button
id="addButton"
disabled$="[[!_newValue.length]]"
link
- on-tap="_handleAddTap">Add</gr-button>
+ on-click="_handleAddTap">Add</gr-button>
</div>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
index ab4d286cd7..bb0d501442 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
@@ -67,7 +67,7 @@
},
_handleDelete(e) {
- const value = Polymer.dom(e).localTarget.dataItem;
+ const value = Polymer.dom(e).localTarget.dataset.item;
this._dispatchChanged(
this.pluginOption.info.values.filter(str => str !== value));
},
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
index dc3f67ecbf..39e4ddcd39 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-config-array-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-plugin-config-array-editor.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
index 9be526efb2..ee5dd835ba 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
@@ -14,8 +14,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
index d6484d8d0f..1dbfdc807e 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-plugin-list',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -65,6 +64,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.ListViewBehavior,
],
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
index e42e37476b..24856873bc 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-plugin-list.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index b6e56de393..ea12908cb2 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
@@ -48,20 +49,20 @@ limitations under the License.
align-items: center;
}
.weblink {
- margin-right: .2em;
+ margin-right: var(--spacing-xs);
}
.weblinks.show,
.referenceContainer {
display: block;
}
.rightsText {
- margin-right: .3rem;
+ margin-right: var(--spacing-s);
}
.editing gr-button,
.admin #editBtn {
display: inline-block;
- margin: 1em 0;
+ margin: var(--spacing-l) 0;
}
.editing #editInheritFromInput {
display: inline-block;
@@ -95,16 +96,16 @@ limitations under the License.
</template>
</div>
<gr-button id="editBtn"
- on-tap="_handleEdit">[[_editOrCancel(_editing)]]</gr-button>
+ on-click="_handleEdit">[[_editOrCancel(_editing)]]</gr-button>
<gr-button id="saveBtn"
primary
class$="[[_computeSaveBtnClass(_ownerOf)]]"
- on-tap="_handleSave"
+ on-click="_handleSave"
disabled$="[[!_modified]]">Save</gr-button>
<gr-button id="saveReviewBtn"
primary
class$="[[_computeSaveReviewBtnClass(_canUpload)]]"
- on-tap="_handleSaveForReview"
+ on-click="_handleSaveForReview"
disabled$="[[!_modified]]">Save for review</gr-button>
<template
is="dom-repeat"
@@ -124,7 +125,7 @@ limitations under the License.
</template>
<div class="referenceContainer">
<gr-button id="addReferenceBtn"
- on-tap="_handleCreateSection">Add Reference</gr-button>
+ on-click="_handleCreateSection">Add Reference</gr-button>
</div>
</div>
</main>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index 1b8b322c14..1e6a43129b 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -70,7 +70,6 @@
Polymer({
is: 'gr-repo-access',
- _legacyUndefinedCheck: true,
properties: {
repo: {
@@ -118,6 +117,7 @@
behaviors: [
Gerrit.AccessBehavior,
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -237,7 +237,7 @@
},
_computeWebLinkClass(weblinks) {
- return weblinks.length ? 'show' : '';
+ return weblinks && weblinks.length ? 'show' : '';
},
_computeShowInherit(inheritsFrom) {
@@ -413,8 +413,11 @@
if (!Object.keys(addRemoveObj.add).length &&
!Object.keys(addRemoveObj.remove).length &&
!addRemoveObj.parent) {
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message: NOTHING_TO_SAVE}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {message: NOTHING_TO_SAVE},
+ bubbles: true,
+ composed: true,
+ }));
return;
}
const obj = {
@@ -450,12 +453,12 @@
},
_computeSaveBtnClass(ownerOf) {
- return ownerOf.length < 0 ? 'invisible' : '';
+ return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
},
_computeMainClass(ownerOf, canUpload, editing) {
const classList = [];
- if (ownerOf.length > 0 || canUpload) {
+ if (ownerOf && ownerOf.length > 0 || canUpload) {
classList.push('admin');
}
if (editing) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index a33b4bee2e..1660088447 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-access</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-access.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
index 7db4e4c16f..29bc02d00c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -23,14 +23,15 @@ limitations under the License.
<style include="shared-styles">
:host {
display: block;
- margin-bottom: 2em;
+ margin-bottom: var(--spacing-xxl);
}
</style>
<h3>[[title]]</h3>
<gr-button
title$="[[tooltip]]"
disabled$="[[disabled]]"
- on-tap="_onCommandTap">
+ on-click
+ ="_onCommandTap">
[[title]]
</gr-button>
</template>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
index e0becaf68c..026c9908ef 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-repo-command',
- _legacyUndefinedCheck: true,
properties: {
title: String,
@@ -34,7 +33,8 @@
*/
_onCommandTap() {
- this.dispatchEvent(new CustomEvent('command-tap', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('command-tap', {bubbles: true, composed: true}));
},
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
index 9f9ac922ea..49d8765159 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-command</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-command.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
index dba01aacfc..5089f346b3 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.html
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-subpage-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
index 09c902621d..3b4811e0ee 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
@@ -28,7 +28,6 @@
Polymer({
is: 'gr-repo-commands',
- _legacyUndefinedCheck: true,
properties: {
params: Object,
@@ -42,6 +41,10 @@
_canCreate: Boolean,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
attached() {
this._loadRepo();
@@ -75,8 +78,9 @@
_handleRunningGC() {
return this.$.restAPI.runRepoGC(this.repo).then(response => {
if (response.status === 200) {
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message: GC_MESSAGE}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'show-alert',
+ {detail: {message: GC_MESSAGE}, bubbles: true, composed: true}));
}
});
},
@@ -101,7 +105,7 @@
CREATE_CHANGE_SUCCEEDED_MESSAGE :
CREATE_CHANGE_FAILED_MESSAGE;
this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message}, bubbles: true}));
+ {detail: {message}, bubbles: true, composed: true}));
if (!change) { return; }
Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
index 76c65e8b32..2976923805 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-commands</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-commands.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
index 1d49db9a86..8af3a92b40 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.html
@@ -14,7 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -24,7 +25,7 @@ limitations under the License.
<style include="shared-styles">
:host {
display: block;
- margin-bottom: 2em;
+ margin-bottom: var(--spacing-xxl);
}
.loading #dashboards,
#loadingContainer {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
index f72e986384..71cc5713fd 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-repo-dashboards',
- _legacyUndefinedCheck: true,
properties: {
repo: {
@@ -33,6 +32,10 @@
_dashboards: Array,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
_repoChanged(repo) {
this._loading = true;
if (!repo) { return Promise.resolve(); }
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
index 94bf5e09d5..4f76983480 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-dashboards</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-dashboards.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
index fccfa6a0c8..2f244f83f4 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
@@ -17,8 +17,9 @@ limitations under the License.
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -64,15 +65,14 @@ limitations under the License.
display: none;
}
.revisionEdit gr-button {
- margin-left: .6em;
+ margin-left: var(--spacing-m);
}
.editBtn {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
.canEdit .revisionEdit{
align-items: center;
display: flex;
- line-height: 1;
}
.deleteButton:not(.show) {
display: none;
@@ -120,23 +120,26 @@ limitations under the License.
</span>
<gr-button
link
- on-tap="_handleEditRevision"
+ on-click="_handleEditRevision"
class="editBtn">
edit
</gr-button>
- <input
- is=iron-input
+ <iron-input
bind-value="{{_revisedRef}}"
class="editItem">
+ <input
+ is="iron-input"
+ bind-value="{{_revisedRef}}">
+ </iron-input>
<gr-button
link
- on-tap="_handleCancelRevision"
+ on-click="_handleCancelRevision"
class="cancelBtn editItem">
Cancel
</gr-button>
<gr-button
link
- on-tap="_handleSaveRevision"
+ on-click="_handleSaveRevision"
class="saveBtn editItem"
disabled="[[!_revisedRef]]">
Save
@@ -172,7 +175,7 @@ limitations under the License.
<gr-button
link
class$="deleteButton [[_computeHideDeleteClass(_isOwner, item.can_delete)]]"
- on-tap="_handleDeleteItem">
+ on-click="_handleDeleteItem">
Delete
</gr-button>
</td>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
index e4000e064c..905232223e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
@@ -26,7 +26,6 @@
Polymer({
is: 'gr-repo-detail-list',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -85,6 +84,7 @@
behaviors: [
Gerrit.ListViewBehavior,
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index 427a78ab5a..44d9b271f8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-detail-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-detail-list.html">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
index 0490db2486..5e82c1e0cf 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
@@ -14,10 +14,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
@@ -30,6 +29,20 @@ limitations under the License.
<template>
<style include="shared-styles"></style>
<style include="gr-table-styles"></style>
+ <style>
+ .genericList tr td:last-of-type {
+ text-align: left;
+ }
+ .genericList tr th:last-of-type {
+ text-align: left;
+ }
+ .readOnly {
+ text-align: center;
+ }
+ .changesLink, .name, .repositoryBrowser, .readOnly {
+ white-space:nowrap;
+ }
+ </style>
<gr-list-view
create-new=[[_createNewCapability]]
filter="[[_filter]]"
@@ -42,10 +55,10 @@ limitations under the License.
<table id="list" class="genericList">
<tr class="headerRow">
<th class="name topHeader">Repository Name</th>
- <th class="description topHeader">Repository Description</th>
- <th class="changesLink topHeader">Changes</th>
<th class="repositoryBrowser topHeader">Repository Browser</th>
- <th class="readOnly topHeader">Read only</th>
+ <th class="changesLink topHeader">Changes</th>
+ <th class="topHeader readOnly">Read only</th>
+ <th class="description topHeader">Repository Description</th>
</tr>
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
<td>Loading...</td>
@@ -56,8 +69,6 @@ limitations under the License.
<td class="name">
<a href$="[[_computeRepoUrl(item.name)]]">[[item.name]]</a>
</td>
- <td class="description">[[item.description]]</td>
- <td class="changesLink"><a href$="[[_computeChangesLink(item.name)]]">(view all)</a></td>
<td class="repositoryBrowser">
<template is="dom-repeat"
items="[[_computeWeblink(item)]]" as="link">
@@ -65,11 +76,13 @@ limitations under the License.
class="webLink"
rel="noopener"
target="_blank">
- ([[link.name]])
+ [[link.name]]
</a>
</template>
</td>
+ <td class="changesLink"><a href$="[[_computeChangesLink(item.name)]]">view all</a></td>
<td class="readOnly">[[_readOnly(item)]]</td>
+ <td class="description">[[item.description]]</td>
</tr>
</template>
</tbody>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
index d4eb29bf15..6f347f0af1 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-repo-list',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -126,7 +125,9 @@
.then(repos => {
// Late response.
if (filter !== this._filter || !repos) { return; }
- this._repos = repos;
+ this._repos = repos.filter(repo =>
+ repo.name.toLowerCase().includes(filter.toLowerCase())
+ );
this._loading = false;
});
},
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
index 4bc023ffda..1300b2090c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-list.html">
@@ -34,19 +36,22 @@ limitations under the License.
</test-fixture>
<script>
- let counter;
- const repoGenerator = () => {
+ function createRepo(name, counter) {
return {
- id: `test${++counter}`,
+ id: `${name}${counter}`,
+ name: `${name}`,
state: 'ACTIVE',
web_links: [
{
name: 'diffusion',
- url: `https://phabricator.example.org/r/project/test${counter}`,
+ url: `https://phabricator.example.org/r/project/${name}${counter}`,
},
],
};
- };
+ }
+
+ let counter;
+ const repoGenerator = () => createRepo('test', ++counter);
suite('gr-repo-list tests', () => {
let element;
@@ -151,6 +156,15 @@ limitations under the License.
done();
});
});
+
+ test('filter is case insensitive', async () => {
+ const repoStub = sandbox.stub(element.$.restAPI, 'getRepos');
+ const repos = [createRepo('aSDf', 0)];
+ repoStub.withArgs('asdf').returns(Promise.resolve(repos));
+ element._filter = 'asdf';
+ await element._getRepos('asdf', 25, 0);
+ assert.equal(element._repos.length, 1);
+ });
});
suite('loading', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.html
index 7f2cbe7a25..d2093e4378 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.html
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
@@ -35,14 +35,14 @@ limitations under the License.
<style include="gr-subpage-styles">
.inherited {
color: var(--deemphasized-text-color);
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
section.section:not(.ARRAY) .title {
align-items: center;
display: flex;
}
section.section.ARRAY .title {
- padding-top: .75em;
+ padding-top: var(--spacing-m);
}
</style>
<div class="gr-form-styles">
@@ -87,12 +87,18 @@ limitations under the License.
</gr-select>
</template>
<template is="dom-if" if="[[_isString(option.info.type)]]">
- <input
- is="iron-input"
- value="[[option.info.value]]"
+ <iron-input
+ bind-value="[[option.info.value]]"
on-input="_handleStringChange"
data-option-key$="[[option._key]]"
- disabled$="[[_computeDisabled(option.info.editable)]]"></input>
+ disabled$="[[_computeDisabled(option.info.editable)]]">
+ <input
+ is="iron-input"
+ value="[[option.info.value]]"
+ on-input="_handleStringChange"
+ data-option-key$="[[option._key]]"
+ disabled$="[[_computeDisabled(option.info.editable)]]">
+ </iron-input>
</template>
<template is="dom-if" if="[[option.info.inherited_value]]">
<span class="inherited">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
index 6d7677e4bf..bfa8832a99 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
@@ -71,7 +71,10 @@
return editable === 'false';
},
- _computeChecked(value) {
+ /**
+ * @param {string} value - fallback to 'false' if undefined
+ */
+ _computeChecked(value = 'false') {
return JSON.parse(value);
},
@@ -123,8 +126,8 @@
notifyPath: `${name}.${notifyPath}`,
};
- this.dispatchEvent(new CustomEvent(this.PLUGIN_CONFIG_CHANGED,
- {detail, bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ this.PLUGIN_CONFIG_CHANGED, {detail, bubbles: true, composed: true}));
},
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
index ba0c8769ba..0a6846fc08 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-plugin-config</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-plugin-config.html">
@@ -151,7 +153,8 @@ limitations under the License.
const select = element.$$('select');
assert.ok(select);
select.value = 'newTest';
- select.dispatchEvent(new Event('change', {bubbles: true}));
+ select.dispatchEvent(new Event(
+ 'change', {bubbles: true, composed: true}));
flushAsynchronousOperations();
assert.isTrue(buildStub.called);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
index 8604ad8743..5de77b9dda 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
@@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
<link rel="import" href="../../shared/gr-download-commands/gr-download-commands.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
@@ -270,12 +272,18 @@ limitations under the License.
<section>
<span class="title">Maximum Git object size limit</span>
<span class="value">
- <input
- id="maxGitObjSizeInput"
+ <iron-input
+ id="maxGitObjSizeIronInput"
bind-value="{{_repoConfig.max_object_size_limit.configured_value}}"
- is="iron-input"
type="text"
disabled$="[[_readOnly]]">
+ <input
+ id="maxGitObjSizeInput"
+ bind-value="{{_repoConfig.max_object_size_limit.configured_value}}"
+ is="iron-input"
+ type="text"
+ disabled$="[[_readOnly]]">
+ </iron-input>
<template is="dom-if" if="[[_repoConfig.max_object_size_limit.value]]">
effective: [[_repoConfig.max_object_size_limit.value]] bytes
</template>
@@ -356,7 +364,7 @@ limitations under the License.
</template>
</div>
<gr-button
- on-tap="_handleSaveRepoConfig"
+ on-click="_handleSaveRepoConfig"
disabled$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">Save changes</gr-button>
</fieldset>
<gr-endpoint-decorator name="repo-config">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
index ff630c49eb..153a13799c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
@@ -53,7 +53,6 @@
Polymer({
is: 'gr-repo',
- _legacyUndefinedCheck: true,
properties: {
params: Object,
@@ -109,6 +108,10 @@
_schemesObj: Object,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_handleConfigChanged(_repoConfig.*)',
],
@@ -309,6 +312,9 @@
},
_computeCommands(repo, schemesObj, _selectedScheme) {
+ if (!schemesObj || !repo || !_selectedScheme) {
+ return [];
+ }
const commands = [];
let commandObj;
if (schemesObj.hasOwnProperty(_selectedScheme)) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index 60645bb667..4e81565ec9 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo.html">
@@ -100,10 +102,12 @@ limitations under the License.
const SCHEMES = {http: {}, repo: {}, ssh: {}};
function getFormFields() {
- const selects = Polymer.dom(element.root).querySelectorAll('select');
- const textareas =
- Polymer.dom(element.root).querySelectorAll('iron-autogrow-textarea');
- const inputs = Polymer.dom(element.root).querySelectorAll('input');
+ const selects = Array.from(
+ Polymer.dom(element.root).querySelectorAll('select'));
+ const textareas = Array.from(
+ Polymer.dom(element.root).querySelectorAll('iron-autogrow-textarea'));
+ const inputs = Array.from(
+ Polymer.dom(element.root).querySelectorAll('input'));
return inputs.concat(textareas).concat(selects);
}
@@ -362,8 +366,9 @@ limitations under the License.
configInputObj.private_by_default;
element.$.matchAuthoredDateWithCommitterDateSelect.bindValue =
configInputObj.match_author_to_committer_date;
- element.$.maxGitObjSizeInput.bindValue =
- configInputObj.max_object_size_limit;
+ const inputElement = Polymer.Element ?
+ element.$.maxGitObjSizeIronInput : element.$.maxGitObjSizeInput;
+ inputElement.bindValue = configInputObj.max_object_size_limit;
element.$.contributorAgreementSelect.bindValue =
configInputObj.use_contributor_agreements;
element.$.useSignedOffBySelect.bindValue =
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
index c8ae650fec..9820e31235 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
@@ -15,22 +15,25 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-rule-editor">
<template>
<style include="shared-styles">
:host {
border-bottom: 1px solid var(--border-color);
- padding: .7em;
+ padding: var(--spacing-m);
display: block;
}
#removeBtn {
@@ -44,7 +47,7 @@ limitations under the License.
display: flex;
}
#options > * {
- margin-right: .5em;
+ margin-right: var(--spacing-m);
}
#mainContainer {
align-items: baseline;
@@ -146,14 +149,14 @@ limitations under the License.
<gr-button
link
id="removeBtn"
- on-tap="_handleRemoveRule">Remove</gr-button>
+ on-click="_handleRemoveRule">Remove</gr-button>
</div>
<div
id="deletedContainer"
class$="gr-form-styles [[_computeSectionClass(editing, _deleted)]]">
[[groupName]] was deleted
<gr-button link
- id="undoRemoveBtn" on-tap="_handleUndoRemove">Undo</gr-button>
+ id="undoRemoveBtn" on-click="_handleUndoRemove">Undo</gr-button>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
index 84c61016d6..8d94a90541 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -66,7 +66,6 @@
Polymer({
is: 'gr-rule-editor',
- _legacyUndefinedCheck: true,
properties: {
hasRange: Boolean,
@@ -97,6 +96,11 @@
behaviors: [
Gerrit.AccessBehavior,
Gerrit.BaseUrlBehavior,
+ /**
+ * Unused in this element, but called by other elements in tests
+ * e.g gr-permission_test.
+ */
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -115,11 +119,20 @@
this._setupValues(this.rule);
},
+ attached() {
+ if (!this.rule) { return; } // Check needed for test purposes.
+ if (!this._originalRuleValues) {
+ // Observer _handleValueChange is called after the ready()
+ // method finishes. Original values must be set later to
+ // avoid set .modified flag to true
+ this._setOriginalRuleValues(this.rule.value);
+ }
+ },
+
_setupValues(rule) {
if (!rule.value) {
this._setDefaultRuleValues();
}
- this._setOriginalRuleValues(rule.value);
},
_computeForce(permission, action) {
@@ -211,12 +224,13 @@
_handleRemoveRule() {
if (this.rule.value.added) {
- this.dispatchEvent(new CustomEvent('added-rule-removed',
- {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'added-rule-removed', {bubbles: true, composed: true}));
}
this._deleted = true;
this.rule.value.deleted = true;
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_handleUndoRemove() {
@@ -238,7 +252,8 @@
if (!this._originalRuleValues) { return; }
this.rule.value.modified = true;
// Allows overall access page to know a change has been made.
- this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('access-modified', {bubbles: true, composed: true}));
},
_setOriginalRuleValues(value) {
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 7f5bf6abab..6d533afcdd 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-rule-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-rule-editor.html">
@@ -200,7 +202,7 @@ limitations under the License.
});
suite('already existing generic rule', () => {
- setup(() => {
+ setup(done => {
element.group = 'Group Name';
element.permission = 'submit';
element.rule = {
@@ -216,6 +218,10 @@ limitations under the License.
// by the parent element.
element._setupValues(element.rule);
flushAsynchronousOperations();
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -311,7 +317,7 @@ limitations under the License.
});
suite('new edit rule', () => {
- setup(() => {
+ setup(done => {
element.group = 'Group Name';
element.permission = 'editTopicName';
element.rule = {
@@ -321,6 +327,10 @@ limitations under the License.
element._setupValues(element.rule);
flushAsynchronousOperations();
element.rule.value.added = true;
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -360,7 +370,7 @@ limitations under the License.
});
suite('already existing rule with labels', () => {
- setup(() => {
+ setup(done => {
element.label = {values: [
{value: -2, text: 'This shall not be merged'},
{value: -1, text: 'I would prefer this is not merged as is'},
@@ -382,6 +392,10 @@ limitations under the License.
element.section = 'refs/*';
element._setupValues(element.rule);
flushAsynchronousOperations();
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -414,7 +428,7 @@ limitations under the License.
});
suite('new rule with labels', () => {
- setup(() => {
+ setup(done => {
sandbox.spy(element, '_setDefaultRuleValues');
element.label = {values: [
{value: -2, text: 'This shall not be merged'},
@@ -432,6 +446,10 @@ limitations under the License.
element._setupValues(element.rule);
flushAsynchronousOperations();
element.rule.value.added = true;
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -472,7 +490,7 @@ limitations under the License.
});
suite('already existing push rule', () => {
- setup(() => {
+ setup(done => {
element.group = 'Group Name';
element.permission = 'push';
element.rule = {
@@ -485,6 +503,10 @@ limitations under the License.
element.section = 'refs/*';
element._setupValues(element.rule);
flushAsynchronousOperations();
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -513,7 +535,7 @@ limitations under the License.
});
suite('new push rule', () => {
- setup(() => {
+ setup(done => {
element.group = 'Group Name';
element.permission = 'push';
element.rule = {
@@ -523,6 +545,10 @@ limitations under the License.
element._setupValues(element.rule);
flushAsynchronousOperations();
element.rule.value.added = true;
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -553,7 +579,7 @@ limitations under the License.
});
suite('already existing edit rule', () => {
- setup(() => {
+ setup(done => {
element.group = 'Group Name';
element.permission = 'editTopicName';
element.rule = {
@@ -566,6 +592,10 @@ limitations under the License.
element.section = 'refs/*';
element._setupValues(element.rule);
flushAsynchronousOperations();
+ flush(() => {
+ element.attached();
+ done();
+ });
});
test('_ruleValues and _originalRuleValues are set correctly', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index a6dde7a3d4..a6c86bba27 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -19,7 +19,7 @@ limitations under the License.
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-change-list-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
@@ -29,6 +29,8 @@ limitations under the License.
<link rel="import" href="../../shared/gr-limited-text/gr-limited-text.html">
<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
<dom-module id="gr-change-list-item">
<template>
@@ -76,7 +78,7 @@ limitations under the License.
display: inline-flex;
}
.status .comma {
- padding-right: .2rem;
+ padding-right: var(--spacing-xs);
}
/* Used to hide the leading separator comma for statuses. */
.status .comma:first-of-type {
@@ -85,7 +87,7 @@ limitations under the License.
.size gr-tooltip-content {
margin: -.4rem -.6rem;
max-width: 2.5rem;
- padding: .4rem .6rem;
+ padding: var(--spacing-m) var(--spacing-l);
}
a {
color: inherit;
@@ -97,6 +99,8 @@ limitations under the License.
}
.u-monospace {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
}
.u-green {
color: var(--vote-text-color-recommended);
@@ -104,10 +108,6 @@ limitations under the License.
.u-red {
color: var(--vote-text-color-disliked);
}
- .label.u-green:not(.u-monospace),
- .label.u-red:not(.u-monospace) {
- font-size: 1.2rem;
- }
.u-gray-background {
background-color: var(--table-header-background-color);
}
@@ -159,16 +159,14 @@ limitations under the License.
<td class="cell owner"
hidden$="[[isColumnHidden('Owner', visibleChangeTableColumns)]]">
<gr-account-link
- account="[[change.owner]]"
- additional-text="[[_computeAccountStatusString(change.owner)]]"></gr-account-link>
+ account="[[change.owner]]"></gr-account-link>
</td>
<td class="cell assignee"
hidden$="[[isColumnHidden('Assignee', visibleChangeTableColumns)]]">
<template is="dom-if" if="[[change.assignee]]">
<gr-account-link
id="assigneeAccountLink"
- account="[[change.assignee]]"
- additional-text="[[_computeAccountStatusString(change.assignee)]]"></gr-account-link>
+ account="[[change.assignee]]"></gr-account-link>
</template>
<template is="dom-if" if="[[!change.assignee]]">
<span class="placeholder">--</span>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index ecc7532a89..8eb3989114 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -26,7 +26,6 @@
Polymer({
is: 'gr-change-list-item',
- _legacyUndefinedCheck: true,
properties: {
visibleChangeTableColumns: Array,
@@ -40,11 +39,6 @@
type: String,
computed: '_computeChangeURL(change)',
},
- needsReview: {
- type: Boolean,
- reflectToAttribute: true,
- computed: '_computeItemNeedsReview(change.reviewed)',
- },
statuses: {
type: Array,
computed: 'changeStatuses(change)',
@@ -78,10 +72,6 @@
});
},
- _computeItemNeedsReview(reviewed) {
- return !reviewed;
- },
-
_computeChangeURL(change) {
return Gerrit.Nav.getUrlForChange(change);
},
@@ -174,10 +164,6 @@
return str;
},
- _computeAccountStatusString(account) {
- return account && account.status ? `(${account.status})` : '';
- },
-
_computeSizeTooltip(change) {
if (change.insertions + change.deletions === 0 ||
isNaN(change.insertions + change.deletions)) {
@@ -214,6 +200,7 @@
this.set('change.reviewed', newVal);
this.dispatchEvent(new CustomEvent('toggle-reviewed', {
bubbles: true,
+ composed: true,
detail: {change: this.change, reviewed: newVal},
}));
},
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 35f81bcd47..afae619e62 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-list-item</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -187,14 +189,6 @@ limitations under the License.
};
flushAsynchronousOperations();
assert.isOk(element.$$('.assignee gr-account-link'));
- assert.equal(Polymer.dom(element.root)
- .querySelector('#assigneeAccountLink').additionalText, '(test)');
- });
-
- test('_computeAccountStatusString', () => {
- assert.equal(element._computeAccountStatusString({}), '');
- assert.equal(element._computeAccountStatusString({status: 'Working'}),
- '(Working)');
});
test('TShirt sizing tooltip', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
index 48d5075d2f..3ac1d5f92e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
@@ -17,7 +17,8 @@ limitations under the License.
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -35,7 +36,7 @@ limitations under the License.
}
.loading {
color: var(--deemphasized-text-color);
- padding: 1em var(--default-horizontal-margin);
+ padding: var(--spacing-l);
}
gr-change-list {
width: 100%;
@@ -67,7 +68,7 @@ limitations under the License.
@media only screen and (max-width: 50em) {
.loading,
.error {
- padding: 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-l);
}
}
</style>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index 6d051329db..cf6da73a07 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -18,7 +18,7 @@
'use strict';
const LookupQueryPatterns = {
- CHANGE_ID: /^\s*i?[0-9a-f]{8,40}\s*$/i,
+ CHANGE_ID: /^\s*i?[0-9a-f]{7,40}\s*$/i,
CHANGE_NUM: /^\s*[1-9][0-9]*\s*$/g,
};
@@ -31,7 +31,6 @@
Polymer({
is: 'gr-change-list-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -41,6 +40,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -162,8 +162,7 @@
for (const query in LookupQueryPatterns) {
if (LookupQueryPatterns.hasOwnProperty(query) &&
this._query.match(LookupQueryPatterns[query])) {
- this._replaceCurrentLocation(
- Gerrit.Nav.getUrlForChange(changes[0]));
+ Gerrit.Nav.navigateToChange(changes[0]);
return;
}
}
@@ -185,10 +184,6 @@
});
},
- _replaceCurrentLocation(url) {
- window.location.replace(url);
- },
-
_getChanges() {
return this.$.restAPI.getChanges(this._changesPerPage, this._query,
this._offset);
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
index 39113646cd..54885ccaf2 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-list-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-list-view.html">
@@ -194,7 +196,6 @@ limitations under the License.
suite('query based navigation', () => {
setup(() => {
- sandbox.stub(Gerrit.Nav, 'getUrlForChange', () => '/r/c/1');
});
teardown(done => {
@@ -205,10 +206,11 @@ limitations under the License.
});
test('Searching for a change ID redirects to change', done => {
+ const change = {_number: 1};
sandbox.stub(element, '_getChanges')
- .returns(Promise.resolve([{_number: 1}]));
- sandbox.stub(element, '_replaceCurrentLocation', url => {
- assert.equal(url, '/r/c/1');
+ .returns(Promise.resolve([change]));
+ sandbox.stub(Gerrit.Nav, 'navigateToChange', url => {
+ assert.equal(url, change);
done();
});
@@ -216,10 +218,11 @@ limitations under the License.
});
test('Searching for a change num redirects to change', done => {
+ const change = {_number: 1};
sandbox.stub(element, '_getChanges')
- .returns(Promise.resolve([{_number: 1}]));
- sandbox.stub(element, '_replaceCurrentLocation', url => {
- assert.equal(url, '/r/c/1');
+ .returns(Promise.resolve([change]));
+ sandbox.stub(Gerrit.Nav, 'navigateToChange', url => {
+ assert.equal(url, change);
done();
});
@@ -227,10 +230,11 @@ limitations under the License.
});
test('Commit hash redirects to change', done => {
+ const change = {_number: 1};
sandbox.stub(element, '_getChanges')
- .returns(Promise.resolve([{_number: 1}]));
- sandbox.stub(element, '_replaceCurrentLocation', url => {
- assert.equal(url, '/r/c/1');
+ .returns(Promise.resolve([change]));
+ sandbox.stub(Gerrit.Nav, 'navigateToChange', url => {
+ assert.equal(url, change);
done();
});
@@ -240,7 +244,7 @@ limitations under the License.
test('Searching for an invalid change ID searches', () => {
sandbox.stub(element, '_getChanges')
.returns(Promise.resolve([]));
- const stub = sandbox.stub(element, '_replaceCurrentLocation');
+ const stub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
element.params = {view: Gerrit.Nav.View.SEARCH, query: CHANGE_ID};
flushAsynchronousOperations();
@@ -251,7 +255,7 @@ limitations under the License.
test('Change ID with multiple search results searches', () => {
sandbox.stub(element, '_getChanges')
.returns(Promise.resolve([{}, {}]));
- const stub = sandbox.stub(element, '_replaceCurrentLocation');
+ const stub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
element.params = {view: Gerrit.Nav.View.SEARCH, query: CHANGE_ID};
flushAsynchronousOperations();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
index ef17baad32..699f07a32d 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
@@ -20,12 +20,14 @@ limitations under the License.
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-change-list-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../gr-change-list-item/gr-change-list-item.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<dom-module id="gr-change-list">
<template>
@@ -35,6 +37,18 @@ limitations under the License.
border-collapse: collapse;
width: 100%;
}
+ .section-count-label {
+ color: var(--deemphasized-text-color);
+ }
+ a.section-title:hover {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-count-label {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-name {
+ text-decoration: underline;
+ }
</style>
<table id="changeList">
<tr class="topHeader">
@@ -68,8 +82,9 @@ limitations under the License.
<td class="star" hidden$="[[!showStar]]" hidden></td>
<td class="cell"
colspan$="[[_computeColspan(changeTableColumns, labelNames)]]">
- <a href$="[[_sectionHref(changeSection.query)]]">
- [[changeSection.name]]
+ <a href$="[[_sectionHref(changeSection.query)]]" class="section-title">
+ <span class="section-name">[[changeSection.name]]</span>
+ <span class="section-count-label">[[changeSection.countLabel]]</span>
</a>
</td>
</tr>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index 587719df55..5006f1e5e8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -24,7 +24,6 @@
Polymer({
is: 'gr-change-list',
- _legacyUndefinedCheck: true,
/**
* Fired when next page key shortcut was pressed.
@@ -105,6 +104,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
Gerrit.ChangeTableBehavior,
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.RESTClientBehavior,
Gerrit.URLEncodingBehavior,
@@ -158,6 +158,11 @@
},
_computePreferences(account, preferences) {
+ // Polymer 2: check for undefined
+ if ([account, preferences].some(arg => arg === undefined)) {
+ return;
+ }
+
this.changeTableColumns = this.columnNames;
if (account) {
@@ -173,6 +178,7 @@
},
_computeColspan(changeTableColumns, labelNames) {
+ if (!changeTableColumns || !labelNames) return;
return changeTableColumns.length + labelNames.length +
NUMBER_FIXED_COLUMNS;
},
@@ -210,8 +216,19 @@
this.sections = changes ? [{results: changes}] : [];
},
+ _processQuery(query) {
+ let tokens = query.split(' ');
+ const invalidTokens = ['limit:', 'age:', '-age:'];
+ tokens = tokens.filter(token => {
+ return !invalidTokens.some(invalidToken => {
+ return token.startsWith(invalidToken);
+ });
+ });
+ return tokens.join(' ');
+ },
+
_sectionHref(query) {
- return Gerrit.Nav.getUrlForSearchQuery(query);
+ return Gerrit.Nav.getUrlForSearchQuery(this._processQuery(query));
},
/**
@@ -238,13 +255,13 @@
_computeItemNeedsReview(account, change, showReviewedState) {
return showReviewedState && !change.reviewed &&
!change.work_in_progress &&
- this.changeIsOpen(change.status) &&
+ this.changeIsOpen(change) &&
(!account || account._account_id != change.owner._account_id);
},
_computeItemHighlight(account, change) {
// Do not show the assignee highlight if the change is not open.
- if (!change.assignee ||
+ if (!change ||!change.assignee ||
!account ||
CLOSED_STATUS.indexOf(change.status) !== -1) {
return false;
@@ -352,16 +369,16 @@
},
_getListItems() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this.root).querySelectorAll('gr-change-list-item'));
},
_sectionsChanged() {
// Flush DOM operations so that the list item elements will be loaded.
- Polymer.dom.flush();
- this.$.cursor.stops = this._getListItems();
- this.$.cursor.moveToStart();
+ Polymer.RenderStatus.afterNextRender(this, () => {
+ this.$.cursor.stops = this._getListItems();
+ this.$.cursor.moveToStart();
+ });
},
_isOutgoing(section) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 99741a88db..817de6f128 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="gr-change-list.html">
@@ -170,11 +172,11 @@ limitations under the License.
{_number: 2},
];
flushAsynchronousOperations();
- const elementItems = Polymer.dom(element.root).querySelectorAll(
- 'gr-change-list-item');
- assert.equal(elementItems.length, 3);
+ Polymer.RenderStatus.afterNextRender(element, () => {
+ const elementItems = Polymer.dom(element.root).querySelectorAll(
+ 'gr-change-list-item');
+ assert.equal(elementItems.length, 3);
- flush(() => {
assert.isTrue(elementItems[0].hasAttribute('selected'));
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
assert.equal(element.selectedIndex, 1);
@@ -431,6 +433,59 @@ limitations under the License.
});
});
+ suite('dashboard queries', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ test('query without age and limit unchanged', () => {
+ const query = 'status:closed owner:me';
+ assert.deepEqual(element._processQuery(query), query);
+ });
+
+ test('query with age and limit', () => {
+ const query = 'status:closed age:1week limit:10 owner:me';
+ const expectedQuery = 'status:closed owner:me';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+
+ test('query with age', () => {
+ const query = 'status:closed age:1week owner:me';
+ const expectedQuery = 'status:closed owner:me';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+
+ test('query with limit', () => {
+ const query = 'status:closed limit:10 owner:me';
+ const expectedQuery = 'status:closed owner:me';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+
+ test('query with age as value and not key', () => {
+ const query = 'status:closed random:age';
+ const expectedQuery = 'status:closed random:age';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+
+ test('query with limit as value and not key', () => {
+ const query = 'status:closed random:limit';
+ const expectedQuery = 'status:closed random:limit';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+
+ test('query with -age key', () => {
+ const query = 'status:closed -age:1week';
+ const expectedQuery = 'status:closed';
+ assert.deepEqual(element._processQuery(query), expectedQuery);
+ });
+ });
+
suite('gr-change-list sections', () => {
let element;
let sandbox;
@@ -442,7 +497,7 @@ limitations under the License.
teardown(() => { sandbox.restore(); });
- test('keyboard shortcuts', () => {
+ test('keyboard shortcuts', done => {
element.selectedIndex = 0;
element.sections = [
{
@@ -468,42 +523,45 @@ limitations under the License.
},
];
flushAsynchronousOperations();
- const elementItems = Polymer.dom(element.root).querySelectorAll(
- 'gr-change-list-item');
- assert.equal(elementItems.length, 9);
-
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
- assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
-
- const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
- assert.equal(element.selectedIndex, 2);
-
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
- assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
- 'Should navigate to /c/2/');
-
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
- assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
- 'Should navigate to /c/1/');
-
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
- assert.equal(element.selectedIndex, 4);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
- assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
- 'Should navigate to /c/4/');
-
- MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
- const change = element._changeForIndex(element.selectedIndex);
- assert.equal(change.reviewed, true,
- 'Should mark change as reviewed');
- MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
- assert.equal(change.reviewed, false,
- 'Should mark change as unreviewed');
+ Polymer.RenderStatus.afterNextRender(element, () => {
+ const elementItems = Polymer.dom(element.root).querySelectorAll(
+ 'gr-change-list-item');
+ assert.equal(elementItems.length, 9);
+
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ assert.equal(element.selectedIndex, 1);
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+
+ const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ assert.equal(element.selectedIndex, 2);
+
+ MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
+ 'Should navigate to /c/2/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ assert.equal(element.selectedIndex, 1);
+ MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
+ 'Should navigate to /c/1/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ assert.equal(element.selectedIndex, 4);
+ MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
+ 'Should navigate to /c/4/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
+ const change = element._changeForIndex(element.selectedIndex);
+ assert.equal(change.reviewed, true,
+ 'Should mark change as reviewed');
+ MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
+ assert.equal(change.reviewed, false,
+ 'Should mark change as unreviewed');
+ done();
+ });
});
test('highlight attribute is updated correctly', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.html b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.html
index ecbd67e543..e88368dc58 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -30,7 +30,7 @@ limitations under the License.
#graphic,
#help {
display: inline-block;
- margin: .5em;
+ margin: var(--spacing-m);
}
#graphic #circle {
align-items: center;
@@ -51,14 +51,14 @@ limitations under the License.
text-align: center;
}
#help {
- padding-top: 1.35em;
+ padding-top: var(--spacing-xl);
vertical-align: top;
}
#help h1 {
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
}
#help p {
- margin-bottom: .6em;
+ margin-bottom: var(--spacing-m);
max-width: 35em;
}
@media only screen and (max-width: 50em) {
@@ -82,7 +82,7 @@ limitations under the License.
other git code review tools. Click on the `Create Change' button
and follow the step by step instructions.
</p>
- <gr-button on-tap="_handleCreateTap">Create Change</gr-button>
+ <gr-button on-click="_handleCreateTap">Create Change</gr-button>
</div>
</template>
<script src="gr-create-change-help.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
index 42d7bd768d..19e7a254a6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-create-change-help',
- _legacyUndefinedCheck: true,
/**
* Fired when the "Create change" button is tapped.
@@ -29,7 +28,8 @@
_handleCreateTap(e) {
e.preventDefault();
- this.dispatchEvent(new CustomEvent('create-tap', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('create-tap', {bubbles: true, composed: true}));
},
});
})();
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
index 09d95fd3e5..c43d62a188 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-change-help</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html
index e6d123c7cd..9e86058e03 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
@@ -25,10 +25,10 @@ limitations under the License.
<style include="shared-styles">
ol {
list-style: decimal;
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
p {
- margin-bottom: .75em;
+ margin-bottom: var(--spacing-m);
}
#commandsDialog {
max-width: 40em;
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
index e4958c52c6..5abb257b09 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
@@ -25,7 +25,7 @@
Polymer({
is: 'gr-create-commands-dialog',
- _legacyUndefinedCheck: true,
+
properties: {
branch: String,
_createNewCommitCommand: {
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
index e00037db35..89ad5732c7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-create-commands-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-create-commands-dialog.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html
index d12d84b940..def5228743 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-repo-branch-picker/gr-repo-branch-picker.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
index ed87e9ea7e..c2bfbf5e29 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
@@ -26,7 +26,7 @@
Polymer({
is: 'gr-create-destination-dialog',
- _legacyUndefinedCheck: true,
+
properties: {
_repo: String,
_branch: String,
@@ -46,9 +46,13 @@
this.$.createOverlay.close();
},
- _pickerConfirm() {
+ _pickerConfirm(e) {
this.$.createOverlay.close();
const detail = {repo: this._repo, branch: this._branch};
+ // e is a 'confirm' event from gr-dialog. We want to fire a more detailed
+ // 'confirm' event here, so let's stop propagation of the bare event.
+ e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(new CustomEvent('confirm', {detail, bubbles: false}));
},
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index b0ba8a2b92..41475a040f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
@@ -38,7 +39,7 @@ limitations under the License.
}
.loading {
color: var(--deemphasized-text-color);
- padding: 1em var(--default-horizontal-margin);
+ padding: var(--spacing-l);
}
gr-change-list {
width: 100%;
@@ -52,7 +53,7 @@ limitations under the License.
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
- padding: .25em var(--default-horizontal-margin);
+ padding: var(--spacing-xs) var(--spacing-l);
}
.banner gr-button {
--gr-button: {
@@ -67,7 +68,7 @@ limitations under the License.
}
@media only screen and (max-width: 50em) {
.loading {
- padding: 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-l);
}
}
</style>
@@ -80,7 +81,7 @@ limitations under the License.
<gr-button
class="delete"
link
- on-tap="_handleOpenDeleteDialog">Delete All</gr-button>
+ on-click="_handleOpenDeleteDialog">Delete All</gr-button>
</div>
</div>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 5faef085d5..b762ab3f01 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-dashboard-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -76,6 +75,7 @@
],
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.RESTClientBehavior,
],
@@ -144,12 +144,6 @@
return Promise.resolve();
}
- const user = params.user || 'self';
-
- // NOTE: This method may be called before attachment. Fire title-change
- // in an async so that attachment to the DOM can take place first.
- const title = params.title || this._computeTitle(user);
- this.async(() => this.fire('title-change', {title}));
return this._reload();
},
@@ -170,11 +164,19 @@
const checkForNewUser = !project && user === 'self';
return dashboardPromise
- .then(res => this._fetchDashboardChanges(res, checkForNewUser))
+ .then(res => {
+ if (res && res.title) {
+ this.fire('title-change', {title: res.title});
+ }
+ return this._fetchDashboardChanges(res, checkForNewUser);
+ })
.then(() => {
this._maybeShowDraftsBanner();
this.$.reporting.dashboardDisplayed();
}).catch(err => {
+ this.fire('title-change', {
+ title: title || this._computeTitle(user),
+ });
console.warn(err);
}).then(() => { this._loading = false; });
},
@@ -196,7 +198,7 @@
section.query);
if (checkForNewUser) {
- queries.push('owner:self');
+ queries.push('owner:self limit:1');
}
return this.$.restAPI.getChanges(null, queries, null, this.options)
@@ -208,6 +210,7 @@
}
this._results = changes.map((results, i) => ({
name: res.sections[i].name,
+ countLabel: this._computeSectionCountLabel(results),
query: res.sections[i].query,
results,
isOutgoing: res.sections[i].isOutgoing,
@@ -217,6 +220,16 @@
});
},
+ _computeSectionCountLabel(changes) {
+ if (!changes || !changes.length || changes.length == 0) {
+ return '';
+ }
+ const more = changes[changes.length - 1]._more_changes;
+ const numChanges = changes.length;
+ const andMore = more ? ' and more' : '';
+ return `(${numChanges}${andMore})`;
+ },
+
_computeUserHeaderClass(params) {
if (!params || !!params.project || !params.user
|| params.user === 'self') {
@@ -248,7 +261,7 @@
if (!draftSection || !draftSection.results.length) { return; }
const closedChanges = draftSection.results
- .filter(change => !this.changeIsOpen(change.status));
+ .filter(change => !this.changeIsOpen(change));
if (!closedChanges.length) { return; }
this._showDraftsBanner = true;
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 54c8ea988d..41d4192be9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-dashboard-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-dashboard-view.html">
@@ -147,6 +149,27 @@ limitations under the License.
assert.equal(element._computeTitle('not self'), 'Dashboard for not self');
});
+ suite('_computeSectionCountLabel', () => {
+ test('empty changes dont count label', () => {
+ assert.equal('', element._computeSectionCountLabel([]));
+ });
+
+ test('1 change', () => {
+ assert.equal('(1)',
+ element._computeSectionCountLabel(['1']));
+ });
+
+ test('2 changes', () => {
+ assert.equal('(2)',
+ element._computeSectionCountLabel(['1', '2']));
+ });
+
+ test('1 change and more', () => {
+ assert.equal('(1 and more)',
+ element._computeSectionCountLabel([{_more_changes: true}]));
+ });
+ });
+
suite('_isViewActive', () => {
test('nothing happens when user param is falsy', () => {
element.params = {};
@@ -182,7 +205,7 @@ limitations under the License.
return paramsChangedPromise.then(() => {
assert.isTrue(
getChangesStub.calledWith(
- null, ['1', '2', 'owner:self'], null, element.options));
+ null, ['1', '2', 'owner:self limit:1'], null, element.options));
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html
index 2394e24b7b..d445185e95 100644
--- a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html
+++ b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
<link rel="import" href="../gr-create-change-help/gr-create-change-help.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
index 14d0cb01e9..acc4295bcd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
+++ b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-embed-dashboard',
- _legacyUndefinedCheck: true,
+
properties: {
account: Object,
sections: Array,
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.html b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.html
index 23287254d1..0b4459cd45 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.html
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/dashboard-header-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
index 67fbd979d4..7ae4dab5ed 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-repo-header',
- _legacyUndefinedCheck: true,
+
properties: {
/** @type {?String} */
repo: {
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
index a561e09c75..266818ed13 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-header</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-header.html">
@@ -44,30 +46,13 @@ limitations under the License.
teardown(() => { sandbox.restore(); });
- test('loads and clears account info', done => {
- sandbox.stub(element.$.restAPI, 'getAccountDetails')
- .returns(Promise.resolve({
- name: 'foo',
- email: 'bar',
- registered_on: '2015-03-12 18:32:08.000000000',
- }));
- sandbox.stub(element.$.restAPI, 'getAccountStatus')
- .returns(Promise.resolve('baz'));
-
- element.userId = 'foo.bar@baz';
- flush(() => {
- assert.isOk(element._accountDetails);
- assert.isOk(element._status);
-
- element.userId = null;
- flush(() => {
- flushAsynchronousOperations();
- assert.isNull(element._accountDetails);
- assert.isNull(element._status);
-
- done();
- });
- });
+ test('repoUrl reset once repo changed', () => {
+ sandbox.stub(Gerrit.Nav, 'getUrlForRepo',
+ repoName => `http://test.com/${repoName}`
+ );
+ assert.equal(element._repoUrl, undefined);
+ element.repo = 'test';
+ assert.equal(element._repoUrl, 'http://test.com/test');
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.html b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.html
index 6accdfc8ad..fed1c121a3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.html
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
index c7fda2ce62..6afc169000 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-user-header',
- _legacyUndefinedCheck: true,
+
properties: {
/** @type {?String} */
userId: {
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
index c33be3b440..e837a5b13d 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-user-header</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-user-header.html">
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
deleted file mode 100644
index c53c111624..0000000000
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ /dev/null
@@ -1,185 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-(function() {
- 'use strict';
-
- Polymer({
- is: 'gr-account-entry',
- _legacyUndefinedCheck: true,
-
- /**
- * Fired when an account is entered.
- *
- * @event add
- */
-
- /**
- * When allowAnyInput is true, account-text-changed is fired when input text
- * changed. This is needed so that the reply dialog's save button can be
- * enabled for arbitrary cc's, which don't need a 'commit'.
- *
- * @event account-text-changed
- */
- properties: {
- allowAnyInput: Boolean,
- borderless: Boolean,
- change: Object,
- filter: Function,
- placeholder: String,
- /**
- * When true, account-entry uses the account suggest API endpoint, which
- * suggests any account in that Gerrit instance (and does not suggest
- * groups).
- *
- * When false/undefined, account-entry uses the suggest_reviewers API
- * endpoint, which suggests any account or group in that Gerrit instance
- * that is not already a reviewer (or is not CCed) on that change.
- */
- allowAnyUser: Boolean,
-
- // suggestFrom = 0 to enable default suggestions.
- suggestFrom: {
- type: Number,
- value: 0,
- },
-
- query: {
- type: Function,
- value() {
- return this._getReviewerSuggestions.bind(this);
- },
- },
-
- _config: Object,
- /** The value of the autocomplete entry. */
- _inputText: {
- type: String,
- observer: '_inputTextChanged',
- },
-
- _loggedIn: Boolean,
- },
-
- behaviors: [
- Gerrit.AnonymousNameBehavior,
- ],
-
- attached() {
- this.$.restAPI.getConfig().then(cfg => {
- this._config = cfg;
- });
- this.$.restAPI.getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
- });
- },
-
- get focusStart() {
- return this.$.input.focusStart;
- },
-
- focus() {
- this.$.input.focus();
- },
-
- clear() {
- this.$.input.clear();
- },
-
- setText(text) {
- this.$.input.setText(text);
- },
-
- getText() {
- return this.$.input.text;
- },
-
- _handleInputCommit(e) {
- this.fire('add', {value: e.detail.value});
- this.$.input.focus();
- },
-
- _accountOrAnon(reviewer) {
- return this.getUserName(this._config, reviewer, false);
- },
-
- _inputTextChanged(text) {
- if (text.length && this.allowAnyInput) {
- this.dispatchEvent(new CustomEvent('account-text-changed',
- {bubbles: true}));
- }
- },
-
- _makeSuggestion(reviewer) {
- let name;
- let value;
- const generateStatusStr = function(account) {
- return account.status ? '(' + account.status + ')' : '';
- };
- if (reviewer.account) {
- // Reviewer is an account suggestion from getChangeSuggestedReviewers.
- const reviewerName = this._accountOrAnon(reviewer.account);
- const reviewerEmail = this._reviewerEmail(reviewer.account.email);
- const reviewerStatus = generateStatusStr(reviewer.account);
- name = [reviewerName, reviewerEmail, reviewerStatus]
- .filter(p => p.length > 0).join(' ');
- value = reviewer;
- } else if (reviewer.group) {
- // Reviewer is a group suggestion from getChangeSuggestedReviewers.
- name = reviewer.group.name + ' (group)';
- value = reviewer;
- } else if (reviewer._account_id) {
- // Reviewer is an account suggestion from getSuggestedAccounts.
- const reviewerName = this._accountOrAnon(reviewer);
- const reviewerEmail = this._reviewerEmail(reviewer.email);
- const reviewerStatus = generateStatusStr(reviewer);
- name = [reviewerName, reviewerEmail, reviewerStatus]
- .filter(p => p.length > 0).join(' ');
- value = {account: reviewer, count: 1};
- }
- return {name, value};
- },
-
- _getReviewerSuggestions(input) {
- if (!this.change || !this.change._number || !this._loggedIn) {
- return Promise.resolve([]);
- }
-
- const api = this.$.restAPI;
- const xhr = this.allowAnyUser ?
- api.getSuggestedAccounts(`cansee:${this.change._number} ${input}`) :
- api.getChangeSuggestedReviewers(this.change._number, input);
-
- return xhr.then(reviewers => {
- if (!reviewers) { return []; }
- if (!this.filter) {
- return reviewers.map(this._makeSuggestion.bind(this));
- }
- return reviewers
- .filter(this.filter)
- .map(this._makeSuggestion.bind(this));
- });
- },
-
- _reviewerEmail(email) {
- if (typeof email !== 'undefined') {
- return '<' + email + '>';
- }
-
- return '';
- },
- });
-})();
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
deleted file mode 100644
index 724f7dac0b..0000000000
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ /dev/null
@@ -1,274 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2016 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-account-entry</title>
-
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../scripts/util.js"></script>
-
-<link rel="import" href="gr-account-entry.html">
-
-<script>void(0);</script>
-
-<test-fixture id="basic">
- <template>
- <gr-account-entry></gr-account-entry>
- </template>
-</test-fixture>
-
-<script>
- suite('gr-account-entry tests', () => {
- let sandbox;
- let _nextAccountId = 0;
- const makeAccount = function(opt_status) {
- const accountId = ++_nextAccountId;
- return {
- _account_id: accountId,
- name: 'name ' + accountId,
- email: 'email ' + accountId,
- status: opt_status,
- };
- };
- let _nextAccountId2 = 0;
- const makeAccount2 = function(opt_status) {
- const accountId2 = ++_nextAccountId2;
- return {
- _account_id: accountId2,
- email: 'email ' + accountId2,
- status: opt_status,
- };
- };
- let _nextAccountId3 = 0;
- const makeAccount3 = function(opt_status) {
- const accountId3 = ++_nextAccountId3;
- return {
- _account_id: accountId3,
- name: 'name ' + accountId3,
- status: opt_status,
- };
- };
-
- let owner;
- let existingReviewer1;
- let existingReviewer2;
- let suggestion1;
- let suggestion2;
- let suggestion3;
- let element;
-
- setup(done => {
- owner = makeAccount();
- existingReviewer1 = makeAccount();
- existingReviewer2 = makeAccount();
- suggestion1 = {account: makeAccount()};
- suggestion2 = {account: makeAccount()};
- suggestion3 = {
- group: {
- id: 'suggested group id',
- name: 'suggested group',
- },
- };
-
- stub('gr-rest-api-interface', {
- getLoggedIn() { return Promise.resolve(true); },
- });
-
- element = fixture('basic');
- element.change = {
- _number: 42,
- owner,
- reviewers: {
- CC: [existingReviewer1],
- REVIEWER: [existingReviewer2],
- },
- };
- sandbox = sinon.sandbox.create();
- return flush(done);
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- suite('stubbed values for _getReviewerSuggestions', () => {
- setup(() => {
- stub('gr-rest-api-interface', {
- getChangeSuggestedReviewers() {
- const redundantSuggestion1 = {account: existingReviewer1};
- const redundantSuggestion2 = {account: existingReviewer2};
- const redundantSuggestion3 = {account: owner};
- return Promise.resolve([redundantSuggestion1, redundantSuggestion2,
- redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
- },
- });
- });
-
- test('_makeSuggestion formats account or group accordingly', () => {
- let account = makeAccount();
- const account2 = makeAccount2();
- const account3 = makeAccount3();
- let suggestion = element._makeSuggestion({account});
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '>',
- value: {account},
- });
-
- const group = {name: 'test'};
- suggestion = element._makeSuggestion({group});
- assert.deepEqual(suggestion, {
- name: group.name + ' (group)',
- value: {group},
- });
-
- suggestion = element._makeSuggestion(account);
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '>',
- value: {account, count: 1},
- });
-
- element._config = {
- user: {
- anonymous_coward_name: 'Anonymous Coward',
- },
- };
- assert.deepEqual(element._accountOrAnon(account2), 'Anonymous');
-
- account = makeAccount('OOO');
-
- suggestion = element._makeSuggestion({account});
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '> (OOO)',
- value: {account},
- });
-
- suggestion = element._makeSuggestion(account);
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '> (OOO)',
- value: {account, count: 1},
- });
-
- sandbox.stub(element, '_reviewerEmail',
- () => { return ''; });
-
- suggestion = element._makeSuggestion(account3);
- assert.deepEqual(suggestion, {
- name: account3.name,
- value: {account: account3, count: 1},
- });
- });
-
- test('_reviewerEmail', () => {
- assert.equal(
- element._reviewerEmail('email@gerritreview.com'),
- '<email@gerritreview.com>');
- assert.equal(element._reviewerEmail(undefined), '');
- });
-
- test('_getReviewerSuggestions excludes owner+reviewers', done => {
- element._getReviewerSuggestions().then(reviewers => {
- // Default is no filtering.
- assert.equal(reviewers.length, 6);
-
- // Set up filter that only accepts suggestion1.
- const accountId = suggestion1.account._account_id;
- element.filter = function(suggestion) {
- return suggestion.account &&
- suggestion.account._account_id === accountId;
- };
-
- element._getReviewerSuggestions().then(reviewers => {
- assert.deepEqual(reviewers, [element._makeSuggestion(suggestion1)]);
- }).then(done);
- });
- });
-
- test('_getReviewerSuggestions short circuits when logged out', () => {
- // API call is already stubbed.
- const xhrSpy = element.$.restAPI.getChangeSuggestedReviewers;
- element._loggedIn = false;
- return element._getReviewerSuggestions('').then(() => {
- assert.isFalse(xhrSpy.called);
- element._loggedIn = true;
- return element._getReviewerSuggestions('').then(() => {
- assert.isTrue(xhrSpy.called);
- });
- });
- });
- });
-
- test('allowAnyUser', done => {
- const suggestReviewerStub =
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- const suggestAccountStub =
- sandbox.stub(element.$.restAPI, 'getSuggestedAccounts')
- .returns(Promise.resolve([]));
-
- element._getReviewerSuggestions('').then(() => {
- assert.isTrue(suggestReviewerStub.calledOnce);
- assert.isTrue(suggestReviewerStub.calledWith(42, ''));
- assert.isFalse(suggestAccountStub.called);
- element.allowAnyUser = true;
-
- element._getReviewerSuggestions('').then(() => {
- assert.isTrue(suggestReviewerStub.calledOnce);
- assert.isTrue(suggestAccountStub.calledOnce);
- assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
- done();
- });
- });
- });
- test('account-text-changed fired when input text changed and allowAnyInput',
- () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const changeStub = sandbox.stub();
- element.allowAnyInput = true;
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- element.addEventListener('account-text-changed', changeStub);
- element.$.input.text = 'a';
- assert.isTrue(changeStub.calledOnce);
- element.$.input.text = 'ab';
- assert.isTrue(changeStub.calledTwice);
- });
-
- test('account-text-changed not fired when input text changed without ' +
- 'allowAnyUser', () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const changeStub = sandbox.stub();
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- element.addEventListener('account-text-changed', changeStub);
- element.$.input.text = 'a';
- assert.isFalse(changeStub.called);
- });
-
- test('setText', () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const suggestSpy = sandbox.spy(element.$.input, 'query');
- element.setText('test text');
- flushAsynchronousOperations();
-
- assert.equal(element.$.input.$.input.value, 'test text');
- assert.isFalse(suggestSpy.called);
- });
- });
-</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 278875e7e4..e12f10def9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../admin/gr-create-change-dialog/gr-create-change-dialog.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
@@ -62,13 +62,13 @@ limitations under the License.
color: var(--deemphasized-text-color);
}
#confirmSubmitDialog .changeSubject {
- margin: 1em;
+ margin: var(--spacing-l);
text-align: center;
}
iron-icon {
color: inherit;
height: 1.2rem;
- margin-right: .2rem;
+ margin-right: var(--spacing-xs);
width: 1.2rem;
}
gr-button {
@@ -92,7 +92,7 @@ limitations under the License.
}
gr-button {
--gr-button: {
- padding: .5em;
+ padding: var(--spacing-m);
white-space: nowrap;
}
}
@@ -101,7 +101,7 @@ limitations under the License.
margin: 0;
}
#actionLoadingMessage {
- margin: .5em;
+ margin: var(--spacing-m);
text-align: center;
}
#moreMessage {
@@ -124,11 +124,12 @@ limitations under the License.
link
title$="[[action.title]]"
has-tooltip="[[_computeHasTooltip(action.title)]]"
+ position-below="true"
data-action-key$="[[action.__key]]"
data-action-type$="[[action.__type]]"
data-label$="[[action.label]]"
disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
- on-tap="_handleActionTap">
+ on-click="_handleActionTap">
<iron-icon class$="[[_computeHasIcon(action)]]" icon$="gr-icons:[[action.icon]]"></iron-icon>
[[action.label]]
</gr-button>
@@ -144,11 +145,12 @@ limitations under the License.
link
title$="[[action.title]]"
has-tooltip="[[_computeHasTooltip(action.title)]]"
+ position-below="true"
data-action-key$="[[action.__key]]"
data-action-type$="[[action.__type]]"
data-label$="[[action.label]]"
disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
- on-tap="_handleActionTap">
+ on-click="_handleActionTap">
<iron-icon class$="[[_computeHasIcon(action)]]" icon$="gr-icons:[[action.icon]]"></iron-icon>
[[action.label]]
</gr-button>
@@ -177,7 +179,7 @@ limitations under the License.
on-cancel="_handleConfirmDialogCancel"
branch="[[change.branch]]"
has-parent="[[hasParent]]"
- rebase-on-current="[[revisionActions.rebase.rebaseOnCurrent]]"
+ rebase-on-current="[[_computeRebaseOnCurrent(_revisionRebaseAction)]]"
hidden></gr-confirm-rebase-dialog>
<gr-confirm-cherrypick-dialog id="confirmCherrypick"
class="confirmDialog"
@@ -217,7 +219,7 @@ limitations under the License.
id="confirmSubmitDialog"
class="confirmDialog"
change="[[change]]"
- action="[[revisionActions.submit]]"
+ action="[[_revisionSubmitAction]]"
on-cancel="_handleConfirmDialogCancel"
on-confirm="_handleSubmitConfirm" hidden></gr-confirm-submit-dialog>
<gr-dialog id="createFollowUpDialog"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 9182bbceb6..9c0c38fc2d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -50,7 +50,6 @@
OPTIONAL: 'OPTIONAL',
};
- // TODO(davido): Add the rest of the change actions.
const ChangeActions = {
ABANDON: 'abandon',
DELETE: '/',
@@ -72,7 +71,6 @@
WIP: 'wip',
};
- // TODO(andybons): Add the rest of the revision actions.
const RevisionActions = {
CHERRYPICK: 'cherrypick',
REBASE: 'rebase',
@@ -193,7 +191,6 @@
Polymer({
is: 'gr-change-actions',
- _legacyUndefinedCheck: true,
/**
* Fired when the change should be reloaded.
@@ -269,8 +266,23 @@
/** @type {?} */
revisionActions: {
type: Object,
+ notify: true,
value() { return {}; },
},
+ // If property binds directly to [[revisionActions.submit]] it is not
+ // updated when revisionActions doesn't contain submit action.
+ /** @type {?} */
+ _revisionSubmitAction: {
+ type: Object,
+ computed: '_getSubmitAction(revisionActions)',
+ },
+ // If property binds directly to [[revisionActions.rebase]] it is not
+ // updated when revisionActions doesn't contain rebase action.
+ /** @type {?} */
+ _revisionRebaseAction: {
+ type: Object,
+ computed: '_getRebaseAction(revisionActions)',
+ },
privateByDefault: String,
_loading: {
@@ -394,6 +406,7 @@
RevisionActions,
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
],
@@ -415,6 +428,26 @@
this._handleLoadingComplete();
},
+ _getSubmitAction(revisionActions) {
+ return this._getRevisionAction(revisionActions, 'submit', null);
+ },
+
+ _getRebaseAction(revisionActions) {
+ return this._getRevisionAction(revisionActions, 'rebase', null);
+ },
+
+ _getRevisionAction(revisionActions, actionName, emptyActionValue) {
+ if (!revisionActions) {
+ return undefined;
+ }
+ if (revisionActions[actionName] === undefined) {
+ // Return null to fire an event when reveisionActions was loaded
+ // but doesn't contain actionName. undefined doesn't fire an event
+ return emptyActionValue;
+ }
+ return revisionActions[actionName];
+ },
+
reload() {
if (!this.changeNum || !this.latestPatchNum) {
return Promise.resolve();
@@ -425,6 +458,10 @@
if (!revisionActions) { return; }
this.revisionActions = revisionActions;
+ this._sendShowRevisionActions({
+ change: this.change,
+ revisionActions,
+ });
this._handleLoadingComplete();
}).catch(err => {
this.fire('show-alert', {message: ERR_REVISION_ACTIONS});
@@ -437,6 +474,13 @@
Gerrit.awaitPluginsLoaded().then(() => this._loading = false);
},
+ _sendShowRevisionActions(detail) {
+ this.$.jsAPI.handleEvent(
+ this.$.jsAPI.EventType.SHOW_REVISION_ACTIONS,
+ detail
+ );
+ },
+
_changeChanged() {
this.reload();
},
@@ -553,6 +597,15 @@
_actionsChanged(actionsChangeRecord, revisionActionsChangeRecord,
additionalActionsChangeRecord) {
+ // Polymer 2: check for undefined
+ if ([
+ actionsChangeRecord,
+ revisionActionsChangeRecord,
+ additionalActionsChangeRecord,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
const additionalActions = (additionalActionsChangeRecord &&
additionalActionsChangeRecord.base) || [];
this.hidden = this._keyCount(actionsChangeRecord) === 0 &&
@@ -573,14 +626,25 @@
* @param {string=} actionName
*/
_deleteAndNotify(actionName) {
- if (this.actions[actionName]) {
+ if (this.actions && this.actions[actionName]) {
delete this.actions[actionName];
- this.notifyPath('actions.' + actionName);
+ // We assign a fake value of 'false' to support Polymer 2
+ // see https://github.com/Polymer/polymer/issues/2631
+ this.notifyPath('actions.' + actionName, false);
}
},
_editStatusChanged(editMode, editPatchsetLoaded,
editBasedOnCurrentPatchSet, disableEdit) {
+ // Polymer 2: check for undefined
+ if ([
+ editMode,
+ editBasedOnCurrentPatchSet,
+ disableEdit,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
if (disableEdit) {
this._deleteAndNotify('publishEdit');
this._deleteAndNotify('rebaseEdit');
@@ -589,10 +653,10 @@
this._deleteAndNotify('edit');
return;
}
- if (editPatchsetLoaded) {
+ if (this.actions && editPatchsetLoaded) {
// Only show actions that mutate an edit if an actual edit patch set
// is loaded.
- if (this.changeIsOpen(this.change.status)) {
+ if (this.changeIsOpen(this.change)) {
if (editBasedOnCurrentPatchSet) {
if (!this.actions.publishEdit) {
this.set('actions.publishEdit', PUBLISH_EDIT);
@@ -614,7 +678,7 @@
this._deleteAndNotify('deleteEdit');
}
- if (this.changeIsOpen(this.change.status)) {
+ if (this.actions && this.changeIsOpen(this.change)) {
// Only show edit button if there is no edit patchset loaded and the
// file list is not in edit mode.
if (editPatchsetLoaded || editMode) {
@@ -843,7 +907,7 @@
_handleActionTap(e) {
e.preventDefault();
let el = Polymer.dom(e).localTarget;
- while (el.is !== 'gr-button') {
+ while (el.tagName.toLowerCase() !== 'gr-button') {
if (!el.parentElement) { return; }
el = el.parentElement;
}
@@ -967,8 +1031,9 @@
},
_calculateDisabled(action, hasKnownChainState) {
- if (action.__key === 'rebase' && hasKnownChainState === false) {
- return true;
+ if (action.__key === 'rebase') {
+ // Rebase button is only disabled when change has no parent(s).
+ return hasKnownChainState === false;
}
return !action.enabled;
},
@@ -1003,7 +1068,6 @@
_handleCherryPickRestApi(conflicts) {
const el = this.$.confirmCherrypick;
if (!el.branch) {
- // TODO(davido): Fix error handling
this.fire('show-alert', {message: ERR_BRANCH_EMPTY});
return;
}
@@ -1307,6 +1371,17 @@
*/
_computeAllActions(changeActionsRecord, revisionActionsRecord,
primariesRecord, additionalActionsRecord, change) {
+ // Polymer 2: check for undefined
+ if ([
+ changeActionsRecord,
+ revisionActionsRecord,
+ primariesRecord,
+ additionalActionsRecord,
+ change,
+ ].some(arg => arg === undefined)) {
+ return [];
+ }
+
const revisionActionValues = this._getActionValues(revisionActionsRecord,
primariesRecord, additionalActionsRecord, ActionType.REVISION);
const changeActionValues = this._getActionValues(changeActionsRecord,
@@ -1395,6 +1470,13 @@
});
},
+ _computeRebaseOnCurrent(revisionRebaseAction) {
+ if (revisionRebaseAction) {
+ return !!revisionRebaseAction.enabled;
+ }
+ return null;
+ },
+
/**
* Occasionally, a change created by a change action is not yet knwon to the
* API for a brief time. Wait for the given change number to be recognized.
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 9803fabcf3..74d262ab6c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-actions</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -112,6 +114,15 @@ limitations under the License.
sandbox.restore();
});
+ test('show-revision-actions event should fire', done => {
+ const spy = sinon.spy(element, '_sendShowRevisionActions');
+ element.reload();
+ flush(() => {
+ assert.isTrue(spy.called);
+ done();
+ });
+ });
+
test('primary and secondary actions split properly', () => {
// Submit should be the only primary action.
assert.equal(element._topLevelPrimaryActions.length, 1);
@@ -352,7 +363,7 @@ limitations under the License.
action.enabled = false;
assert.equal(
- element._calculateDisabled(action, hasKnownChainState), true);
+ element._calculateDisabled(action, hasKnownChainState), false);
});
test('rebase change', done => {
@@ -1511,4 +1522,69 @@ limitations under the License.
assert.equal(reportStub.lastCall.args[0], 'type-key');
});
});
+
+ suite('getChangeRevisionActions returns only some actions', () => {
+ let element;
+ let sandbox;
+ let changeRevisionActions;
+
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getChangeRevisionActions() {
+ return Promise.resolve(changeRevisionActions);
+ },
+ send(method, url, payload) {
+ return Promise.reject(new Error('error'));
+ },
+ getProjectConfig() { return Promise.resolve({}); },
+ });
+
+ sandbox = sinon.sandbox.create();
+ sandbox.stub(Gerrit, 'awaitPluginsLoaded').returns(Promise.resolve());
+
+ element = fixture('basic');
+ // getChangeRevisionActions is not called without
+ // set the following properies
+ element.change = {};
+ element.changeNum = '42';
+ element.latestPatchNum = '2';
+
+
+ sandbox.stub(element.$.confirmCherrypick.$.restAPI,
+ 'getRepoBranches').returns(Promise.resolve([]));
+ sandbox.stub(element.$.confirmMove.$.restAPI,
+ 'getRepoBranches').returns(Promise.resolve([]));
+ return element.reload();
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('confirmSubmitDialog and confirmRebase properties are changed', () => {
+ changeRevisionActions = {};
+ element.reload();
+ assert.strictEqual(element.$.confirmSubmitDialog.action, null);
+ assert.strictEqual(element.$.confirmRebase.rebaseOnCurrent, null);
+ });
+
+ test('_computeRebaseOnCurrent', () => {
+ const rebaseAction = {
+ enabled: true,
+ label: 'Rebase',
+ method: 'POST',
+ title: 'Rebase onto tip of branch or parent change',
+ };
+
+ // When rebase is enabled initially, rebaseOnCurrent should be set to
+ // true.
+ assert.isTrue(element._computeRebaseOnCurrent(rebaseAction));
+
+ delete rebaseAction.enabled;
+
+ // When rebase is not enabled initially, rebaseOnCurrent should be set to
+ // false.
+ assert.isFalse(element._computeRebaseOnCurrent(rebaseAction));
+ });
+ });
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index f1d3cd3faf..b8bea9ca36 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-metadata</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../plugins/gr-plugin-host/gr-plugin-host.html">
<link rel="import" href="gr-change-metadata.html">
@@ -87,6 +89,7 @@ limitations under the License.
teardown(() => {
sandbox.restore();
+ Gerrit._testOnly_resetPlugins();
});
suite('by default', () => {
@@ -104,7 +107,7 @@ limitations under the License.
suite('with plugin style', () => {
setup(done => {
- Gerrit._resetPlugins();
+ Gerrit._testOnly_resetPlugins();
const pluginHost = fixture('plugin-host');
pluginHost.config = {
plugin: {
@@ -139,7 +142,7 @@ limitations under the License.
new URL('test/plugin.html?' + Math.random(),
window.location.href).toString());
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
element = createElement();
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index a09b730157..6a92d96ecd 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../../styles/gr-change-metadata-shared-styles.html">
+<link rel="import" href="../../../styles/gr-change-view-integration-shared-styles.html">
<link rel="import" href="../../../styles/gr-voting-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
@@ -35,38 +37,17 @@ limitations under the License.
<link rel="import" href="../gr-change-requirements/gr-change-requirements.html">
<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-reviewer-list/gr-reviewer-list.html">
+<link rel="import" href="../../shared/gr-account-list/gr-account-list.html">
+<script src="../../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js"></script>
<dom-module id="gr-change-metadata">
<template>
+ <style include="gr-change-metadata-shared-styles"></style>
<style include="shared-styles">
:host {
display: table;
}
- section {
- display: table-row;
- }
- section:not(:first-of-type) .title,
- section:not(:first-of-type) .value {
- padding-top: .5em;
- }
- section:not(:first-of-type) {
- margin-top: 1em;
- }
- .title,
- .value {
- display: table-cell;
- }
- .title {
- color: var(--deemphasized-text-color);
- font-weight: var(--font-weight-bold);
- max-width: 20em;
- padding-left: var(--metadata-horizontal-padding);
- padding-right: .5em;
- word-break: break-word;
- }
- .value {
- padding-right: var(--metadata-horizontal-padding);
- }
gr-change-requirements {
--requirements-horizontal-padding: var(--metadata-horizontal-padding);
}
@@ -74,7 +55,7 @@ limitations under the License.
max-width: 20ch;
overflow: hidden;
text-overflow: ellipsis;
- vertical-align: middle;
+ vertical-align: top;
white-space: nowrap;
}
gr-editable-label {
@@ -99,14 +80,14 @@ limitations under the License.
pointer-events: none;
}
.hashtagChip {
- margin-bottom: .5em;
+ margin-bottom: var(--spacing-m);
}
#externalStyle {
display: block;
}
.parentList.merge {
list-style-type: decimal;
- padding-left: 1em;
+ padding-left: var(--spacing-l);
}
.parentList gr-commit-info {
display: inline-block;
@@ -116,7 +97,7 @@ limitations under the License.
display: none;
}
.icon {
- margin: -.25em 0;
+ margin: -3px 0;
}
.icon.help,
.icon.notTrusted {
@@ -133,9 +114,12 @@ limitations under the License.
display: inline-block;
}
.separatedSection {
- border-top: 1px solid var(--border-color);
- margin-top: .5em;
- padding: .5em 0;
+ margin-top: var(--spacing-l);
+ padding: var(--spacing-m) 0;
+ }
+ .hashtag gr-linked-chip,
+ .topic gr-linked-chip {
+ --linked-chip-text-color: var(--link-color);
}
</style>
<gr-external-style id="externalStyle" name="change-metadata">
@@ -195,9 +179,9 @@ limitations under the License.
id="assigneeValue"
placeholder="Set assignee..."
accounts="{{_assignee}}"
- change="[[change]]"
readonly="[[_computeAssigneeReadOnly(_mutable, change)]]"
- allow-any-user></gr-account-list>
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
+ </gr-account-list>
</span>
</section>
<section>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 94b85fdf59..4fe5ddc70c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -17,17 +17,6 @@
(function() {
'use strict';
- const Defs = {};
-
- /**
- * @typedef {{
- * message: string,
- * icon: string,
- * class: string,
- * }}
- */
- Defs.PushCertificateValidation;
-
const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
const SubmitTypeLabel = {
@@ -61,7 +50,6 @@
Polymer({
is: 'gr-change-metadata',
- _legacyUndefinedCheck: true,
/**
* Fired when the change topic is changed.
@@ -101,7 +89,7 @@
computed: '_computeHashtagReadOnly(_mutable, change)',
},
/**
- * @type {Defs.PushCertificateValidation}
+ * @type {Gerrit.PushCertificateValidation}
*/
_pushCertificateValidation: {
type: Object,
@@ -177,7 +165,7 @@
},
_computeHideStrategy(change) {
- return !this.changeIsOpen(change.status);
+ return !this.changeIsOpen(change);
},
/**
@@ -215,19 +203,21 @@
this._settingTopic = false;
this.set(['change', 'topic'], newTopic);
if (newTopic !== lastTopic) {
- this.dispatchEvent(
- new CustomEvent('topic-changed', {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'topic-changed', {bubbles: true, composed: true}));
}
});
},
_showAddTopic(changeRecord, settingTopic) {
- const hasTopic = !!changeRecord && !!changeRecord.base.topic;
+ const hasTopic = !!changeRecord &&
+ !!changeRecord.base && !!changeRecord.base.topic;
return !hasTopic && !settingTopic;
},
_showTopicChip(changeRecord, settingTopic) {
- const hasTopic = !!changeRecord && !!changeRecord.base.topic;
+ const hasTopic = !!changeRecord &&
+ !!changeRecord.base && !!changeRecord.base.topic;
return hasTopic && !settingTopic;
},
@@ -241,13 +231,15 @@
this.set(['change', 'hashtags'], newHashtag);
if (newHashtag !== lastHashtag) {
this.dispatchEvent(
- new CustomEvent('hashtag-changed', {bubbles: true}));
+ new CustomEvent('hashtag-changed', {
+ bubbles: true, composed: true}));
}
});
},
_computeTopicReadOnly(mutable, change) {
return !mutable ||
+ !change ||
!change.actions ||
!change.actions.topic ||
!change.actions.topic.enabled;
@@ -255,6 +247,7 @@
_computeHashtagReadOnly(mutable, change) {
return !mutable ||
+ !change ||
!change.actions ||
!change.actions.hashtags ||
!change.actions.hashtags.enabled;
@@ -262,6 +255,7 @@
_computeAssigneeReadOnly(mutable, change) {
return !mutable ||
+ !change ||
!change.actions ||
!change.actions.assignee ||
!change.actions.assignee.enabled;
@@ -291,11 +285,11 @@
},
/**
- * @return {?Defs.PushCertificateValidation} object representing data for
+ * @return {?Gerrit.PushCertificateValidation} object representing data for
* the push validation.
*/
_computePushCertificateValidation(serverConfig, change) {
- if (!serverConfig || !serverConfig.receive ||
+ if (!change || !serverConfig || !serverConfig.receive ||
!serverConfig.receive.enable_signed_push) {
return null;
}
@@ -348,6 +342,7 @@
},
_computeBranchURL(project, branch) {
+ if (!this.change || !this.change.status) return '';
return Gerrit.Nav.getUrlForBranch(branch, project,
this.change.status == this.ChangeStatus.NEW ? 'open' :
this.change.status.toLowerCase());
@@ -368,7 +363,7 @@
target.disabled = false;
this.set(['change', 'topic'], '');
this.dispatchEvent(
- new CustomEvent('topic-changed', {bubbles: true}));
+ new CustomEvent('topic-changed', {bubbles: true, composed: true}));
}).catch(err => {
target.disabled = false;
return;
@@ -407,7 +402,7 @@
* @return {Object|null} either an accound or null.
*/
_getNonOwnerRole(change, role) {
- if (!change.current_revision ||
+ if (!change || !change.current_revision ||
!change.revisions[change.current_revision]) {
return null;
}
@@ -446,13 +441,18 @@
},
_computeParentsLabel(parents) {
- return parents.length > 1 ? 'Parents' : 'Parent';
+ return parents && parents.length > 1 ? 'Parents' : 'Parent';
},
_computeParentListClass(parents, parentIsCurrent) {
+ // Undefined check for polymer 2
+ if (parents === undefined || parentIsCurrent === undefined) {
+ return '';
+ }
+
return [
'parentList',
- parents.length > 1 ? 'merge' : 'nonMerge',
+ parents && parents.length > 1 ? 'merge' : 'nonMerge',
parentIsCurrent ? 'current' : 'notCurrent',
].join(' ');
},
@@ -467,5 +467,12 @@
// dom-if.
this.$$('.topicEditableLabel').open();
},
+
+ _getReviewerSuggestionsProvider(change) {
+ const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
+ change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY);
+ provider.init();
+ return provider;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index ce69d45eb6..d60847b077 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-metadata</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../core/gr-router/gr-router.html">
<link rel="import" href="gr-change-metadata.html">
@@ -747,7 +749,7 @@ limitations under the License.
},
'0.1',
'http://some/plugins/url.html');
- Gerrit._setPluginsCount(0);
+ Gerrit._loadPlugins([]);
flush(() => {
assert.strictEqual(hookEl.plugin, plugin);
assert.strictEqual(hookEl.change, element.change);
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html b/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html
index d0ed4a1c1e..b3aa98f0ea 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html
@@ -7,20 +7,22 @@
</dom-module>
<dom-module id="my-plugin-style">
- <style>
- html {
- --change-metadata-assignee: {
- display: none;
+ <template>
+ <style>
+ html {
+ --change-metadata-assignee: {
+ display: none;
+ }
+ --change-metadata-label-status: {
+ display: none;
+ }
+ --change-metadata-strategy: {
+ display: none;
+ }
+ --change-metadata-topic: {
+ display: none;
+ }
}
- --change-metadata-label-status: {
- display: none;
- }
- --change-metadata-strategy: {
- display: none;
- }
- --change-metadata-topic: {
- display: none;
- }
- }
- </style>
+ </style>
+ </template>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
index e79bce16a0..47ff7f78d8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -34,8 +34,10 @@ limitations under the License.
.status {
color: #FFA62F;
display: inline-block;
- font-family: var(--monospace-font-family);
text-align: center;
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
}
.approved.status {
color: var(--vote-text-color-recommended);
@@ -46,8 +48,8 @@ limitations under the License.
iron-icon {
color: inherit;
}
- .name {
- font-weight: var(--font-weight-bold);
+ .status iron-icon {
+ vertical-align: top;
}
section {
display: table-row;
@@ -57,16 +59,15 @@ limitations under the License.
}
.title {
min-width: 10em;
- padding: .75em .5em 0 var(--requirements-horizontal-padding);
- vertical-align: top;
+ padding: var(--spacing-s) var(--spacing-m) 0 var(--requirements-horizontal-padding);
}
.value {
- padding: .6em .5em 0 0;
- vertical-align: middle;
+ padding: var(--spacing-s) 0 0 0;
}
.title,
.value {
display: table-cell;
+ vertical-align: top;
}
.hidden {
display: none;
@@ -75,12 +76,10 @@ limitations under the License.
cursor: pointer;
}
.showHide .title {
- border-top: 1px solid var(--border-color);
- padding-bottom: .5em;
- padding-top: .5em;
+ padding-bottom: var(--spacing-m);
+ padding-top: var(--spacing-l);
}
.showHide .value {
- border-top: 1px solid var(--border-color);
padding-top: 0;
vertical-align: middle;
}
@@ -89,7 +88,7 @@ limitations under the License.
float: right;
}
.spacer {
- height: .5em;
+ height: var(--spacing-m);
}
</style>
<template
@@ -128,7 +127,7 @@ limitations under the License.
<section class$="spacer [[_computeShowOptional(_optionalLabels.*)]]"></section>
<section
show-bottom-border$="[[_showOptionalLabels]]"
- on-tap="_handleShowHide"
+ on-click="_handleShowHide"
class$="showHide [[_computeShowOptional(_optionalLabels.*)]]">
<div class="title">Other labels</div>
<div class="value">
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
index 4717ff9624..dfdcd59ea5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-change-requirements',
- _legacyUndefinedCheck: true,
properties: {
/** @type {?} */
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
index 3f35158acb..2ceac39606 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-requirements</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-requirements.html">
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 857976223e..29f2d83c50 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -15,11 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/paper-tabs/paper-tabs.html">
+<link rel="import" href="/bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
@@ -42,6 +43,7 @@ limitations under the License.
<link rel="import" href="../../shared/revision-info/revision-info.html">
<link rel="import" href="../gr-change-actions/gr-change-actions.html">
<link rel="import" href="../gr-change-metadata/gr-change-metadata.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-download-dialog/gr-download-dialog.html">
<link rel="import" href="../gr-file-list-header/gr-file-list-header.html">
@@ -61,25 +63,25 @@ limitations under the License.
}
.container.loading {
color: var(--deemphasized-text-color);
- padding: 1em var(--default-horizontal-margin);
+ padding: var(--spacing-l);
}
.header {
align-items: center;
background-color: var(--table-header-background-color);
border-bottom: 1px solid var(--border-color);
display: flex;
- padding: .55em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
z-index: 99; /* Less than gr-overlay's backdrop */
}
.header.editMode {
background-color: var(--edit-mode-background-color);
}
.header .download {
- margin-right: 1em;
+ margin-right: var(--spacing-l);
}
gr-change-status {
display: initial;
- margin: .1em .1em .1em .4em;
+ margin: var(--spacing-xxs) var(--spacing-xxs) var(--spacing-xxs) var(--spacing-s);
}
gr-change-status:first-child {
margin-left: 0;
@@ -88,17 +90,16 @@ limitations under the License.
align-items: center;
display: flex;
flex: 1;
- font-size: 1.2rem;
+ font-size: var(--font-size-h3);
}
.headerTitle .headerSubject {
font-weight: var(--font-weight-bold);
}
#replyBtn {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
gr-change-star {
- font-size: var(--font-size-normal);
- margin-right: .25em;
+ margin-right: var(--spacing-xs);
}
gr-reply-dialog {
width: 60em;
@@ -106,6 +107,9 @@ limitations under the License.
.changeStatus {
text-transform: capitalize;
}
+ .changeInfo {
+ background-color: var(--table-header-background-color);
+ }
/* Strong specificity here is needed due to
https://github.com/Polymer/polymer/issues/2531 */
.container section.changeInfo {
@@ -114,32 +118,33 @@ limitations under the License.
.changeId {
color: var(--deemphasized-text-color);
font-family: var(--font-family);
- margin-top: 1em;
+ margin-top: var(--spacing-l);
}
.changeMetadata {
- border-right: 1px solid var(--border-color);
- }
- /* Prevent plugin text from overflowing. */
- #change_plugins {
- word-break: break-word;
+ /* Limit meta section to half of the screen at max */
+ max-width: 50%;
}
.commitMessage {
font-family: var(--monospace-font-family);
- margin-right: 1em;
- margin-bottom: 1em;
- max-width: var(--commit-message-max-width, 72ch);;
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ margin-right: var(--spacing-l);
+ margin-bottom: var(--spacing-l);
+ /* Account for border and padding and rounding errors. */
+ max-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
}
.commitMessage gr-linked-text {
word-break: break-word;
}
#commitMessageEditor {
- min-width: 72ch;
+ /* Account for border and padding and rounding errors. */
+ min-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
}
.editCommitMessage {
- margin-top: 1em;
+ margin-top: var(--spacing-l);
+
--gr-button: {
- padding-left: 0;
- padding-right: 0;
+ padding: 5px 0px;
}
}
.changeStatuses,
@@ -166,7 +171,7 @@ limitations under the License.
.relatedChanges {
flex: 1 1 auto;
overflow: hidden;
- padding: 1em 0;
+ padding: var(--spacing-l) 0;
}
.mobile {
display: none;
@@ -178,7 +183,7 @@ limitations under the License.
border: 0;
border-top: 1px solid var(--border-color);
height: 0;
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
#commitMessage.collapsed {
max-height: 36em;
@@ -187,7 +192,7 @@ limitations under the License.
#relatedChanges {
}
#relatedChanges.collapsed {
- margin-bottom: 1.1em;
+ margin-bottom: var(--spacing-l);
max-height: var(--relation-chain-max-height, 2em);
overflow: hidden;
}
@@ -195,8 +200,8 @@ limitations under the License.
display: flex;
flex-direction: column;
flex-shrink: 0;
- margin: 1em 0;
- padding: 0 1em;
+ margin: var(--spacing-l) 0;
+ padding: 0 var(--spacing-l);
}
.collapseToggleContainer {
display: flex;
@@ -212,7 +217,7 @@ limitations under the License.
display: block;
}
#relatedChangesToggle {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
padding-top: var(--related-change-btn-top-padding, 0);
}
.showOnEdit {
@@ -231,12 +236,12 @@ limitations under the License.
paper-tabs {
background-color: var(--table-header-background-color);
border-top: 1px solid var(--border-color);
- height: 3rem;
+ height: calc(var(--line-height-normal) + 2*var(--spacing-m));
--paper-tabs-selection-bar-color: var(--link-color);
}
paper-tab {
box-sizing: border-box;
- max-width: 15rem;
+ max-width: 12em;
--paper-tab-ink: var(--link-color);
}
gr-thread-list,
@@ -249,14 +254,9 @@ limitations under the License.
#uploadHelpOverlay {
width: 50em;
}
- @media screen and (min-width: 80em) {
- .commitMessage {
- max-width: var(--commit-message-max-width, 100ch);
- }
- }
#metadata {
- --metadata-horizontal-padding: 1em;
- padding-top: 1em;
+ --metadata-horizontal-padding: var(--spacing-l);
+ padding-top: var(--spacing-l);
width: 100%;
}
/* NOTE: If you update this breakpoint, also update the
@@ -266,8 +266,7 @@ limitations under the License.
padding: 0;
}
#relatedChanges {
- border-top: 1px solid var(--border-color);
- padding-top: 1em;
+ padding-top: var(--spacing-l);
}
#commitAndRelated {
flex-direction: column;
@@ -293,14 +292,14 @@ limitations under the License.
align-items: flex-start;
flex-direction: column;
flex: 1;
- padding: .5em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
gr-change-star {
vertical-align: middle;
}
.headerTitle {
flex-wrap: wrap;
- font-size: 1.1rem;
+ font-size: var(--font-size-h3);
}
.desktop {
display: none;
@@ -322,16 +321,10 @@ limitations under the License.
}
.commitContainer {
margin: 0;
- padding: 1em;
- }
- .relatedChanges,
- .changeMetadata {
- font-size: var(--font-size-normal);
+ padding: var(--spacing-l);
}
.changeMetadata {
- border-bottom: 1px solid var(--border-color);
- border-right: none;
- margin-top: .25em;
+ margin-top: var(--spacing-xs);
max-width: none;
}
#metadata,
@@ -340,7 +333,7 @@ limitations under the License.
}
.commitActions {
display: block;
- margin-top: 1em;
+ margin-top: var(--spacing-l);
width: 100%;
}
.commitMessage {
@@ -366,6 +359,7 @@ limitations under the License.
<div
id="mainContent"
class="container"
+ on-show-checks-table="_handleShowTab"
hidden$="{{_loading}}">
<div class$="[[_computeHeaderClass(_editMode)]]">
<div class="headerTitle">
@@ -405,7 +399,7 @@ limitations under the License.
disable-edit="[[disableEdit]]"
has-parent="[[hasParent]]"
actions="[[_change.actions]]"
- revision-actions="[[_currentRevisionActions]]"
+ revision-actions="{{_currentRevisionActions}}"
change-num="[[_changeNum]]"
change-status="[[_change.status]]"
commit-num="[[_commitInfo.commit]]"
@@ -435,10 +429,6 @@ limitations under the License.
parent-is-current="[[_parentIsCurrent]]"
on-show-reply-dialog="_handleShowReplyDialog">
</gr-change-metadata>
- <!-- Plugins insert content into following container.
- Stop-gap until PolyGerrit plugins interface is ready.
- This will not work with Shadow DOM. -->
- <div id="change_plugins"></div>
</div>
<div id="mainChangeInfo" class="changeInfo-column mainChangeInfo">
<div id="commitAndRelated" class="hideOnMobileOverlay">
@@ -450,7 +440,7 @@ limitations under the License.
hidden$="[[!_loggedIn]]"
primary
disabled="[[_replyDisabled]]"
- on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
+ on-click="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
</div>
<div
id="commitMessage"
@@ -467,7 +457,7 @@ limitations under the License.
</gr-editable-content>
<gr-button link
class="editCommitMessage"
- on-tap="_handleEditCommitMessage"
+ on-click="_handleEditCommitMessage"
hidden$="[[_hideEditCommitMessage]]">Edit</gr-button>
<div class="changeId" hidden$="[[!_changeIdCommitMessageError]]">
<hr>
@@ -487,7 +477,7 @@ limitations under the License.
link
id="commitCollapseToggleButton"
class="collapseToggleButton"
- on-tap="_toggleCommitCollapsed">
+ on-click="_toggleCommitCollapsed">
[[_computeCollapseText(_commitCollapsed)]]
</gr-button>
</div>
@@ -515,7 +505,7 @@ limitations under the License.
link
id="relatedChangesToggleButton"
class="collapseToggleButton"
- on-tap="_toggleRelatedChangesCollapsed">
+ on-click="_toggleRelatedChangesCollapsed">
[[_computeCollapseText(_relatedChangesCollapsed)]]
</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 4788999235..02cf861afa 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -60,11 +60,11 @@
};
const CHANGE_DATA_TIMING_LABEL = 'ChangeDataLoaded';
+ const CHANGE_RELOAD_TIMING_LABEL = 'ChangeReloaded';
const SEND_REPLY_TIMING_LABEL = 'SendReply';
Polymer({
is: 'gr-change-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -203,7 +203,6 @@
_loading: Boolean,
/** @type {?} */
_projectConfig: Object,
- _rebaseOnCurrent: Boolean,
_replyButtonLabel: {
type: String,
value: 'Reply',
@@ -226,7 +225,8 @@
},
_changeStatuses: {
type: String,
- computed: '_computeChangeStatusChips(_change, _mergeable)',
+ computed:
+ '_computeChangeStatusChips(_change, _mergeable, _submitEnabled)',
},
_commitCollapsed: {
type: Boolean,
@@ -247,8 +247,14 @@
value: false,
observer: '_updateToggleContainerClass',
},
- _parentIsCurrent: Boolean,
- _submitEnabled: Boolean,
+ _parentIsCurrent: {
+ type: Boolean,
+ computed: '_isParentCurrent(_currentRevisionActions)',
+ },
+ _submitEnabled: {
+ type: Boolean,
+ computed: '_isSubmitEnabled(_currentRevisionActions)',
+ },
/** @type {?} */
_mergeable: {
@@ -281,6 +287,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
@@ -307,6 +314,7 @@
keyboardShortcuts() {
return {
[this.Shortcut.SEND_REPLY]: null, // DOC_ONLY binding
+ [this.Shortcut.EMOJI_DROPDOWN]: null, // DOC_ONLY binding
[this.Shortcut.REFRESH_CHANGE]: '_handleRefreshChange',
[this.Shortcut.OPEN_REPLY_DIALOG]: '_handleOpenReplyDialog',
[this.Shortcut.OPEN_DOWNLOAD_DIALOG]:
@@ -346,7 +354,7 @@
!== this._dynamicTabHeaderEndpoints.length) {
console.warn('Different number of tab headers and tab content.');
}
- });
+ }).then(() => this._setPrimaryTab());
this.addEventListener('comment-save', this._handleCommentSave.bind(this));
this.addEventListener('comment-refresh', this._reloadDrafts.bind(this));
@@ -410,12 +418,31 @@
this._showMessagesView = this.$.commentTabs.selected === 0;
},
- _handleFileTabChange() {
+ _handleFileTabChange(e) {
const selectedIndex = this.$$('#primaryTabs').selected;
this._showFileTabContent = selectedIndex === 0;
// Initial tab is the static files list.
- this._selectedFilesTabPluginEndpoint =
+ const newSelectedTab =
this._dynamicTabContentEndpoints[selectedIndex - 1];
+ if (newSelectedTab !== this._selectedFilesTabPluginEndpoint) {
+ this._selectedFilesTabPluginEndpoint = newSelectedTab;
+
+ const tabName = this._selectedFilesTabPluginEndpoint || 'files';
+ const source = e && e.type ? e.type : '';
+ this.$.reporting.reportInteraction('tab-changed',
+ `tabname: ${tabName}, source: ${source}`);
+ }
+ },
+
+ _handleShowTab(e) {
+ const idx = this._dynamicTabContentEndpoints.indexOf(e.detail.tab);
+ if (idx === -1) {
+ console.warn(e.detail.tab + ' tab not found');
+ return;
+ }
+ this.$$('#primaryTabs').selected = idx + 1;
+ this.$$('#primaryTabs').scrollIntoView();
+ this.$.reporting.reportInteraction('show-tab', e.detail.tab);
},
_handleEditCommitMessage(e) {
@@ -452,20 +479,33 @@
this._editingCommitMessage = false;
},
- _computeChangeStatusChips(change, mergeable) {
+ _computeChangeStatusChips(change, mergeable, submitEnabled) {
+ // Polymer 2: check for undefined
+ if ([
+ change,
+ mergeable,
+ ].some(arg => arg === undefined)) {
+ // To keep consistent with Polymer 1, we are returning undefined
+ // if not all dependencies are defined
+ return undefined;
+ }
+
// Show no chips until mergeability is loaded.
- if (mergeable === null || mergeable === undefined) { return []; }
+ if (mergeable === null) {
+ return [];
+ }
const options = {
includeDerived: true,
mergeable: !!mergeable,
- submitEnabled: this._submitEnabled,
+ submitEnabled: !!submitEnabled,
};
return this.changeStatuses(change, options);
},
_computeHideEditCommitMessage(loggedIn, editing, change, editMode) {
- if (!loggedIn || editing || change.status === this.ChangeStatus.MERGED ||
+ if (!loggedIn || editing ||
+ (change && change.status === this.ChangeStatus.MERGED) ||
editMode) {
return true;
}
@@ -495,6 +535,7 @@
},
_computeTotalCommentCounts(unresolvedCount, changeComments) {
+ if (!changeComments) return undefined;
const draftCount = changeComments.computeDraftCount();
const unresolvedString = GrCountStringFormatter.computeString(
unresolvedCount, 'unresolved');
@@ -736,17 +777,21 @@
});
},
+ _setPrimaryTab() {
+ // Selected has to be set after the paper-tabs are visible because
+ // the selected underline depends on calculations made by the browser.
+ this.$.commentTabs.selected = 0;
+ const primaryTabs = this.$$('#primaryTabs');
+ if (primaryTabs) primaryTabs.selected = 0;
+ },
+
_performPostLoadTasks() {
this._maybeShowReplyDialog();
this._maybeShowRevertDialog();
this._sendShowChangeEvent();
- // Selected has to be set after the paper-tabs are visible because
- // the selected underline depends on calculations made by the browser.
- this.$.commentTabs.selected = 0;
- const primaryTabs = this.$$('#primaryTabs');
- if (primaryTabs) primaryTabs.selected = 0;
+ this._setPrimaryTab();
this.async(() => {
if (this.viewState.scrollTop) {
@@ -759,7 +804,12 @@
});
},
- _paramsAndChangeChanged(value) {
+ _paramsAndChangeChanged(value, change) {
+ // Polymer 2: check for undefined
+ if ([value, change].some(arg => arg === undefined)) {
+ return;
+ }
+
// If the change number or patch range is different, then reset the
// selected file index.
const patchRangeState = this.viewState.patchRange;
@@ -814,7 +864,8 @@
Gerrit.awaitPluginsLoaded()
.then(this._getLoggedIn.bind(this))
.then(loggedIn => {
- if (!loggedIn || this._change.status !== this.ChangeStatus.MERGED) {
+ if (!loggedIn || !this._change ||
+ this._change.status !== this.ChangeStatus.MERGED) {
// Do not display dialog if not logged-in or the change is not
// merged.
return;
@@ -856,19 +907,22 @@
_changeChanged(change) {
if (!change || !this._patchRange || !this._allPatchSets) { return; }
+ // We get the parent first so we keep the original value for basePatchNum
+ // and not the updated value.
const parent = this._getBasePatchNum(change, this._patchRange);
- this.set('_patchRange.basePatchNum', parent);
this.set('_patchRange.patchNum', this._patchRange.patchNum ||
this.computeLatestPatchNum(this._allPatchSets));
+ this.set('_patchRange.basePatchNum', parent);
+
const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
this.fire('title-change', {title});
},
/**
* Gets base patch number, if it is a parent try and decide from
- * preference weather to default to `auto merge`, `Parent 1` or `PARENT`.
+ * preference whether to default to `auto merge`, `Parent 1` or `PARENT`.
*
* @param {Object} change
* @param {Object} patchRange
@@ -899,8 +953,8 @@
return 'PARENT';
},
- _computeShowPrimaryTabs(dynamicTabContentEndpoints) {
- return dynamicTabContentEndpoints.length > 0;
+ _computeShowPrimaryTabs(dynamicTabHeaderEndpoints) {
+ return dynamicTabHeaderEndpoints && dynamicTabHeaderEndpoints.length > 0;
},
_computeChangeUrl(change) {
@@ -933,6 +987,11 @@
},
_computeChangeIdCommitMessageError(commitMessage, change) {
+ // Polymer 2: check for undefined
+ if ([commitMessage, change].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (!commitMessage) { return CHANGE_ID_ERROR.MISSING; }
// Find the last match in the commit message:
@@ -987,6 +1046,11 @@
},
_computeReplyButtonLabel(changeRecord, canStartReview) {
+ // Polymer 2: check for undefined
+ if ([changeRecord, canStartReview].some(arg => arg === undefined)) {
+ return 'Reply';
+ }
+
if (canStartReview) {
return 'Start review';
}
@@ -1152,6 +1216,7 @@
},
_getProjectConfig() {
+ if (!this._change) return;
return this.$.restAPI.getProjectConfig(this._change.project).then(
config => {
this._projectConfig = config;
@@ -1162,18 +1227,6 @@
return this.$.restAPI.getPreferences();
},
- _updateRebaseAction(revisionActions) {
- if (revisionActions && revisionActions.rebase) {
- revisionActions.rebase.rebaseOnCurrent =
- !!revisionActions.rebase.enabled;
- this._parentIsCurrent = !revisionActions.rebase.enabled;
- revisionActions.rebase.enabled = true;
- } else {
- this._parentIsCurrent = true;
- }
- return revisionActions;
- },
-
_prepareCommitMsgForLinkify(msg) {
// TODO(wyatta) switch linkify sequence, see issue 5526.
// This is a zero-with space. It is added to prevent the linkify library
@@ -1201,7 +1254,7 @@
if (!this._patchRange.patchNum &&
change.current_revision === edit.base_revision) {
change.current_revision = edit.commit.commit;
- this._patchRange.patchNum = this.EDIT_NAME;
+ this.set('_patchRange.patchNum', this.EDIT_NAME);
// Because edits are fibbed as revisions and added to the revisions
// array, and revision actions are always derived from the 'latest'
// patch set, we must copy over actions from the patch set base.
@@ -1239,8 +1292,6 @@
this._latestCommitMessage = null;
}
- // Update the submit enabled based on current revision.
- this._submitEnabled = this._isSubmitEnabled(currentRevision);
const lineHeight = getComputedStyle(this).lineHeight;
@@ -1257,8 +1308,6 @@
currentRevision.commit.commit = latestRevisionSha;
}
this._commitInfo = currentRevision.commit;
- this._currentRevisionActions =
- this._updateRebaseAction(currentRevision.actions);
this._selectedRevision = currentRevision;
// TODO: Fetch and process files.
} else {
@@ -1270,9 +1319,17 @@
});
},
- _isSubmitEnabled(currentRevision) {
- return !!(currentRevision.actions && currentRevision.actions.submit &&
- currentRevision.actions.submit.enabled);
+ _isSubmitEnabled(revisionActions) {
+ return !!(revisionActions && revisionActions.submit &&
+ revisionActions.submit.enabled);
+ },
+
+ _isParentCurrent(revisionActions) {
+ if (revisionActions && revisionActions.rebase) {
+ return !revisionActions.rebase.enabled;
+ } else {
+ return true;
+ }
},
_getEdit() {
@@ -1282,6 +1339,7 @@
_getLatestCommitMessage() {
return this.$.restAPI.getChangeCommitInfo(this._changeNum,
this.computeLatestPatchNum(this._allPatchSets)).then(commitInfo => {
+ if (!commitInfo) return Promise.resolve();
this._latestCommitMessage =
this._prepareCommitMsgForLinkify(commitInfo.message);
});
@@ -1349,15 +1407,17 @@
/**
* Reload the change.
*
- * @param {boolean=} opt_reloadRelatedChanges Reloads the related chanegs
- * when true.
+ * @param {boolean=} opt_isLocationChange Reloads the related changes
+ * when true and ends reporting events that started on location change.
* @return {Promise} A promise that resolves when the core data has loaded.
* Some non-core data loading may still be in-flight when the core data
* promise resolves.
*/
- _reload(opt_reloadRelatedChanges) {
+ _reload(opt_isLocationChange) {
this._loading = true;
this._relatedChangesCollapsed = true;
+ this.$.reporting.time(CHANGE_RELOAD_TIMING_LABEL);
+ this.$.reporting.time(CHANGE_DATA_TIMING_LABEL);
// Array to house all promises related to data requests.
const allDataPromises = [];
@@ -1370,7 +1430,13 @@
// Resolves when the loading flag is set to false, meaning that some
// change content may start appearing.
const loadingFlagSet = detailCompletes
- .then(() => { this._loading = false; });
+ .then(() => { this._loading = false; })
+ .then(() => {
+ this.$.reporting.timeEnd(CHANGE_RELOAD_TIMING_LABEL);
+ if (opt_isLocationChange) {
+ this.$.reporting.changeDisplayed();
+ }
+ });
// Resolves when the project config has loaded.
const projectConfigLoaded = detailCompletes
@@ -1430,20 +1496,20 @@
coreDataPromise = mergeabilityLoaded;
}
- if (opt_reloadRelatedChanges) {
+ if (opt_isLocationChange) {
const relatedChangesLoaded = coreDataPromise
.then(() => this.$.relatedChanges.reload());
allDataPromises.push(relatedChangesLoaded);
}
- this.$.reporting.time(CHANGE_DATA_TIMING_LABEL);
Promise.all(allDataPromises).then(() => {
this.$.reporting.timeEnd(CHANGE_DATA_TIMING_LABEL);
- this.$.reporting.changeFullyLoaded();
+ if (opt_isLocationChange) {
+ this.$.reporting.changeFullyLoaded();
+ }
});
- return coreDataPromise
- .then(() => { this.$.reporting.changeDisplayed(); });
+ return coreDataPromise;
},
/**
@@ -1458,6 +1524,10 @@
},
_getMergeability() {
+ if (!this._change) {
+ this._mergeable = null;
+ return Promise.resolve();
+ }
// If the change is closed, it is not mergeable. Note: already merged
// changes are obviously not mergeable, but the mergeability API will not
// answer for abandoned changes.
@@ -1595,8 +1665,7 @@
_computeShowRelatedToggle() {
// Make sure the max height has been applied, since there is now content
// to populate.
- // TODO update to polymer 2.x syntax
- if (!this.getComputedStyleValue('--relation-chain-max-height')) {
+ if (!util.getComputedStyleValue('--relation-chain-max-height', this)) {
this._updateRelatedChangeMaxHeight();
}
// Prevents showMore from showing when click on related change, since the
@@ -1690,6 +1759,10 @@
},
_computeEditMode(patchRangeRecord, paramsRecord) {
+ if ([patchRangeRecord, paramsRecord].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (paramsRecord.base && paramsRecord.base.edit) { return true; }
const patchRange = patchRangeRecord.base || {};
@@ -1775,7 +1848,7 @@
},
_computeCurrentRevision(currentRevision, revisions) {
- return revisions && revisions[currentRevision];
+ return currentRevision && revisions && revisions[currentRevision];
},
_computeDiffPrefsDisabled(disableDiffPrefs, loggedIn) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index f74160b503..e00bab5860 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="../../edit/gr-edit-constants.html">
<link rel="import" href="gr-change-view.html">
@@ -79,7 +81,7 @@ limitations under the License.
});
element = fixture('basic');
sandbox.stub(element.$.actions, 'reload').returns(Promise.resolve());
- Gerrit._setPluginsCount(0);
+ Gerrit._loadPlugins([]);
});
teardown(done => {
@@ -90,9 +92,7 @@ limitations under the License.
});
getCustomCssValue = cssParam => {
- // TODO: Update to be compatible with 2.x when we upgrade from
- // 1.x to 2.x.
- return element.getComputedStyleValue(cssParam);
+ return util.getComputedStyleValue(cssParam, element);
};
test('_handleMessageAnchorTap', () => {
@@ -345,8 +345,10 @@ limitations under the License.
],
};
setup(() => {
+ // Fake computeDraftCount as its required for ChangeComments,
+ // see gr-comment-api#reloadDrafts.
reloadStub = sandbox.stub(element.$.commentAPI, 'reloadDrafts')
- .returns(Promise.resolve({drafts}));
+ .returns(Promise.resolve({drafts, computeDraftCount: () => 1}));
});
test('drafts are reloaded when reload-drafts fired', done => {
@@ -530,65 +532,11 @@ limitations under the License.
assert.equal(result, 'CC=\u200Btest@google.com');
}),
- test('_updateRebaseAction', () => {
- const currentRevisionActions = {
- cherrypick: {
- enabled: true,
- label: 'Cherry Pick',
- method: 'POST',
- title: 'cherrypick',
- },
- rebase: {
- enabled: true,
- label: 'Rebase',
- method: 'POST',
- title: 'Rebase onto tip of branch or parent change',
- },
- };
- element._parentIsCurrent = undefined;
-
- // Rebase enabled should always end up true.
- // When rebase is enabled initially, rebaseOnCurrent should be set to
- // true.
- assert.equal(element._updateRebaseAction(currentRevisionActions),
- currentRevisionActions);
-
- assert.isTrue(currentRevisionActions.rebase.enabled);
- assert.isTrue(currentRevisionActions.rebase.rebaseOnCurrent);
- assert.isFalse(element._parentIsCurrent);
-
- delete currentRevisionActions.rebase.enabled;
-
- // When rebase is not enabled initially, rebaseOnCurrent should be set to
- // false.
- assert.equal(element._updateRebaseAction(currentRevisionActions),
- currentRevisionActions);
-
- assert.isTrue(currentRevisionActions.rebase.enabled);
- assert.isFalse(currentRevisionActions.rebase.rebaseOnCurrent);
- assert.isTrue(element._parentIsCurrent);
- });
-
test('_isSubmitEnabled', () => {
assert.isFalse(element._isSubmitEnabled({}));
- assert.isFalse(element._isSubmitEnabled({actions: {}}));
- assert.isFalse(element._isSubmitEnabled({actions: {submit: {}}}));
+ assert.isFalse(element._isSubmitEnabled({submit: {}}));
assert.isTrue(element._isSubmitEnabled(
- {actions: {submit: {enabled: true}}}));
- });
-
- test('_updateRebaseAction sets _parentIsCurrent on no rebase', () => {
- const currentRevisionActions = {
- cherrypick: {
- enabled: true,
- label: 'Cherry Pick',
- method: 'POST',
- title: 'cherrypick',
- },
- };
- element._parentIsCurrent = undefined;
- element._updateRebaseAction(currentRevisionActions);
- assert.isTrue(element._parentIsCurrent);
+ {submit: {enabled: true}}));
});
test('_reload is called when an approved label is removed', () => {
@@ -1599,32 +1547,44 @@ limitations under the License.
sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
// Delete
- fileList.dispatchEvent(new CustomEvent('file-action-tap',
- {detail: {action: Actions.DELETE.id, path: 'foo'}, bubbles: true}));
+ fileList.dispatchEvent(new CustomEvent('file-action-tap', {
+ detail: {action: Actions.DELETE.id, path: 'foo'},
+ bubbles: true,
+ composed: true,
+ }));
flushAsynchronousOperations();
assert.isTrue(controls.openDeleteDialog.called);
assert.equal(controls.openDeleteDialog.lastCall.args[0], 'foo');
// Restore
- fileList.dispatchEvent(new CustomEvent('file-action-tap',
- {detail: {action: Actions.RESTORE.id, path: 'foo'}, bubbles: true}));
+ fileList.dispatchEvent(new CustomEvent('file-action-tap', {
+ detail: {action: Actions.RESTORE.id, path: 'foo'},
+ bubbles: true,
+ composed: true,
+ }));
flushAsynchronousOperations();
assert.isTrue(controls.openRestoreDialog.called);
assert.equal(controls.openRestoreDialog.lastCall.args[0], 'foo');
// Rename
- fileList.dispatchEvent(new CustomEvent('file-action-tap',
- {detail: {action: Actions.RENAME.id, path: 'foo'}, bubbles: true}));
+ fileList.dispatchEvent(new CustomEvent('file-action-tap', {
+ detail: {action: Actions.RENAME.id, path: 'foo'},
+ bubbles: true,
+ composed: true,
+ }));
flushAsynchronousOperations();
assert.isTrue(controls.openRenameDialog.called);
assert.equal(controls.openRenameDialog.lastCall.args[0], 'foo');
// Open
- fileList.dispatchEvent(new CustomEvent('file-action-tap',
- {detail: {action: Actions.OPEN.id, path: 'foo'}, bubbles: true}));
+ fileList.dispatchEvent(new CustomEvent('file-action-tap', {
+ detail: {action: Actions.OPEN.id, path: 'foo'},
+ bubbles: true,
+ composed: true,
+ }));
flushAsynchronousOperations();
assert.isTrue(Gerrit.Nav.getEditUrlForDiff.called);
@@ -1830,5 +1790,50 @@ limitations under the License.
MockInteractions.tap(element.$.changeStar.$$('button'));
assert.isTrue(stub.called);
});
+
+ suite('gr-reporting tests', () => {
+ setup(() => {
+ element._patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 1,
+ };
+ sandbox.stub(element, '_getChangeDetail').returns(Promise.resolve());
+ sandbox.stub(element, '_getProjectConfig').returns(Promise.resolve());
+ sandbox.stub(element, '_reloadComments').returns(Promise.resolve());
+ sandbox.stub(element, '_getMergeability').returns(Promise.resolve());
+ sandbox.stub(element, '_getLatestCommitMessage')
+ .returns(Promise.resolve());
+ });
+
+ test('don\'t report changedDisplayed on reply', done => {
+ const changeDisplayStub =
+ sandbox.stub(element.$.reporting, 'changeDisplayed');
+ const changeFullyLoadedStub =
+ sandbox.stub(element.$.reporting, 'changeFullyLoaded');
+ element._handleReplySent();
+ flush(() => {
+ assert.isFalse(changeDisplayStub.called);
+ assert.isFalse(changeFullyLoadedStub.called);
+ done();
+ });
+ });
+
+ test('report changedDisplayed on _paramsChanged', done => {
+ const changeDisplayStub =
+ sandbox.stub(element.$.reporting, 'changeDisplayed');
+ const changeFullyLoadedStub =
+ sandbox.stub(element.$.reporting, 'changeFullyLoaded');
+ element._paramsChanged({
+ view: Gerrit.Nav.View.CHANGE,
+ changeNum: 101,
+ project: 'test-project',
+ });
+ flush(() => {
+ assert.isTrue(changeDisplayStub.called);
+ assert.isTrue(changeFullyLoadedStub.called);
+ done();
+ });
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index e4183df6ff..a7e65a300b 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -18,7 +18,7 @@ limitations under the License.
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -43,10 +43,10 @@ limitations under the License.
}
.container {
display: flex;
- margin: .5em 0;
+ margin: var(--spacing-m) 0;
}
.lineNum {
- margin-right: .5em;
+ margin-right: var(--spacing-m);
min-width: 10em;
text-align: right;
}
@@ -57,7 +57,7 @@ limitations under the License.
@media screen and (max-width: 50em) {
.container {
flex-direction: column;
- margin: 0 0 .5em .5em;
+ margin: 0 0 var(--spacing-m) var(--spacing-m);
}
.lineNum {
min-width: initial;
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index b19ca62dce..42cb976ff3 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -18,7 +18,6 @@
'use strict';
Polymer({
is: 'gr-comment-list',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.BaseUrlBehavior,
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index 9996abce14..c18ae8d9f1 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-comment-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-comment-list.html">
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
index b6c8fcc6c2..902bf41095 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
index ddab319bfe..e2fcdffb2c 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-commit-info',
- _legacyUndefinedCheck: true,
properties: {
change: Object,
@@ -47,11 +46,21 @@
},
_computeShowWebLink(change, commitInfo, serverConfig) {
+ // Polymer 2: check for undefined
+ if ([change, commitInfo, serverConfig].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const weblink = this._getWeblink(change, commitInfo, serverConfig);
return !!weblink && !!weblink.url;
},
_computeWebLink(change, commitInfo, serverConfig) {
+ // Polymer 2: check for undefined
+ if ([change, commitInfo, serverConfig].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const {url} = this._getWeblink(change, commitInfo, serverConfig) || {};
return url;
},
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
index 438a3f300e..f271a706e5 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-commit-info</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../core/gr-router/gr-router.html">
<link rel="import" href="gr-commit-info.html">
@@ -119,6 +121,7 @@ limitations under the License.
},
],
};
+ element.serverConfig = {};
assert.isOk(element._computeShowWebLink(element.change,
element.commitInfo, element.serverConfig));
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
index 8803eb3e62..05a2bb2575 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -42,6 +43,8 @@ limitations under the License.
}
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
padding: 0;
width: 73ch; /* Add a char to account for the border. */
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
index a371e13eb9..524876e21f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-confirm-abandon-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -38,6 +37,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -55,6 +55,7 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this._confirm();
},
@@ -64,6 +65,7 @@
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
index 95d53740ec..cc4b80ea6a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-abandon-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-abandon-dialog.html">
@@ -49,19 +51,26 @@ limitations under the License.
test('_handleConfirmTap', () => {
const confirmHandler = sandbox.stub();
element.addEventListener('confirm', confirmHandler);
- sandbox.stub(element, '_confirm');
+ sandbox.spy(element, '_handleConfirmTap');
+ sandbox.spy(element, '_confirm');
element.$$('gr-dialog').fire('confirm');
assert.isTrue(confirmHandler.called);
+ assert.isTrue(confirmHandler.calledOnce);
+ assert.isTrue(element._handleConfirmTap.called);
assert.isTrue(element._confirm.called);
+ assert.isTrue(element._confirm.called);
+ assert.isTrue(element._confirm.calledOnce);
});
test('_handleCancelTap', () => {
const cancelHandler = sandbox.stub();
element.addEventListener('cancel', cancelHandler);
- sandbox.stub(element, '_handleCancelTap');
+ sandbox.spy(element, '_handleCancelTap');
element.$$('gr-dialog').fire('cancel');
assert.isTrue(cancelHandler.called);
+ assert.isTrue(cancelHandler.calledOnce);
assert.isTrue(element._handleCancelTap.called);
+ assert.isTrue(element._handleCancelTap.calledOnce);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html
index cd196eceae..b9e91559a6 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
index 2e8af0e140..a0da331fa3 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
@@ -19,7 +19,10 @@
Polymer({
is: 'gr-confirm-cherrypick-conflict-dialog',
- _legacyUndefinedCheck: true,
+
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
/**
* Fired when the confirm button is pressed.
@@ -35,11 +38,13 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
index 77b102ca2c..f411de45b5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-cherrypick-conflict-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-cherrypick-conflict-dialog.html">
@@ -47,19 +49,23 @@ limitations under the License.
test('_handleConfirmTap', () => {
const confirmHandler = sandbox.stub();
element.addEventListener('confirm', confirmHandler);
- sandbox.stub(element, '_handleConfirmTap');
+ sandbox.spy(element, '_handleConfirmTap');
element.$$('gr-dialog').fire('confirm');
assert.isTrue(confirmHandler.called);
+ assert.isTrue(confirmHandler.calledOnce);
assert.isTrue(element._handleConfirmTap.called);
+ assert.isTrue(element._handleConfirmTap.calledOnce);
});
test('_handleCancelTap', () => {
const cancelHandler = sandbox.stub();
element.addEventListener('cancel', cancelHandler);
- sandbox.stub(element, '_handleCancelTap');
+ sandbox.spy(element, '_handleCancelTap');
element.$$('gr-dialog').fire('cancel');
assert.isTrue(cancelHandler.called);
+ assert.isTrue(cancelHandler.calledOnce);
assert.isTrue(element._handleCancelTap.called);
+ assert.isTrue(element._handleCancelTap.calledOnce);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
index 84558ac427..8ddcd83a9a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
@@ -15,8 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
@@ -43,16 +45,16 @@ limitations under the License.
.main label,
.main input[type="text"] {
display: block;
- font: inherit;
width: 100%;
}
.main .message {
- border: groove;
width: 100%;
}
iron-autogrow-textarea {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
padding: 0;
-
--iron-autogrow-textarea: {
font-family: var(--monospace-font-family);
width: 72ch;
@@ -77,12 +79,17 @@ limitations under the License.
<label for="baseInput">
Provide base commit sha1 for cherry-pick
</label>
- <input
- is="iron-input"
- id="baseCommitInput"
+ <iron-input
maxlength="40"
placeholder="(optional)"
bind-value="{{baseCommit}}">
+ <input
+ is="iron-input"
+ id="baseCommitInput"
+ maxlength="40"
+ placeholder="(optional)"
+ bind-value="{{baseCommit}}">
+ </iron-input>
<label for="messageInput">
Cherry Pick Commit Message
</label>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
index bdc0bb2722..5278540d15 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-confirm-cherrypick-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -51,11 +50,24 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_computeMessage(changeStatus, commitNum, commitMessage)',
],
_computeMessage(changeStatus, commitNum, commitMessage) {
+ // Polymer 2: check for undefined
+ if ([
+ changeStatus,
+ commitNum,
+ commitMessage,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
let newMessage = commitMessage;
if (changeStatus === 'MERGED') {
@@ -66,11 +78,13 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
index 5c51fe0bb5..22a2abab3a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-cherrypick-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-cherrypick-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
index 621ef0a942..24c1132887 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
@@ -47,11 +48,9 @@ limitations under the License.
.main label,
.main input[type="text"] {
display: block;
- font: inherit;
width: 100%;
}
.main .message {
- border: groove;
width: 100%;
}
.warning {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
index 91600ff541..c6b0adfd83 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-confirm-move-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -47,13 +46,19 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
index e619425225..8d6e029d54 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-move-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-move-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
index 912bbfa6a2..cf2721ac78 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -41,14 +41,13 @@ limitations under the License.
.parentRevisionContainer label,
.parentRevisionContainer input[type="text"] {
display: block;
- font: inherit;
width: 100%;
}
.parentRevisionContainer label {
- margin-bottom: .2em;
+ margin-bottom: var(--spacing-xs);
}
.rebaseOption {
- margin: .5em 0;
+ margin: var(--spacing-m) 0;
}
</style>
<gr-dialog
@@ -63,7 +62,7 @@ limitations under the License.
<input id="rebaseOnParentInput"
name="rebaseOptions"
type="radio"
- on-tap="_handleRebaseOnParent">
+ on-click="_handleRebaseOnParent">
<label id="rebaseOnParentLabel" for="rebaseOnParentInput">
Rebase on parent change
</label>
@@ -78,7 +77,7 @@ limitations under the License.
name="rebaseOptions"
type="radio"
disabled$="[[!_displayTipOption(rebaseOnCurrent, hasParent)]]"
- on-tap="_handleRebaseOnTip">
+ on-click="_handleRebaseOnTip">
<label id="rebaseOnTipLabel" for="rebaseOnTipInput">
Rebase on top of the [[branch]]
branch<span hidden$="[[!hasParent]]">
@@ -94,7 +93,7 @@ limitations under the License.
<input id="rebaseOnOtherInput"
name="rebaseOptions"
type="radio"
- on-tap="_handleRebaseOnOther">
+ on-click="_handleRebaseOnOther">
<label id="rebaseOnOtherLabel" for="rebaseOnOtherInput">
Rebase on a specific change, ref, or commit <span hidden$="[[!hasParent]]">
(breaks relation chain)
@@ -107,7 +106,7 @@ limitations under the License.
query="[[_query]]"
no-debounce
text="{{_text}}"
- on-tap="_handleEnterChangeNumberTap"
+ on-click="_handleEnterChangeNumberClick"
allow-non-suggested-values
placeholder="Change number, ref, or commit hash">
</gr-autocomplete>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 77bc798f7b..54ce271b5b 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-confirm-rebase-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -120,6 +119,7 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(new CustomEvent('confirm',
{detail: {base: this._getSelectedBase()}}));
this._text = '';
@@ -127,6 +127,7 @@
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(new CustomEvent('cancel'));
this._text = '';
},
@@ -135,7 +136,7 @@
this.$.parentInput.focus();
},
- _handleEnterChangeNumberTap() {
+ _handleEnterChangeNumberClick() {
this.$.rebaseOnOtherInput.checked = true;
},
@@ -144,6 +145,11 @@
* the corresponding value to be submitted.
*/
_updateSelectedOption(rebaseOnCurrent, hasParent) {
+ // Polymer 2: check for undefined
+ if ([rebaseOnCurrent, hasParent].some(arg => arg === undefined)) {
+ return;
+ }
+
if (this._displayParentOption(rebaseOnCurrent, hasParent)) {
this.$.rebaseOnParentInput.checked = true;
} else if (this._displayTipOption(rebaseOnCurrent, hasParent)) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index c6e9ec4804..cd5b13087c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-rebase-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-rebase-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
index 9e5f1de4ef..2e1e6ae2c9 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<dom-module id="gr-confirm-revert-dialog">
<template>
@@ -37,6 +39,8 @@ limitations under the License.
}
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
padding: 0;
width: 73ch; /* Add a char to account for the border. */
@@ -53,15 +57,17 @@ limitations under the License.
on-cancel="_handleCancelTap">
<div class="header" slot="header">Revert Merged Change</div>
<div class="main" slot="main">
- <label for="messageInput">
- Revert Commit Message
- </label>
- <iron-autogrow-textarea
- id="messageInput"
- class="message"
- autocomplete="on"
- max-rows="15"
- bind-value="{{message}}"></iron-autogrow-textarea>
+ <gr-endpoint-decorator name="confirm-revert-change">
+ <label for="messageInput">
+ Revert Commit Message
+ </label>
+ <iron-autogrow-textarea
+ id="messageInput"
+ class="message"
+ autocomplete="on"
+ max-rows="15"
+ bind-value="{{message}}"></iron-autogrow-textarea>
+ </gr-endpoint-decorator>
</div>
</gr-dialog>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
index 93e21c77c8..662b64c349 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-confirm-revert-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -40,6 +39,10 @@
message: String,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
populateRevertMessage(message, commitHash) {
// Figure out what the revert title should be.
const originalTitle = message.split('\n')[0];
@@ -56,11 +59,13 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index c5a1bde748..6e41555051 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-revert-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-confirm-revert-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.html
index b9c32ed570..02385cfb8d 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.html
@@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-confirm-submit-dialog">
@@ -29,7 +31,7 @@ limitations under the License.
min-width: 40em;
}
p {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
@media screen and (max-width: 50em) {
#dialog {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
index 282b394b50..8c9377815e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-confirm-submit-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -61,11 +60,13 @@
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(new CustomEvent('confirm', {bubbles: false}));
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(new CustomEvent('cancel', {bubbles: false}));
},
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
index dd61d1c1c9..56a9485366 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-confirm-submit-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="gr-confirm-submit-dialog.html">
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index 28d25d27da..e20bbd76a7 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -31,7 +32,7 @@ limitations under the License.
}
section {
display: flex;
- padding: .5em 1.5em;
+ padding: var(--spacing-m) var(--spacing-xl);
}
section:not(:first-of-type) {
border-top: 1px solid var(--border-color);
@@ -39,7 +40,7 @@ limitations under the License.
.flexContainer {
display: flex;
justify-content: space-between;
- padding-top: .75em;
+ padding-top: var(--spacing-m);
}
.footer {
justify-content: flex-end;
@@ -52,15 +53,15 @@ limitations under the License.
}
.patchFiles,
.archivesContainer {
- padding-bottom: .5em;
+ padding-bottom: var(--spacing-m);
}
.patchFiles {
- margin-right: 2em;
+ margin-right: var(--spacing-xxl);
}
.patchFiles a,
.archives a {
display: inline-block;
- margin-right: 1em;
+ margin-right: var(--spacing-l);
}
.patchFiles a:last-of-type,
.archives a:last-of-type {
@@ -120,7 +121,7 @@ limitations under the License.
<span class="closeButtonContainer">
<gr-button id="closeButton"
link
- on-tap="_handleCloseTap">Close</gr-button>
+ on-click="_handleCloseTap">Close</gr-button>
</span>
</section>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index b6c155e2ad..b297a14a31 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-download-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the user presses the close button.
@@ -48,6 +47,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
],
@@ -70,16 +70,17 @@
_computeDownloadCommands(change, patchNum, _selectedScheme) {
let commandObj;
+ if (!change) return [];
for (const rev of Object.values(change.revisions || {})) {
if (this.patchNumEquals(rev._number, patchNum) &&
- rev.fetch.hasOwnProperty(_selectedScheme)) {
+ rev && rev.fetch && rev.fetch.hasOwnProperty(_selectedScheme)) {
commandObj = rev.fetch[_selectedScheme].commands;
break;
}
}
const commands = [];
for (const title in commandObj) {
- if (!commandObj.hasOwnProperty(title)) { continue; }
+ if (!commandObj || !commandObj.hasOwnProperty(title)) { continue; }
commands.push({
title,
command: commandObj[title],
@@ -116,6 +117,10 @@
* @return {string} Not sure why there was a mismatch
*/
_computeDownloadLink(change, patchNum, opt_zip) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum].some(arg => arg === undefined)) {
+ return '';
+ }
return this.changeBaseURL(change.project, change._number, patchNum) +
'/patch?' + (opt_zip ? 'zip' : 'download');
},
@@ -129,6 +134,11 @@
* @return {string}
*/
_computeDownloadFilename(change, patchNum, opt_zip) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum].some(arg => arg === undefined)) {
+ return '';
+ }
+
let shortRev = '';
for (const rev in change.revisions) {
if (this.patchNumEquals(change.revisions[rev]._number, patchNum)) {
@@ -140,6 +150,10 @@
},
_computeHidePatchFile(change, patchNum) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum].some(arg => arg === undefined)) {
+ return false;
+ }
for (const rev of Object.values(change.revisions || {})) {
if (this.patchNumEquals(rev._number, patchNum)) {
const parentLength = rev.commit && rev.commit.parents ?
@@ -151,11 +165,20 @@
},
_computeArchiveDownloadLink(change, patchNum, format) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum, format].some(arg => arg === undefined)) {
+ return '';
+ }
return this.changeBaseURL(change.project, change._number, patchNum) +
'/archive?format=' + format;
},
_computeSchemes(change, patchNum) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum].some(arg => arg === undefined)) {
+ return [];
+ }
+
for (const rev of Object.values(change.revisions || {})) {
if (this.patchNumEquals(rev._number, patchNum)) {
const fetch = rev.fetch;
@@ -175,6 +198,7 @@
_handleCloseTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('close', null, {bubbles: false});
},
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 214363b1cf..82574808b4 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-download-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-download-dialog.html">
@@ -144,7 +146,8 @@ limitations under the License.
});
test('anchors use download attribute', () => {
- const anchors = Polymer.dom(element.root).querySelectorAll('a');
+ const anchors = Array.from(
+ Polymer.dom(element.root).querySelectorAll('a'));
assert.isTrue(!anchors.some(a => !a.hasAttribute('download')));
});
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index f7d90eb472..9146125d4e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
@@ -29,6 +30,7 @@ limitations under the License.
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../gr-file-list-constants.html">
+<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<dom-module id="gr-file-list-header">
<template>
@@ -47,7 +49,7 @@ limitations under the License.
background-color: var(--table-header-background-color);
border-top: 1px solid var(--border-color);
display: flex;
- padding: 6px var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
.patchInfo-left {
align-items: baseline;
@@ -143,7 +145,7 @@ limitations under the License.
margin-right: -5px;
}
.fileViewActionsLabel {
- margin-right: .2rem;
+ margin-right: var(--spacing-xs);
}
@media screen and (max-width: 50em) {
.patchInfo-header .desktop {
@@ -216,28 +218,28 @@ limitations under the License.
<span class$="[[_computeUploadHelpContainerClass(change, account)]]">
<gr-button link
class="upload"
- on-tap="_handleUploadTap">Update Change</gr-button>
+ on-click="_handleUploadTap">Update Change</gr-button>
</span>
<span class="downloadContainer desktop">
<gr-button link
class="download"
- on-tap="_handleDownloadTap">Download</gr-button>
+ on-click="_handleDownloadTap">Download</gr-button>
</span>
<span class$="includedInContainer [[_hideIncludedIn(change)]] desktop">
<gr-button link
class="includedIn"
- on-tap="_handleIncludedInTap">Included In</gr-button>
+ on-click="_handleIncludedInTap">Included In</gr-button>
</span>
<template is="dom-if"
if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
<gr-button
id="expandBtn"
link
- on-tap="_expandAllDiffs">Expand All</gr-button>
+ on-click="_expandAllDiffs">Expand All</gr-button>
<gr-button
id="collapseBtn"
link
- on-tap="_collapseAllDiffs">Collapse All</gr-button>
+ on-click="_collapseAllDiffs">Collapse All</gr-button>
</template>
<template is="dom-if"
if="[[!_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
@@ -261,7 +263,7 @@ limitations under the License.
has-tooltip
title="Diff preferences"
class="prefsButton desktop"
- on-tap="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
+ on-click="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
</span>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
index 8321879ee0..d6fbfc4e18 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
@@ -23,7 +23,6 @@
Polymer({
is: 'gr-file-list-header',
- _legacyUndefinedCheck: true,
/**
* @event expand-diffs
@@ -94,6 +93,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
],
@@ -132,10 +132,20 @@
},
_computeDescriptionReadOnly(loggedIn, change, account) {
+ // Polymer 2: check for undefined
+ if ([loggedIn, change, account].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return !(loggedIn && (account._account_id === change.owner._account_id));
},
_computePatchSetDescription(change, patchNum) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum].some(arg => arg === undefined)) {
+ return;
+ }
+
const rev = this.getRevisionByPatchNum(change.revisions, patchNum);
this._patchsetDescription = (rev && rev.description) ?
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
@@ -217,6 +227,7 @@
_handleDownloadTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(
new CustomEvent('open-download-dialog', {bubbles: false}));
},
@@ -239,6 +250,7 @@
_handleUploadTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.dispatchEvent(
new CustomEvent('open-upload-help-dialog', {bubbles: false}));
},
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index adfeeb4c30..ac626ab61a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-file-list-header</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="gr-file-list-header.html">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index f9ee54fca9..649aa5311b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/async-foreach-behavior/async-foreach-behavior.html">
<link rel="import" href="../../../behaviors/dom-util-behavior/dom-util-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -47,8 +48,8 @@ limitations under the License.
align-items: center;
border-top: 1px solid var(--border-color);
display: flex;
- min-height: 2.25em;
- padding: .2em var(--default-horizontal-margin) .2em calc(var(--default-horizontal-margin) - .35rem);
+ min-height: calc(var(--line-height-normal) + 2*var(--spacing-s));
+ padding: var(--spacing-xs) var(--spacing-l) var(--spacing-xs) calc(var(--spacing-l) - .35rem);
}
:host(.loading) .row {
opacity: .5;
@@ -115,16 +116,13 @@ limitations under the License.
cursor: pointer;
flex: 1;
text-decoration: none;
- white-space: nowrap;
+ /* Wrap it into multiple lines if too long. */
+ white-space: normal;
+ word-break: break-word;
}
.path:hover :first-child {
text-decoration: underline;
}
- .path,
- .path div {
- overflow: hidden;
- text-overflow: ellipsis;
- }
.oldPath {
color: var(--deemphasized-text-color);
}
@@ -137,16 +135,18 @@ limitations under the License.
min-width: 7.5em;
}
.comments {
- padding-left: 2em;
- min-width: 20em;
+ padding-left: var(--spacing-l);
+ min-width: 7.5em;
}
.row:not(.header-row) .stats,
.total-stats {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
display: flex;
}
.sizeBars {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
min-width: 7em;
text-align: center;
}
@@ -165,18 +165,18 @@ limitations under the License.
color: var(--vote-text-color-disliked);
text-align: left;
min-width: 4em;
- padding-left: 0.5em;
+ padding-left: var(--spacing-s);
}
.drafts {
color: #C62828;
font-weight: var(--font-weight-bold);
}
.show-hide {
- margin-left: .35em;
+ margin-left: var(--spacing-s);
width: 1.9em;
}
.fileListButton {
- margin: .5em;
+ margin: var(--spacing-m);
}
.totalChanges {
justify-content: flex-end;
@@ -200,15 +200,11 @@ limitations under the License.
.truncatedFileName {
display: none;
}
- .expanded .fullFileName {
- white-space: normal;
- word-wrap: break-word;
- }
.mobile {
display: none;
}
.reviewed {
- margin-left: 2em;
+ margin-left: var(--spacing-xxl);
width: 15em;
}
.reviewed label {
@@ -234,7 +230,7 @@ limitations under the License.
}
.reviewedLabel {
color: var(--deemphasized-text-color);
- margin-right: 1em;
+ margin-right: var(--spacing-l);
opacity: 0;
}
.reviewedLabel.isReviewed {
@@ -247,10 +243,12 @@ limitations under the License.
.markReviewed,
.pathLink {
display: inline-block;
- margin: -.2em 0;
- padding: .4em 0;
+ margin: -2px 0;
+ padding: var(--spacing-s) 0;
}
- @media screen and (max-width: 50em) {
+
+ /** small screen breakpoint: 768px */
+ @media screen and (max-width: 55em) {
.desktop {
display: none;
}
@@ -285,7 +283,7 @@ limitations under the License.
</style>
<div
id="container"
- on-tap="_handleFileListTap">
+ on-click="_handleFileListClick">
<div class="header-row row">
<div class="status"></div>
<div class="path">File</div>
@@ -498,7 +496,7 @@ limitations under the License.
<gr-button
class="fileListButton"
id="incrementButton"
- link on-tap="_incrementNumFilesShown">
+ link on-click="_incrementNumFilesShown">
[[_computeIncrementText(numFilesShown, _files)]]
</gr-button>
<gr-tooltip-content
@@ -508,7 +506,7 @@ limitations under the License.
<gr-button
class="fileListButton"
id="showAllButton"
- link on-tap="_showAllFiles">
+ link on-click="_showAllFiles">
[[_computeShowAllText(_files)]]
</gr-button><!--
--></gr-tooltip-content>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 8dba99d8fb..8d9b9058a2 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -41,29 +41,8 @@
U: 'Unchanged',
};
- const Defs = {};
-
- /**
- * Object containing layout values to be used in rendering size-bars.
- * `max{Inserted,Deleted}` represent the largest values of the
- * `lines_inserted` and `lines_deleted` fields of the files respectively. The
- * `max{Addition,Deletion}Width` represent the width of the graphic allocated
- * to the insertion or deletion side respectively. Finally, the
- * `deletionOffset` value represents the x-position for the deletion bar.
- *
- * @typedef {{
- * maxInserted: number,
- * maxDeleted: number,
- * maxAdditionWidth: number,
- * maxDeletionWidth: number,
- * deletionOffset: number,
- * }}
- */
- Defs.LayoutStats;
-
Polymer({
is: 'gr-file-list',
- _legacyUndefinedCheck: true,
/**
* Fired when a draft refresh should get triggered
@@ -148,7 +127,7 @@
_shownFiles: {
type: Array,
- computed: '_computeFilesShown(numFilesShown, _files.*)',
+ computed: '_computeFilesShown(numFilesShown, _files)',
},
/**
@@ -166,7 +145,7 @@
type: Boolean,
observer: '_loadingChanged',
},
- /** @type {Defs.LayoutStats|undefined} */
+ /** @type {Gerrit.LayoutStats|undefined} */
_sizeBarLayout: {
type: Object,
computed: '_computeSizeBarLayout(_shownFiles.*)',
@@ -203,6 +182,7 @@
behaviors: [
Gerrit.AsyncForeachBehavior,
Gerrit.DomUtilBehavior,
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.PathListBehavior,
@@ -332,7 +312,6 @@
},
get diffs() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this.root).querySelectorAll('gr-diff-host'));
},
@@ -512,7 +491,7 @@
this.set(['_files', index, 'isReviewed'], reviewed);
if (index < this._shownFiles.length) {
- this.set(['_shownFiles', index, 'isReviewed'], reviewed);
+ this.notifyPath(`_shownFiles.${index}.isReviewed`);
}
this._saveReviewedState(path, reviewed);
@@ -562,7 +541,7 @@
* Handle all events from the file list dom-repeat so event handleers don't
* have to get registered for potentially very long lists.
*/
- _handleFileListTap(e) {
+ _handleFileListClick(e) {
// Traverse upwards to find the row element if the target is not the row.
let row = e.target;
while (!row.classList.contains('row') && row.parentElement) {
@@ -800,6 +779,11 @@
},
_computeDiffURL(change, patchNum, basePatchNum, path, editMode) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum, basePatchNum, path, editMode]
+ .some(arg => arg === undefined)) {
+ return;
+ }
// TODO(kaspern): Fix editing for commit messages and merge lists.
if (editMode && path !== this.COMMIT_MESSAGE_PATH &&
path !== this.MERGE_LIST_PATH) {
@@ -860,8 +844,19 @@
},
_computeFiles(filesByPath, changeComments, patchRange, reviewed, loading) {
+ // Polymer 2: check for undefined
+ if ([
+ filesByPath,
+ changeComments,
+ patchRange,
+ reviewed,
+ loading,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
// Await all promises resolving from reload. @See Issue 9057
- if (loading) { return; }
+ if (loading || !changeComments) { return; }
const commentedPaths = changeComments.getPaths(patchRange);
const files = Object.assign({}, filesByPath);
@@ -879,10 +874,15 @@
},
_computeFilesShown(numFilesShown, files) {
+ // Polymer 2: check for undefined
+ if ([numFilesShown, files].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const previousNumFilesShown = this._shownFiles ?
this._shownFiles.length : 0;
- const filesShown = files.base.slice(0, numFilesShown);
+ const filesShown = files.slice(0, numFilesShown);
this.fire('files-shown-changed', {length: filesShown.length});
// Start the timer for the rendering work hwere because this is where the
@@ -904,12 +904,13 @@
},
_filesChanged() {
- Polymer.dom.flush();
- // Polymer2: querySelectorAll returns NodeList instead of Array.
- const files = Array.from(
- Polymer.dom(this.root).querySelectorAll('.file-row'));
- this.$.fileCursor.stops = files;
- this.$.fileCursor.setCursorAtIndex(this.selectedIndex, true);
+ if (this._files && this._files.length > 0) {
+ Polymer.dom.flush();
+ const files = Array.from(
+ Polymer.dom(this.root).querySelectorAll('.file-row'));
+ this.$.fileCursor.stops = files;
+ this.$.fileCursor.setCursorAtIndex(this.selectedIndex, true);
+ }
},
_incrementNumFilesShown() {
@@ -947,6 +948,11 @@
},
_computePatchSetDescription(revisions, patchNum) {
+ // Polymer 2: check for undefined
+ if ([revisions, patchNum].some(arg => arg === undefined)) {
+ return '';
+ }
+
const rev = this.getRevisionByPatchNum(revisions, patchNum);
return (rev && rev.description) ?
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
@@ -1178,7 +1184,8 @@
/**
* Compute size bar layout values from the file list.
*
- * @return {Defs.LayoutStats|undefined}
+ * @return {Gerrit.LayoutStats|undefined}
+ *
*/
_computeSizeBarLayout(shownFilesRecord) {
if (!shownFilesRecord || !shownFilesRecord.base) { return undefined; }
@@ -1214,7 +1221,7 @@
* Get the width of the addition bar for a file.
*
* @param {Object} file
- * @param {Defs.LayoutStats} stats
+ * @param {Gerrit.LayoutStats} stats
* @return {number}
*/
_computeBarAdditionWidth(file, stats) {
@@ -1232,7 +1239,7 @@
* Get the x-offset of the addition bar for a file.
*
* @param {Object} file
- * @param {Defs.LayoutStats} stats
+ * @param {Gerrit.LayoutStats} stats
* @return {number}
*/
_computeBarAdditionX(file, stats) {
@@ -1244,7 +1251,7 @@
* Get the width of the deletion bar for a file.
*
* @param {Object} file
- * @param {Defs.LayoutStats} stats
+ * @param {Gerrit.LayoutStats} stats
* @return {number}
*/
_computeBarDeletionWidth(file, stats) {
@@ -1261,7 +1268,8 @@
/**
* Get the x-offset of the deletion bar for a file.
*
- * @param {Defs.LayoutStats} stats
+ * @param {Gerrit.LayoutStats} stats
+ *
* @return {number}
*/
_computeBarDeletionX(stats) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index fffac8e2e0..4c102a2c07 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-file-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
<script src="../../../scripts/util.js"></script>
@@ -815,18 +817,18 @@ limitations under the License.
assert.isTrue(commitReviewLabel.classList.contains('isReviewed'));
assert.equal(markReviewLabel.textContent, 'MARK UNREVIEWED');
- const tapSpy = sandbox.spy(element, '_handleFileListTap');
+ const clickSpy = sandbox.spy(element, '_handleFileListClick');
MockInteractions.tap(markReviewLabel);
assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', false));
assert.isFalse(commitReviewLabel.classList.contains('isReviewed'));
assert.equal(markReviewLabel.textContent, 'MARK REVIEWED');
- assert.isTrue(tapSpy.lastCall.args[0].defaultPrevented);
+ assert.isTrue(clickSpy.lastCall.args[0].defaultPrevented);
MockInteractions.tap(markReviewLabel);
assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', true));
assert.isTrue(commitReviewLabel.classList.contains('isReviewed'));
assert.equal(markReviewLabel.textContent, 'MARK UNREVIEWED');
- assert.isTrue(tapSpy.lastCall.args[0].defaultPrevented);
+ assert.isTrue(clickSpy.lastCall.args[0].defaultPrevented);
});
test('_computeFileStatusLabel', () => {
@@ -834,7 +836,7 @@ limitations under the License.
assert.equal(element._computeFileStatusLabel('M'), 'Modified');
});
- test('_handleFileListTap', () => {
+ test('_handleFileListClick', () => {
element._filesByPath = {
'/COMMIT_MSG': {},
'f1.txt': {},
@@ -846,7 +848,7 @@ limitations under the License.
patchNum: '2',
};
- const tapSpy = sandbox.spy(element, '_handleFileListTap');
+ const clickSpy = sandbox.spy(element, '_handleFileListClick');
const reviewStub = sandbox.stub(element, '_reviewFile');
const toggleExpandSpy = sandbox.spy(element, '_togglePathExpanded');
@@ -856,26 +858,26 @@ limitations under the License.
// Click on the expand button, resulting in _togglePathExpanded being
// called and not resulting in a call to _reviewFile.
row.querySelector('div.show-hide').click();
- assert.isTrue(tapSpy.calledOnce);
+ assert.isTrue(clickSpy.calledOnce);
assert.isTrue(toggleExpandSpy.calledOnce);
assert.isFalse(reviewStub.called);
// Click inside the diff. This should result in no additional calls to
// _togglePathExpanded or _reviewFile.
Polymer.dom(element.root).querySelector('gr-diff-host').click();
- assert.isTrue(tapSpy.calledTwice);
+ assert.isTrue(clickSpy.calledTwice);
assert.isTrue(toggleExpandSpy.calledOnce);
assert.isFalse(reviewStub.called);
// Click the reviewed checkbox, resulting in a call to _reviewFile, but
// no additional call to _togglePathExpanded.
row.querySelector('.markReviewed').click();
- assert.isTrue(tapSpy.calledThrice);
+ assert.isTrue(clickSpy.calledThrice);
assert.isTrue(toggleExpandSpy.calledOnce);
assert.isTrue(reviewStub.calledOnce);
});
- test('_handleFileListTap editMode', () => {
+ test('_handleFileListClick editMode', () => {
element._filesByPath = {
'/COMMIT_MSG': {},
'f1.txt': {},
@@ -888,12 +890,12 @@ limitations under the License.
};
element.editMode = true;
flushAsynchronousOperations();
- const tapSpy = sandbox.spy(element, '_handleFileListTap');
+ const clickSpy = sandbox.spy(element, '_handleFileListClick');
const toggleExpandSpy = sandbox.spy(element, '_togglePathExpanded');
- // Tap the edit controls. Should be ignored by _handleFileListTap.
+ // Tap the edit controls. Should be ignored by _handleFileListClick.
MockInteractions.tap(element.$$('.editFileControls'));
- assert.isTrue(tapSpy.calledOnce);
+ assert.isTrue(clickSpy.calledOnce);
assert.isFalse(toggleExpandSpy.called);
});
@@ -1211,22 +1213,6 @@ limitations under the License.
assert.isFalse(element.classList.contains('loading'));
});
- test('no execute _computeDiffURL before patchNum is knwon', done => {
- const urlStub = sandbox.stub(element, '_computeDiffURL');
- element.change = {_number: 123};
- element.patchRange = {patchNum: undefined, basePatchNum: 'PARENT'};
- element._filesByPath = {'foo/bar.cpp': {}};
- element.editMode = false;
- flush(() => {
- assert.isFalse(urlStub.called);
- element.set('patchRange.patchNum', 4);
- flush(() => {
- assert.isTrue(urlStub.called);
- done();
- });
- });
- });
-
suite('size bars', () => {
test('_computeSizeBarLayout', () => {
assert.isUndefined(element._computeSizeBarLayout(null));
@@ -1713,7 +1699,9 @@ limitations under the License.
// Commit message should not have edit controls.
const editControls =
- Polymer.dom(element.root).querySelectorAll('.row:not(.header-row)')
+ Array.from(
+ Polymer.dom(element.root)
+ .querySelectorAll('.row:not(.header-row)'))
.map(row => row.querySelector('gr-edit-file-controls'));
assert.isTrue(editControls[0].classList.contains('invisible'));
});
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
index b824f1c249..0afd214640 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
@@ -15,8 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-included-in-dialog">
@@ -27,44 +30,41 @@ limitations under the License.
display: block;
max-height: 80vh;
overflow-y: auto;
- padding: 4.5em 1em 1em 1em;
+ padding: 4.5em var(--spacing-l) var(--spacing-l) var(--spacing-l);
}
header {
background-color: var(--dialog-background-color);
border-bottom: 1px solid var(--border-color);
left: 0;
- padding: 1em;
+ padding: var(--spacing-l);
position: absolute;
right: 0;
top: 0;
}
#title {
display: inline-block;
- font-size: 1.2rem;
- margin-top: .2em;
- }
- h2 {
- font-size: 1rem;
+ font-size: var(--font-size-h3);
+ margin-top: var(--spacing-xs);
}
#filterInput {
display: inline-block;
float: right;
- margin: 0 1em;
- padding: .2em;
+ margin: 0 var(--spacing-l);
+ padding: var(--spacing-xs);
}
.closeButtonContainer {
float: right;
}
ul {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
ul li {
border: 1px solid var(--border-color);
- border-radius: .2em;
+ border-radius: var(--border-radius);
background: var(--chip-background-color);
display: inline-block;
- margin: 0 .2em .4em .2em;
- padding: .2em .4em;
+ margin: 0 var(--spacing-xs) var(--spacing-s) var(--spacing-xs);
+ padding: var(--spacing-xs) var(--spacing-s);
}
.loading.loaded {
display: none;
@@ -75,13 +75,19 @@ limitations under the License.
<span class="closeButtonContainer">
<gr-button id="closeButton"
link
- on-tap="_handleCloseTap">Close</gr-button>
+ on-click="_handleCloseTap">Close</gr-button>
</span>
- <input
- id="filterInput"
+ <iron-input
+ id="filterInput"
+ placeholder="Filter"
+ bind-value="{{_filterText}}"
+ >
+ <input
is="iron-input"
placeholder="Filter"
- on-bind-value-changed="_onFilterChanged">
+ bind-value="{{_filterText}}"
+ />
+ </iron-input>
</header>
<div class$="[[_computeLoadingClass(_loaded)]]">Loading...</div>
<template
@@ -89,7 +95,7 @@ limitations under the License.
items="[[_computeGroups(_includedIn, _filterText)]]"
as="group">
<div>
- <h2>[[group.title]]:</h2>
+ <span>[[group.title]]:</span>
<ul>
<template is="dom-repeat" items="[[group.items]]">
<li>[[item]]</li>
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
index 7755a60099..9d9f654ca8 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-included-in-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the user presses the close button.
@@ -45,6 +44,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
loadData() {
if (!this.changeNum) { return; }
this._filterText = '';
@@ -62,7 +65,9 @@
},
_computeGroups(includedIn, filterText) {
- if (!includedIn) { return []; }
+ if (!includedIn || filterText === undefined) {
+ return [];
+ }
const filter = item => !filterText.length ||
item.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
@@ -84,17 +89,12 @@
_handleCloseTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('close', null, {bubbles: false});
},
_computeLoadingClass(loaded) {
return loaded ? 'loading loaded' : 'loading';
},
-
- _onFilterChanged() {
- this.debounce('filter-change', () => {
- this._filterText = this.$.filterInput.bindValue;
- }, 100);
- },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
index 539011a6b4..ad2ad5a171 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-included-in-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-included-in-dialog.html">
@@ -80,5 +82,20 @@ limitations under the License.
{title: 'Tags', items: ['v2.0', 'v2.1']},
]);
});
+
+ test('_computeGroups with .bindValue', done => {
+ element.$.filterInput.bindValue = 'stable-3.2';
+ const includedIn = {branches: [], tags: []};
+ includedIn.branches.push('master', 'stable-3.2');
+
+ setTimeout(() => {
+ const filterText = element._filterText;
+ assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ {title: 'Branches', items: ['stable-3.2']},
+ ]);
+
+ done();
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
index 27c5baa969..220546b3ad 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-selector/iron-selector.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-selector/iron-selector.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../../styles/gr-voting-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -28,12 +28,12 @@ limitations under the License.
.labelContainer {
align-items: center;
display: flex;
- margin-bottom: .5em;
+ margin-bottom: var(--spacing-m);
}
.labelName {
display: inline-block;
flex: 0 0 auto;
- margin-right: .5em;
+ margin-right: var(--spacing-m);
min-width: 7em;
text-align: left;
width: 20%;
@@ -47,7 +47,7 @@ limitations under the License.
.selectedValueText {
color: var(--deemphasized-text-color);
font-style: italic;
- margin: 0 .5em 0 .5em;
+ margin: 0 var(--spacing-m);
}
.selectedValueText.hidden {
display: none;
@@ -60,7 +60,7 @@ limitations under the License.
--gr-button: {
background-color: var(--button-background-color, var(--table-header-background-color));
color: var(--primary-text-color);
- padding: .2em .85em;
+ padding: var(--spacing-xs) var(--spacing-m);
@apply --vote-chip-styles;
}
}
@@ -108,6 +108,7 @@ limitations under the License.
<span class="placeholder" data-label$="[[label.name]]"></span>
</template>
<iron-selector
+ id="labelSelector"
attr-for-selected="value"
selected="[[_computeLabelValue(labels, permittedLabels, label)]]"
hidden$="[[!_computeAnyPermittedLabelValues(permittedLabels, label.name)]]"
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index 9b5847d7f1..76e6e64781 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-label-score-row',
- _legacyUndefinedCheck: true,
/**
* Fired when any label is changed.
@@ -66,7 +65,7 @@
},
get _ironSelector() {
- return this.$$('iron-selector');
+ return this.$ && this.$.labelSelector;
},
_computeBlankItems(permittedLabels, label, side) {
@@ -97,19 +96,30 @@
},
_computeButtonClass(value, index, totalItems) {
+ const classes = [];
+ if (value === this.selectedValue) {
+ classes.push('iron-selected');
+ }
+
if (value < 0 && index === 0) {
- return 'min';
+ classes.push('min');
} else if (value < 0) {
- return 'negative';
+ classes.push('negative');
} else if (value > 0 && index === totalItems - 1) {
- return 'max';
+ classes.push('max');
} else if (value > 0) {
- return 'positive';
+ classes.push('positive');
+ } else {
+ classes.push('neutral');
}
- return 'neutral';
+
+ return classes.join(' ');
},
_computeLabelValue(labels, permittedLabels, label) {
+ if ([labels, permittedLabels, label].some(arg => arg === undefined)) {
+ return null;
+ }
if (!labels[label.name]) { return null; }
const labelValue = this._getLabelValue(labels, permittedLabels, label);
const len = permittedLabels[label.name] != null ?
@@ -133,11 +143,12 @@
const name = e.target.selectedItem.name;
const value = e.target.selectedItem.getAttribute('value');
this.dispatchEvent(new CustomEvent(
- 'labels-changed', {detail: {name, value}, bubbles: true}));
+ 'labels-changed',
+ {detail: {name, value}, bubbles: true, composed: true}));
},
_computeAnyPermittedLabelValues(permittedLabels, label) {
- return permittedLabels.hasOwnProperty(label) &&
+ return permittedLabels && permittedLabels.hasOwnProperty(label) &&
permittedLabels[label].length;
},
@@ -147,6 +158,11 @@
},
_computePermittedLabelValues(permittedLabels, label) {
+ // Polymer 2: check for undefined
+ if ([permittedLabels, label].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return permittedLabels[label];
},
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
index 1e4d471a5f..519fbb80be 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-label-score-row</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-label-score-row.html">
@@ -106,7 +108,7 @@ limitations under the License.
test('label picker', () => {
const labelsChangedHandler = sandbox.stub();
element.addEventListener('labels-changed', labelsChangedHandler);
- assert.ok(element.$$('iron-selector'));
+ assert.ok(element.$.labelSelector);
MockInteractions.tap(element.$$(
'gr-button[value="-1"]'));
flushAsynchronousOperations();
@@ -155,9 +157,9 @@ limitations under the License.
test('correct item is selected', () => {
// 1 should be the value of the selected item
- assert.strictEqual(element.$$('iron-selector').selected, '+1');
+ assert.strictEqual(element.$.labelSelector.selected, '+1');
assert.strictEqual(
- element.$$('iron-selector').selectedItem
+ element.$.labelSelector.selectedItem
.textContent.trim(), '+1');
assert.strictEqual(
element.$.selectedValueLabel.textContent.trim(), 'good');
@@ -234,7 +236,7 @@ limitations under the License.
default_value: 0,
},
};
- const selector = element.$$('iron-selector');
+ const selector = element.$.labelSelector;
element.set('label', {name: 'Verified', value: ' 0'});
flushAsynchronousOperations();
assert.strictEqual(selector.selected, ' 0');
@@ -251,21 +253,21 @@ limitations under the License.
],
};
flushAsynchronousOperations();
- assert.isOk(element.$$('iron-selector'));
- assert.isFalse(element.$$('iron-selector').hidden);
+ assert.isOk(element.$.labelSelector);
+ assert.isFalse(element.$.labelSelector.hidden);
element.permittedLabels = {};
flushAsynchronousOperations();
- assert.isOk(element.$$('iron-selector'));
- assert.isTrue(element.$$('iron-selector').hidden);
+ assert.isOk(element.$.labelSelector);
+ assert.isTrue(element.$.labelSelector.hidden);
element.permittedLabels = {Verified: []};
flushAsynchronousOperations();
- assert.isOk(element.$$('iron-selector'));
- assert.isTrue(element.$$('iron-selector').hidden);
+ assert.isOk(element.$.labelSelector);
+ assert.isTrue(element.$.labelSelector.hidden);
});
- test('asymetrical labels', () => {
+ test('asymetrical labels', done => {
element.permittedLabels = {
'Code-Review': [
'-2',
@@ -279,30 +281,35 @@ limitations under the License.
'+1',
],
};
- flushAsynchronousOperations();
- assert.strictEqual(element.$$('iron-selector')
- .items.length, 2);
- assert.strictEqual(Polymer.dom(element.root).
- querySelectorAll('.placeholder').length, 3);
-
- element.permittedLabels = {
- 'Code-Review': [
- ' 0',
- '+1',
- ],
- 'Verified': [
- '-2',
- '-1',
- ' 0',
- '+1',
- '+2',
- ],
- };
- flushAsynchronousOperations();
- assert.strictEqual(element.$$('iron-selector')
- .items.length, 5);
- assert.strictEqual(Polymer.dom(element.root).
- querySelectorAll('.placeholder').length, 0);
+ flush(() => {
+ assert.strictEqual(element.$.labelSelector
+ .items.length, 2);
+ assert.strictEqual(
+ Polymer.dom(element.root).querySelectorAll('.placeholder').length,
+ 3);
+
+ element.permittedLabels = {
+ 'Code-Review': [
+ ' 0',
+ '+1',
+ ],
+ 'Verified': [
+ '-2',
+ '-1',
+ ' 0',
+ '+1',
+ '+2',
+ ],
+ };
+ flush(() => {
+ assert.strictEqual(element.$.labelSelector
+ .items.length, 5);
+ assert.strictEqual(
+ Polymer.dom(element.root).querySelectorAll('.placeholder').length,
+ 0);
+ done();
+ });
+ });
});
test('default_value', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
index 7dd4c76cc8..c607a9f899 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-label-score-row/gr-label-score-row.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -28,6 +28,9 @@ limitations under the License.
text-align: center;
width: 100%;
}
+ gr-label-score-row.no-access {
+ display: var(--label-no-access-display, initial);
+ }
@media only screen and (max-width: 25em) {
:host {
text-align: center;
@@ -36,6 +39,7 @@ limitations under the License.
</style>
<template is="dom-repeat" items="[[_labels]]" as="label">
<gr-label-score-row
+ class$="[[_computeLabelAccessClass(label.name, permittedLabels)]]"
label="[[label]]"
name="[[label.name]]"
labels="[[change.labels]]"
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
index eaf39bc4a7..dffba3e60d 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-label-scores',
- _legacyUndefinedCheck: true,
+
properties: {
_labels: {
type: Array,
@@ -82,7 +82,12 @@
return null;
},
- _computeLabels(labelRecord) {
+ _computeLabels(labelRecord, account) {
+ // Polymer 2: check for undefined
+ if ([labelRecord, account].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const labelsObj = labelRecord.base;
if (!labelsObj) { return []; }
return Object.keys(labelsObj).sort().map(key => {
@@ -115,5 +120,19 @@
_changeIsMerged(changeStatus) {
return changeStatus === 'MERGED';
},
+
+ /**
+ * @param {string|undefined} label
+ * @param {Object|undefined} permittedLabels
+ * @return {string}
+ */
+ _computeLabelAccessClass(label, permittedLabels) {
+ if (label == null || permittedLabels == null) {
+ return '';
+ }
+
+ return permittedLabels.hasOwnProperty(label) &&
+ permittedLabels[label].length ? 'access' : 'no-access';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index 187c0a6340..b8d471cc29 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-label-scores</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-label-scores.html">
@@ -135,6 +137,25 @@ limitations under the License.
});
});
+ test('_computeLabelAccessClass undefined case', () => {
+ assert.strictEqual(
+ element._computeLabelAccessClass(undefined, undefined), '');
+ assert.strictEqual(
+ element._computeLabelAccessClass('', undefined), '');
+ assert.strictEqual(
+ element._computeLabelAccessClass(undefined, {}), '');
+ });
+
+ test('_computeLabelAccessClass has access', () => {
+ assert.strictEqual(
+ element._computeLabelAccessClass('foo', {foo: ['']}), 'access');
+ });
+
+ test('_computeLabelAccessClass no access', () => {
+ assert.strictEqual(
+ element._computeLabelAccessClass('zap', {foo: ['']}), 'no-access');
+ });
+
test('changes in label score are reflected in _labels', () => {
element.change = {
_number: '123',
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index d3a46feec2..8317e2fa71 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-account-label/gr-account-label.html">
+<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
@@ -36,16 +38,17 @@ limitations under the License.
display: block;
position: relative;
cursor: pointer;
+ overflow-y: hidden;
}
:host(.expanded) {
cursor: auto;
}
:host > div {
- padding: 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-l);
}
gr-avatar {
position: absolute;
- left: var(--default-horizontal-margin);
+ left: var(--spacing-l);
}
.collapsed .contentContainer {
align-items: baseline;
@@ -54,11 +57,11 @@ limitations under the License.
white-space: nowrap;
}
.contentContainer {
- margin-left: calc(var(--default-horizontal-margin) + 2.5em);
- padding: 10px 0;
+ margin-left: calc(var(--spacing-l) + 2.5em);
+ padding: var(--spacing-m) 0;
}
.showAvatar.collapsed .contentContainer {
- margin-left: calc(var(--default-horizontal-margin) + 1.75em);
+ margin-left: calc(var(--spacing-l) + 1.75em);
}
.hideAvatar.collapsed .contentContainer,
.hideAvatar.expanded .contentContainer {
@@ -67,17 +70,17 @@ limitations under the License.
.showAvatar.collapsed .contentContainer,
.hideAvatar.collapsed .contentContainer,
.hideAvatar.expanded .contentContainer {
- padding: .75em 0;
+ padding: var(--spacing-m) 0;
}
.collapsed gr-avatar {
- top: .5em;
- height: 1.75em;
- width: 1.75em;
+ top: var(--spacing-m);
+ height: var(--line-height-normal);
+ width: var(--line-height-normal);
}
.expanded gr-avatar {
- top: 12px;
- height: 2.5em;
- width: 2.5em;
+ top: var(--spacing-l);
+ height: var(--line-height-h1);
+ width: var(--line-height-h1);
}
.name {
font-weight: var(--font-weight-bold);
@@ -111,7 +114,7 @@ limitations under the License.
}
.collapsed .content {
flex: 1;
- margin-right: .25em;
+ margin-right: var(--spacing-xs);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
@@ -122,15 +125,15 @@ limitations under the License.
.collapsed .author {
overflow: hidden;
color: var(--primary-text-color);
- margin-right: .4em;
+ margin-right: var(--spacing-s);
}
.expanded .author {
cursor: pointer;
- margin-bottom: .4em;
+ margin-bottom: var(--spacing-s);
}
.dateContainer {
position: absolute;
- right: var(--default-horizontal-margin);
+ right: var(--spacing-l);
top: 10px;
}
span.date {
@@ -141,17 +144,18 @@ limitations under the License.
}
.dateContainer iron-icon {
cursor: pointer;
+ vertical-align: top;
}
.replyContainer {
- padding: .5em 0 0 0;
+ padding: var(--spacing-m) 0 0 0;
}
.score {
border: 1px solid rgba(0,0,0,.12);
- border-radius: 3px;
+ border-radius: var(--border-radius);
color: var(--primary-text-color);
display: inline-block;
- margin: -.1em 0;
- padding: 0 .1em;
+ margin: -1px 0;
+ padding: 0 var(--spacing-xxs);
}
.score.negative {
background-color: var(--vote-color-disliked);
@@ -174,7 +178,7 @@ limitations under the License.
<div class$="[[_computeClass(_expanded, showAvatar, message)]]">
<gr-avatar account="[[author]]" image-size="100"></gr-avatar>
<div class="contentContainer">
- <div class="author" on-tap="_handleAuthorTap">
+ <div class="author" on-click="_handleAuthorClick">
<span hidden$="[[!showOnBehalfOf]]">
<span class="name">[[message.real_author.name]]</span>
on behalf of
@@ -198,7 +202,7 @@ limitations under the License.
config="[[_projectConfig.commentlinks]]"></gr-formatted-text>
<template is="dom-if" if="[[_expanded]]">
<div class="replyContainer" hidden$="[[!showReplyButton]]" hidden>
- <gr-button link small on-tap="_handleReplyTap">Reply</gr-button>
+ <gr-button link small on-click="_handleReplyTap">Reply</gr-button>
</div>
<gr-comment-list
comments="[[comments]]"
@@ -233,7 +237,7 @@ limitations under the License.
</span>
</template>
<template is="dom-if" if="[[message.id]]">
- <span class="date" on-tap="_handleAnchorTap">
+ <span class="date" on-click="_handleAnchorClick">
<gr-date-formatter
has-tooltip
show-date-and-time
@@ -242,7 +246,7 @@ limitations under the License.
</template>
<iron-icon
id="expandToggle"
- on-tap="_toggleExpanded"
+ on-click="_toggleExpanded"
title="Toggle expanded state"
icon="[[_computeExpandToggleIcon(_expanded)]]">
</span>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 7be1c5d5f6..06bbc93b8c 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-message',
- _legacyUndefinedCheck: true,
/**
* Fired when this message's reply link is tapped.
@@ -37,7 +36,7 @@
*/
listeners: {
- tap: '_handleTap',
+ click: '_handleClick',
},
properties: {
@@ -103,6 +102,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_updateExpandedClass(message.expanded)',
],
@@ -139,7 +142,7 @@
},
_computeShowReplyButton(message, loggedIn) {
- return !!message.message && loggedIn &&
+ return message && !!message.message && loggedIn &&
!this._computeIsAutomated(message);
},
@@ -147,13 +150,13 @@
return expanded;
},
- _handleTap(e) {
+ _handleClick(e) {
if (this.message.expanded) { return; }
e.stopPropagation();
this.set('message.expanded', true);
},
- _handleAuthorTap(e) {
+ _handleAuthorClick(e) {
if (!this.message.expanded) { return; }
e.stopPropagation();
this.set('message.expanded', false);
@@ -187,6 +190,10 @@
},
_computeScoreClass(score, labelExtremes) {
+ // Polymer 2: check for undefined
+ if ([score, labelExtremes].some(arg => arg === undefined)) {
+ return '';
+ }
const classes = [];
if (score.value > 0) {
classes.push('positive');
@@ -212,10 +219,11 @@
return classes.join(' ');
},
- _handleAnchorTap(e) {
+ _handleAnchorClick(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent('message-anchor-tap', {
bubbles: true,
+ composed: true,
detail: {id: this.message.id},
}));
},
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index 566442963e..a90e3ab7f2 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-message</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-message.html">
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 80708a1d33..d71beb4d81 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../gr-message/gr-message.html">
@@ -38,10 +38,10 @@ limitations under the License.
display: flex;
justify-content: space-between;
min-height: 3.2em;
- padding: .5em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
#messageControlsContainer {
- padding: 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-l);
}
.highlighted {
animation: 3s fadeOut;
@@ -58,7 +58,7 @@ limitations under the License.
justify-content: center;
}
#messageControlsContainer gr-button {
- padding: 0.4em 0;
+ padding: var(--spacing-s) 0;
}
.container {
align-items: center;
@@ -78,14 +78,14 @@ limitations under the License.
<gr-button
id="collapse-messages"
link
- on-tap="_handleExpandCollapseTap">
+ on-click="_handleExpandCollapseTap">
[[_computeExpandCollapseMessage(_expanded)]]
</gr-button>
</div>
<span
id="messageControlsContainer"
hidden$="[[_computeShowHideTextHidden(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]">
- <gr-button id="oldMessagesBtn" link on-tap="_handleShowAllTap">
+ <gr-button id="oldMessagesBtn" link on-click="_handleShowAllTap">
[[_computeNumMessagesText(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]
</gr-button>
<span
@@ -93,7 +93,7 @@ limitations under the License.
hidden$="[[_computeIncrementHidden(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]">
<span class="transparent separator"></span>
<gr-button id="incrementMessagesBtn" link
- on-tap="_handleIncrementShownMessages">
+ on-click="_handleIncrementShownMessages">
[[_computeIncrementText(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]
</gr-button>
</span>
@@ -109,7 +109,7 @@ limitations under the License.
hide-automated="[[_hideAutomated]]"
project-name="[[projectName]]"
show-reply-button="[[showReplyButtons]]"
- on-message-anchor-tap="_handleAnchorTap"
+ on-message-anchor-tap="_handleAnchorClick"
label-extremes="[[_labelExtremes]]"
data-message-id$="[[message.id]]"></gr-message>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index d1cd08d428..5652eca4f1 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -27,7 +27,6 @@
Polymer({
is: 'gr-messages-list',
- _legacyUndefinedCheck: true,
properties: {
changeNum: Number,
@@ -117,6 +116,11 @@
},
_computeItems(messages, reviewerUpdates) {
+ // Polymer 2: check for undefined
+ if ([messages, reviewerUpdates].some(arg => arg === undefined)) {
+ return [];
+ }
+
messages = messages || [];
reviewerUpdates = reviewerUpdates || [];
let mi = 0;
@@ -156,10 +160,18 @@
},
_expandedChanged(exp) {
- for (let i = 0; i < this._processedMessages.length; i++) {
- this._processedMessages[i].expanded = exp;
- if (i < this._visibleMessages.length) {
- this.set(['_visibleMessages', i, 'expanded'], exp);
+ if (this._processedMessages) {
+ for (let i = 0; i < this._processedMessages.length; i++) {
+ this._processedMessages[i].expanded = exp;
+ }
+ }
+ // _visibleMessages is a subarray of _processedMessages
+ // _processedMessages contains all items from _visibleMessages
+ // At this point all _visibleMessages.expanded values are set,
+ // and notifyPath must be used to notify Polymer about changes.
+ if (this._visibleMessages) {
+ for (let i = 0; i < this._visibleMessages.length; i++) {
+ this.notifyPath(`_visibleMessages.${i}.expanded`);
}
}
},
@@ -190,7 +202,7 @@
this.handleExpandCollapse(!this._expanded);
},
- _handleAnchorTap(e) {
+ _handleAnchorClick(e) {
this.scrollToMessage(e.detail.id);
},
@@ -220,6 +232,9 @@
* @return {!Object} Hash of arrays of comments, filename as key.
*/
_computeCommentsForMessage(changeComments, message) {
+ if ([changeComments, message].some(arg => arg === undefined)) {
+ return [];
+ }
const comments = changeComments.getAllPublishedComments();
if (message._index === undefined || !comments || !this.messages) {
return [];
@@ -268,8 +283,13 @@
* more visible messages in the list.
*/
_getDelta(visibleMessages, messages, hideAutomated) {
+ if ([visibleMessages, messages].some(arg => arg === undefined)) {
+ return 0;
+ }
+
let delta = MESSAGES_INCREMENT;
const msgsRemaining = messages.length - visibleMessages.length;
+
if (hideAutomated) {
let counter = 0;
let i;
@@ -286,6 +306,10 @@
* exist in _visibleMessages.
*/
_numRemaining(visibleMessages, messages, hideAutomated) {
+ if ([visibleMessages, messages].some(arg => arg === undefined)) {
+ return 0;
+ }
+
if (hideAutomated) {
return this._getHumanMessages(messages).length -
this._getHumanMessages(visibleMessages).length;
@@ -308,6 +332,10 @@
_computeShowHideTextHidden(visibleMessages, messages,
hideAutomated) {
+ if ([visibleMessages, messages].some(arg => arg === undefined)) {
+ return 0;
+ }
+
if (hideAutomated) {
messages = this._getHumanMessages(messages);
visibleMessages = this._getHumanMessages(visibleMessages);
@@ -331,7 +359,9 @@
},
_processedMessagesChanged(messages) {
- this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
+ if (messages) {
+ this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
+ }
},
_computeNumMessagesText(visibleMessages, messages,
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 9572de4646..315403ea32 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-messages-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
index 30ebc08d26..696ffdfaae 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
@@ -14,8 +14,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
@@ -29,7 +30,7 @@ limitations under the License.
display: block;
}
h3 {
- margin: .5em 0 0;
+ margin: var(--spacing-m) 0 0;
}
section {
margin-bottom: 1.4em; /* Same as line height for collapse purposes */
@@ -74,7 +75,7 @@ limitations under the License.
.status {
color: var(--deemphasized-text-color);
font-weight: var(--font-weight-bold);
- margin-left: .25em;
+ margin-left: var(--spacing-xs);
}
.notCurrent {
color: #e65100;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 07f6e20d0d..9103f4fc83 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-related-changes-list',
- _legacyUndefinedCheck: true,
/**
* Fired when a new section is loaded so that the change view can determine
@@ -78,6 +77,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
],
@@ -121,7 +121,7 @@
];
// Get conflicts if change is open and is mergeable.
- if (this.changeIsOpen(this.change.status) && this.mergeable) {
+ if (this.changeIsOpen(this.change) && this.mergeable) {
promises.push(this._getConflicts().then(response => {
// Because the server doesn't always return a response and the
// template expects an array, always return an array.
@@ -208,6 +208,9 @@
_computeChangeContainerClass(currentChange, relatedChange) {
const classes = ['changeContainer'];
+ if ([relatedChange, currentChange].some(arg => arg === undefined)) {
+ return classes;
+ }
if (this._changesEqual(relatedChange, currentChange)) {
classes.push('thisChange');
}
@@ -244,6 +247,9 @@
* @return {number}
*/
_getChangeNumber(change) {
+ // Default to 0 if change property is not defined.
+ if (!change) return 0;
+
if (change.hasOwnProperty('_change_number')) {
return change._change_number;
}
@@ -294,6 +300,17 @@
_resultsChanged(related, submittedTogether, conflicts,
cherryPicks, sameTopic) {
+ // Polymer 2: check for undefined
+ if ([
+ related,
+ submittedTogether,
+ conflicts,
+ cherryPicks,
+ sameTopic,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
const results = [
related && related.changes,
submittedTogether && submittedTogether.changes,
@@ -316,8 +333,14 @@
},
_computeConnectedRevisions(change, patchNum, relatedChanges) {
+ // Polymer 2: check for undefined
+ if ([change, patchNum, relatedChanges].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const connected = [];
let changeRevision;
+ if (!change) { return []; }
for (const rev in change.revisions) {
if (this.patchNumEquals(change.revisions[rev]._number, patchNum)) {
changeRevision = rev;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index c82bc31483..f04d40e3bc 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-related-changes-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-related-changes-list.html">
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
index 692cb93866..3632348f76 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reply-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../plugins/gr-plugin-host/gr-plugin-host.html">
@@ -118,18 +120,18 @@ limitations under the License.
// resolving.
sandbox.stub(element, '_purgeReviewersPendingRemove');
- element.$$('#ccs').$.entry.setText('test');
+ element.$.ccs.$.entry.setText('test');
MockInteractions.tap(element.$$('gr-button.send'));
assert.isFalse(sendStub.called);
flushAsynchronousOperations();
- element.$$('#ccs').$.entry.setText('test@test.test');
+ element.$.ccs.$.entry.setText('test@test.test');
MockInteractions.tap(element.$$('gr-button.send'));
assert.isTrue(sendStub.called);
});
test('lgtm plugin', done => {
- Gerrit._resetPlugins();
+ Gerrit._testOnly_resetPlugins();
const pluginHost = fixture('plugin-host');
pluginHost.config = {
plugin: {
@@ -149,7 +151,8 @@ limitations under the License.
flush(() => {
const textarea = element.$.textarea.getNativeTextarea();
textarea.value = 'LGTM';
- textarea.dispatchEvent(new CustomEvent('input', {bubbles: true}));
+ textarea.dispatchEvent(new CustomEvent(
+ 'input', {bubbles: true, composed: true}));
const labelScoreRows = Polymer.dom(element.$.labelScores.root)
.querySelector('gr-label-score-row[name="Code-Review"]');
const selectedBtn = Polymer.dom(labelScoreRows.root)
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index f8d43cb3ea..e836cccccf 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -15,12 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
@@ -31,9 +32,12 @@ limitations under the License.
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-storage/gr-storage.html">
-<link rel="import" href="../gr-account-list/gr-account-list.html">
+<link rel="import" href="../../shared/gr-account-list/gr-account-list.html">
<link rel="import" href="../gr-label-scores/gr-label-scores.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../change/gr-comment-list/gr-comment-list.html">
+<script src="../../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js"></script>
<dom-module id="gr-reply-dialog">
<template>
@@ -57,7 +61,7 @@ limitations under the License.
section {
border-top: 1px solid var(--border-color);
flex-shrink: 0;
- padding: .5em 1.5em;
+ padding: var(--spacing-m) var(--spacing-xl);
width: 100%;
}
.actions {
@@ -70,7 +74,7 @@ limitations under the License.
z-index: 1;
}
.actions .right gr-button {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
.peopleContainer,
.labelsContainer {
@@ -82,13 +86,13 @@ limitations under the License.
}
.peopleList {
display: flex;
- padding-top: .1em;
+ padding-top: var(--spacing-xxs);
}
.peopleListLabel {
color: var(--deemphasized-text-color);
- margin-top: .2em;
+ margin-top: var(--spacing-xs);
min-width: 7em;
- padding-right: .5em;
+ padding-right: var(--spacing-m);
}
gr-account-list {
display: flex;
@@ -97,11 +101,11 @@ limitations under the License.
min-height: 1.8em;
}
#reviewerConfirmationOverlay {
- padding: 1em;
+ padding: var(--spacing-l);
text-align: center;
}
.reviewerConfirmationButtons {
- margin-top: 1em;
+ margin-top: var(--spacing-l);
}
.groupName {
font-weight: var(--font-weight-bold);
@@ -124,14 +128,14 @@ limitations under the License.
}
.previewContainer gr-formatted-text {
background: var(--table-header-background-color);
- padding: 1em;
+ padding: var(--spacing-l);
}
.draftsContainer h3 {
- margin-top: .25em;
+ margin-top: var(--spacing-xs);
}
#checkingStatusLabel,
#notLatestLabel {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
#checkingStatusLabel {
color: var(--deemphasized-text-color);
@@ -149,8 +153,8 @@ limitations under the License.
}
#pluginMessage {
color: var(--deemphasized-text-color);
- margin-left: 1em;
- margin-bottom: .5em;
+ margin-left: var(--spacing-l);
+ margin-bottom: var(--spacing-m);
}
#pluginMessage:empty {
display: none;
@@ -164,11 +168,11 @@ limitations under the License.
id="reviewers"
accounts="{{_reviewers}}"
removable-values="[[change.removable_reviewers]]"
- change="[[change]]"
filter="[[filterReviewerSuggestion]]"
pending-confirmation="{{_reviewerPendingConfirmation}}"
placeholder="Add reviewer..."
- on-account-text-changed="_handleAccountTextEntry">
+ on-account-text-changed="_handleAccountTextEntry"
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
</gr-account-list>
</div>
<div class="peopleList">
@@ -176,12 +180,12 @@ limitations under the License.
<gr-account-list
id="ccs"
accounts="{{_ccs}}"
- change="[[change]]"
filter="[[filterCCSuggestion]]"
pending-confirmation="{{_ccPendingConfirmation}}"
allow-any-input
placeholder="Add CC..."
- on-account-text-changed="_handleAccountTextEntry">
+ on-account-text-changed="_handleAccountTextEntry"
+ suggestions-provider="[[_getCcSuggestionsProvider(change)]]">
</gr-account-list>
</div>
<gr-overlay
@@ -201,8 +205,8 @@ limitations under the License.
Are you sure you want to add them all?
</div>
<div class="reviewerConfirmationButtons">
- <gr-button on-tap="_confirmPendingReviewer">Yes</gr-button>
- <gr-button on-tap="_cancelPendingReviewer">No</gr-button>
+ <gr-button on-click="_confirmPendingReviewer">Yes</gr-button>
+ <gr-button on-click="_cancelPendingReviewer">No</gr-button>
</div>
</gr-overlay>
</section>
@@ -273,7 +277,7 @@ limitations under the License.
class="action save"
has-tooltip
title="[[_saveTooltip]]"
- on-tap="_saveTapHandler">Save</gr-button>
+ on-click="_saveTapHandler">Save</gr-button>
</template>
<span
id="checkingStatusLabel"
@@ -284,7 +288,7 @@ limitations under the License.
id="notLatestLabel"
hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
[[_computePatchSetWarning(patchNum, _labelsChanged)]]
- <gr-button link on-tap="_reload">Reload</gr-button>
+ <gr-button link on-click="_reload">Reload</gr-button>
</span>
</div>
<div class="right">
@@ -292,7 +296,7 @@ limitations under the License.
link
id="cancelButton"
class="action cancel"
- on-tap="_cancelTapHandler">Cancel</gr-button>
+ on-click="_cancelTapHandler">Cancel</gr-button>
<gr-button
id="sendButton"
link
@@ -301,7 +305,7 @@ limitations under the License.
class="action send"
has-tooltip
title$="[[_computeSendButtonTooltip(canBeStarted)]]"
- on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
+ on-click="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
</div>
</section>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 43f5e8fbc6..da19e62887 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -48,17 +48,12 @@
SEND: 'Send reply',
};
- // TODO(logan): Remove once the fix for issue 6841 is stable on
- // googlesource.com.
- const START_REVIEW_MESSAGE = 'This change is ready for review.';
-
const EMPTY_REPLY_MESSAGE = 'Cannot send an empty reply.';
const SEND_REPLY_TIMING_LABEL = 'SendReply';
Polymer({
is: 'gr-reply-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when a reply is successfully sent.
@@ -220,12 +215,9 @@
FocusTarget,
- // TODO(logan): Remove once the fix for issue 6841 is stable on
- // googlesource.com.
- START_REVIEW_MESSAGE,
-
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
@@ -314,31 +306,37 @@
},
_ccsChanged(splices) {
- if (splices && splices.indexSplices) {
- this._reviewersMutated = true;
- this._processReviewerChange(splices.indexSplices, ReviewerTypes.CC);
- }
+ this._reviewerTypeChanged(splices, ReviewerTypes.CC);
},
_reviewersChanged(splices) {
+ this._reviewerTypeChanged(splices, ReviewerTypes.REVIEWER);
+ },
+
+ _reviewerTypeChanged(splices, reviewerType) {
if (splices && splices.indexSplices) {
this._reviewersMutated = true;
this._processReviewerChange(splices.indexSplices,
- ReviewerTypes.REVIEWER);
+ reviewerType);
let key;
let index;
let account;
- // Remove any accounts that already exist as a CC.
+ // Remove any accounts that already exist as a CC for reviewer
+ // or vice versa.
+ const isReviewer = ReviewerTypes.REVIEWER === reviewerType;
for (const splice of splices.indexSplices) {
- for (const addedKey of splice.addedKeys) {
- account = this.get(`_reviewers.${addedKey}`);
+ for (let i = 0; i < splice.addedCount; i++) {
+ account = splice.object[splice.index + i];
key = this._accountOrGroupKey(account);
- index = this._ccs.findIndex(
+ const array = isReviewer ? this._ccs : this._reviewers;
+ index = array.findIndex(
account => this._accountOrGroupKey(account) === key);
if (index >= 0) {
- this.splice('_ccs', index, 1);
+ this.splice(isReviewer ? '_ccs' : '_reviewers', index, 1);
+ const moveFrom = isReviewer ? 'CC' : 'reviewer';
+ const moveTo = isReviewer ? 'reviewer' : 'CC';
const message = (account.name || account.email || key) +
- ' moved from CC to reviewer.';
+ ` moved from ${moveFrom} to ${moveTo}.`;
this.fire('show-alert', {message});
}
}
@@ -390,9 +388,6 @@
*
* @param {!Object} account
* @param {string} type
- *
- * * TODO(beckysiegel) submit Polymer PR
- * @suppress {checkTypes}
*/
_removeAccount(account, type) {
if (account._pendingAdd) { return; }
@@ -404,7 +399,7 @@
const reviewers = this.change.reviewers[type] || [];
for (let i = 0; i < reviewers.length; i++) {
if (reviewers[i]._account_id == account._account_id) {
- this.splice(['change', 'reviewers', type], i, 1);
+ this.splice(`change.reviewers.${type}`, i, 1);
break;
}
}
@@ -447,7 +442,7 @@
}
return this._mapReviewer(reviewer);
});
- const ccsEl = this.$$('#ccs');
+ const ccsEl = this.$.ccs;
if (ccsEl) {
for (let reviewer of ccsEl.additions()) {
if (reviewer.account) {
@@ -461,13 +456,6 @@
this.disabled = true;
- if (obj.ready && !obj.message) {
- // TODO(logan): The server currently doesn't send email in this case.
- // Insert a dummy message to force an email to be sent. Remove this
- // once the fix for issue 6841 is stable on googlesource.com.
- obj.message = START_REVIEW_MESSAGE;
- }
-
const errFn = this._handle400Error.bind(this);
return this._saveReview(obj, errFn).then(response => {
if (!response) {
@@ -480,18 +468,10 @@
return {};
}
- // TODO(logan): Remove once the required API changes are live and stable
- // on googlesource.com.
- return this._maybeSetReady(startReview, response).catch(err => {
- // We catch error here because we still want to treat this as a
- // successful review.
- console.error('error setting ready:', err);
- }).then(() => {
- this.draft = '';
- this._includeComments = true;
- this.fire('send', null, {bubbles: false});
- return accountAdditions;
- });
+ this.draft = '';
+ this._includeComments = true;
+ this.fire('send', null, {bubbles: false});
+ return accountAdditions;
}).then(result => {
this.disabled = false;
return result;
@@ -501,32 +481,6 @@
});
},
- /**
- * Returns a promise resolving to true if review was successfully posted,
- * false otherwise.
- *
- * TODO(logan): Remove this once the required API changes are live and
- * stable on googlesource.com.
- */
- _maybeSetReady(startReview, response) {
- return this.$.restAPI.getResponseObject(response).then(result => {
- if (!startReview || result.ready) {
- return Promise.resolve();
- }
- // We don't have confirmation that review was started, so attempt to
- // start review explicitly.
- return this.$.restAPI.startReview(
- this.change._number, null, response => {
- // If we see a 409 response code, then that means the server
- // *does* support moving from WIP->ready when posting a
- // review. Only alert user for non-409 failures.
- if (response.status !== 409) {
- this.fire('server-error', {response});
- }
- });
- });
- },
-
_focusOn(section) {
// Safeguard- always want to focus on something.
if (!section || section === FocusTarget.ANY) {
@@ -540,7 +494,7 @@
const reviewerEntry = this.$.reviewers.focusStart;
reviewerEntry.async(reviewerEntry.focus);
} else if (section === FocusTarget.CCS) {
- const ccEntry = this.$$('#ccs').focusStart;
+ const ccEntry = this.$.ccs.focusStart;
ccEntry.async(ccEntry.focus);
}
},
@@ -571,31 +525,31 @@
//
this.disabled = false;
- if (response.status !== 400) {
- // This is all restAPI does when there is no custom error handling.
- this.fire('server-error', {response});
- return response;
- }
-
- // Process the response body, format a better error message, and fire
- // an event for gr-event-manager to display.
- const jsonPromise = this.$.restAPI.getResponseObject(response);
+ // Using response.clone() here, because getResponseObject() and
+ // potentially the generic error handler will want to call text() on the
+ // response object, which can only be done once per object.
+ const jsonPromise = this.$.restAPI.getResponseObject(response.clone());
return jsonPromise.then(result => {
- const errors = [];
- for (const state of ['reviewers', 'ccs']) {
- if (!result.hasOwnProperty(state)) { continue; }
- for (const reviewer of Object.values(result[state])) {
- if (reviewer.error) {
- errors.push(reviewer.error);
+ // Only perform custom error handling for 400s and a parseable
+ // ReviewResult response.
+ if (response.status === 400 && result) {
+ const errors = [];
+ for (const state of ['reviewers', 'ccs']) {
+ if (!result.hasOwnProperty(state)) { continue; }
+ for (const reviewer of Object.values(result[state])) {
+ if (reviewer.error) {
+ errors.push(reviewer.error);
+ }
}
}
+ response = {
+ ok: false,
+ status: response.status,
+ text() { return Promise.resolve(errors.join(', ')); },
+ };
}
- response = {
- ok: false,
- status: response.status,
- text() { return Promise.resolve(errors.join(', ')); },
- };
this.fire('server-error', {response});
+ return null; // Means that the error has been handled.
});
},
@@ -622,6 +576,11 @@
},
_changeUpdated(changeRecord, owner) {
+ // Polymer 2: check for undefined
+ if ([changeRecord, owner].some(arg => arg === undefined)) {
+ return;
+ }
+
this._rebuildReviewerArrays(changeRecord.base, owner);
},
@@ -712,7 +671,7 @@
_saveTapHandler(e) {
e.preventDefault();
- if (!this.$$('#ccs').submitEntryText()) {
+ if (!this.$.ccs.submitEntryText()) {
// Do not proceed with the save if there is an invalid email entry in
// the text field of the CC entry.
return;
@@ -728,7 +687,7 @@
},
_submit() {
- if (!this.$$('#ccs').submitEntryText()) {
+ if (!this.$.ccs.submitEntryText()) {
// Do not proceed with the send if there is an invalid email entry in
// the text field of the CC entry.
return;
@@ -736,6 +695,7 @@
if (this._sendDisabled) {
this.dispatchEvent(new CustomEvent('show-alert', {
bubbles: true,
+ composed: true,
detail: {message: EMPTY_REPLY_MESSAGE},
}));
return;
@@ -743,6 +703,13 @@
return this.send(this._includeComments, this.canBeStarted)
.then(keepReviewers => {
this._purgeReviewersPendingRemove(false, keepReviewers);
+ })
+ .catch(err => {
+ this.dispatchEvent(new CustomEvent('show-error', {
+ bubbles: true,
+ composed: true,
+ detail: {message: `Error submitting review ${err}`},
+ }));
});
},
@@ -763,7 +730,7 @@
_confirmPendingReviewer() {
if (this._ccPendingConfirmation) {
- this.$$('#ccs').confirmGroup(this._ccPendingConfirmation.group);
+ this.$.ccs.confirmGroup(this._ccPendingConfirmation.group);
this._focusOn(FocusTarget.CCS);
} else {
this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
@@ -831,7 +798,8 @@
_reload() {
// Load the current change without any patch range.
- location.href = this.getBaseUrl() + '/c/' + this.change._number;
+ Gerrit.Nav.navigateToChange(this.change);
+ this.cancel();
},
_computeSendButtonLabel(canBeStarted) {
@@ -848,6 +816,19 @@
_computeSendButtonDisabled(buttonLabel, drafts, text, reviewersMutated,
labelsChanged, includeComments, disabled) {
+ // Polymer 2: check for undefined
+ if ([
+ buttonLabel,
+ drafts,
+ text,
+ reviewersMutated,
+ labelsChanged,
+ includeComments,
+ disabled,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (disabled) { return true; }
if (buttonLabel === ButtonLabels.START_REVIEW) { return false; }
const hasDrafts = includeComments && Object.keys(drafts).length;
@@ -869,5 +850,19 @@
_sendDisabledChanged(sendDisabled) {
this.dispatchEvent(new CustomEvent('send-disabled-changed'));
},
+
+ _getReviewerSuggestionsProvider(change) {
+ const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
+ change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER);
+ provider.init();
+ return provider;
+ },
+
+ _getCcSuggestionsProvider(change) {
+ const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
+ change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.CC);
+ provider.init();
+ return provider;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 5577c1b586..d8d49cf5a5 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reply-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-reply-dialog.html">
@@ -33,6 +35,25 @@ limitations under the License.
</test-fixture>
<script>
+ function cloneableResponse(status, text) {
+ return {
+ ok: false,
+ status,
+ text() {
+ return Promise.resolve(text);
+ },
+ clone() {
+ return {
+ ok: false,
+ status,
+ text() {
+ return Promise.resolve(text);
+ },
+ };
+ },
+ };
+ }
+
suite('gr-reply-dialog tests', () => {
let element;
let changeNum;
@@ -256,17 +277,19 @@ limitations under the License.
});
});
- test('setlabelValue', () => {
+ test('setlabelValue', done => {
element._account = {_account_id: 1};
- flushAsynchronousOperations();
- const label = 'Verified';
- const value = '+1';
- element.setLabelValue(label, value);
- flushAsynchronousOperations();
- const labels = element.$.labelScores.getLabelValues();
- assert.deepEqual(labels, {
- 'Code-Review': 0,
- 'Verified': 1,
+ flush(() => {
+ const label = 'Verified';
+ const value = '+1';
+ element.setLabelValue(label, value);
+
+ const labels = element.$.labelScores.getLabelValues();
+ assert.deepEqual(labels, {
+ 'Code-Review': 0,
+ 'Verified': 1,
+ });
+ done();
});
});
@@ -289,6 +312,26 @@ limitations under the License.
});
}
+ function isFocusInsideElement(element) {
+ // In Polymer 2 focused element either <paper-input> or nested
+ // native input <input> element depending on the current focus
+ // in browser window.
+ // For example, the focus is changed if the developer console
+ // get a focus.
+ let activeElement = getActiveElement();
+ while (activeElement) {
+ if (activeElement === element) {
+ return true;
+ }
+ if (activeElement.parentElement) {
+ activeElement = activeElement.parentElement;
+ } else {
+ activeElement = activeElement.getRootNode().host;
+ }
+ }
+ return false;
+ }
+
function testConfirmationDialog(done, cc) {
const yesButton =
element.$$('.reviewerConfirmationButtons gr-button:first-child');
@@ -342,10 +385,11 @@ limitations under the License.
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
// We should be focused on account entry input.
- assert.equal(getActiveElement().id, 'input');
+ assert.isTrue(
+ isFocusInsideElement(element.$.reviewers.$.entry.$.input.$.input));
// No reviewer/CC should have been added.
- assert.equal(element.$$('#ccs').additions().length, 0);
+ assert.equal(element.$.ccs.additions().length, 0);
assert.equal(element.$.reviewers.additions().length, 0);
// Reopen confirmation dialog.
@@ -370,7 +414,7 @@ limitations under the License.
}).then(() => {
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
const additions = cc ?
- element.$$('#ccs').additions() :
+ element.$.ccs.additions() :
element.$.reviewers.additions();
assert.deepEqual(
additions,
@@ -387,7 +431,13 @@ limitations under the License.
]);
// We should be focused on account entry input.
- assert.equal(getActiveElement().id, 'input');
+ if (cc) {
+ assert.isTrue(
+ isFocusInsideElement(element.$.ccs.$.entry.$.input.$.input));
+ } else {
+ assert.isTrue(
+ isFocusInsideElement(element.$.reviewers.$.entry.$.input.$.input));
+ }
}).then(done);
}
@@ -409,10 +459,10 @@ limitations under the License.
test('_reviewersMutated when account-text-change is fired from ccs', () => {
flushAsynchronousOperations();
assert.isFalse(element._reviewersMutated);
- assert.isTrue(element.$$('#ccs').allowAnyInput);
+ assert.isTrue(element.$.ccs.allowAnyInput);
assert.isFalse(element.$$('#reviewers').allowAnyInput);
- element.$$('#ccs').dispatchEvent(new CustomEvent('account-text-changed',
- {bubbles: true}));
+ element.$.ccs.dispatchEvent(new CustomEvent('account-text-changed',
+ {bubbles: true, composed: true}));
assert.isTrue(element._reviewersMutated);
});
@@ -471,11 +521,7 @@ limitations under the License.
sandbox.stub(window, 'fetch', () => {
const text = '....{"reviewers":{"id1":{"error":"first error"}},' +
'"ccs":{"id2":{"error":"second error"}}}';
- return Promise.resolve({
- ok: false,
- status: 400,
- text() { return Promise.resolve(text); },
- });
+ return Promise.resolve(cloneableResponse(400, text));
});
element.addEventListener('server-error', event => {
@@ -493,6 +539,27 @@ limitations under the License.
flush(() => { element.send(); });
});
+ test('non-json 400 is treated as a normal server-error', done => {
+ sandbox.stub(window, 'fetch', () => {
+ const text = 'Comment validation error!';
+ return Promise.resolve(cloneableResponse(400, text));
+ });
+
+ element.addEventListener('server-error', event => {
+ if (event.target !== element) {
+ return;
+ }
+ event.detail.response.text().then(body => {
+ assert.equal(body, 'Comment validation error!');
+ done();
+ });
+ });
+
+ // Async tick is needed because iron-selector content is distributed and
+ // distributed content requires an observer to be set up.
+ flush(() => { element.send(); });
+ });
+
test('filterReviewerSuggestion', () => {
const owner = makeAccount();
const reviewer1 = makeAccount();
@@ -528,7 +595,7 @@ limitations under the License.
const textareaStub = sandbox.stub(element.$.textarea, 'async');
const reviewerEntryStub = sandbox.stub(element.$.reviewers.focusStart,
'async');
- const ccStub = sandbox.stub(element.$$('#ccs').focusStart, 'async');
+ const ccStub = sandbox.stub(element.$.ccs.focusStart, 'async');
element._focusOn();
assert.equal(element._chooseFocusTarget.callCount, 1);
assert.deepEqual(textareaStub.callCount, 1);
@@ -695,6 +762,40 @@ limitations under the License.
assert.deepEqual(element._reviewersPendingRemove.CC, [cc1, cc4, cc3]);
});
+ test('moving from reviewer to cc', () => {
+ element._reviewersPendingRemove = {
+ CC: [],
+ REVIEWER: [],
+ };
+ flushAsynchronousOperations();
+
+ const reviewer1 = makeAccount();
+ const reviewer2 = makeAccount();
+ const reviewer3 = makeAccount();
+ const cc1 = makeAccount();
+ const cc2 = makeAccount();
+ const cc3 = makeAccount();
+ const cc4 = makeAccount();
+ element._reviewers = [reviewer1, reviewer2, reviewer3];
+ element._ccs = [cc1, cc2, cc3, cc4];
+ element.push('_ccs', reviewer1);
+ flushAsynchronousOperations();
+
+ assert.deepEqual(element._reviewers,
+ [reviewer2, reviewer3]);
+ assert.deepEqual(element._ccs, [cc1, cc2, cc3, cc4, reviewer1]);
+ assert.deepEqual(element._reviewersPendingRemove.REVIEWER, [reviewer1]);
+
+ element.push('_ccs', reviewer3, reviewer2);
+ flushAsynchronousOperations();
+
+ assert.deepEqual(element._reviewers, []);
+ assert.deepEqual(element._ccs,
+ [cc1, cc2, cc3, cc4, reviewer1, reviewer3, reviewer2]);
+ assert.deepEqual(element._reviewersPendingRemove.REVIEWER,
+ [reviewer1, reviewer3, reviewer2]);
+ });
+
test('migrate reviewers between states', done => {
element._reviewersPendingRemove = {
CC: [],
@@ -702,7 +803,7 @@ limitations under the License.
};
flushAsynchronousOperations();
const reviewers = element.$.reviewers;
- const ccs = element.$$('#ccs');
+ const ccs = element.$.ccs;
const reviewer1 = makeAccount();
const reviewer2 = makeAccount();
const cc1 = makeAccount();
@@ -801,60 +902,50 @@ limitations under the License.
const error1 = 'error 1';
const error2 = 'error 2';
const error3 = 'error 3';
- const response = {
- status: 400,
- text() {
- return Promise.resolve(')]}\'' + JSON.stringify({
- reviewers: {
- username1: {
- input: 'user 1',
- error: error1,
- },
- username2: {
- input: 'user 2',
- error: error2,
- },
- },
- ccs: {
- username3: {
- input: 'user 3',
- error: error3,
- },
- },
- }));
+ const text = ')]}\'' + JSON.stringify({
+ reviewers: {
+ username1: {
+ input: 'user 1',
+ error: error1,
+ },
+ username2: {
+ input: 'user 2',
+ error: error2,
+ },
},
- };
+ ccs: {
+ username3: {
+ input: 'user 3',
+ error: error3,
+ },
+ },
+ });
element.addEventListener('server-error', e => {
e.detail.response.text().then(text => {
assert.equal(text, [error1, error2, error3].join(', '));
done();
});
});
- element._handle400Error(response);
+ element._handle400Error(cloneableResponse(400, text));
});
test('_handle400Error CCs only', done => {
const error1 = 'error 1';
- const response = {
- status: 400,
- text() {
- return Promise.resolve(')]}\'' + JSON.stringify({
- ccs: {
- username1: {
- input: 'user 1',
- error: error1,
- },
- },
- }));
+ const text = ')]}\'' + JSON.stringify({
+ ccs: {
+ username1: {
+ input: 'user 1',
+ error: error1,
+ },
},
- };
+ });
element.addEventListener('server-error', e => {
e.detail.response.text().then(text => {
assert.equal(text, error1);
done();
});
});
- element._handle400Error(response);
+ element._handle400Error(cloneableResponse(400, text));
});
test('fires height change when the drafts load', done => {
@@ -898,21 +989,6 @@ limitations under the License.
assert.isFalse(startReviewStub.called);
});
});
-
- test('fall back to start review against old backend', () => {
- stubSaveReview(review => {
- return {}; // old backend won't set ready: true
- });
-
- return element.send(true, true).then(() => {
- assert.isTrue(startReviewStub.called);
- }).then(() => {
- startReviewStub.reset();
- return element.send(true, false);
- }).then(() => {
- assert.isFalse(startReviewStub.called);
- });
- });
});
suite('start review and save buttons', () => {
@@ -938,28 +1014,9 @@ limitations under the License.
});
});
- test('dummy message to force email on start review', () => {
- stubSaveReview(review => {
- assert.equal(review.message, element.START_REVIEW_MESSAGE);
- return {ready: true};
- });
- return element.send(true, true);
- });
-
test('buttons disabled until all API calls are resolved', () => {
stubSaveReview(review => {
- return {}; // old backend won't set ready: true
- });
- // Check that element is disabled asynchronously after the setReady
- // promise is returned. The element should not be reenabled until
- // that promise is resolved.
- sandbox.stub(element, '_maybeSetReady', (startReview, response) => {
- return new Promise(resolve => {
- Polymer.Base.async(() => {
- assert.isTrue(element.disabled);
- resolve();
- });
- });
+ return {ready: true};
});
return element.send(true, true).then(() => {
assert.isFalse(element.disabled);
@@ -979,11 +1036,6 @@ limitations under the License.
assert.isFalse(element.disabled);
}
- function assertDialogClosed() {
- assert.strictEqual('', element.draft);
- assert.isFalse(element.disabled);
- }
-
test('error occurs in _saveReview', () => {
stubSaveReview(review => {
throw expectedError;
@@ -994,46 +1046,6 @@ limitations under the License.
});
});
- test('error occurs during startReview', () => {
- stubSaveReview(review => {
- return {}; // old backend won't set ready: true
- });
- const errorStub = sandbox.stub(
- console, 'error', (msg, err) => undefined);
- sandbox.stub(element.$.restAPI, 'startReview', () => {
- throw expectedError;
- });
- return element.send(true, true).then(() => {
- assertDialogClosed();
- assert.isTrue(
- errorStub.calledWith('error setting ready:', expectedError));
- });
- });
-
- test('non-ok response received by startReview', () => {
- stubSaveReview(review => {
- return {}; // old backend won't set ready: true
- });
- sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
- f({status: 500});
- });
- return element.send(true, true).then(() => {
- assertDialogClosed();
- });
- });
-
- test('409 response received by startReview', () => {
- stubSaveReview(review => {
- return {}; // old backend won't set ready: true
- });
- sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
- f({status: 409});
- });
- return element.send(true, true).then(() => {
- assertDialogClosed();
- });
- });
-
suite('pending diff drafts?', () => {
test('yes', () => {
const promise = mockPromise();
@@ -1065,20 +1077,84 @@ limitations under the License.
test('_computeSendButtonDisabled', () => {
const fn = element._computeSendButtonDisabled.bind(element);
- assert.isFalse(fn('Start review'));
- assert.isTrue(fn('Send', {}, '', false, false, false));
+ assert.isFalse(fn(
+ /* buttonLabel= */ 'Start review',
+ /* drafts= */ {},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ false,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
+ assert.isTrue(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ false,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
// Mock nonempty comment draft array, with seding comments.
- assert.isFalse(fn('Send', {file: ['draft']}, '', false, false, true));
+ assert.isFalse(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {file: ['draft']},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ false,
+ /* includeComments= */ true,
+ /* disabled= */ false
+ ));
// Mock nonempty comment draft array, without seding comments.
- assert.isTrue(fn('Send', {file: ['draft']}, '', false, false, false));
+ assert.isTrue(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {file: ['draft']},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ false,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
// Mock nonempty change message.
- assert.isFalse(fn('Send', {}, 'test', false, false, false));
+ assert.isFalse(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {},
+ /* text= */ 'test',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ false,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
// Mock reviewers mutated.
- assert.isFalse(fn('Send', {}, '', true, false, false));
+ assert.isFalse(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {},
+ /* text= */ '',
+ /* reviewersMutated= */ true,
+ /* labelsChanged= */ false,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
// Mock labels changed.
- assert.isFalse(fn('Send', {}, '', false, true, false));
+ assert.isFalse(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ true,
+ /* includeComments= */ false,
+ /* disabled= */ false
+ ));
// Whole dialog is disabled.
- assert.isTrue(fn('Send', {}, '', false, true, false, true));
+ assert.isTrue(fn(
+ /* buttonLabel= */ 'Send',
+ /* drafts= */ {},
+ /* text= */ '',
+ /* reviewersMutated= */ false,
+ /* labelsChanged= */ true,
+ /* includeComments= */ false,
+ /* disabled= */ true
+ ));
});
test('_submit blocked when no mutations exist', () => {
@@ -1092,7 +1168,7 @@ limitations under the License.
MockInteractions.tap(element.$$('gr-button.send'));
assert.isFalse(sendStub.called);
- element.diffDrafts = {test: true};
+ element.diffDrafts = {test: [{val: true}]};
flushAsynchronousOperations();
MockInteractions.tap(element.$$('gr-button.send'));
@@ -1104,7 +1180,7 @@ limitations under the License.
// computed to false.
element.diffDrafts = {};
assert.equal(element.getFocusStops().end, element.$.cancelButton);
- element.diffDrafts = {test: true};
+ element.diffDrafts = {test: [{val: true}]};
assert.equal(element.getFocusStops().end, element.$.sendButton);
});
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
index 73e8bea5d1..a5875ab1c7 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -32,53 +32,35 @@ limitations under the License.
opacity: .8;
pointer-events: none;
}
- .autocompleteContainer {
- position: relative;
- }
- .hiddenReviewers {
- margin-top: .3em;
- }
- .inputContainer {
- display: flex;
- margin-top: .25em;
- }
- .inputContainer input {
- flex: 1;
- font: inherit;
- }
- gr-account-chip {
- margin-top: .3em;
+ .container > :not(:first-child) {
+ margin-top: var(--spacing-s);
}
gr-button {
--gr-button: {
- padding-left: 0;
- padding-right: 0;
- }
- }
- @media screen and (max-width: 50em), screen and (min-width: 75em) {
- gr-account-chip:first-of-type {
- margin-top: 0;
+ padding: 0px 0px;
}
}
</style>
- <template is="dom-repeat" items="[[_displayedReviewers]]" as="reviewer">
- <gr-account-chip class="reviewer" account="[[reviewer]]"
- on-remove="_handleRemove"
- additional-text="[[_computeReviewerTooltip(reviewer, change)]]"
- removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
- </gr-account-chip>
- </template>
- <gr-button
- class="hiddenReviewers"
- link
- hidden$="[[!_hiddenReviewerCount]]"
- on-tap="_handleViewAll">and [[_hiddenReviewerCount]] more</gr-button>
- <div class="controlsContainer" hidden$="[[!mutable]]">
+ <div class="container">
+ <template is="dom-repeat" items="[[_displayedReviewers]]" as="reviewer">
+ <gr-account-chip class="reviewer" account="[[reviewer]]"
+ on-remove="_handleRemove"
+ additional-text="[[_computeReviewerTooltip(reviewer, change)]]"
+ removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
+ </gr-account-chip>
+ </template>
<gr-button
+ class="hiddenReviewers"
link
- id="addReviewer"
- class="addReviewer"
- on-tap="_handleAddTap">[[_addLabel]]</gr-button>
+ hidden$="[[!_hiddenReviewerCount]]"
+ on-click="_handleViewAll">and [[_hiddenReviewerCount]] more</gr-button>
+ <div class="controlsContainer" hidden$="[[!mutable]]">
+ <gr-button
+ link
+ id="addReviewer"
+ class="addReviewer"
+ on-click="_handleAddTap">[[_addLabel]]</gr-button>
+ </div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
index 4f1b05155c..c94625d55f 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-reviewer-list',
- _legacyUndefinedCheck: true,
/**
* Fired when the "Add reviewer..." button is tapped.
@@ -75,6 +74,10 @@
_xhrPromise: Object,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_reviewersChanged(change.reviewers.*, change.owner)',
],
@@ -163,6 +166,11 @@
},
_reviewersChanged(changeRecord, owner) {
+ // Polymer 2: check for undefined
+ if ([changeRecord, owner].some(arg => arg === undefined)) {
+ return;
+ }
+
let result = [];
const reviewers = changeRecord.base;
for (const key in reviewers) {
@@ -180,10 +188,10 @@
return reviewer._account_id != owner._account_id;
});
- // If there is one more than the max reviewers, don't show the 'show
- // more' button, because it takes up just as much space.
+ // If there is one or two more than the max reviewers, don't show the
+ // 'show more' button, because it takes up just as much space.
if (this.maxReviewersDisplayed &&
- this._reviewers.length > this.maxReviewersDisplayed + 1) {
+ this._reviewers.length > this.maxReviewersDisplayed + 2) {
this._displayedReviewers =
this._reviewers.slice(0, this.maxReviewersDisplayed);
} else {
@@ -192,6 +200,11 @@
},
_computeHiddenCount(reviewers, displayedReviewers) {
+ // Polymer 2: check for undefined
+ if ([reviewers, displayedReviewers].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return reviewers.length - displayedReviewers.length;
},
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index 81827fdd4d..6936a04199 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reviewer-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-reviewer-list.html">
@@ -212,10 +214,10 @@ limitations under the License.
assert.isTrue(element.$$('.hiddenReviewers').hidden);
});
- test('show all reviewers button with 7 reviewers', () => {
+ test('show all reviewers button with 8 reviewers', () => {
const reviewers = [];
element.maxReviewersDisplayed = 5;
- for (let i = 0; i < 7; i++) {
+ for (let i = 0; i < 8; i++) {
reviewers.push(
{email: i+'reviewer@google.com', name: 'reviewer-' + i});
}
@@ -229,9 +231,9 @@ limitations under the License.
CC: reviewers,
},
};
- assert.equal(element._hiddenReviewerCount, 2);
+ assert.equal(element._hiddenReviewerCount, 3);
assert.equal(element._displayedReviewers.length, 5);
- assert.equal(element._reviewers.length, 7);
+ assert.equal(element._reviewers.length, 8);
assert.isFalse(element.$$('.hiddenReviewers').hidden);
});
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
index 4d8e5aea52..4c82d2a166 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-comment-thread/gr-comment-thread.html">
@@ -26,11 +26,11 @@ limitations under the License.
#threads {
display: block;
min-height: 20rem;
- padding: 1rem;
+ padding: var(--spacing-l);
}
gr-comment-thread {
display: block;
- margin-bottom: .5rem;
+ margin-bottom: var(--spacing-m);
max-width: 80ch;
}
.header {
@@ -41,7 +41,7 @@ limitations under the License.
display: flex;
justify-content: left;
min-height: 3.2em;
- padding: .5em var(--default-horizontal-margin);
+ padding: var(--spacing-m) var(--spacing-l);
}
.toggleItem.draftToggle {
display: none;
@@ -52,7 +52,7 @@ limitations under the License.
.toggleItem {
align-items: center;
display: flex;
- margin-right: 1rem;
+ margin-right: var(--spacing-l);
}
.draftsOnly:not(.unresolvedOnly) gr-comment-thread[has-draft],
.unresolvedOnly:not(.draftsOnly) gr-comment-thread[unresolved],
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index 317685b207..747a47a992 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -25,7 +25,6 @@
Polymer({
is: 'gr-thread-list',
- _legacyUndefinedCheck: true,
properties: {
/** @type {?} */
@@ -95,12 +94,35 @@
},
_computeFilteredThreads(sortedThreads, unresolvedOnly, draftsOnly) {
+ // Polymer 2: check for undefined
+ if ([
+ sortedThreads,
+ unresolvedOnly,
+ draftsOnly,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return sortedThreads.filter(c => {
if (draftsOnly) {
return c.hasDraft;
} else if (unresolvedOnly) {
return c.unresolved;
} else {
+ const comments = c && c.thread && c.thread.comments;
+ let robotComment = false;
+ let humanReplyToRobotComment = false;
+ comments.forEach(comment => {
+ if (comment.robot_id) {
+ robotComment = true;
+ } else if (robotComment) {
+ // Robot comment exists and human comment exists after it
+ humanReplyToRobotComment = true;
+ }
+ });
+ if (robotComment) {
+ return humanReplyToRobotComment ? c : false;
+ }
return c;
}
}).map(threadInfo => threadInfo.thread);
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index 792644e7b0..ff65aa81ca 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-thread-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-thread-list.html">
@@ -168,6 +170,68 @@ limitations under the License.
rootId: 'zcf0b9fa_fe1a5f62',
start_datetime: '2018-02-09 18:49:18.000000000',
},
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'rc1',
+ line: 5,
+ updated: '2019-02-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ robot_id: 'rc1',
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 5,
+ rootId: 'rc1',
+ start_datetime: '2019-02-08 18:49:18.000000000',
+ },
+ {
+ comments: [
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'rc2',
+ line: 5,
+ updated: '2019-03-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ robot_id: 'rc2',
+ },
+ {
+ __path: '/COMMIT_MSG',
+ author: {
+ _account_id: 1000000,
+ name: 'user',
+ username: 'user',
+ },
+ patch_set: 4,
+ id: 'c2_1',
+ line: 5,
+ updated: '2019-03-08 18:49:18.000000000',
+ message: 'test',
+ unresolved: true,
+ },
+ ],
+ patchNum: 4,
+ path: '/COMMIT_MSG',
+ line: 5,
+ rootId: 'rc2',
+ start_datetime: '2019-03-08 18:49:18.000000000',
+ },
];
flushAsynchronousOperations();
threadElements = Polymer.dom(element.root)
@@ -192,46 +256,69 @@ limitations under the License.
});
test('_computeSortedThreads', () => {
- assert.equal(element._sortedThreads.length, 5);
+ assert.equal(element._sortedThreads.length, 7);
// Draft and unresolved
assert.equal(element._sortedThreads[0].thread.rootId,
'ecf0b9fa_fe1a5f62');
- // unresolved
+ // Unresolved robot comment
assert.equal(element._sortedThreads[1].thread.rootId,
+ 'rc2');
+ // Unresolved robot comment
+ assert.equal(element._sortedThreads[2].thread.rootId,
+ 'rc1');
+ // unresolved
+ assert.equal(element._sortedThreads[3].thread.rootId,
'scaddf38_44770ec1');
// unresolved
- assert.equal(element._sortedThreads[2].thread.rootId,
+ assert.equal(element._sortedThreads[4].thread.rootId,
'8caddf38_44770ec1');
// resolved and draft
- assert.equal(element._sortedThreads[3].thread.rootId,
+ assert.equal(element._sortedThreads[5].thread.rootId,
'zcf0b9fa_fe1a5f62');
// resolved
- assert.equal(element._sortedThreads[4].thread.rootId,
+ assert.equal(element._sortedThreads[6].thread.rootId,
'09a9fb0a_1484e6cf');
});
+ test('filtered threads do not contain robot comments without reply', () => {
+ const thread = element.threads.find(thread => thread.rootId === 'rc1');
+ assert.equal(element._filteredThreads.includes(thread), false);
+ });
+
+ test('filtered threads contains robot comments with reply', () => {
+ const thread = element.threads.find(thread => thread.rootId === 'rc2');
+ assert.equal(element._filteredThreads.includes(thread), true);
+ });
+
+
test('thread removal', () => {
- threadElements[1].fire('thread-discard', {rootId: 'scaddf38_44770ec1'});
+ threadElements[1].fire('thread-discard', {rootId: 'rc2'});
flushAsynchronousOperations();
- assert.equal(element._sortedThreads.length, 4);
+ assert.equal(element._sortedThreads.length, 6);
assert.equal(element._sortedThreads[0].thread.rootId,
'ecf0b9fa_fe1a5f62');
- // unresolved
+ // Unresolved robot comment
assert.equal(element._sortedThreads[1].thread.rootId,
+ 'rc1');
+ // unresolved
+ assert.equal(element._sortedThreads[2].thread.rootId,
+ 'scaddf38_44770ec1');
+ // unresolved
+ assert.equal(element._sortedThreads[3].thread.rootId,
'8caddf38_44770ec1');
// resolved and draft
- assert.equal(element._sortedThreads[2].thread.rootId,
+ assert.equal(element._sortedThreads[4].thread.rootId,
'zcf0b9fa_fe1a5f62');
// resolved
- assert.equal(element._sortedThreads[3].thread.rootId,
+ assert.equal(element._sortedThreads[5].thread.rootId,
'09a9fb0a_1484e6cf');
});
- test('toggle unresolved only shows unressolved comments', () => {
+ test('toggle unresolved only shows unresolved comments', () => {
MockInteractions.tap(element.$.unresolvedToggle);
flushAsynchronousOperations();
assert.equal(Polymer.dom(element.root)
- .querySelectorAll('gr-comment-thread').length, 3);
+ .querySelectorAll('gr-comment-thread').length, 5);
});
test('toggle drafts only shows threads with draft comments', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
index 792c300383..e3cee56d23 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
@@ -32,11 +33,11 @@ limitations under the License.
width: 100%;
}
ol {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
list-style: decimal;
}
p {
- margin-bottom: .75em;
+ margin-bottom: var(--spacing-m);
}
</style>
<gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
index df96be2d8f..092204ad60 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
@@ -29,7 +29,6 @@
Polymer({
is: 'gr-upload-help-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the user presses the close button.
@@ -58,6 +57,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
attached() {
this.$.restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
@@ -73,11 +76,21 @@
_handleCloseTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('close', null, {bubbles: false});
},
_computeFetchCommand(revision, preferredDownloadCommand,
preferredDownloadScheme) {
+ // Polymer 2: check for undefined
+ if ([
+ revision,
+ preferredDownloadCommand,
+ preferredDownloadScheme,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (!revision) { return; }
if (!revision || !revision.fetch) { return; }
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
index a5a6e7698f..577b978d05 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-upload-help-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-upload-help-dialog.html">
@@ -73,31 +75,40 @@ limitations under the License.
assert.isUndefined(element._computeFetchCommand({fetch: {}}));
});
+ test('not all defined', () => {
+ assert.isUndefined(
+ element._computeFetchCommand(testRev, undefined, ''));
+ assert.isUndefined(
+ element._computeFetchCommand(testRev, '', undefined));
+ assert.isUndefined(
+ element._computeFetchCommand(undefined, '', ''));
+ });
+
test('insufficiently defined scheme', () => {
assert.isUndefined(
- element._computeFetchCommand(testRev, undefined, 'badscheme'));
+ element._computeFetchCommand(testRev, '', 'badscheme'));
const rev = Object.assign({}, testRev);
rev.fetch = Object.assign({}, testRev.fetch, {nocmds: {commands: {}}});
assert.isUndefined(
- element._computeFetchCommand(rev, undefined, 'nocmds'));
+ element._computeFetchCommand(rev, '', 'nocmds'));
rev.fetch.nocmds.commands.unsupported = 'unsupported';
assert.isUndefined(
- element._computeFetchCommand(rev, undefined, 'nocmds'));
+ element._computeFetchCommand(rev, '', 'nocmds'));
});
test('default scheme and command', () => {
- const cmd = element._computeFetchCommand(testRev);
+ const cmd = element._computeFetchCommand(testRev, '', '');
assert.isTrue(cmd === 'http checkout' || cmd === 'ssh pull');
});
test('default command', () => {
assert.strictEqual(
- element._computeFetchCommand(testRev, undefined, 'http'),
+ element._computeFetchCommand(testRev, '', 'http'),
'http checkout');
assert.strictEqual(
- element._computeFetchCommand(testRev, undefined, 'ssh'),
+ element._computeFetchCommand(testRev, '', 'ssh'),
'ssh pull');
});
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
index 2c2fb4ea8c..5152ef9555 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
@@ -15,18 +15,19 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-avatar/gr-avatar.html">
<dom-module id="gr-account-dropdown">
<template>
<style include="shared-styles">
gr-dropdown {
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
--gr-button: {
color: var(--header-text-color);
}
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index af4510d360..7cbe9883a3 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-account-dropdown',
- _legacyUndefinedCheck: true,
properties: {
account: Object,
@@ -58,7 +57,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
detached() {
@@ -66,6 +65,11 @@
},
_getLinks(switchAccountUrl, path) {
+ // Polymer 2: check for undefined
+ if ([switchAccountUrl, path].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const links = [{name: 'Settings', url: '/settings/'}];
if (switchAccountUrl) {
const replacements = {path};
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
index fe63a3e4c1..e29faa847f 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-account-dropdown</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-account-dropdown.html">
@@ -78,11 +80,15 @@ limitations under the License.
});
test('switch account', () => {
+ // Missing params.
+ assert.isUndefined(element._getLinks());
+ assert.isUndefined(element._getLinks(null));
+
// No switch account link.
- assert.equal(element._getLinks(null).length, 2);
+ assert.equal(element._getLinks(null, '').length, 2);
// Unparameterized switch account link.
- let links = element._getLinks('/switch-account');
+ let links = element._getLinks('/switch-account', '');
assert.equal(links.length, 3);
assert.deepEqual(links[1], {
name: 'Switch account',
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html
index f8bf33c2d4..09b928ed67 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
index 5679408286..8d3b58e363 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-error-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the dismiss button is pressed.
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
index e2c314b0dd..648f8be28f 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-error-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-error-dialog.html">
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
index 4ca106eeb6..048d39227e 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
@@ -16,7 +16,8 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../core/gr-error-dialog/gr-error-dialog.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-alert/gr-alert.html">
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 38dfecb5a3..5865e3c5c5 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -27,10 +27,10 @@
Polymer({
is: 'gr-error-manager',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
],
properties: {
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index 88e3efd9dc..9140c17a0d 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-error-manager</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-error-manager.html">
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.html b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.html
index 2ff7953a37..a8632762b1 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.html
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-key-binding-display">
@@ -24,10 +24,10 @@ limitations under the License.
.key {
background-color: var(--chip-background-color);
border: 1px solid var(--border-color);
- border-radius: 3px;
+ border-radius: var(--border-radius);
display: inline-block;
font-weight: var(--font-weight-bold);
- padding: .1em .5em;
+ padding: var(--spacing-xxs) var(--spacing-m);
text-align: center;
}
</style>
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
index e8c6479947..89d1091086 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-key-binding-display',
- _legacyUndefinedCheck: true,
properties: {
/** @type {Array<string>} */
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
index 0361d76d93..39c8af8012 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-key-binding-display</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-key-binding-display.html">
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index e3552ccd25..5494f626cc 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../gr-key-binding-display/gr-key-binding-display.html">
@@ -30,11 +31,11 @@ limitations under the License.
overflow-y: auto;
}
header{
- padding: 1em;
+ padding: var(--spacing-l);
}
main {
display: flex;
- padding: 0 2em 2em;
+ padding: 0 var(--spacing-xxl) var(--spacing-xxl);
}
header {
align-items: center;
@@ -43,18 +44,18 @@ limitations under the License.
justify-content: space-between;
}
table:last-of-type {
- margin-left: 3em;
+ margin-left: var(--spacing-xxl);
}
td {
- padding: .2em 0;
+ padding: var(--spacing-xs) 0;
}
td:first-child {
- padding-right: .5em;
+ padding-right: var(--spacing-m);
text-align: right;
}
.header {
font-weight: var(--font-weight-bold);
- padding-top: 1em;
+ padding-top: var(--spacing-l);
}
.modifier {
font-weight: normal;
@@ -62,7 +63,7 @@ limitations under the License.
</style>
<header>
<h3>Keyboard shortcuts</h3>
- <gr-button link on-tap="_handleCloseTap">Close</gr-button>
+ <gr-button link on-click="_handleCloseTap">Close</gr-button>
</header>
<main>
<table>
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
index f206db1496..4bc6e11bdc 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-keyboard-shortcuts-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the user presses the close button.
@@ -51,6 +50,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -70,6 +70,7 @@
_handleCloseTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('close', null, {bubbles: false});
},
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
index 50579dda40..1a3d6c78e6 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-key-binding-display</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-keyboard-shortcuts-dialog.html">
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index e552cdad36..d29858eedd 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -14,10 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/docs-url-behavior/docs-url-behavior.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
@@ -45,7 +46,6 @@ limitations under the License.
.bigTitle:hover {
text-decoration: underline;
}
- /* TODO (viktard): Clean-up after chromium-style migrates to component. */
.titleText::before {
background-image: var(--header-icon);
background-size: var(--header-icon-size) var(--header-icon-size);
@@ -62,7 +62,7 @@ limitations under the License.
}
ul {
list-style: none;
- padding-left: 1em;
+ padding-left: var(--spacing-l);
}
.links > li {
cursor: default;
@@ -86,16 +86,16 @@ limitations under the License.
justify-content: flex-end;
}
.rightItems gr-endpoint-decorator:not(:empty) {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
gr-smart-search {
flex-grow: 1;
- margin-left: .5em;
+ margin: 0 var(--spacing-m);
max-width: 500px;
}
gr-dropdown,
.browse {
- padding: .6em .5em;
+ padding: var(--spacing-m);
}
gr-dropdown {
--gr-dropdown-item: {
@@ -103,7 +103,7 @@ limitations under the License.
}
}
.settingsButton {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
.browse {
color: var(--header-text-color);
@@ -128,13 +128,13 @@ limitations under the License.
.accountContainer {
align-items: center;
display: flex;
- margin: 0 -.5em 0 .5em;
+ margin: 0 calc(0 - var(--spacing-m)) 0 var(--spacing-m);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.loginButton, .registerButton {
- padding: .5em 1em;
+ padding: var(--spacing-m) var(--spacing-l);
}
.dropdown-trigger {
text-decoration: none;
@@ -160,7 +160,7 @@ limitations under the License.
}
@media screen and (max-width: 50em) {
.bigTitle {
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
font-weight: var(--font-weight-bold);
}
gr-smart-search,
@@ -173,10 +173,10 @@ limitations under the License.
display: inline-flex;
}
.accountContainer {
- margin-left: .5em !important;
+ margin-left: var(--spacing-m) !important;
}
gr-dropdown {
- padding: .5em 0 .5em .5em;
+ padding: var(--spacing-m) 0 var(--spacing-m) var(--spacing-m);
}
}
</style>
@@ -188,16 +188,16 @@ limitations under the License.
</a>
<ul class="links">
<template is="dom-repeat" items="[[_links]]" as="linkGroup">
- <li class$="[[linkGroup.class]]">
- <gr-dropdown
- link
- down-arrow
- items = [[linkGroup.links]]
- horizontal-align="left">
- <span class="linksTitle" id="[[linkGroup.title]]">
- [[linkGroup.title]]
- </span>
- </gr-dropdown>
+ <li class$="[[_computeLinkGroupClass(linkGroup)]]">
+ <gr-dropdown
+ link
+ down-arrow
+ items = [[linkGroup.links]]
+ horizontal-align="left">
+ <span class="linksTitle" id="[[linkGroup.title]]">
+ [[linkGroup.title]]
+ </span>
+ </gr-dropdown>
</li>
</template>
</ul>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 69fc89ff0b..ab9b70e096 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -71,7 +71,6 @@
Polymer({
is: 'gr-main-header',
- _legacyUndefinedCheck: true,
hostAttributes: {
role: 'banner',
@@ -138,6 +137,7 @@
Gerrit.AdminNavBehavior,
Gerrit.BaseUrlBehavior,
Gerrit.DocsUrlBehavior,
+ Gerrit.FireBehavior,
],
observers: [
@@ -180,6 +180,17 @@
},
_computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) {
+ // Polymer 2: check for undefined
+ if ([
+ defaultLinks,
+ userLinks,
+ adminLinks,
+ topMenus,
+ docBaseUrl,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const links = defaultLinks.map(menu => {
return {
title: menu.title,
@@ -213,7 +224,7 @@
});
if (m.name in topMenuLinks) {
items.forEach(link => { topMenuLinks[m.name].push(link); });
- } else {
+ } else if (items.length > 0) {
links.push({
title: m.name,
links: topMenuLinks[m.name] = items,
@@ -318,7 +329,16 @@
_onMobileSearchTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('mobile-search', null, {bubbles: false});
},
+
+ _computeLinkGroupClass(linkGroup) {
+ if (linkGroup && linkGroup.class) {
+ return linkGroup.class;
+ }
+
+ return '';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index 89b3908754..3309aa594e 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-main-header</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-main-header.html">
@@ -109,22 +111,34 @@ limitations under the License.
}];
// When no admin links are passed, it should use the default.
- assert.deepEqual(element._computeLinks(defaultLinks, [], adminLinks, []),
- defaultLinks.concat({
- title: 'Browse',
- links: adminLinks,
- }));
- assert.deepEqual(
- element._computeLinks(defaultLinks, userLinks, adminLinks, []),
- defaultLinks.concat([
- {
- title: 'Your',
- links: userLinks,
- },
- {
- title: 'Browse',
- links: adminLinks,
- }]));
+ assert.deepEqual(element._computeLinks(
+ defaultLinks,
+ /* userLinks= */[],
+ adminLinks,
+ /* topMenus= */[],
+ /* docBaseUrl= */ ''
+ ),
+ defaultLinks.concat({
+ title: 'Browse',
+ links: adminLinks,
+ }));
+ assert.deepEqual(element._computeLinks(
+ defaultLinks,
+ userLinks,
+ adminLinks,
+ /* topMenus= */[],
+ /* docBaseUrl= */ ''
+ ),
+ defaultLinks.concat([
+ {
+ title: 'Your',
+ links: userLinks,
+ },
+ {
+ title: 'Browse',
+ links: adminLinks,
+ }])
+ );
});
test('documentation links', () => {
@@ -166,7 +180,13 @@ limitations under the License.
url: 'https://gerrit/plugins/plugin-manager/static/index.html',
}],
}];
- assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ /* defaultLinks= */ [],
+ /* userLinks= */ [],
+ adminLinks,
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Browse',
links: adminLinks,
},
@@ -196,7 +216,13 @@ limitations under the License.
url: '/plugins/myplugin/index.html',
}],
}];
- assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ /* defaultLinks= */ [],
+ /* userLinks= */ [],
+ adminLinks,
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Browse',
links: adminLinks,
},
@@ -229,7 +255,13 @@ limitations under the License.
url: 'https://gerrit/plugins/plugin-manager/static/create.html',
}],
}];
- assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ /* defaultLinks= */ [],
+ /* userLinks= */ [],
+ adminLinks,
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Browse',
links: adminLinks,
}, {
@@ -260,7 +292,13 @@ limitations under the License.
url: 'https://gerrit/plugins/plugin-manager/static/index.html',
}],
}];
- assert.deepEqual(element._computeLinks(defaultLinks, [], [], topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ defaultLinks,
+ /* userLinks= */ [],
+ /* adminLinks= */ [],
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Faves',
links: defaultLinks[0].links.concat([{
name: 'Manage',
@@ -285,7 +323,13 @@ limitations under the License.
url: 'https://gerrit/plugins/plugin-manager/static/index.html',
}],
}];
- assert.deepEqual(element._computeLinks([], userLinks, [], topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ /* defaultLinks= */ [],
+ userLinks,
+ /* adminLinks= */ [],
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Your',
links: userLinks.concat([{
name: 'Manage',
@@ -310,7 +354,13 @@ limitations under the License.
url: 'https://gerrit/plugins/plugin-manager/static/index.html',
}],
}];
- assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+ assert.deepEqual(element._computeLinks(
+ /* defaultLinks= */ [],
+ /* userLinks= */ [],
+ adminLinks,
+ topMenus,
+ /* baseDocUrl= */ ''
+ ), [{
title: 'Browse',
links: adminLinks.concat([{
name: 'Manage',
@@ -348,4 +398,4 @@ limitations under the License.
assert.equal(element._registerText, 'Sign up');
});
});
-</script>
+ </script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index ce2012770e..c8bf6c1236 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -115,6 +115,7 @@ limitations under the License.
query: 'assignee:${user} (-is:wip OR owner:self OR assignee:self) ' +
'is:open -is:ignored',
hideIfEmpty: true,
+ suffixForDashboard: 'limit:25',
},
{
// WIP open changes owned by viewing user. This section is omitted when
@@ -123,6 +124,7 @@ limitations under the License.
query: 'is:open owner:${user} is:wip',
selfOnly: true,
hideIfEmpty: true,
+ suffixForDashboard: 'limit:25',
},
{
// Non-WIP open changes owned by viewed user. Filter out changes ignored
@@ -130,6 +132,7 @@ limitations under the License.
name: 'Outgoing reviews',
query: 'is:open owner:${user} -is:wip -is:ignored',
isOutgoing: true,
+ suffixForDashboard: 'limit:25',
},
{
// Non-WIP open changes not owned by the viewed user, that the viewed user
@@ -138,12 +141,14 @@ limitations under the License.
name: 'Incoming reviews',
query: 'is:open -owner:${user} -is:wip -is:ignored ' +
'(reviewer:${user} OR assignee:${user})',
+ suffixForDashboard: 'limit:25',
},
{
// Non-WIP open changes the viewed user is CCed on. Changes ignored by the
// viewing user are filtered out.
name: 'CCed on',
query: 'is:open -is:wip -is:ignored cc:${user}',
+ suffixForDashboard: 'limit:10',
},
{
name: 'Recently closed',
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
index 2f7233812e..73ce86a3a1 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-navigation</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector.js
deleted file mode 100644
index 28c46f4e9e..0000000000
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-(function() {
- 'use strict';
-
- const JANK_SLEEP_TIME_MS = 1000;
-
- const GrJankDetector = {
- // Slowdowns counter.
- jank: 0,
- fps: 0,
- _lastFrameTime: 0,
-
- start() {
- this._requestAnimationFrame(this._detect.bind(this));
- },
-
- _requestAnimationFrame(callback) {
- window.requestAnimationFrame(callback);
- },
-
- _detect(now) {
- if (this._lastFrameTime === 0) {
- this._lastFrameTime = now;
- this.fps = 0;
- this._requestAnimationFrame(this._detect.bind(this));
- return;
- }
- const fpsNow = 1000/(now - this._lastFrameTime);
- this._lastFrameTime = now;
- // Calculate moving average within last 3 measurements.
- this.fps = this.fps === 0 ? fpsNow : ((this.fps * 2 + fpsNow) / 3);
- if (this.fps > 10) {
- this._requestAnimationFrame(this._detect.bind(this));
- } else {
- this.jank++;
- console.warn('JANK', this.jank);
- this._lastFrameTime = 0;
- window.setTimeout(
- () => this._requestAnimationFrame(this._detect.bind(this)),
- JANK_SLEEP_TIME_MS);
- }
- },
- };
-
- window.GrJankDetector = GrJankDetector;
-})();
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector_test.html
deleted file mode 100644
index 6faeec1d81..0000000000
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-jank-detector_test.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2018 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-jank-detector</title>
-
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-
-<script src="gr-jank-detector.js"></script>
-
-<script>
- suite('gr-jank-detector tests', () => {
- let sandbox;
- let clock;
- let instance;
-
- const NOW_TIME = 100;
-
- setup(() => {
- sandbox = sinon.sandbox.create();
- clock = sinon.useFakeTimers(NOW_TIME);
- instance = GrJankDetector;
- instance._lastFrameTime = 0;
- sandbox.stub(instance, '_requestAnimationFrame');
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- test('start() installs frame callback', () => {
- sandbox.stub(instance, '_detect');
- instance._requestAnimationFrame.callsArg(0);
- instance.start();
- assert.isTrue(instance._detect.calledOnce);
- });
-
- test('measures fps', () => {
- instance._detect(10);
- instance._detect(30);
- assert.equal(instance.fps, 50);
- });
-
- test('detects jank', () => {
- let now = 10;
- instance._detect(now);
- const fastFrame = () => instance._detect(now += 20);
- const slowFrame = () => instance._detect(now += 300);
- fastFrame();
- assert.equal(instance.jank, 0);
- _.times(4, slowFrame);
- assert.equal(instance.jank, 0);
- instance._requestAnimationFrame.reset();
- slowFrame();
- assert.equal(instance.jank, 1);
- assert.isFalse(instance._requestAnimationFrame.called);
- clock.tick(1000);
- assert.isTrue(instance._requestAnimationFrame.called);
- });
- });
-</script>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
index 935de6bf1e..0ba8a22e80 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.html
@@ -15,9 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-reporting">
- <script src="gr-jank-detector.js"></script>
<script src="gr-reporting.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 0947d77d09..276c137264 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -49,14 +49,6 @@
STARTED_HIDDEN: 'hidden',
};
- // Frame rate related constants.
- const JANK = {
- TYPE: 'lifecycle',
- CATEGORY: 'UI Latency',
- // Reported events - alphabetize below.
- COUNT: 'Jank count',
- };
-
// Navigation reporting constants.
const NAVIGATION = {
TYPE: 'nav-report',
@@ -78,24 +70,33 @@
CHANGE_DISPLAYED: 'ChangeDisplayed',
CHANGE_LOAD_FULL: 'ChangeFullyLoaded',
DASHBOARD_DISPLAYED: 'DashboardDisplayed',
+ DIFF_VIEW_CONTENT_DISPLAYED: 'DiffViewOnlyContent',
DIFF_VIEW_DISPLAYED: 'DiffViewDisplayed',
+ DIFF_VIEW_LOAD_FULL: 'DiffViewFullyLoaded',
FILE_LIST_DISPLAYED: 'FileListDisplayed',
PLUGINS_LOADED: 'PluginsLoaded',
STARTUP_CHANGE_DISPLAYED: 'StartupChangeDisplayed',
STARTUP_CHANGE_LOAD_FULL: 'StartupChangeFullyLoaded',
STARTUP_DASHBOARD_DISPLAYED: 'StartupDashboardDisplayed',
+ STARTUP_DIFF_VIEW_CONTENT_DISPLAYED: 'StartupDiffViewOnlyContent',
STARTUP_DIFF_VIEW_DISPLAYED: 'StartupDiffViewDisplayed',
+ STARTUP_DIFF_VIEW_LOAD_FULL: 'StartupDiffViewFullyLoaded',
STARTUP_FILE_LIST_DISPLAYED: 'StartupFileListDisplayed',
WEB_COMPONENTS_READY: 'WebComponentsReady',
+ METRICS_PLUGIN_LOADED: 'MetricsPluginLoaded',
};
const STARTUP_TIMERS = {};
STARTUP_TIMERS[TIMER.PLUGINS_LOADED] = 0;
+ STARTUP_TIMERS[TIMER.METRICS_PLUGIN_LOADED] = 0;
STARTUP_TIMERS[TIMER.STARTUP_CHANGE_DISPLAYED] = 0;
STARTUP_TIMERS[TIMER.STARTUP_CHANGE_LOAD_FULL] = 0;
STARTUP_TIMERS[TIMER.STARTUP_DASHBOARD_DISPLAYED] = 0;
+ STARTUP_TIMERS[TIMER.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED] = 0;
STARTUP_TIMERS[TIMER.STARTUP_DIFF_VIEW_DISPLAYED] = 0;
+ STARTUP_TIMERS[TIMER.STARTUP_DIFF_VIEW_LOAD_FULL] = 0;
STARTUP_TIMERS[TIMER.STARTUP_FILE_LIST_DISPLAYED] = 0;
+ STARTUP_TIMERS[TIMING.APP_STARTED] = 0;
// WebComponentsReady timer is triggered from gr-router.
STARTUP_TIMERS[TIMER.WEB_COMPONENTS_READY] = 0;
@@ -106,6 +107,9 @@
const pending = [];
+ const loadedPlugins = [];
+ const detectedExtensions = [];
+
const onError = function(oldOnError, msg, url, line, column, error) {
if (oldOnError) {
oldOnError(msg, url, line, column, error);
@@ -113,7 +117,13 @@
if (error) {
line = line || error.lineNumber;
column = column || error.columnNumber;
- msg = msg || error.toString();
+ let shortenedErrorStack = msg;
+ if (error.stack) {
+ const errorStackLines = error.stack.split('\n');
+ shortenedErrorStack = errorStackLines.slice(0,
+ Math.min(3, errorStackLines.length)).join('\n');
+ }
+ msg = shortenedErrorStack || error.toString();
}
const payload = {
url,
@@ -138,13 +148,10 @@
};
catchErrors();
- GrJankDetector.start();
-
// The Polymer pass of JSCompiler requires this to be reassignable
// eslint-disable-next-line prefer-const
let GrReporting = Polymer({
is: 'gr-reporting',
- _legacyUndefinedCheck: true,
properties: {
category: String,
@@ -173,9 +180,15 @@
!this._baselines.hasOwnProperty(TIMER.PLUGINS_LOADED);
},
+ _isMetricsPluginLoaded() {
+ return this._arePluginsLoaded() || this._baselines &&
+ !this._baselines.hasOwnProperty(TIMER.METRICS_PLUGIN_LOADED);
+ },
+
reporter(...args) {
- const report = (this._arePluginsLoaded() && !pending.length) ?
+ const report = (this._isMetricsPluginLoaded() && !pending.length) ?
this.defaultReporter : this.cachingReporter;
+ args.splice(4, 0, loadedPlugins, detectedExtensions);
report.apply(this, args);
},
@@ -186,23 +199,33 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
+ * @param {Array} plugins
+ * @param {Array} extensions
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- defaultReporter(type, category, eventName, eventValue, opt_noLog) {
+ defaultReporter(type, category, eventName, eventValue,
+ loadedPlugins, detectedExtensions, opt_noLog) {
const detail = {
type,
category,
name: eventName,
value: eventValue,
};
+ if (category === TIMING.CATEGORY_UI_LATENCY) {
+ detail.loadedPlugins = loadedPlugins;
+ detail.detectedExtensions = detectedExtensions;
+ }
document.dispatchEvent(new CustomEvent(type, {detail}));
if (opt_noLog) { return; }
if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
- console.error(eventValue.error || eventName);
+ console.error(eventValue && eventValue.error || eventName);
} else {
- console.log(eventName + (eventValue !== undefined ?
- (': ' + eventValue) : ''));
+ if (eventValue !== undefined) {
+ console.log(`Reporting: ${eventName}: ${eventValue}`);
+ } else {
+ console.log(`Reporting: ${eventName}`);
+ }
}
},
@@ -214,22 +237,27 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
+ * @param {Array} plugins
+ * @param {Array} extensions
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- cachingReporter(type, category, eventName, eventValue, opt_noLog) {
+ cachingReporter(type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog) {
if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
- console.error(eventValue.error || eventName);
+ console.error(eventValue && eventValue.error || eventName);
}
- if (this._arePluginsLoaded()) {
+ if (this._isMetricsPluginLoaded()) {
if (pending.length) {
for (const args of pending.splice(0)) {
this.reporter(...args);
}
}
- this.reporter(type, category, eventName, eventValue, opt_noLog);
+ this.reporter(type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog);
} else {
- pending.push([type, category, eventName, eventValue, opt_noLog]);
+ pending.push([type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog]);
}
},
@@ -237,8 +265,7 @@
* User-perceived app start time, should be reported when the app is ready.
*/
appStarted(hidden) {
- this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
- TIMING.APP_STARTED, this.now());
+ this.timeEnd(TIMING.APP_STARTED);
if (hidden) {
this.reporter(PAGE_VISIBILITY.TYPE, PAGE_VISIBILITY.CATEGORY,
PAGE_VISIBILITY.STARTED_HIDDEN);
@@ -261,18 +288,15 @@
},
beforeLocationChanged() {
- if (GrJankDetector.jank > 0) {
- this.reporter(
- JANK.TYPE, JANK.CATEGORY, JANK.COUNT, GrJankDetector.jank);
- GrJankDetector.jank = 0;
- }
for (const prop of Object.keys(this._baselines)) {
delete this._baselines[prop];
}
this.time(TIMER.CHANGE_DISPLAYED);
this.time(TIMER.CHANGE_LOAD_FULL);
this.time(TIMER.DASHBOARD_DISPLAYED);
+ this.time(TIMER.DIFF_VIEW_CONTENT_DISPLAYED);
this.time(TIMER.DIFF_VIEW_DISPLAYED);
+ this.time(TIMER.DIFF_VIEW_LOAD_FULL);
this.time(TIMER.FILE_LIST_DISPLAYED);
},
@@ -313,6 +337,23 @@
}
},
+ diffViewFullyLoaded() {
+ if (this._baselines.hasOwnProperty(TIMER.STARTUP_DIFF_VIEW_LOAD_FULL)) {
+ this.timeEnd(TIMER.STARTUP_DIFF_VIEW_LOAD_FULL);
+ } else {
+ this.timeEnd(TIMER.DIFF_VIEW_LOAD_FULL);
+ }
+ },
+
+ diffViewContentDisplayed() {
+ if (this._baselines.hasOwnProperty(
+ TIMER.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED)) {
+ this.timeEnd(TIMER.STARTUP_DIFF_VIEW_CONTENT_DISPLAYED);
+ } else {
+ this.timeEnd(TIMER.DIFF_VIEW_CONTENT_DISPLAYED);
+ }
+ },
+
fileListDisplayed() {
if (this._baselines.hasOwnProperty(TIMER.STARTUP_FILE_LIST_DISPLAYED)) {
this.timeEnd(TIMER.STARTUP_FILE_LIST_DISPLAYED);
@@ -323,6 +364,16 @@
reportExtension(name) {
this.reporter(EXTENSION.TYPE, EXTENSION.DETECTED, name);
+ if (!detectedExtensions.includes(name)) {
+ detectedExtensions.push(name);
+ }
+ },
+
+ pluginLoaded(name) {
+ if (name.startsWith('metrics-')) {
+ this.timeEnd(TIMER.METRICS_PLUGIN_LOADED);
+ }
+ loadedPlugins.push(name);
},
pluginsLoaded(pluginsList) {
@@ -336,6 +387,7 @@
*/
time(name) {
this._baselines[name] = this.now();
+ window.performance.mark(`${name}-start`);
},
/**
@@ -344,8 +396,18 @@
timeEnd(name) {
if (!this._baselines.hasOwnProperty(name)) { return; }
const baseTime = this._baselines[name];
- this._reportTiming(name, this.now() - baseTime);
delete this._baselines[name];
+ this._reportTiming(name, this.now() - baseTime);
+
+ // Finalize the interval. Either from a registered start mark or
+ // the navigation start time (if baseTime is 0).
+ if (baseTime !== 0) {
+ window.performance.measure(name, `${name}-start`);
+ } else {
+ // Microsft Edge does not handle the 2nd param correctly
+ // (if undefined).
+ window.performance.measure(name);
+ }
},
/**
@@ -465,7 +527,7 @@
reportErrorDialog(message) {
this.reporter(ERROR_DIALOG.TYPE, ERROR_DIALOG.CATEGORY,
- 'ErrorDialog: ' + message);
+ 'ErrorDialog: ' + message, {error: new Error(message)});
},
});
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 29e70ea74e..4c561a2c91 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reporting</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-reporting.html">
@@ -93,11 +95,7 @@ limitations under the License.
test('beforeLocationChanged', () => {
element._baselines['garbage'] = 'monster';
sandbox.stub(element, 'time');
- GrJankDetector.jank = 42;
element.beforeLocationChanged();
- assert.equal(GrJankDetector.jank, 0);
- assert.isTrue(element.reporter.calledWithExactly(
- 'lifecycle', 'UI Latency', 'Jank count', 42));
assert.isTrue(element.time.calledWithExactly('DashboardDisplayed'));
assert.isTrue(element.time.calledWithExactly('ChangeDisplayed'));
assert.isTrue(element.time.calledWithExactly('ChangeFullyLoaded'));
@@ -193,7 +191,6 @@ limitations under the License.
assert.isTrue(element.reporter.calledOnce);
assert.throws(() => {
timer.end();
- done();
}, 'Timer for "foo-bar" already ended.');
});
@@ -263,8 +260,8 @@ limitations under the License.
test('pluginsLoaded reports time', () => {
sandbox.stub(element, 'now').returns(42);
element.pluginsLoaded();
- assert.isTrue(element.defaultReporter.calledWithExactly(
- 'timing-report', 'UI Latency', 'PluginsLoaded', 42, undefined
+ assert.isTrue(element.defaultReporter.calledWith(
+ 'timing-report', 'UI Latency', 'PluginsLoaded', 42
));
});
@@ -285,6 +282,24 @@ limitations under the License.
assert.isTrue(element.defaultReporter.called);
});
+ test('reports plugins in timing events', () => {
+ element.pluginsLoaded = [];
+ sandbox.stub(element, 'now').returns(42);
+ element.pluginLoaded('metrics-xyz1');
+ // element.pluginLoaded('foo');
+ element.time('timeAction');
+ element.timeEnd('timeAction');
+ assert.isTrue(element.defaultReporter.getCall(1).calledWith(
+ 'timing-report', 'UI Latency', 'timeAction', 0,
+ ['metrics-xyz1']
+ ));
+ });
+
+ test('reports if metrics plugin xyz is loaded', () => {
+ element.pluginLoaded('metrics-xyz');
+ assert.isTrue(element.defaultReporter.called);
+ });
+
test('reports cached events preserving order', () => {
element.time('foo');
element.time('bar');
@@ -334,6 +349,7 @@ limitations under the License.
test('is reported', () => {
const error = new Error('bar');
+ error.stack = undefined;
emulateThrow('bar', 'http://url', 4, 2, error);
assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
const payload = reporter.lastCall.args[3];
@@ -345,6 +361,15 @@ limitations under the License.
});
});
+ test('is reported with 3 lines of stack', () => {
+ const error = new Error('bar');
+ emulateThrow('bar', 'http://url', 4, 2, error);
+ const expectedStack = error.stack.split('\n').slice(0, 3)
+ .join('\n');
+ assert.isTrue(reporter.calledWith('error', 'exception',
+ expectedStack));
+ });
+
test('prevent default event handler', () => {
assert.isTrue(emulateThrow());
});
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.html b/polygerrit-ui/app/elements/core/gr-router/gr-router.html
index 68ddef626d..71a58321a7 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.html
@@ -14,9 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
@@ -28,6 +29,6 @@ limitations under the License.
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-reporting id="reporting"></gr-reporting>
</template>
- <script src="../../../bower_components/page/page.js"></script>
+ <script src="/bower_components/page/page.js"></script>
<script src="gr-router.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 2e1f5b8369..738a89b727 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -200,7 +200,9 @@
const reporting = document.createElement('gr-reporting');
window.addEventListener('load', () => {
- reporting.pageLoaded();
+ setTimeout(() => {
+ reporting.pageLoaded();
+ }, 0);
});
window.addEventListener('WebComponentsReady', () => {
@@ -210,7 +212,6 @@
Polymer({
is: 'gr-router',
- _legacyUndefinedCheck: true,
properties: {
_app: {
@@ -228,6 +229,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -238,7 +240,17 @@
},
_setParams(params) {
- this._app.params = params;
+ this._appElement().params = params;
+ },
+
+ _appElement() {
+ // In Polymer2 you have to reach through the shadow root of the app
+ // element. This obviously breaks encapsulation.
+ // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
+ // explicitly in app, or by delegating to it.
+ return document.getElementById('app-element') ||
+ document.getElementById('app').shadowRoot.getElementById(
+ 'app-element');
},
_redirect(url) {
@@ -1404,9 +1416,7 @@
}
},
- // TODO fix this so it properly redirects
- // to /settings#Agreements (Scrolls down)
- _handleAgreementsRoute(data) {
+ _handleAgreementsRoute() {
this._redirect('/settings/#Agreements');
},
@@ -1505,7 +1515,7 @@
// Note: the app's 404 display is tightly-coupled with catching 404
// network responses, so we simulate a 404 response status to display it.
// TODO: Decouple the gr-app error view from network responses.
- this._app.dispatchEvent(new CustomEvent('page-error',
+ this._appElement().dispatchEvent(new CustomEvent('page-error',
{detail: {response: {status: 404}}}));
},
});
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 168dc289db..5c7addc563 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-router</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-router.html">
@@ -671,11 +673,12 @@ limitations under the License.
});
test('_handleDefaultRoute on first load', () => {
- element._app = {dispatchEvent: sinon.stub()};
+ const appElementStub = {dispatchEvent: sinon.stub()};
+ element._appElement = () => appElementStub;
element._handleDefaultRoute();
- assert.isTrue(element._app.dispatchEvent.calledOnce);
+ assert.isTrue(appElementStub.dispatchEvent.calledOnce);
assert.equal(
- element._app.dispatchEvent.lastCall.args[0].detail.response.status,
+ appElementStub.dispatchEvent.lastCall.args[0].detail.response.status,
404);
});
@@ -691,7 +694,8 @@ limitations under the License.
sandbox.stub(window, 'page');
element._startRouter();
- element._app = {dispatchEvent: sinon.stub()};
+ const appElementStub = {dispatchEvent: sinon.stub()};
+ element._appElement = () => appElementStub;
element._handleDefaultRoute();
onExit('', () => {}); // we left page;
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
index 3a48213bdb..0cdef8ca76 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -17,7 +17,7 @@ limitations under the License.
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -30,11 +30,10 @@ limitations under the License.
gr-autocomplete {
background-color: var(--view-background-color);
border: 1px solid var(--border-color);
- border-radius: 2px;
+ border-radius: var(--border-radius);
flex: 1;
- font: inherit;
outline: none;
- padding: .25em;
+ padding: var(--spacing-xs);
}
</style>
<form>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index c877ac412d..0030babe97 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -60,7 +60,6 @@
'is:merged',
'is:open',
'is:owner',
- 'is:pending',
'is:private',
'is:reviewed',
'is:reviewer',
@@ -90,7 +89,6 @@
'status:closed',
'status:merged',
'status:open',
- 'status:pending',
'status:reviewed',
'topic:',
'tr:',
@@ -106,7 +104,6 @@
Polymer({
is: 'gr-search-bar',
- _legacyUndefinedCheck: true,
/**
* Fired when a search is committed
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index b162828ec9..a4927c3c07 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-search-bar</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="gr-search-bar.html">
<script src="../../../scripts/util.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
index 4c98068b5e..c4ae41b2fa 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
@@ -14,9 +14,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-search-bar/gr-search-bar.html">
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
index 65141aa4b4..2446486fcb 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
@@ -23,7 +23,6 @@
Polymer({
is: 'gr-smart-search',
- _legacyUndefinedCheck: true,
properties: {
searchQuery: String,
@@ -49,7 +48,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
attached() {
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
index af0fc3cb07..a70eb7c3e3 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-smart-search</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-smart-search.html">
diff --git a/polygerrit-ui/app/elements/custom-dark-theme_test.html b/polygerrit-ui/app/elements/custom-dark-theme_test.html
new file mode 100644
index 0000000000..4cf35f16a2
--- /dev/null
+++ b/polygerrit-ui/app/elements/custom-dark-theme_test.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-app-it_test</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../test/common-test-setup.html"/>
+<link rel="import" href="./gr-app.html">
+
+<script>void(0);</script>
+
+<test-fixture id="element">
+ <template>
+ <gr-app id="app"></gr-app>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-app custom dark theme tests', () => {
+ let sandbox;
+ let element;
+
+ setup(done => {
+ sandbox = sinon.sandbox.create();
+ stub('gr-reporting', {
+ appStarted: sandbox.stub(),
+ });
+ stub('gr-account-dropdown', {
+ _getTopContent: sinon.stub(),
+ });
+ stub('gr-rest-api-interface', {
+ getAccount() { return Promise.resolve(null); },
+ getAccountCapabilities() { return Promise.resolve({}); },
+ getConfig() {
+ return Promise.resolve({
+ plugin: {
+ js_resource_paths: [],
+ html_resource_paths: [
+ new URL('test/plugin.html', window.location.href).toString(),
+ ],
+ },
+ });
+ },
+ getVersion() { return Promise.resolve(42); },
+ getLoggedIn() { return Promise.resolve(false); },
+ });
+
+ window.localStorage.setItem('dark-theme', 'true');
+
+ element = fixture('element');
+
+ const importSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForAll,
+ '_import');
+ const importForThemeSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForTheme,
+ '_import');
+ Gerrit.awaitPluginsLoaded().then(() => {
+ Promise.all(importSpy.returnValues.concat(importForThemeSpy.returnValues))
+ .then(() => {
+ flush(done);
+ });
+ });
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('applies the right theme', () => {
+ assert.equal(
+ util.getComputedStyleValue('--primary-text-color', element),
+ 'red');
+ assert.equal(
+ util.getComputedStyleValue('--header-background-color', element),
+ 'black');
+ assert.equal(
+ util.getComputedStyleValue('--footer-background-color', element),
+ 'yellow');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/gr-app-it_test.html b/polygerrit-ui/app/elements/custom-light-theme_test.html
index 2601aebe14..e346af53bc 100644
--- a/polygerrit-ui/app/elements/gr-app-it_test.html
+++ b/polygerrit-ui/app/elements/custom-light-theme_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-app-it_test</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../test/common-test-setup.html"/>
<link rel="import" href="gr-app.html">
@@ -33,7 +35,7 @@ limitations under the License.
</test-fixture>
<script>
- suite('gr-app integration tests', () => {
+ suite('gr-app custom light theme tests', () => {
let sandbox;
let element;
@@ -61,13 +63,22 @@ limitations under the License.
getVersion() { return Promise.resolve(42); },
getLoggedIn() { return Promise.resolve(false); },
});
+
+ window.localStorage.removeItem('dark-theme');
+
element = fixture('element');
- const importSpy = sandbox.spy(element.$.externalStyle, '_import');
+ const importSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForAll,
+ '_import');
+ const importForThemeSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForTheme,
+ '_import');
Gerrit.awaitPluginsLoaded().then(() => {
- Promise.all(importSpy.returnValues).then(() => {
- flush(done);
- });
+ Promise.all(importSpy.returnValues.concat(importForThemeSpy.returnValues))
+ .then(() => {
+ flush(done);
+ });
});
});
@@ -75,17 +86,15 @@ limitations under the License.
sandbox.restore();
});
- test('applies --primary-text-color', () => {
+ test('applies the right theme', () => {
assert.equal(
- element.getComputedStyleValue('--primary-text-color'), '#F00BAA');
- });
-
- test('applies --header-background-color', () => {
- assert.equal(element.getComputedStyleValue('--header-background-color'),
+ util.getComputedStyleValue('--primary-text-color', element),
+ '#F00BAA');
+ assert.equal(
+ util.getComputedStyleValue('--header-background-color', element),
'#F01BAA');
- });
- test('applies --footer-background-color', () => {
- assert.equal(element.getComputedStyleValue('--footer-background-color'),
+ assert.equal(
+ util.getComputedStyleValue('--footer-background-color', element),
'#F02BAA');
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
index 7bf71f5c66..b7994e6078 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
@@ -19,7 +19,6 @@
Polymer({
is: 'comment-api-mock',
- _legacyUndefinedCheck: true,
properties: {
_changeComments: Object,
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.html
index c31bd11660..317e9e5c73 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
index 37491d265a..1e8158da19 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
@@ -19,35 +19,6 @@
const PARENT = 'PARENT';
- const Defs = {};
-
- /**
- * @typedef {{
- * basePatchNum: (string|number),
- * patchNum: (number),
- * }}
- */
- Defs.patchRange;
-
- /**
- * @typedef {{
- * changeNum: number,
- * path: string,
- * patchRange: !Defs.patchRange,
- * projectConfig: (Object|undefined),
- * }}
- */
- Defs.commentMeta;
-
- /**
- * @typedef {{
- * meta: !Defs.commentMeta,
- * left: !Array,
- * right: !Array,
- * }}
- */
- Defs.commentsBySide;
-
/**
* Construct a change comments object, which can be data-bound to child
* elements of that which uses the gr-comment-api.
@@ -92,7 +63,7 @@
* Paths with comments are mapped to true, whereas paths without comments
* are not mapped.
*
- * @param {Defs.patchRange=} opt_patchRange The patch-range object containing
+ * @param {Gerrit.PatchRange=} opt_patchRange The patch-range object containing
* patchNum and basePatchNum properties to represent the range.
* @return {!Object}
*/
@@ -251,17 +222,26 @@
* arrays of comments in on either side of the patch range for that path.
*
* @param {!string} path
- * @param {!Defs.patchRange} patchRange The patch-range object containing patchNum
+ * @param {!Gerrit.PatchRange} patchRange The patch-range object containing patchNum
* and basePatchNum properties to represent the range.
* @param {Object=} opt_projectConfig Optional project config object to
* include in the meta sub-object.
- * @return {!Defs.commentsBySide}
+ * @return {!Gerrit.CommentsBySide}
*/
ChangeComments.prototype.getCommentsBySideForPath = function(path,
patchRange, opt_projectConfig) {
- const comments = this.comments[path] || [];
- const drafts = this.drafts[path] || [];
- const robotComments = this.robotComments[path] || [];
+ let comments = [];
+ let drafts = [];
+ let robotComments = [];
+ if (this.comments && this.comments[path]) {
+ comments = this.comments[path];
+ }
+ if (this.drafts && this.drafts[path]) {
+ drafts = this.drafts[path];
+ }
+ if (this.robotComments && this.robotComments[path]) {
+ robotComments = this.robotComments[path];
+ }
drafts.forEach(d => { d.__draft = true; });
@@ -430,7 +410,7 @@
* given patch range.
*
* @param {!Object} comment
- * @param {!Defs.patchRange} range
+ * @param {!Gerrit.PatchRange} range
* @return {boolean}
*/
ChangeComments.prototype._isInBaseOfPatchRange = function(comment, range) {
@@ -462,7 +442,7 @@
* given patch range.
*
* @param {!Object} comment
- * @param {!Defs.patchRange} range
+ * @param {!Gerrit.PatchRange} range
* @return {boolean}
*/
ChangeComments.prototype._isInRevisionOfPatchRange = function(comment,
@@ -475,7 +455,7 @@
* Whether the given comment should be included in the given patch range.
*
* @param {!Object} comment
- * @param {!Defs.patchRange} range
+ * @param {!Gerrit.PatchRange} range
* @return {boolean|undefined}
*/
ChangeComments.prototype._isInPatchRange = function(comment, range) {
@@ -485,7 +465,6 @@
Polymer({
is: 'gr-comment-api',
- _legacyUndefinedCheck: true,
properties: {
_changeComments: Object,
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
index 1ca2a69349..47181f900f 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-comment-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="./gr-comment-api.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
index 56a6fb9b1a..549bf4318c 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
@@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-coverage-layer">
<template>
</template>
+ <script src="../../../types/types.js"></script>
<script src="../gr-diff-highlight/gr-annotation.js"></script>
<script src="gr-coverage-layer.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
index e8d6900c7d..3d9c17221a 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
@@ -17,27 +17,6 @@
(function() {
'use strict';
- /** @enum {string} */
- Gerrit.CoverageType = {
- /**
- * start_character and end_character of the range will be ignored for this
- * type.
- */
- COVERED: 'COVERED',
- /**
- * start_character and end_character of the range will be ignored for this
- * type.
- */
- NOT_COVERED: 'NOT_COVERED',
- PARTIALLY_COVERED: 'PARTIALLY_COVERED',
- /**
- * You don't have to use this. If there is no coverage information for a
- * range, then it implicitly means NOT_INSTRUMENTED. start_character and
- * end_character of the range will be ignored for this type.
- */
- NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
- };
-
const TOOLTIP_MAP = new Map([
[Gerrit.CoverageType.COVERED, 'Covered by tests.'],
[Gerrit.CoverageType.NOT_COVERED, 'Not covered by tests.'],
@@ -45,15 +24,6 @@
[Gerrit.CoverageType.NOT_INSTRUMENTED, 'Not instrumented by any tests.'],
]);
- /**
- * @typedef {{
- * side: string,
- * type: Gerrit.CoverageType,
- * code_range: Gerrit.Range,
- * }}
- */
- Gerrit.CoverageRange;
-
Polymer({
is: 'gr-coverage-layer',
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
index edd88a24e1..45a67e1261 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-coverage-layer</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<script src="../gr-diff/gr-diff-line.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
index 11bea8c820..283b7fdb1e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
@@ -95,7 +95,7 @@
image._width = imageEl.naturalWidth;
this._updateImageLabel(section, className, image);
}.bind(this);
- imageEl.src = 'data:' + image.type + ';base64, ' + image.body;
+ imageEl.setAttribute('src', `data:${image.type};base64, ${image.body}`);
imageEl.addEventListener('error', () => {
imageEl.remove();
td.textContent = '[Image failed to load]';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
index 1ef278f99d..bb590ba5f4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
@@ -35,6 +35,9 @@
if (group.dueToRebase) {
sectionEl.classList.add('dueToRebase');
}
+ if (group.ignoredWhitespaceOnly) {
+ sectionEl.classList.add('ignoredWhitespaceOnly');
+ }
const pairs = group.getSideBySidePairs();
for (let i = 0; i < pairs.length; i++) {
sectionEl.appendChild(this._createRow(sectionEl, pairs[i].left,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index 6be209799e..144cc5605f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -35,9 +35,18 @@
if (group.dueToRebase) {
sectionEl.classList.add('dueToRebase');
}
+ if (group.ignoredWhitespaceOnly) {
+ sectionEl.classList.add('ignoredWhitespaceOnly');
+ }
for (let i = 0; i < group.lines.length; ++i) {
- sectionEl.appendChild(this._createRow(sectionEl, group.lines[i]));
+ const line = group.lines[i];
+ // If only whitespace has changed and the settings ask for whitespace to
+ // be ignored, only render the right-side line in unified diff mode.
+ if (group.ignoredWhitespaceOnly && line.type == GrDiffLine.Type.REMOVE) {
+ continue;
+ }
+ sectionEl.appendChild(this._createRow(sectionEl, line));
}
return sectionEl;
};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
new file mode 100644
index 0000000000..19e017d565
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>GrDiffBuilderUnified</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<script src="../../../scripts/util.js"></script>
+<script src="../gr-diff/gr-diff-line.js"></script>
+<script src="../gr-diff/gr-diff-group.js"></script>
+<script src="../gr-diff-highlight/gr-annotation.js"></script>
+<script src="gr-diff-builder.js"></script>
+<script src="gr-diff-builder-unified.js"></script>
+
+<script>void(0);</script>
+
+<script>
+ suite('GrDiffBuilderUnified tests', () => {
+ let prefs;
+ let outputEl;
+ let diffBuilder;
+
+ setup(()=> {
+ prefs = {
+ line_length: 10,
+ show_tabs: true,
+ tab_size: 4,
+ };
+ outputEl = document.createElement('div');
+ diffBuilder = new GrDiffBuilderUnified({}, prefs, outputEl, []);
+ });
+
+ suite('buildSectionElement for BOTH group', () => {
+ let lines;
+ let group;
+
+ setup(() => {
+ lines = [
+ new GrDiffLine(GrDiffLine.Type.BOTH, 1, 2),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 2, 3),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 3, 4),
+ ];
+ lines[0].text = 'def hello_world():';
+ lines[1].text = ' print "Hello World";';
+ lines[2].text = ' return True';
+
+ group = new GrDiffGroup(GrDiffGroup.Type.BOTH, lines);
+ });
+
+ test('creates the section', () => {
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ assert.isTrue(sectionEl.classList.contains('section'));
+ assert.isTrue(sectionEl.classList.contains('both'));
+ });
+
+ test('creates each unchanged row once', () => {
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ const rowEls = sectionEl.querySelectorAll('.diff-row');
+
+ assert.equal(rowEls.length, 3);
+
+ assert.equal(
+ rowEls[0].querySelector('.lineNum.left').textContent,
+ lines[0].beforeNumber);
+ assert.equal(
+ rowEls[0].querySelector('.lineNum.right').textContent,
+ lines[0].afterNumber);
+ assert.equal(
+ rowEls[0].querySelector('.content').textContent, lines[0].text);
+
+ assert.equal(
+ rowEls[1].querySelector('.lineNum.left').textContent,
+ lines[1].beforeNumber);
+ assert.equal(
+ rowEls[1].querySelector('.lineNum.right').textContent,
+ lines[1].afterNumber);
+ assert.equal(
+ rowEls[1].querySelector('.content').textContent, lines[1].text);
+
+ assert.equal(
+ rowEls[2].querySelector('.lineNum.left').textContent,
+ lines[2].beforeNumber);
+ assert.equal(
+ rowEls[2].querySelector('.lineNum.right').textContent,
+ lines[2].afterNumber);
+ assert.equal(
+ rowEls[2].querySelector('.content').textContent, lines[2].text);
+ });
+ });
+
+ suite('buildSectionElement for DELTA group', () => {
+ let lines;
+ let group;
+
+ setup(() => {
+ lines = [
+ new GrDiffLine(GrDiffLine.Type.REMOVE, 1),
+ new GrDiffLine(GrDiffLine.Type.REMOVE, 2),
+ new GrDiffLine(GrDiffLine.Type.ADD, 2),
+ new GrDiffLine(GrDiffLine.Type.ADD, 3),
+ ];
+ lines[0].text = 'def hello_world():';
+ lines[1].text = ' print "Hello World"';
+ lines[2].text = 'def hello_universe()';
+ lines[3].text = ' print "Hello Universe"';
+
+ group = new GrDiffGroup(GrDiffGroup.Type.DELTA, lines);
+ });
+
+ test('creates the section', () => {
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ assert.isTrue(sectionEl.classList.contains('section'));
+ assert.isTrue(sectionEl.classList.contains('delta'));
+ });
+
+ test('creates the section with class if ignoredWhitespaceOnly', () => {
+ group.ignoredWhitespaceOnly = true;
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ assert.isTrue(sectionEl.classList.contains('ignoredWhitespaceOnly'));
+ });
+
+ test('creates the section with class if dueToRebase', () => {
+ group.dueToRebase = true;
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ assert.isTrue(sectionEl.classList.contains('dueToRebase'));
+ });
+
+ test('creates first the removed and then the added rows', () => {
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ const rowEls = sectionEl.querySelectorAll('.diff-row');
+
+ assert.equal(rowEls.length, 4);
+
+ assert.equal(
+ rowEls[0].querySelector('.lineNum.left').textContent,
+ lines[0].beforeNumber);
+ assert.isNotOk(rowEls[0].querySelector('.lineNum.right'));
+ assert.equal(
+ rowEls[0].querySelector('.content').textContent, lines[0].text);
+
+ assert.equal(
+ rowEls[1].querySelector('.lineNum.left').textContent,
+ lines[1].beforeNumber);
+ assert.isNotOk(rowEls[1].querySelector('.lineNum.right'));
+ assert.equal(
+ rowEls[1].querySelector('.content').textContent, lines[1].text);
+
+ assert.isNotOk(rowEls[2].querySelector('.lineNum.left'));
+ assert.equal(
+ rowEls[2].querySelector('.lineNum.right').textContent,
+ lines[2].afterNumber);
+ assert.equal(
+ rowEls[2].querySelector('.content').textContent, lines[2].text);
+
+ assert.isNotOk(rowEls[3].querySelector('.lineNum.left'));
+ assert.equal(
+ rowEls[3].querySelector('.lineNum.right').textContent,
+ lines[3].afterNumber);
+ assert.equal(
+ rowEls[3].querySelector('.content').textContent, lines[3].text);
+ });
+
+ test('creates only the added rows if only ignored whitespace', () => {
+ group.ignoredWhitespaceOnly = true;
+ const sectionEl = diffBuilder.buildSectionElement(group);
+ const rowEls = sectionEl.querySelectorAll('.diff-row');
+
+ assert.equal(rowEls.length, 2);
+
+ assert.isNotOk(rowEls[0].querySelector('.lineNum.left'));
+ assert.equal(
+ rowEls[0].querySelector('.lineNum.right').textContent,
+ lines[2].afterNumber);
+ assert.equal(
+ rowEls[0].querySelector('.content').textContent, lines[2].text);
+
+ assert.isNotOk(rowEls[1].querySelector('.lineNum.left'));
+ assert.equal(
+ rowEls[1].querySelector('.lineNum.right').textContent,
+ lines[3].afterNumber);
+ assert.equal(
+ rowEls[1].querySelector('.content').textContent, lines[3].text);
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index d42d8c1fd6..40fbe3c7e4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -14,12 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../gr-coverage-layer/gr-coverage-layer.html">
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
<link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
-<link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html">
<dom-module id="gr-diff-builder">
<template>
@@ -29,9 +28,6 @@ limitations under the License.
<gr-ranged-comment-layer
id="rangeLayer"
comment-ranges="[[commentRanges]]"></gr-ranged-comment-layer>
- <gr-syntax-layer
- id="syntaxLayer"
- diff="[[diff]]"></gr-syntax-layer>
<gr-coverage-layer
id="coverageLayerLeft"
coverage-ranges="[[_leftCoverageRanges]]"
@@ -43,7 +39,6 @@ limitations under the License.
<gr-diff-processor
id="processor"
groups="{{_groups}}"></gr-diff-processor>
- <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
</template>
<script src="../../../scripts/util.js"></script>
<script src="../gr-diff/gr-diff-line.js"></script>
@@ -63,18 +58,10 @@ limitations under the License.
UNIFIED: 'UNIFIED_DIFF',
};
- // If any line of the diff is more than the character limit, then disable
- // syntax highlighting for the entire file.
- const SYNTAX_MAX_LINE_LENGTH = 500;
-
- // Disable syntax highlighting if the overall diff is too large.
- const SYNTAX_MAX_DIFF_LENGTH = 20000;
-
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
Polymer({
is: 'gr-diff-builder',
- _legacyUndefinedCheck: true,
/**
* Fired when the diff begins rendering.
@@ -83,21 +70,13 @@ limitations under the License.
*/
/**
- * Fired when the diff finishes rendering text content and starts
- * syntax highlighting.
+ * Fired when the diff finishes rendering text content.
*
* @event render-content
*/
- /**
- * Fired when the diff finishes syntax highlighting.
- *
- * @event render-syntax
- */
-
properties: {
diff: Object,
- diffPath: String,
changeNum: String,
patchNum: String,
viewMode: String,
@@ -138,8 +117,16 @@ limitations under the License.
* @type {?Object}
*/
_cancelableRenderPromise: Object,
+ layers: {
+ type: Array,
+ value: [],
+ },
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
get diffElement() {
return this.queryEffectiveChildren('#diffTable');
},
@@ -163,11 +150,10 @@ limitations under the License.
// attached before plugins are installed.
this._setupAnnotationLayers();
- this.$.syntaxLayer.enabled = prefs.syntax_highlighting;
this._showTabs = !!prefs.show_tabs;
this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
- // Stop the processor and syntax layer (if they're running).
+ // Stop the processor if it's running.
this.cancel();
this._builder = this._getDiffBuilder(this.diff, prefs);
@@ -180,7 +166,8 @@ limitations under the License.
const isBinary = !!(this.isImageDiff || this.diff.binary);
- this.dispatchEvent(new CustomEvent('render-start', {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'render-start', {bubbles: true, composed: true}));
this._cancelableRenderPromise = util.makeCancelable(
this.$.processor.process(this.diff.content, isBinary)
.then(() => {
@@ -188,17 +175,7 @@ limitations under the License.
this._builder.renderDiff();
}
this.dispatchEvent(new CustomEvent('render-content',
- {bubbles: true}));
-
- if (this._diffTooLargeForSyntax()) {
- this.$.syntaxLayer.enabled = false;
- }
-
- return this.$.syntaxLayer.process();
- })
- .then(() => {
- this.dispatchEvent(
- new CustomEvent('render-syntax', {bubbles: true}));
+ {bubbles: true, composed: true}));
}));
return this._cancelableRenderPromise
.finally(() => { this._cancelableRenderPromise = null; })
@@ -211,7 +188,6 @@ limitations under the License.
_setupAnnotationLayers() {
const layers = [
this._createTrailingWhitespaceLayer(),
- this.$.syntaxLayer,
this._createIntralineLayer(),
this._createTabIndicatorLayer(),
this.$.rangeLayer,
@@ -219,12 +195,9 @@ limitations under the License.
this.$.coverageLayerRight,
];
- // Get layers from plugins (if any).
- for (const pluginLayer of this.$.jsAPI.getDiffLayers(
- this.diffPath, this.changeNum, this.patchNum)) {
- layers.push(pluginLayer);
+ if (this.layers) {
+ layers.push(...this.layers);
}
-
this._layers = layers;
},
@@ -289,7 +262,7 @@ limitations under the License.
const contextIndex = groups.findIndex(group =>
group.element === sectionEl
);
- groups.splice(...[contextIndex, 1].concat(newGroups));
+ groups.splice(contextIndex, 1, ...newGroups);
for (const newGroup of newGroups) {
this._builder.emitGroup(newGroup, sectionEl);
@@ -301,7 +274,6 @@ limitations under the License.
cancel() {
this.$.processor.cancel();
- this.$.syntaxLayer.cancel();
if (this._cancelableRenderPromise) {
this._cancelableRenderPromise.cancel();
this._cancelableRenderPromise = null;
@@ -314,7 +286,7 @@ limitations under the License.
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {
message,
- }, bubbles: true}));
+ }, bubbles: true, composed: true}));
throw Error(`Invalid preference value: ${pref}`);
},
@@ -440,45 +412,10 @@ limitations under the License.
};
},
- /**
- * @return {boolean} whether any of the lines in _groups are longer
- * than SYNTAX_MAX_LINE_LENGTH.
- */
- _anyLineTooLong() {
- return this._groups.reduce((acc, group) => {
- return acc || group.lines.reduce((acc, line) => {
- return acc || line.text.length >= SYNTAX_MAX_LINE_LENGTH;
- }, false);
- }, false);
- },
-
- _diffTooLargeForSyntax() {
- return this._anyLineTooLong() ||
- this.getDiffLength() > SYNTAX_MAX_DIFF_LENGTH;
- },
-
setBlame(blame) {
if (!this._builder || !blame) { return; }
this._builder.setBlame(blame);
},
-
- /**
- * Get the approximate length of the diff as the sum of the maximum
- * length of the chunks.
- *
- * @return {number}
- */
- getDiffLength() {
- return this.diff.content.reduce((sum, sec) => {
- if (sec.hasOwnProperty('ab')) {
- return sum + sec.ab.length;
- } else {
- return sum + Math.max(
- sec.hasOwnProperty('a') ? sec.a.length : 0,
- sec.hasOwnProperty('b') ? sec.b.length : 0);
- }
- }, 0);
- },
});
})();
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index 65a56f04fe..54303f611d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -115,18 +115,6 @@
group.element = element;
};
- GrDiffBuilder.prototype.renderSection = function(element) {
- for (let i = 0; i < this.groups.length; i++) {
- const group = this.groups[i];
- if (group.element === element) {
- const newElement = this.buildSectionElement(group);
- group.element.parentElement.replaceChild(newElement, group.element);
- group.element = newElement;
- break;
- }
- }
- };
-
GrDiffBuilder.prototype.getGroupsByLineRange = function(
startLine, endLine, opt_side) {
const groups = [];
@@ -233,57 +221,38 @@
group => { return group.element; });
};
- // TODO(wyatta): Move this completely into the processor.
- GrDiffBuilder.prototype._insertContextGroups = function(groups, lines,
- hiddenRange) {
- const linesBeforeCtx = lines.slice(0, hiddenRange[0]);
- const hiddenLines = lines.slice(hiddenRange[0], hiddenRange[1]);
- const linesAfterCtx = lines.slice(hiddenRange[1]);
-
- if (linesBeforeCtx.length > 0) {
- groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
- }
-
- const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
- ctxLine.contextGroup =
- new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines);
- groups.push(new GrDiffGroup(GrDiffGroup.Type.CONTEXT_CONTROL,
- [ctxLine]));
+ GrDiffBuilder.prototype._createContextControl = function(section, line) {
+ if (!line.contextGroups) return null;
- if (linesAfterCtx.length > 0) {
- groups.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesAfterCtx));
- }
- };
+ const numLines =
+ line.contextGroups[line.contextGroups.length - 1].lineRange.left.end -
+ line.contextGroups[0].lineRange.left.start + 1;
- GrDiffBuilder.prototype._createContextControl = function(section, line) {
- if (!line.contextGroup || !line.contextGroup.lines.length) {
- return null;
- }
+ if (numLines === 0) return null;
const td = this._createElement('td');
- const showPartialLinks =
- line.contextGroup.lines.length > PARTIAL_CONTEXT_AMOUNT;
+ const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
if (showPartialLinks) {
td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.ABOVE, section, line));
+ GrDiffBuilder.ContextButtonType.ABOVE, section, line, numLines));
td.appendChild(document.createTextNode(' - '));
}
td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.ALL, section, line));
+ GrDiffBuilder.ContextButtonType.ALL, section, line, numLines));
if (showPartialLinks) {
td.appendChild(document.createTextNode(' - '));
td.appendChild(this._createContextButton(
- GrDiffBuilder.ContextButtonType.BELOW, section, line));
+ GrDiffBuilder.ContextButtonType.BELOW, section, line, numLines));
}
return td;
};
- GrDiffBuilder.prototype._createContextButton = function(type, section, line) {
- const contextLines = line.contextGroup.lines;
+ GrDiffBuilder.prototype._createContextButton = function(type, section, line,
+ numLines) {
const context = PARTIAL_CONTEXT_AMOUNT;
const button = this._createElement('gr-button', 'showContext');
@@ -291,20 +260,20 @@
button.setAttribute('no-uppercase', true);
let text;
- const groups = []; // The groups that replace this one if tapped.
+ let groups = []; // The groups that replace this one if tapped.
if (type === GrDiffBuilder.ContextButtonType.ALL) {
- text = 'Show ' + contextLines.length + ' common line';
- if (contextLines.length > 1) { text += 's'; }
- groups.push(line.contextGroup);
+ text = 'Show ' + numLines + ' common line';
+ if (numLines > 1) { text += 's'; }
+ groups.push(...line.contextGroups);
} else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
text = '+' + context + '↑';
- this._insertContextGroups(groups, contextLines,
- [context, contextLines.length]);
+ groups = GrDiffGroup.hideInContextControl(line.contextGroups,
+ context, numLines);
} else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
text = '+' + context + '↓';
- this._insertContextGroups(groups, contextLines,
- [0, contextLines.length - context]);
+ groups = GrDiffGroup.hideInContextControl(line.contextGroups,
+ 0, numLines - context);
}
Polymer.dom(button).textContent = text;
@@ -337,8 +306,6 @@
return td;
} else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
td.classList.add('contextLineNum');
- td.setAttribute('data-value', '@@');
- td.textContent = '@@';
} else if (line.type === GrDiffLine.Type.BOTH || line.type === type) {
td.classList.add('lineNum');
td.setAttribute('data-value', number);
@@ -353,6 +320,12 @@
if (line.type !== GrDiffLine.Type.BLANK) {
td.classList.add('content');
}
+
+ // If intraline info is not available, the entire line will be
+ // considered as changed and marked as dark red / green color
+ if (!line.hasIntralineInfo) {
+ td.classList.add('no-intraline-info');
+ }
td.classList.add(line.type);
const lineLimit =
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index a8db47a7c2..42414b7de4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-builder</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
<script src="../gr-diff/gr-diff-line.js"></script>
@@ -28,13 +30,14 @@ limitations under the License.
<script src="../gr-diff-highlight/gr-annotation.js"></script>
<script src="gr-diff-builder.js"></script>
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<link rel="import" href="gr-diff-builder.html">
<script>void(0);</script>
<test-fixture id="basic">
- <template>
+ <template is="dom-template">
<gr-diff-builder>
<table id="diffTable"></table>
</gr-diff-builder>
@@ -88,26 +91,37 @@ limitations under the License.
});
test('context control buttons', () => {
- const section = {};
- const line = {contextGroup: {lines: []}};
-
// Create 10 lines.
+ const lines = [];
for (let i = 0; i < 10; i++) {
- line.contextGroup.lines.push('lorem upsum');
+ const line = new GrDiffLine(GrDiffLine.Type.BOTH);
+ line.beforeNumber = i + 1;
+ line.afterNumber = i + 1;
+ line.text = 'lorem upsum';
+ lines.push(line);
}
+ const contextLine = {
+ contextGroups: [new GrDiffGroup(GrDiffGroup.Type.BOTH, lines)],
+ };
+
+ const section = {};
// Does not include +10 buttons when there are fewer than 11 lines.
- let td = builder._createContextControl(section, line);
+ let td = builder._createContextControl(section, contextLine);
let buttons = td.querySelectorAll('gr-button.showContext');
assert.equal(buttons.length, 1);
assert.equal(Polymer.dom(buttons[0]).textContent, 'Show 10 common lines');
// Add another line.
- line.contextGroup.lines.push('lorem upsum');
+ const line = new GrDiffLine(GrDiffLine.Type.BOTH);
+ line.text = 'lorem upsum';
+ line.beforeNumber = 11;
+ line.afterNumber = 11;
+ contextLine.contextGroups[0].addLine(line);
// Includes +10 buttons when there are at least 11 lines.
- td = builder._createContextControl(section, line);
+ td = builder._createContextControl(section, contextLine);
buttons = td.querySelectorAll('gr-button.showContext');
assert.equal(buttons.length, 3);
@@ -577,31 +591,39 @@ limitations under the License.
});
});
- suite('layers from plugins', () => {
+ suite('layers', () => {
let element;
let initialLayersCount;
-
+ let withLayerCount;
setup(() => {
+ const layers = [];
element = fixture('basic');
+ element.layers = layers;
element._showTrailingWhitespace = true;
element._setupAnnotationLayers();
initialLayersCount = element._layers.length;
});
- test('no plugin layers', () => {
- const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers')
- .returns([]);
+ test('no layers', () => {
element._setupAnnotationLayers();
- assert.isTrue(getDiffLayersStub.called);
assert.equal(element._layers.length, initialLayersCount);
});
- test('with plugin layers', () => {
- const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers')
- .returns([{}, {}]);
- element._setupAnnotationLayers();
- assert.isTrue(getDiffLayersStub.called);
- assert.equal(element._layers.length, initialLayersCount + 2);
+ suite('with layers', () => {
+ const layers = [{}, {}];
+ setup(() => {
+ element = fixture('basic');
+ element.layers = layers;
+ element._showTrailingWhitespace = true;
+ element._setupAnnotationLayers();
+ withLayerCount = element._layers.length;
+ });
+ test('with layers', () => {
+ element._setupAnnotationLayers();
+ assert.equal(element._layers.length, withLayerCount);
+ assert.equal(initialLayersCount + layers.length,
+ withLayerCount);
+ });
});
});
@@ -712,7 +734,6 @@ limitations under the License.
element.viewMode = 'SIDE_BY_SIDE';
processStub = sandbox.stub(element.$.processor, 'process')
.returns(Promise.resolve());
- sandbox.stub(element, '_anyLineTooLong').returns(true);
keyLocations = {left: {}, right: {}};
prefs = {
line_length: 10,
@@ -802,15 +823,6 @@ limitations under the License.
element.render(keyLocations, prefs).then(done);
});
- test('renderSection', () => {
- let section = outputEl.querySelector('stub:nth-of-type(2)');
- const prevInnerHTML = section.innerHTML;
- section.innerHTML = 'wiped';
- element._builder.renderSection(section);
- section = outputEl.querySelector('stub:nth-of-type(2)');
- assert.equal(section.innerHTML, prevInnerHTML);
- });
-
test('addColumns is called', done => {
element.render(keyLocations, {}).then(done);
assert.isTrue(element._builder.addColumns.called);
@@ -841,37 +853,14 @@ limitations under the License.
.map(c => { return c.args[0].type; });
assert.include(firedEventTypes, 'render-start');
assert.include(firedEventTypes, 'render-content');
- assert.include(firedEventTypes, 'render-syntax');
- done();
- });
- });
-
- test('rendering normal-sized diff does not disable syntax', () => {
- assert.isTrue(element.$.syntaxLayer.enabled);
- });
-
- test('rendering large diff disables syntax', done => {
- // Before it renders, set the first diff line to 500 '*' characters.
- element.diff.content[0].a = [new Array(501).join('*')];
- const prefs = {
- line_length: 10,
- show_tabs: true,
- tab_size: 4,
- context: -1,
- syntax_highlighting: true,
- };
- element.render(keyLocations, prefs).then(() => {
- assert.isFalse(element.$.syntaxLayer.enabled);
done();
});
});
test('cancel', () => {
const processorCancelStub = sandbox.stub(element.$.processor, 'cancel');
- const syntaxCancelStub = sandbox.stub(element.$.syntaxLayer, 'cancel');
element.cancel();
assert.isTrue(processorCancelStub.called);
- assert.isTrue(syntaxCancelStub.called);
});
});
@@ -900,10 +889,6 @@ limitations under the License.
});
});
- test('getDiffLength', () => {
- assert.equal(element.getDiffLength(diff), 52);
- });
-
test('getContentByLine', () => {
let actual;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
index c24574e6a2..99d0498098 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<dom-module id="gr-diff-cursor">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index 1cfd5e75b6..6ddb3902f3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -37,7 +37,6 @@
Polymer({
is: 'gr-diff-cursor',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -65,7 +64,10 @@
/**
* If set, the cursor will attempt to move to the line number (instead of
* the first chunk) the next time the diff renders. It is set back to null
- * when used.
+ * when used. It should be only used if you want the line to be focused
+ * after initialization of the component and page should scroll
+ * to that position. This parameter should be set at most for one gr-diff
+ * element in the page.
*
* @type {?number}
*/
@@ -136,11 +138,11 @@
}
},
- moveToNextChunk() {
+ moveToNextChunk(opt_clipToTop) {
this.$.cursorManager.next(this._isFirstRowOfChunk.bind(this),
target => {
return target.parentNode.scrollHeight;
- });
+ }, opt_clipToTop);
this._fixSide();
},
@@ -192,16 +194,20 @@
},
getTargetDiffElement() {
- // Find the parent diff element of the cursor row.
- for (let diff = this.diffRow; diff; diff = diff.parentElement) {
- if (diff.tagName === 'GR-DIFF') { return diff; }
+ if (!this.diffRow) return null;
+
+ const hostOwner = Polymer.dom(/** @type {Node} */ (this.diffRow))
+ .getOwnerRoot();
+ if (hostOwner && hostOwner.host &&
+ hostOwner.host.tagName === 'GR-DIFF') {
+ return hostOwner.host;
}
return null;
},
moveToFirstChunk() {
this.$.cursorManager.moveToStart();
- this.moveToNextChunk();
+ this.moveToNextChunk(true);
},
reInitCursor() {
@@ -224,8 +230,12 @@
handleDiffUpdate() {
this._updateStops();
-
if (!this.diffRow) {
+ // does not scroll during init unless requested
+ const scrollingBehaviorForInit = this.initialLineNumber ?
+ ScrollBehavior.KEEP_VISIBLE :
+ ScrollBehavior.NEVER;
+ this._scrollBehavior = scrollingBehaviorForInit;
this.reInitCursor();
}
this._scrollBehavior = ScrollBehavior.KEEP_VISIBLE;
@@ -296,7 +306,7 @@
},
_rowHasThread(row) {
- return row.querySelector('.comment-thread');
+ return row.querySelector('.thread-group');
},
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index f111378a74..1c1100d70b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -18,14 +18,17 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-cursor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../gr-diff/gr-diff.html">
<link rel="import" href="./gr-diff-cursor.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<script>void(0);</script>
@@ -207,40 +210,55 @@ limitations under the License.
assert.equal(cursorElement.side, 'left');
});
- test('initialLineNumber disabled', done => {
+ test('initialLineNumber not provided', done => {
+ let scrollBehaviorDuringMove;
const moveToNumStub = sandbox.stub(cursorElement, 'moveToLineNumber');
- const moveToChunkStub = sandbox.stub(cursorElement, 'moveToFirstChunk');
+ const moveToChunkStub = sandbox.stub(cursorElement, 'moveToFirstChunk',
+ () => { scrollBehaviorDuringMove = cursorElement._scrollBehavior; });
function renderHandler() {
diffElement.removeEventListener('render', renderHandler);
assert.isFalse(moveToNumStub.called);
assert.isTrue(moveToChunkStub.called);
+ assert.equal(scrollBehaviorDuringMove, 'never');
+ assert.equal(cursorElement._scrollBehavior, 'keep-visible');
done();
}
diffElement.addEventListener('render', renderHandler);
diffElement._diffChanged(mockDiffResponse.diffResponse);
});
- test('initialLineNumber enabled', done => {
- const moveToNumStub = sandbox.stub(cursorElement, 'moveToLineNumber');
+ test('initialLineNumber provided', done => {
+ let scrollBehaviorDuringMove;
+ const moveToNumStub = sandbox.stub(cursorElement, 'moveToLineNumber',
+ () => { scrollBehaviorDuringMove = cursorElement._scrollBehavior; });
const moveToChunkStub = sandbox.stub(cursorElement, 'moveToFirstChunk');
-
function renderHandler() {
diffElement.removeEventListener('render', renderHandler);
assert.isFalse(moveToChunkStub.called);
assert.isTrue(moveToNumStub.called);
assert.equal(moveToNumStub.lastCall.args[0], 10);
assert.equal(moveToNumStub.lastCall.args[1], 'right');
+ assert.equal(scrollBehaviorDuringMove, 'keep-visible');
+ assert.equal(cursorElement._scrollBehavior, 'keep-visible');
done();
}
diffElement.addEventListener('render', renderHandler);
-
cursorElement.initialLineNumber = 10;
cursorElement.side = 'right';
diffElement._diffChanged(mockDiffResponse.diffResponse);
});
+ test('getTargetDiffElement', () => {
+ cursorElement.initialLineNumber = 1;
+ assert.isTrue(!!cursorElement.diffRow);
+ assert.equal(
+ cursorElement.getTargetDiffElement(),
+ diffElement
+ );
+ });
+
test('getAddress', () => {
// It should initialize to the first chunk: line 5 of the revision.
assert.deepEqual(cursorElement.getAddress(),
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
index c07d3708e7..c1bf3edc1c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-annotation</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="gr-annotation.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html
index c912a16223..3b1719011f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.html
@@ -14,10 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../gr-selection-action-box/gr-selection-action-box.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../gr-selection-action-box/gr-selection-action-box.html">
<dom-module id="gr-diff-highlight">
<template>
@@ -25,14 +26,6 @@ limitations under the License.
:host {
position: relative;
}
- .contentWrapper ::content .range {
- background-color: var(--diff-highlight-range-color);
- display: inline;
- }
- .contentWrapper ::content .rangeHighlight {
- background-color: var(--diff-highlight-range-hover-color);
- display: inline;
- }
gr-selection-action-box {
/**
* Needs z-index to apear above wrapped content, since it's inseted
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index c820668547..b159b51bd6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-diff-highlight',
- _legacyUndefinedCheck: true,
properties: {
/** @type {!Array<!Gerrit.HoveredRange>} */
@@ -36,6 +35,10 @@
_cachedDiffBuilder: Object,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
listeners: {
'comment-thread-mouseleave': '_handleCommentThreadMouseleave',
'comment-thread-mouseenter': '_handleCommentThreadMouseenter',
@@ -160,6 +163,11 @@
* })|null|!Object}
*/
_getNormalizedRange(selection) {
+ /* On Safari the ShadowRoot.getSelection() isn't there and the only thing
+ we can get is a single Range */
+ if (selection instanceof Range) {
+ return this._normalizeRange(selection);
+ }
const rangeCount = selection.rangeCount;
if (rangeCount === 0) {
return null;
@@ -320,12 +328,19 @@
},
_handleSelection(selection, isMouseUp) {
+ /* On Safari, the selection events may return a null range that should
+ be ignored */
+ if (!selection) {
+ return;
+ }
const normalizedRange = this._getNormalizedRange(selection);
if (!this._isRangeValid(normalizedRange)) {
this._removeActionBox();
return;
}
- const domRange = selection.getRangeAt(0);
+ /* On Safari the ShadowRoot.getSelection() isn't there and the only thing
+ we can get is a single Range */
+ const domRange = selection instanceof Range ? selection:selection.getRangeAt(0);
const start = normalizedRange.start;
const end = normalizedRange.end;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index 2e50fdbab1..c929e1ed6e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-highlight</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-diff-highlight.html">
@@ -92,7 +94,7 @@ limitations under the License.
<tbody class="section contextControl">
<tr class="diff-row side-by-side" left-type="contextControl" right-type="contextControl">
- <td class="left contextLineNum" data-value="@@"></td>
+ <td class="left contextLineNum"></td>
<td>
<gr-button>+10↑</gr-button>
-
@@ -100,7 +102,7 @@ limitations under the License.
-
<gr-button>+10↓</gr-button>
</td>
- <td class="right contextLineNum" data-value="@@"></td>
+ <td class="right contextLineNum"></td>
<td>
<gr-button>+10↑</gr-button>
-
@@ -179,8 +181,8 @@ limitations under the License.
element.commentRanges = [{side: 'right'}];
sandbox.stub(element, 'set');
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseenter', {bubbles: true}));
+ threadEl.dispatchEvent(new CustomEvent(
+ 'comment-thread-mouseenter', {bubbles: true, composed: true}));
assert.isFalse(element.set.called);
});
@@ -204,8 +206,8 @@ limitations under the License.
}}];
sandbox.stub(element, 'set');
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseenter', {bubbles: true}));
+ threadEl.dispatchEvent(new CustomEvent(
+ 'comment-thread-mouseenter', {bubbles: true, composed: true}));
assert.isTrue(element.set.called);
const args = element.set.lastCall.args;
assert.deepEqual(args[0], ['commentRanges', 0, 'hovering']);
@@ -221,8 +223,8 @@ limitations under the License.
element.commentRanges = [{side: 'right'}];
sandbox.stub(element, 'set');
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseleave', {bubbles: true}));
+ threadEl.dispatchEvent(new CustomEvent(
+ 'comment-thread-mouseleave', {bubbles: true, composed: true}));
assert.isFalse(element.set.called);
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
index 927c7591a6..cb482b2f17 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
@@ -54,7 +54,7 @@
if (element.nodeName === '#text') {
element = element.parentElement;
}
- while (!element.classList.contains('contentText')) {
+ while (element && !element.classList.contains('contentText')) {
if (element.parentElement === null) {
return target;
}
@@ -80,7 +80,7 @@
if (n === child) {
break;
}
- if (n.childNodes && n.childNodes.length !== 0) {
+ if (n && n.childNodes && n.childNodes.length !== 0) {
const arr = [];
for (const childNode of n.childNodes) {
arr.push(childNode);
@@ -102,7 +102,9 @@
* @return {number} The length of the text.
*/
_getLength(node) {
- return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length;
+ return node
+ ? node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length
+ : 0;
},
};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
index 53ce6e6975..7d60f134bd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.html
@@ -15,13 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-comment-thread/gr-comment-thread.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="../gr-diff/gr-diff.html">
+<link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html">
<dom-module id="gr-diff-host">
<template>
@@ -48,7 +50,13 @@ limitations under the License.
revision-image=[[_revisionImage]]
coverage-ranges="[[_coverageRanges]]"
blame="[[_blame]]"
- diff="[[diff]]"></gr-diff>
+ layers="[[_layers]]"
+ diff="[[diff]]">
+ </gr-diff>
+ <gr-syntax-layer
+ id="syntaxLayer"
+ enabled="[[_syntaxHighlightingEnabled]]"
+ diff="[[diff]]"></gr-syntax-layer>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-reporting id="reporting" category="diff"></gr-reporting>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 9760d50580..2dd19a00eb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -35,6 +35,16 @@
SYNTAX: 'Diff Syntax Render',
};
+ // Disable syntax highlighting if the overall diff is too large.
+ const SYNTAX_MAX_DIFF_LENGTH = 20000;
+
+ // If any line of the diff is more than the character limit, then disable
+ // syntax highlighting for the entire file.
+ const SYNTAX_MAX_LINE_LENGTH = 500;
+
+ // 120 lines is good enough threshold for full-sized window viewport
+ const NUM_OF_LINES_THRESHOLD_FOR_VIEWPORT = 120;
+
const WHITESPACE_IGNORE_NONE = 'IGNORE_NONE';
/**
@@ -66,7 +76,6 @@
*/
Polymer({
is: 'gr-diff-host',
- _legacyUndefinedCheck: true,
/**
* Fired when the user selects a line.
@@ -111,7 +120,9 @@
commitRange: Object,
filesWeblinks: {
type: Object,
- value() { return {}; },
+ value() {
+ return {};
+ },
notify: true,
},
hidden: {
@@ -194,9 +205,7 @@
},
/**
- * TODO(brohlfs): Replace Object type by Gerrit.CoverageRange.
- *
- * @type {!Array<!Object>}
+ * @type {!Array<!Gerrit.CoverageRange>}
*/
_coverageRanges: {
type: Array,
@@ -209,9 +218,21 @@
type: Number,
computed: '_computeParentIndex(patchRange.*)',
},
+
+ _syntaxHighlightingEnabled: {
+ type: Boolean,
+ computed:
+ '_isSyntaxHighlightingEnabled(prefs.*, diff)',
+ },
+
+ _layers: {
+ type: Array,
+ value: [],
+ },
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
],
@@ -229,7 +250,6 @@
'render-start': '_handleRenderStart',
'render-content': '_handleRenderContent',
- 'render-syntax': '_handleRenderSyntax',
'normalize-range': '_handleNormalizeRange',
},
@@ -237,6 +257,7 @@
observers: [
'_whitespaceChanged(prefs.ignore_whitespace, _loadedWhitespaceLevel,' +
' noRenderOnPrefsChange)',
+ '_syntaxHighlightingChanged(noRenderOnPrefsChange, prefs.*)',
],
ready() {
@@ -251,34 +272,35 @@
});
},
- /** @return {!Promise} */
- reload() {
+ /**
+ * @param {boolean=} haveParamsChanged ends reporting events that started
+ * on location change.
+ * @return {!Promise}
+ **/
+ reload(haveParamsChanged) {
this._loading = true;
this._errorMessage = null;
const whitespaceLevel = this._getIgnoreWhitespace();
- this._coverageRanges = [];
- const {changeNum, path, patchRange: {basePatchNum, patchNum}} = this;
- this.$.jsAPI.getCoverageRanges(changeNum, path, basePatchNum, patchNum).
- then(coverageRanges => {
- if (changeNum !== this.changeNum ||
- path !== this.path ||
- basePatchNum !== this.patchRange.basePatchNum ||
- patchNum !== this.patchRange.patchNum) {
- return;
- }
- this._coverageRanges = coverageRanges;
- }).catch(err => {
- console.warn('Loading coverage ranges failed: ', err);
- });
+ const layers = [this.$.syntaxLayer];
+ // Get layers from plugins (if any).
+ for (const pluginLayer of this.$.jsAPI.getDiffLayers(
+ this.path, this.changeNum, this.patchNum)) {
+ layers.push(pluginLayer);
+ }
+ this._layers = layers;
+ if (haveParamsChanged) {
+ // We listen on render viewport only on DiffPage (on paramsChanged)
+ this._listenToViewportRender();
+ }
+
+ this._coverageRanges = [];
+ this._getCoverageData();
const diffRequest = this._getDiff()
.then(diff => {
this._loadedWhitespaceLevel = whitespaceLevel;
this._reportDiff(diff);
- if (this._getIgnoreWhitespace() !== WHITESPACE_IGNORE_NONE) {
- return this._translateChunksToIgnore(diff);
- }
return diff;
})
.catch(e => {
@@ -293,6 +315,8 @@
return this._loadDiffAssets(diff);
});
+ // Not waiting for coverage ranges intentionally as
+ // plugin loading should not block the content rendering
return Promise.all([diffRequest, assetRequest])
.then(results => {
const diff = results[0];
@@ -301,8 +325,20 @@
}
this.filesWeblinks = this._getFilesWeblinks(diff);
return new Promise(resolve => {
- const callback = () => {
- resolve();
+ const callback = event => {
+ const needsSyntaxHighlighting = event.detail
+ && event.detail.contentRendered;
+ if (needsSyntaxHighlighting) {
+ this.$.reporting.time(TimingLabel.SYNTAX);
+ this.$.syntaxLayer.process().then(() => {
+ this.$.reporting.timeEnd(TimingLabel.SYNTAX);
+ this.$.reporting.timeEnd(TimingLabel.TOTAL);
+ resolve();
+ });
+ } else {
+ this.$.reporting.timeEnd(TimingLabel.TOTAL);
+ resolve();
+ }
this.removeEventListener('render', callback);
};
this.addEventListener('render', callback);
@@ -315,8 +351,53 @@
.then(() => { this._loading = false; });
},
+ _getCoverageData() {
+ const {changeNum, path, patchRange: {basePatchNum, patchNum}} = this;
+ this.$.jsAPI.getCoverageAnnotationApi().
+ then(coverageAnnotationApi => {
+ if (!coverageAnnotationApi) return;
+ const provider = coverageAnnotationApi.getCoverageProvider();
+ return provider(changeNum, path, basePatchNum, patchNum)
+ .then(coverageRanges => {
+ if (!coverageRanges ||
+ changeNum !== this.changeNum ||
+ path !== this.path ||
+ basePatchNum !== this.patchRange.basePatchNum ||
+ patchNum !== this.patchRange.patchNum) {
+ return;
+ }
+
+ const existingCoverageRanges = this._coverageRanges;
+ this._coverageRanges = coverageRanges;
+
+ // Notify with existing coverage ranges
+ // in case there is some existing coverage data that needs to be removed
+ existingCoverageRanges.forEach(range => {
+ coverageAnnotationApi.notify(
+ path,
+ range.code_range.start_line,
+ range.code_range.end_line,
+ range.side);
+ });
+
+ // Notify with new coverage data
+ coverageRanges.forEach(range => {
+ coverageAnnotationApi.notify(
+ path,
+ range.code_range.start_line,
+ range.code_range.end_line,
+ range.side);
+ });
+ });
+ }).catch(err => {
+ console.warn('Loading coverage ranges failed: ', err);
+ });
+ },
+
_getFilesWeblinks(diff) {
- if (!this.commitRange) { return {}; }
+ if (!this.commitRange) {
+ return {};
+ }
return {
meta_a: Gerrit.Nav.getFileWebLinks(
this.projectName, this.commitRange.baseCommit, this.path,
@@ -375,7 +456,6 @@
* @return {!Array<!HTMLElement>}
*/
getThreadEls() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this.$.diff).querySelectorAll('.comment-thread'));
},
@@ -444,7 +524,9 @@
* Report info about the diff response.
*/
_reportDiff(diff) {
- if (!diff || !diff.content) { return; }
+ if (!diff || !diff.content) {
+ return;
+ }
// Count the delta lines stemming from normal deltas, and from
// due_to_rebase deltas.
@@ -672,7 +754,7 @@
* @param {!Gerrit.Range=} range
* @return {?Node}
*/
- _getThreadEl(lineNum, commentSide, range=undefined) {
+ _getThreadEl(lineNum, commentSide, range = undefined) {
let line;
if (commentSide === GrDiffBuilder.Side.LEFT) {
line = {beforeNumber: lineNum};
@@ -737,50 +819,6 @@
matchers.some(matcher => matcher(threadEl)));
},
- /**
- * Take a diff that was loaded with a ignore-whitespace other than
- * IGNORE_NONE, and convert delta chunks labeled as common into shared
- * chunks.
- *
- * @param {!Object} diff
- * @returns {!Object}
- */
- _translateChunksToIgnore(diff) {
- const newDiff = Object.assign({}, diff);
- const mergedContent = [];
-
- // Was the last chunk visited a shared chunk?
- let lastWasShared = false;
-
- for (const chunk of diff.content) {
- if (lastWasShared && chunk.common && chunk.b) {
- // The last chunk was shared and this chunk should be ignored, so
- // add its revision content to the previous chunk.
- mergedContent[mergedContent.length - 1].ab.push(...chunk.b);
- } else if (chunk.common && !chunk.b) {
- // If the chunk should be ignored, but it doesn't have revision
- // content, then drop it and continue without updating lastWasShared.
- continue;
- } else if (lastWasShared && chunk.ab) {
- // Both the last chunk and the current chunk are shared. Merge this
- // chunk's shared content into the previous shared content.
- mergedContent[mergedContent.length - 1].ab.push(...chunk.ab);
- } else if (!lastWasShared && chunk.common && chunk.b) {
- // If the previous chunk was not shared, but this one should be
- // ignored, then add it as a shared chunk.
- mergedContent.push({ab: chunk.b});
- } else {
- // Otherwise add the chunk as is.
- mergedContent.push(chunk);
- }
-
- lastWasShared = !!mergedContent[mergedContent.length - 1].ab;
- }
-
- newDiff.content = mergedContent;
- return newDiff;
- },
-
_getIgnoreWhitespace() {
if (!this.prefs || !this.prefs.ignore_whitespace) {
return WHITESPACE_IGNORE_NONE;
@@ -788,14 +826,42 @@
return this.prefs.ignore_whitespace;
},
- _whitespaceChanged(preferredWhitespaceLevel, loadedWhitespaceLevel,
+ _whitespaceChanged(
+ preferredWhitespaceLevel, loadedWhitespaceLevel,
noRenderOnPrefsChange) {
+ // Polymer 2: check for undefined
+ if ([
+ preferredWhitespaceLevel,
+ loadedWhitespaceLevel,
+ noRenderOnPrefsChange,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
if (preferredWhitespaceLevel !== loadedWhitespaceLevel &&
!noRenderOnPrefsChange) {
this.reload();
}
},
+ _syntaxHighlightingChanged(noRenderOnPrefsChange, prefsChangeRecord) {
+ // Polymer 2: check for undefined
+ if ([
+ noRenderOnPrefsChange,
+ prefsChangeRecord,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
+ if (prefsChangeRecord.path !== 'prefs.syntax_highlighting') {
+ return;
+ }
+
+ if (!noRenderOnPrefsChange) {
+ this.reload();
+ }
+ },
+
/**
* @param {Object} patchRangeRecord
* @return {number|null}
@@ -841,8 +907,8 @@
},
_handleCommentSaveOrDiscard() {
- this.dispatchEvent(new CustomEvent('diff-comments-modified',
- {bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'diff-comments-modified', {bubbles: true, composed: true}));
},
_removeComment(comment) {
@@ -877,6 +943,42 @@
item => item.__draftID === comment.__draftID);
},
+ _isSyntaxHighlightingEnabled(preferenceChangeRecord, diff) {
+ if (!preferenceChangeRecord ||
+ !preferenceChangeRecord.base ||
+ !preferenceChangeRecord.base.syntax_highlighting ||
+ !diff) {
+ return false;
+ }
+ return !this._anyLineTooLong(diff) &&
+ this.$.diff.getDiffLength(diff) <= SYNTAX_MAX_DIFF_LENGTH;
+ },
+
+ /**
+ * @return {boolean} whether any of the lines in diff are longer
+ * than SYNTAX_MAX_LINE_LENGTH.
+ */
+ _anyLineTooLong(diff) {
+ if (!diff) return false;
+ return diff.content.some(section => {
+ const lines = section.ab ?
+ section.ab :
+ (section.a || []).concat(section.b || []);
+ return lines.some(line => line.length >= SYNTAX_MAX_LINE_LENGTH);
+ });
+ },
+
+ _listenToViewportRender() {
+ const renderUpdateListener = start => {
+ if (start > NUM_OF_LINES_THRESHOLD_FOR_VIEWPORT) {
+ this.$.reporting.diffViewDisplayed();
+ this.$.syntaxLayer.removeListener(renderUpdateListener);
+ }
+ };
+
+ this.$.syntaxLayer.addListener(renderUpdateListener);
+ },
+
_handleRenderStart() {
this.$.reporting.time(TimingLabel.TOTAL);
this.$.reporting.time(TimingLabel.CONTENT);
@@ -884,12 +986,7 @@
_handleRenderContent() {
this.$.reporting.timeEnd(TimingLabel.CONTENT);
- this.$.reporting.time(TimingLabel.SYNTAX);
- },
-
- _handleRenderSyntax() {
- this.$.reporting.timeEnd(TimingLabel.SYNTAX);
- this.$.reporting.timeEnd(TimingLabel.TOTAL);
+ this.$.reporting.diffViewContentDisplayed();
},
_handleNormalizeRange(event) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index 6e7c239e08..94b2f7d200 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-diff-host.html">
@@ -49,7 +51,6 @@ limitations under the License.
time: sandbox.stub(),
timeEnd: sandbox.stub(),
});
-
element = fixture('basic');
});
@@ -57,6 +58,22 @@ limitations under the License.
sandbox.restore();
});
+
+ suite('plugin layers', () => {
+ const pluginLayers = [{annotate: () => {}}, {annotate: () => {}}];
+ setup(() => {
+ stub('gr-js-api-interface', {
+ getDiffLayers() { return pluginLayers; },
+ });
+ element = fixture('basic');
+ });
+ test('plugin layers requested', () => {
+ element.patchRange = {};
+ element.reload();
+ assert(element.$.jsAPI.getDiffLayers.called);
+ });
+ });
+
suite('handle comment-update', () => {
setup(() => {
sandbox.stub(element, '_commentsChanged');
@@ -250,7 +267,15 @@ limitations under the License.
element.path = 'some/path';
element.projectName = 'Some project';
const threadEls = threads.map(
- thread => element._createThreadElement(thread));
+ thread => {
+ const threadEl = element._createThreadElement(thread);
+ // Polymer 2 doesn't fire ready events and doesn't execute
+ // observers if element is not added to the Dom.
+ // See https://github.com/Polymer/old-docs-site/issues/2322
+ // and https://github.com/Polymer/polymer/issues/4526
+ element._attachThreadElement(threadEl);
+ return threadEl;
+ });
assert.equal(threadEls.length, 2);
assert.equal(threadEls[0].rootId, 4711);
assert.equal(threadEls[1].rootId, 42);
@@ -269,7 +294,7 @@ limitations under the License.
suite('render reporting', () => {
test('starts total and content timer on render-start', done => {
element.dispatchEvent(
- new CustomEvent('render-start', {bubbles: true}));
+ new CustomEvent('render-start', {bubbles: true, composed: true}));
assert.isTrue(element.$.reporting.time.calledWithExactly(
'Diff Total Render'));
assert.isTrue(element.$.reporting.time.calledWithExactly(
@@ -277,24 +302,82 @@ limitations under the License.
done();
});
- test('ends content and starts syntax timer on render-content', done => {
+ test('ends content timer on render-content', () => {
element.dispatchEvent(
- new CustomEvent('render-content', {bubbles: true}));
- assert.isTrue(element.$.reporting.time.calledWithExactly(
- 'Diff Syntax Render'));
+ new CustomEvent('render-content', {bubbles: true, composed: true}));
assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
'Diff Content Render'));
- done();
});
- test('ends total and syntax timer on render-syntax', done => {
- element.dispatchEvent(
- new CustomEvent('render-syntax', {bubbles: true}));
- assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
- 'Diff Total Render'));
- assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
- 'Diff Syntax Render'));
- done();
+ test('ends total and syntax timer after syntax layer processing', done => {
+ let notifySyntaxProcessed;
+ sandbox.stub(element.$.syntaxLayer, 'process').returns(new Promise(
+ resolve => {
+ notifySyntaxProcessed = resolve;
+ }));
+ sandbox.stub(element.$.restAPI, 'getDiff').returns(
+ Promise.resolve({content: []}));
+ element.patchRange = {};
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ return element.reload();
+ });
+ // Multiple cascading microtasks are scheduled.
+ setTimeout(() => {
+ notifySyntaxProcessed();
+ // Assert after the notification task is processed.
+ Promise.resolve().then(() => {
+ assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
+ 'Diff Total Render'));
+ assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
+ 'Diff Syntax Render'));
+ assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
+ 'StartupDiffViewOnlyContent'));
+ done();
+ });
+ });
+ });
+
+ test('ends total timer w/ no syntax layer processing', done => {
+ sandbox.stub(element.$.restAPI, 'getDiff').returns(
+ Promise.resolve({content: []}));
+ element.patchRange = {};
+ element.reload();
+ // Multiple cascading microtasks are scheduled.
+ setTimeout(() => {
+ assert.isTrue(element.$.reporting.timeEnd.calledOnce);
+ assert.isTrue(element.$.reporting.timeEnd.calledWithExactly(
+ 'Diff Total Render'));
+ done();
+ });
+ });
+
+ test('completes reload promise after syntax layer processing', done => {
+ let notifySyntaxProcessed;
+ sandbox.stub(element.$.syntaxLayer, 'process').returns(new Promise(
+ resolve => {
+ notifySyntaxProcessed = resolve;
+ }));
+ sandbox.stub(element.$.restAPI, 'getDiff').returns(
+ Promise.resolve({content: []}));
+ element.patchRange = {};
+ let reloadComplete = false;
+ element.$.restAPI.getDiffPreferences().then(prefs => {
+ element.prefs = prefs;
+ return element.reload();
+ }).then(() => {
+ reloadComplete = true;
+ });
+ // Multiple cascading microtasks are scheduled.
+ setTimeout(() => {
+ assert.isFalse(reloadComplete);
+ notifySyntaxProcessed();
+ // Assert after the notification task is processed.
+ setTimeout(() => {
+ assert.isTrue(reloadComplete);
+ done();
+ });
+ });
});
});
@@ -303,6 +386,7 @@ limitations under the License.
// Stub the network calls into requests that never resolve.
sandbox.stub(element, '_getDiff', () => new Promise(() => {}));
+ element.patchRange = {};
element.reload();
assert.isTrue(cancelStub.called);
@@ -366,6 +450,7 @@ limitations under the License.
(changeNum, basePatchNum, patchNum, path, onErr) => {
onErr(error);
});
+ element.patchRange = {};
return element.reload().then(() => {
assert.isTrue(onErrStub.calledOnce);
});
@@ -723,6 +808,7 @@ limitations under the License.
test('delegates cancel()', () => {
const stub = sandbox.stub(element.$.diff, 'cancel');
+ element.patchRange = {};
element.reload();
assert.isTrue(stub.calledOnce);
assert.equal(stub.lastCall.args.length, 0);
@@ -1241,9 +1327,11 @@ limitations under the License.
const l = document.createElement('div');
l.setAttribute('comment-side', 'left');
+ l.setAttribute('line-num', 'FILE');
const r = document.createElement('div');
r.setAttribute('comment-side', 'right');
+ r.setAttribute('line-num', 'FILE');
const threadEls = [l, r];
assert.deepEqual(element._filterThreadElsForLocation(threadEls, line),
@@ -1256,86 +1344,154 @@ limitations under the License.
Gerrit.DiffSide.RIGHT), [r]);
});
- suite('_translateChunksToIgnore', () => {
- let content;
-
+ suite('syntax layer with syntax_highlighting on', () => {
setup(() => {
- content = [
- {ab: ['one', 'two']},
- {a: ['three'], b: ['different three']},
- {b: ['four']},
- {ab: ['five', 'six']},
- {a: ['seven']},
- {ab: ['eight', 'nine']},
- ];
+ const prefs = {
+ line_length: 10,
+ show_tabs: true,
+ tab_size: 4,
+ context: -1,
+ syntax_highlighting: true,
+ };
+ element.patchRange = {};
+ element.prefs = prefs;
});
- test('does nothing to unmarked diff', () => {
- assert.deepEqual(element._translateChunksToIgnore({content}),
- {content});
+ test('gr-diff-host provides syntax highlighting layer to gr-diff', () => {
+ element.reload();
+ assert.equal(element.$.diff.layers[0], element.$.syntaxLayer);
});
- test('merges marked delta chunk', () => {
- content[1].common = true;
- assert.deepEqual(element._translateChunksToIgnore({content}), {
- content: [
- {ab: ['one', 'two', 'different three']},
- {b: ['four']},
- {ab: ['five', 'six']},
- {a: ['seven']},
- {ab: ['eight', 'nine']},
- ],
- });
+ test('rendering normal-sized diff does not disable syntax', () => {
+ element.diff = {
+ content: [{
+ a: ['foo'],
+ }],
+ };
+ assert.isTrue(element.$.syntaxLayer.enabled);
});
- test('merges marked addition chunk', () => {
- content[2].common = true;
- assert.deepEqual(element._translateChunksToIgnore({content}), {
- content: [
- {ab: ['one', 'two']},
- {a: ['three'], b: ['different three']},
- {ab: ['four', 'five', 'six']},
- {a: ['seven']},
- {ab: ['eight', 'nine']},
- ],
+ test('rendering large diff disables syntax', () => {
+ // Before it renders, set the first diff line to 500 '*' characters.
+ element.diff = {
+ content: [{
+ a: [new Array(501).join('*')],
+ }],
+ };
+ assert.isFalse(element.$.syntaxLayer.enabled);
+ });
+
+ test('starts syntax layer processing on render event', done => {
+ sandbox.stub(element.$.syntaxLayer, 'process').returns(Promise.resolve());
+ sandbox.stub(element.$.restAPI, 'getDiff').returns(
+ Promise.resolve({content: []}));
+ element.reload();
+ setTimeout(() => {
+ element.dispatchEvent(
+ new CustomEvent('render', {bubbles: true, composed: true}));
+ assert.isTrue(element.$.syntaxLayer.process.called);
+ done();
});
});
+ });
+
+ suite('syntax layer with syntax_highlgihting off', () => {
+ setup(() => {
+ const prefs = {
+ line_length: 10,
+ show_tabs: true,
+ tab_size: 4,
+ context: -1,
+ };
+ element.diff = {
+ content: [{
+ a: ['foo'],
+ }],
+ };
+ element.patchRange = {};
+ element.prefs = prefs;
+ });
- test('merges multiple marked delta', () => {
- content[1].common = true;
- content[2].common = true;
- assert.deepEqual(element._translateChunksToIgnore({content}), {
- content: [
- {ab: ['one', 'two', 'different three', 'four', 'five', 'six']},
- {a: ['seven']},
- {ab: ['eight', 'nine']},
- ],
+ test('gr-diff-host provides syntax highlighting layer', () => {
+ element.reload();
+ assert.equal(element.$.diff.layers[0], element.$.syntaxLayer);
+ });
+
+ test('syntax layer should be disabled', () => {
+ assert.isFalse(element.$.syntaxLayer.enabled);
+ });
+
+ test('still disabled for large diff', () => {
+ // Before it renders, set the first diff line to 500 '*' characters.
+ element.diff = {
+ content: [{
+ a: [new Array(501).join('*')],
+ }],
+ };
+ assert.isFalse(element.$.syntaxLayer.enabled);
+ });
+ });
+
+ suite('coverage layer', () => {
+ let notifyStub;
+ setup(() => {
+ notifyStub = sinon.stub();
+ stub('gr-js-api-interface', {
+ getCoverageAnnotationApi() {
+ return Promise.resolve({
+ notify: notifyStub,
+ getCoverageProvider() {
+ return () => Promise.resolve([
+ {
+ type: 'COVERED',
+ side: 'right',
+ code_range: {
+ start_line: 1,
+ end_line: 2,
+ },
+ },
+ {
+ type: 'NOT_COVERED',
+ side: 'right',
+ code_range: {
+ start_line: 3,
+ end_line: 4,
+ },
+ },
+ ]);
+ },
+ });
+ },
});
+ element = fixture('basic');
+ const prefs = {
+ line_length: 10,
+ show_tabs: true,
+ tab_size: 4,
+ context: -1,
+ };
+ element.diff = {
+ content: [{
+ a: ['foo'],
+ }],
+ };
+ element.patchRange = {};
+ element.prefs = prefs;
});
- test('marked deletion chunks are omitted', () => {
- content[4].common = true;
- assert.deepEqual(element._translateChunksToIgnore({content}), {
- content: [
- {ab: ['one', 'two']},
- {a: ['three'], b: ['different three']},
- {b: ['four']},
- {ab: ['five', 'six', 'eight', 'nine']},
- ],
+ test('getCoverageAnnotationApi should be called', done => {
+ element.reload();
+ flush(() => {
+ assert.isTrue(element.$.jsAPI.getCoverageAnnotationApi.calledOnce);
+ done();
});
});
- test('marked deltas can start shared chunks', () => {
- content[0] = {a: ['one'], b: ['two'], common: true};
- assert.deepEqual(element._translateChunksToIgnore({content}), {
- content: [
- {ab: ['two']},
- {a: ['three'], b: ['different three']},
- {b: ['four']},
- {ab: ['five', 'six']},
- {a: ['seven']},
- {ab: ['eight', 'nine']},
- ],
+ test('coverageRangeChanged should be called', done => {
+ element.reload();
+ flush(() => {
+ assert.equal(notifyStub.callCount, 2);
+ done();
});
});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.html b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.html
index 8251e53ffb..47cf7711a0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -41,7 +42,7 @@ limitations under the License.
has-tooltip
class$="[[_computeSelectedClass(mode, _VIEW_MODES.SIDE_BY_SIDE)]]"
title="Side-by-side diff"
- on-tap="_handleSideBySideTap">
+ on-click="_handleSideBySideTap">
<iron-icon icon="gr-icons:side-by-side"></iron-icon>
</gr-button>
<gr-button
@@ -50,7 +51,7 @@ limitations under the License.
has-tooltip
title="Unified diff"
class$="[[_computeSelectedClass(mode, _VIEW_MODES.UNIFIED)]]"
- on-tap="_handleUnifiedTap">
+ on-click="_handleUnifiedTap">
<iron-icon icon="gr-icons:unified"></iron-icon>
</gr-button>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
index e2d6a28415..88dd91a8ca 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-diff-mode-selector',
- _legacyUndefinedCheck: true,
properties: {
mode: {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
index c0111067f2..adeaa1509b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-mode-selector</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-diff-mode-selector.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.html
index b850f2cc8c..b0167b3636 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-diff-preferences/gr-diff-preferences.html">
@@ -26,7 +27,7 @@ limitations under the License.
<style include="shared-styles">
.diffHeader,
.diffActions {
- padding: 1em 1.5em;
+ padding: var(--spacing-l) var(--spacing-xl);
}
.diffHeader,
.diffActions {
@@ -42,7 +43,7 @@ limitations under the License.
justify-content: flex-end;
}
.diffPrefsOverlay gr-button {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
div.edited:after {
color: var(--deemphasized-text-color);
@@ -50,7 +51,7 @@ limitations under the License.
}
#diffPreferences {
display: flex;
- padding: .35em 1.5em;
+ padding: var(--spacing-s) var(--spacing-xl);
}
</style>
<gr-overlay id="diffPrefsOverlay" with-backdrop>
@@ -63,13 +64,13 @@ limitations under the License.
<gr-button
id="cancelButton"
link
- on-tap="_handleCancelDiff">
+ on-click="_handleCancelDiff">
Cancel
</gr-button>
<gr-button
id="saveButton"
link primary
- on-tap="_handleSaveDiffPreferences"
+ on-click="_handleSaveDiffPreferences"
disabled$="[[!_diffPrefsChanged]]">
Save
</gr-button>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
index 7f7cd733a7..9b723bdee5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-diff-preferences-dialog',
- _legacyUndefinedCheck: true,
properties: {
/** @type {?} */
@@ -39,15 +38,19 @@
_diffPrefsChanged: Boolean,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
getFocusStops() {
return {
- start: this.$.contextSelect,
+ start: this.$.diffPreferences.$.contextSelect,
end: this.$.saveButton,
};
},
resetFocus() {
- this.$.contextSelect.focus();
+ this.$.diffPreferences.$.contextSelect.focus();
},
_computeHeaderClass(changed) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html
index 663cf258a6..922ac87086 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-diff-processor">
<script src="../gr-diff/gr-diff-line.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
index 2cc69e664d..e052a8fddf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -24,12 +24,6 @@
RIGHT: 'right',
};
- const DiffGroupType = {
- ADDED: 'b',
- BOTH: 'ab',
- REMOVED: 'a',
- };
-
const DiffHighlights = {
ADDED: 'edit_b',
REMOVED: 'edit_a',
@@ -48,19 +42,30 @@
/**
* Converts the API's `DiffContent`s to `GrDiffGroup`s for rendering.
*
- * This includes a number of tasks:
+ * Glossary:
+ * - "chunk": A single `DiffContent` as returned by the API.
+ * - "group": A single `GrDiffGroup` as used for rendering.
+ * - "common" chunk/group: A chunk/group that should be considered unchanged
+ * for diffing purposes. This can mean its either actually unchanged, or it
+ * has only whitespace changes.
+ * - "key location": A line number and side of the diff that should not be
+ * collapsed e.g. because a comment is attached to it, or because it was
+ * provided in the URL and thus should be visible
+ * - "uncollapsible" chunk/group: A chunk/group that is either not "common",
+ * or cannot be collapsed because it contains a key location
+ *
+ * Here a a number of tasks this processor performs:
+ * - splitting large chunks to allow more granular async rendering
* - adding a group for the "File" pseudo line that file-level comments can
* be attached to
- * - replacing unchanged parts of the diff that are outside the user's
+ * - replacing common parts of the diff that are outside the user's
* context setting and do not have comments with a group representing the
- * "expand context" widget. This may require splitting a `DiffContent` so
+ * "expand context" widget. This may require splitting a chunk/group so
* that the part that is within the context or has comments is shown, while
* the rest is not.
- * - splitting large `DiffContent`s to allow more granular async rendering
*/
Polymer({
is: 'gr-diff-processor',
- _legacyUndefinedCheck: true,
properties: {
@@ -127,13 +132,16 @@
},
/**
- * Asynchronously process the diff object into groups. As it processes, it
+ * Asynchronously process the diff chunks into groups. As it processes, it
* will splice groups into the `groups` property of the component.
*
- * @return {Promise} A promise that resolves when the diff is completely
- * processed.
+ * @param {!Array<!Gerrit.DiffChunk>} chunks
+ * @param {boolean} isBinary
+ *
+ * @return {!Promise<!Array<!Object>>} A promise that resolves with an
+ * array of GrDiffGroups when the diff is completely processed.
*/
- process(content, isBinary) {
+ process(chunks, isBinary) {
// Cancel any still running process() calls, because they append to the
// same groups field.
this.cancel();
@@ -150,11 +158,11 @@
new Promise(resolve => {
const state = {
lineNums: {left: 0, right: 0},
- sectionIndex: 0,
+ chunkIndex: 0,
};
- content = this._splitLargeChunks(content);
- content = this._splitUnchangedChunksWithComments(content);
+ chunks = this._splitLargeChunks(chunks);
+ chunks = this._splitCommonChunksWithKeyLocations(chunks);
let currentBatch = 0;
const nextStep = () => {
@@ -163,23 +171,23 @@
return;
}
// If we are done, resolve the promise.
- if (state.sectionIndex >= content.length) {
- resolve(this.groups);
+ if (state.chunkIndex >= chunks.length) {
+ resolve();
this._nextStepHandle = null;
return;
}
- // Process the next section and incorporate the result.
- const result = this._processNext(state, content);
- for (const group of result.groups) {
+ // Process the next chunk and incorporate the result.
+ const stateUpdate = this._processNext(state, chunks);
+ for (const group of stateUpdate.groups) {
this.push('groups', group);
currentBatch += group.lines.length;
}
- state.lineNums.left += result.lineDelta.left;
- state.lineNums.right += result.lineDelta.right;
+ state.lineNums.left += stateUpdate.lineDelta.left;
+ state.lineNums.right += stateUpdate.lineDelta.right;
// Increment the index and recurse.
- state.sectionIndex++;
+ state.chunkIndex = stateUpdate.newChunkIndex;
if (currentBatch >= this._asyncThreshold) {
currentBatch = 0;
this._nextStepHandle = this.async(nextStep, 1);
@@ -208,178 +216,197 @@
},
/**
- * Process the next section of the diff.
+ * Process the next uncollapsible chunk, or the next collapsible chunks.
+ *
+ * @param {!Object} state
+ * @param {!Array<!Object>} chunks
+ * @return {{lineDelta: {left: number, right: number}, groups: !Array<!Object>, newChunkIndex: number}}
*/
- _processNext(state, content) {
- const section = content[state.sectionIndex];
-
- const rows = {
- both: section[DiffGroupType.BOTH] || null,
- added: section[DiffGroupType.ADDED] || null,
- removed: section[DiffGroupType.REMOVED] || null,
- };
-
- const highlights = {
- added: section[DiffHighlights.ADDED] || null,
- removed: section[DiffHighlights.REMOVED] || null,
- };
-
- if (rows.both) { // If it's a shared section.
- let sectionEnd = null;
- if (state.sectionIndex === 0) {
- sectionEnd = 'first';
- } else if (state.sectionIndex === content.length - 1) {
- sectionEnd = 'last';
- }
-
- const sharedGroups = this._sharedGroupsFromRows(
- rows.both,
- content.length > 1 ? this.context : WHOLE_FILE,
- state.lineNums.left,
- state.lineNums.right,
- sectionEnd);
-
+ _processNext(state, chunks) {
+ const firstUncollapsibleChunkIndex =
+ this._firstUncollapsibleChunkIndex(chunks, state.chunkIndex);
+ if (firstUncollapsibleChunkIndex === state.chunkIndex) {
+ const chunk = chunks[state.chunkIndex];
return {
lineDelta: {
- left: rows.both.length,
- right: rows.both.length,
+ left: this._linesLeft(chunk).length,
+ right: this._linesRight(chunk).length,
},
- groups: sharedGroups,
+ groups: [this._chunkToGroup(
+ chunk, state.lineNums.left + 1, state.lineNums.right + 1)],
+ newChunkIndex: state.chunkIndex + 1,
};
- } else { // Otherwise it's a delta section.
- const deltaGroup = this._deltaGroupFromRows(
- rows.added,
- rows.removed,
- state.lineNums.left,
- state.lineNums.right,
- highlights);
- deltaGroup.dueToRebase = section.due_to_rebase;
+ }
- return {
- lineDelta: {
- left: rows.removed ? rows.removed.length : 0,
- right: rows.added ? rows.added.length : 0,
- },
- groups: [deltaGroup],
- };
+ return this._processCollapsibleChunks(
+ state, chunks, firstUncollapsibleChunkIndex);
+ },
+
+ _linesLeft(chunk) {
+ return chunk.ab || chunk.a || [];
+ },
+
+ _linesRight(chunk) {
+ return chunk.ab || chunk.b || [];
+ },
+
+ _firstUncollapsibleChunkIndex(chunks, offset) {
+ let chunkIndex = offset;
+ while (chunkIndex < chunks.length &&
+ this._isCollapsibleChunk(chunks[chunkIndex])) {
+ chunkIndex++;
}
+ return chunkIndex;
+ },
+
+ _isCollapsibleChunk(chunk) {
+ return (chunk.ab || chunk.common) && !chunk.keyLocation;
},
/**
- * Take rows of a shared diff section and produce an array of corresponding
- * (potentially collapsed) groups.
+ * Process a stretch of collapsible chunks.
*
- * @param {!Array<string>} rows
- * @param {number} context
- * @param {number} startLineNumLeft
- * @param {number} startLineNumRight
- * @param {?string=} opt_sectionEnd String representing whether this is the
- * first section or the last section or neither. Use the values 'first',
- * 'last' and null respectively.
- * @return {!Array<!Object>} Array of GrDiffGroup
+ * Outputs up to three groups:
+ * 1) Visible context before the hidden common code, unless it's the
+ * very beginning of the file.
+ * 2) Context hidden behind a context bar, unless empty.
+ * 3) Visible context after the hidden common code, unless it's the very
+ * end of the file.
+ *
+ * @param {!Object} state
+ * @param {!Array<Object>} chunks
+ * @param {number} firstUncollapsibleChunkIndex
+ * @return {{lineDelta: {left: number, right: number}, groups: !Array<!Object>, newChunkIndex: number}}
*/
- _sharedGroupsFromRows(rows, context, startLineNumLeft,
- startLineNumRight, opt_sectionEnd) {
- const result = [];
- const lines = [];
- let line;
-
- // Map each row to a GrDiffLine.
- for (let i = 0; i < rows.length; i++) {
- line = new GrDiffLine(GrDiffLine.Type.BOTH);
- line.text = rows[i];
- line.beforeNumber = ++startLineNumLeft;
- line.afterNumber = ++startLineNumRight;
- lines.push(line);
- }
-
- // Find the hidden range based on the user's context preference. If this
- // is the first or the last section of the diff, make sure the collapsed
- // part of the section extends to the edge of the file.
- const hiddenRange = [context, rows.length - context];
- if (opt_sectionEnd === 'first') {
- hiddenRange[0] = 0;
- } else if (opt_sectionEnd === 'last') {
- hiddenRange[1] = rows.length;
+ _processCollapsibleChunks(
+ state, chunks, firstUncollapsibleChunkIndex) {
+ const collapsibleChunks = chunks.slice(
+ state.chunkIndex, firstUncollapsibleChunkIndex);
+ const lineCount = collapsibleChunks.reduce(
+ (sum, chunk) => sum + this._commonChunkLength(chunk), 0);
+
+ let groups = this._chunksToGroups(
+ collapsibleChunks,
+ state.lineNums.left + 1,
+ state.lineNums.right + 1);
+
+ if (this.context !== WHOLE_FILE) {
+ const hiddenStart = state.chunkIndex === 0 ? 0 : this.context;
+ const hiddenEnd = lineCount - (
+ firstUncollapsibleChunkIndex === chunks.length ?
+ 0 : this.context);
+ groups = GrDiffGroup.hideInContextControl(
+ groups, hiddenStart, hiddenEnd);
}
- // If there is a range to hide.
- if (context !== WHOLE_FILE && hiddenRange[1] - hiddenRange[0] > 1) {
- const linesBeforeCtx = lines.slice(0, hiddenRange[0]);
- const hiddenLines = lines.slice(hiddenRange[0], hiddenRange[1]);
- const linesAfterCtx = lines.slice(hiddenRange[1]);
-
- if (linesBeforeCtx.length > 0) {
- result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesBeforeCtx));
- }
-
- const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
- ctxLine.contextGroup =
- new GrDiffGroup(GrDiffGroup.Type.BOTH, hiddenLines);
- result.push(new GrDiffGroup(GrDiffGroup.Type.CONTEXT_CONTROL,
- [ctxLine]));
+ return {
+ lineDelta: {
+ left: lineCount,
+ right: lineCount,
+ },
+ groups,
+ newChunkIndex: firstUncollapsibleChunkIndex,
+ };
+ },
- if (linesAfterCtx.length > 0) {
- result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, linesAfterCtx));
- }
- } else {
- result.push(new GrDiffGroup(GrDiffGroup.Type.BOTH, lines));
- }
+ _commonChunkLength(chunk) {
+ console.assert(chunk.ab || chunk.common);
+ console.assert(
+ !chunk.a || (chunk.b && chunk.a.length === chunk.b.length),
+ `common chunk needs same number of a and b lines: `, chunk);
+ return this._linesLeft(chunk).length;
+ },
- return result;
+ /**
+ * @param {!Array<!Object>} chunks
+ * @param {number} offsetLeft
+ * @param {number} offsetRight
+ * @return {!Array<!Object>} (GrDiffGroup)
+ */
+ _chunksToGroups(chunks, offsetLeft, offsetRight) {
+ return chunks.map(chunk => {
+ const group = this._chunkToGroup(chunk, offsetLeft, offsetRight);
+ const chunkLength = this._commonChunkLength(chunk);
+ offsetLeft += chunkLength;
+ offsetRight += chunkLength;
+ return group;
+ });
},
/**
- * Take the rows of a delta diff section and produce the corresponding
- * group.
- *
- * @param {!Array<string>} rowsAdded
- * @param {!Array<string>} rowsRemoved
- * @param {number} startLineNumLeft
- * @param {number} startLineNumRight
- * @return {!Object} (Gr-Diff-Group)
+ * @param {!Object} chunk
+ * @param {number} offsetLeft
+ * @param {number} offsetRight
+ * @return {!Object} (GrDiffGroup)
*/
- _deltaGroupFromRows(rowsAdded, rowsRemoved, startLineNumLeft,
- startLineNumRight, highlights) {
+ _chunkToGroup(chunk, offsetLeft, offsetRight) {
+ const type = chunk.ab ? GrDiffGroup.Type.BOTH : GrDiffGroup.Type.DELTA;
+ const lines = this._linesFromChunk(chunk, offsetLeft, offsetRight);
+ const group = new GrDiffGroup(type, lines);
+ group.keyLocation = chunk.keyLocation;
+ group.dueToRebase = chunk.due_to_rebase;
+ group.ignoredWhitespaceOnly = chunk.common;
+ return group;
+ },
+
+ _linesFromChunk(chunk, offsetLeft, offsetRight) {
+ if (chunk.ab) {
+ return chunk.ab.map((row, i) => this._lineFromRow(
+ GrDiffLine.Type.BOTH, offsetLeft, offsetRight, row, i));
+ }
let lines = [];
- if (rowsRemoved) {
- lines = lines.concat(this._deltaLinesFromRows(GrDiffLine.Type.REMOVE,
- rowsRemoved, startLineNumLeft, highlights.removed));
+ if (chunk.a) {
+ // Avoiding a.push(...b) because that causes callstack overflows for
+ // large b, which can occur when large files are added removed.
+ lines = lines.concat(this._linesFromRows(
+ GrDiffLine.Type.REMOVE, chunk.a, offsetLeft,
+ chunk[DiffHighlights.REMOVED]));
}
- if (rowsAdded) {
- lines = lines.concat(this._deltaLinesFromRows(GrDiffLine.Type.ADD,
- rowsAdded, startLineNumRight, highlights.added));
+ if (chunk.b) {
+ // Avoiding a.push(...b) because that causes callstack overflows for
+ // large b, which can occur when large files are added removed.
+ lines = lines.concat(this._linesFromRows(
+ GrDiffLine.Type.ADD, chunk.b, offsetRight,
+ chunk[DiffHighlights.ADDED]));
}
- return new GrDiffGroup(GrDiffGroup.Type.DELTA, lines);
+ return lines;
},
/**
- * @return {!Array<!Object>} Array of GrDiffLines
+ * @param {string} lineType (GrDiffLine.Type)
+ * @param {!Array<string>} rows
+ * @param {number} offset
+ * @param {?Array<!Gerrit.IntralineInfo>=} opt_intralineInfos
+ * @return {!Array<!Object>} (GrDiffLine)
*/
- _deltaLinesFromRows(lineType, rows, startLineNum,
- opt_highlights) {
- // Normalize highlights if they have been passed.
- if (opt_highlights) {
- opt_highlights = this._normalizeIntralineHighlights(rows,
- opt_highlights);
- }
+ _linesFromRows(lineType, rows, offset, opt_intralineInfos) {
+ const grDiffHighlights = opt_intralineInfos ?
+ this._convertIntralineInfos(rows, opt_intralineInfos) : undefined;
+ return rows.map((row, i) => this._lineFromRow(
+ lineType, offset, offset, row, i, grDiffHighlights));
+ },
- const lines = [];
- let line;
- for (let i = 0; i < rows.length; i++) {
- line = new GrDiffLine(lineType);
- line.text = rows[i];
- if (lineType === GrDiffLine.Type.ADD) {
- line.afterNumber = ++startLineNum;
- } else {
- line.beforeNumber = ++startLineNum;
- }
- if (opt_highlights) {
- line.highlights = opt_highlights.filter(hl => hl.contentIndex === i);
- }
- lines.push(line);
+ /**
+ * @param {string} type (GrDiffLine.Type)
+ * @param {number} offsetLeft
+ * @param {number} offsetRight
+ * @param {string} row
+ * @param {number} i
+ * @param {!Array<!Object>=} opt_highlights
+ * @return {!Object} (GrDiffLine)
+ */
+ _lineFromRow(type, offsetLeft, offsetRight, row, i, opt_highlights) {
+ const line = new GrDiffLine(type);
+ line.text = row;
+ if (type !== GrDiffLine.Type.ADD) line.beforeNumber = offsetLeft + i;
+ if (type !== GrDiffLine.Type.REMOVE) line.afterNumber = offsetRight + i;
+ if (opt_highlights) {
+ line.hasIntralineInfo = true;
+ line.highlights = opt_highlights.filter(hl => hl.contentIndex === i);
+ } else {
+ line.hasIntralineInfo = false;
}
- return lines;
+ return line;
},
_makeFileComments() {
@@ -402,16 +429,16 @@
* into 2 chunks, one max sized one and the rest (for reasons that are
* unclear to me).
*
- * @param {!Array<!Object>} chunks Chunks as returned from the server
- * @return {!Array<!Object>} Finer grained chunks.
+ * @param {!Array<!Gerrit.DiffChunk>} chunks Chunks as returned from the server
+ * @return {!Array<!Gerrit.DiffChunk>} Finer grained chunks.
*/
_splitLargeChunks(chunks) {
const newChunks = [];
for (const chunk of chunks) {
if (!chunk.ab) {
- for (const group of this._breakdownGroup(chunk)) {
- newChunks.push(group);
+ for (const subChunk of this._breakdownChunk(chunk)) {
+ newChunks.push(subChunk);
}
continue;
}
@@ -421,7 +448,7 @@
// enabled for any other context preference because manipulating the
// chunks in this way violates assumptions by the context grouper logic.
if (this.context === -1 && chunk.ab.length > MAX_GROUP_SIZE * 2) {
- // Split large shared groups in two, where the first is the maximum
+ // Split large shared chunks in two, where the first is the maximum
// group size.
newChunks.push({ab: chunk.ab.slice(0, MAX_GROUP_SIZE)});
newChunks.push({ab: chunk.ab.slice(MAX_GROUP_SIZE)});
@@ -433,21 +460,21 @@
},
/**
- * In order to show comments out of the bounds of the selected context,
- * treat them as separate chunks within the model so that the content (and
- * context surrounding it) renders correctly.
+ * In order to show key locations, such as comments, out of the bounds of
+ * the selected context, treat them as separate chunks within the model so
+ * that the content (and context surrounding it) renders correctly.
*
* @param {!Array<!Object>} chunks DiffContents as returned from server.
* @return {!Array<!Object>} Finer grained DiffContents.
*/
- _splitUnchangedChunksWithComments(chunks) {
+ _splitCommonChunksWithKeyLocations(chunks) {
const result = [];
- let leftLineNum = 0;
- let rightLineNum = 0;
+ let leftLineNum = 1;
+ let rightLineNum = 1;
for (const chunk of chunks) {
// If it isn't a common chunk, append it as-is and update line numbers.
- if (!chunk.ab) {
+ if (!chunk.ab && !chunk.common) {
if (chunk.a) {
leftLineNum += chunk.a.length;
}
@@ -458,87 +485,111 @@
continue;
}
- let currentChunk = {ab: []};
-
- // For each line in the common group.
- for (const line of chunk.ab) {
- leftLineNum++;
- rightLineNum++;
-
- // If this line should not be collapsed.
- if (this.keyLocations[DiffSide.LEFT][leftLineNum] ||
- this.keyLocations[DiffSide.RIGHT][rightLineNum]) {
- // If any lines have been accumulated into the chunk leading up to
- // this non-collapse line, then add them as a chunk and start a new
- // one.
- if (currentChunk.ab && currentChunk.ab.length > 0) {
- result.push(currentChunk);
- currentChunk = {ab: []};
- }
+ if (chunk.common && chunk.a.length != chunk.b.length) {
+ throw new Error(
+ 'DiffContent with common=true must always have equal length');
+ }
+ const numLines = this._commonChunkLength(chunk);
+ const chunkEnds = this._findChunkEndsAtKeyLocations(
+ numLines, leftLineNum, rightLineNum);
+ leftLineNum += numLines;
+ rightLineNum += numLines;
+
+ if (chunk.ab) {
+ result.push(...this._splitAtChunkEnds(chunk.ab, chunkEnds)
+ .map(({lines, keyLocation}) =>
+ Object.assign({}, chunk, {ab: lines, keyLocation})));
+ } else if (chunk.common) {
+ const aChunks = this._splitAtChunkEnds(chunk.a, chunkEnds);
+ const bChunks = this._splitAtChunkEnds(chunk.b, chunkEnds);
+ result.push(...aChunks.map(({lines, keyLocation}, i) =>
+ Object.assign(
+ {}, chunk, {a: lines, b: bChunks[i].lines, keyLocation})));
+ }
+ }
+
+ return result;
+ },
- // Add the non-collapse line as its own chunk.
- result.push({ab: [line]});
- } else {
- // Append the current line to the current chunk.
- currentChunk.ab.push(line);
+ /**
+ * @return {!Array<{offset: number, keyLocation: boolean}>} Offsets of the
+ * new chunk ends, including whether it's a key location.
+ */
+ _findChunkEndsAtKeyLocations(numLines, leftOffset, rightOffset) {
+ const result = [];
+ let lastChunkEnd = 0;
+ for (let i=0; i<numLines; i++) {
+ // If this line should not be collapsed.
+ if (this.keyLocations[DiffSide.LEFT][leftOffset + i] ||
+ this.keyLocations[DiffSide.RIGHT][rightOffset + i]) {
+ // If any lines have been accumulated into the chunk leading up to
+ // this non-collapse line, then add them as a chunk and start a new
+ // one.
+ if (i > lastChunkEnd) {
+ result.push({offset: i, keyLocation: false});
+ lastChunkEnd = i;
}
- }
- if (currentChunk.ab && currentChunk.ab.length > 0) {
- result.push(currentChunk);
+ // Add the non-collapse line as its own chunk.
+ result.push({offset: i + 1, keyLocation: true});
}
}
+ if (numLines > lastChunkEnd) {
+ result.push({offset: numLines, keyLocation: false});
+ }
+
+ return result;
+ },
+
+ _splitAtChunkEnds(lines, chunkEnds) {
+ const result = [];
+ let lastChunkEndOffset = 0;
+ for (const {offset, keyLocation} of chunkEnds) {
+ result.push(
+ {lines: lines.slice(lastChunkEndOffset, offset), keyLocation});
+ lastChunkEndOffset = offset;
+ }
return result;
},
/**
- * The `highlights` array consists of a list of <skip length, mark length>
- * pairs, where the skip length is the number of characters between the
- * end of the previous edit and the start of this edit, and the mark
- * length is the number of edited characters following the skip. The start
- * of the edits is from the beginning of the related diff content lines.
+ * Converts `IntralineInfo`s return by the API to `GrLineHighlights` used
+ * for rendering.
*
- * Note that the implied newline character at the end of each line is
- * included in the length calculation, and thus it is possible for the
- * edits to span newlines.
- *
- * A line highlight object consists of three fields:
- * - contentIndex: The index of the diffChunk `content` field (the line
- * being referred to).
- * - startIndex: Where the highlight should begin.
- * - endIndex: (optional) Where the highlight should end. If omitted, the
- * highlight is meant to be a continuation onto the next line.
+ * @param {!Array<string>} rows
+ * @param {!Array<!Gerrit.IntralineInfo>} intralineInfos
+ * @return {!Array<!Object>} (GrDiffLine.Highlight)
*/
- _normalizeIntralineHighlights(content, highlights) {
- let contentIndex = 0;
+ _convertIntralineInfos(rows, intralineInfos) {
+ let rowIndex = 0;
let idx = 0;
const normalized = [];
- for (const hl of highlights) {
- let line = content[contentIndex] + '\n';
+ for (const [skipLength, markLength] of intralineInfos) {
+ let line = rows[rowIndex] + '\n';
let j = 0;
- while (j < hl[0]) {
+ while (j < skipLength) {
if (idx === line.length) {
idx = 0;
- line = content[++contentIndex] + '\n';
+ line = rows[++rowIndex] + '\n';
continue;
}
idx++;
j++;
}
let lineHighlight = {
- contentIndex,
+ contentIndex: rowIndex,
startIndex: idx,
};
j = 0;
- while (line && j < hl[1]) {
+ while (line && j < markLength) {
if (idx === line.length) {
idx = 0;
- line = content[++contentIndex] + '\n';
+ line = rows[++rowIndex] + '\n';
normalized.push(lineHighlight);
lineHighlight = {
- contentIndex,
+ contentIndex: rowIndex,
startIndex: idx,
};
continue;
@@ -554,32 +605,32 @@
/**
* If a group is an addition or a removal, break it down into smaller groups
- * of that type using the MAX_GROUP_SIZE. If the group is a shared section
+ * of that type using the MAX_GROUP_SIZE. If the group is a shared chunk
* or a delta it is returned as the single element of the result array.
*
- * @param {!Object} group A raw chunk from a diff response.
+ * @param {!Gerrit.DiffChunk} chunk A raw chunk from a diff response.
* @return {!Array<!Array<!Object>>}
*/
- _breakdownGroup(group) {
+ _breakdownChunk(chunk) {
let key = null;
- if (group.a && !group.b) {
+ if (chunk.a && !chunk.b) {
key = 'a';
- } else if (group.b && !group.a) {
+ } else if (chunk.b && !chunk.a) {
key = 'b';
- } else if (group.ab) {
+ } else if (chunk.ab) {
key = 'ab';
}
- if (!key) { return [group]; }
+ if (!key) { return [chunk]; }
- return this._breakdown(group[key], MAX_GROUP_SIZE)
- .map(subgroupLines => {
- const subGroup = {};
- subGroup[key] = subgroupLines;
- if (group.due_to_rebase) {
- subGroup.due_to_rebase = true;
+ return this._breakdown(chunk[key], MAX_GROUP_SIZE)
+ .map(subChunkLines => {
+ const subChunk = {};
+ subChunk[key] = subChunkLines;
+ if (chunk.due_to_rebase) {
+ subChunk.due_to_rebase = true;
}
- return subGroup;
+ return subChunk;
});
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
index 186a49e6a7..c04b066689 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-processor test</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-diff-processor.html">
@@ -60,7 +62,7 @@ limitations under the License.
element.context = 4;
});
- test('process loaded content', done => {
+ test('process loaded content', () => {
const content = [
{
ab: [
@@ -86,7 +88,7 @@ limitations under the License.
},
];
- element.process(content).then(() => {
+ return element.process(content).then(() => {
const groups = element.groups;
assert.equal(groups.length, 4);
@@ -139,31 +141,15 @@ limitations under the License.
'everyone pretend to shower.',
'Fry: Same as every day. Got it.',
]);
-
- done();
});
});
- test('insert context groups', done => {
+ test('first group is for file', () => {
const content = [
- {ab: []},
- {a: ['all work and no play make andybons a dull boy']},
- {ab: []},
- {b: ['elgoog elgoog elgoog']},
- {ab: []},
+ {b: ['foo']},
];
- for (let i = 0; i < 100; i++) {
- content[0].ab.push('all work and no play make jack a dull boy');
- content[4].ab.push('all work and no play make jill a dull girl');
- }
- for (let i = 0; i < 5; i++) {
- content[2].ab.push('no tv and no beer make homer go crazy');
- }
-
- const context = 10;
- element.context = context;
-
- element.process(content).then(() => {
+
+ return element.process(content).then(() => {
const groups = element.groups;
assert.equal(groups[0].type, GrDiffGroup.Type.BOTH);
@@ -171,107 +157,278 @@ limitations under the License.
assert.equal(groups[0].lines[0].text, '');
assert.equal(groups[0].lines[0].beforeNumber, GrDiffLine.FILE);
assert.equal(groups[0].lines[0].afterNumber, GrDiffLine.FILE);
-
- assert.equal(groups[1].type, GrDiffGroup.Type.CONTEXT_CONTROL);
- assert.instanceOf(groups[1].lines[0].contextGroup, GrDiffGroup);
- assert.equal(groups[1].lines[0].contextGroup.lines.length, 90);
- for (const l of groups[1].lines[0].contextGroup.lines) {
- assert.equal(l.text, content[0].ab[0]);
- }
-
- assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[2].lines.length, context);
- for (const l of groups[2].lines) {
- assert.equal(l.text, content[0].ab[0]);
- }
-
- assert.equal(groups[3].type, GrDiffGroup.Type.DELTA);
- assert.equal(groups[3].lines.length, 1);
- assert.equal(groups[3].removes.length, 1);
- assert.equal(groups[3].removes[0].text,
- 'all work and no play make andybons a dull boy');
-
- assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[4].lines.length, 5);
- for (const l of groups[4].lines) {
- assert.equal(l.text, content[2].ab[0]);
- }
-
- assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
- assert.equal(groups[5].lines.length, 1);
- assert.equal(groups[5].adds.length, 1);
- assert.equal(groups[5].adds[0].text, 'elgoog elgoog elgoog');
-
- assert.equal(groups[6].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[6].lines.length, context);
- for (const l of groups[6].lines) {
- assert.equal(l.text, content[4].ab[0]);
- }
-
- assert.equal(groups[7].type, GrDiffGroup.Type.CONTEXT_CONTROL);
- assert.instanceOf(groups[7].lines[0].contextGroup, GrDiffGroup);
- assert.equal(groups[7].lines[0].contextGroup.lines.length, 90);
- for (const l of groups[7].lines[0].contextGroup.lines) {
- assert.equal(l.text, content[4].ab[0]);
- }
-
- done();
});
});
- test('insert context groups', done => {
- const content = [
- {a: ['all work and no play make andybons a dull boy']},
- {ab: []},
- {b: ['elgoog elgoog elgoog']},
- ];
- for (let i = 0; i < 50; i++) {
- content[1].ab.push('no tv and no beer make homer go crazy');
- }
-
- const context = 10;
- element.context = context;
-
- element.process(content).then(() => {
- const groups = element.groups;
-
- assert.equal(groups[0].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[0].lines.length, 1);
- assert.equal(groups[0].lines[0].text, '');
- assert.equal(groups[0].lines[0].beforeNumber, GrDiffLine.FILE);
- assert.equal(groups[0].lines[0].afterNumber, GrDiffLine.FILE);
+ suite('context groups', () => {
+ test('at the beginning, larger than context', () => {
+ element.context = 10;
+ const content = [
+ {ab: new Array(100)
+ .fill('all work and no play make jack a dull boy')},
+ {a: ['all work and no play make andybons a dull boy']},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+
+ assert.equal(groups[1].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[1].lines[0].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[1].lines[0].contextGroups[0].lines.length, 90);
+ for (const l of groups[1].lines[0].contextGroups[0].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 10);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
+ });
+ });
- assert.equal(groups[1].type, GrDiffGroup.Type.DELTA);
- assert.equal(groups[1].lines.length, 1);
- assert.equal(groups[1].removes.length, 1);
- assert.equal(groups[1].removes[0].text,
- 'all work and no play make andybons a dull boy');
+ test('at the beginning, smaller than context', () => {
+ element.context = 10;
+ const content = [
+ {ab: new Array(5)
+ .fill('all work and no play make jack a dull boy')},
+ {a: ['all work and no play make andybons a dull boy']},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+
+ assert.equal(groups[1].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[1].lines.length, 5);
+ for (const l of groups[1].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
+ });
+ });
- assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[2].lines.length, context);
- for (const l of groups[2].lines) {
- assert.equal(l.text, content[1].ab[0]);
- }
+ test('at the end, larger than context', () => {
+ element.context = 10;
+ const content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: new Array(100)
+ .fill('all work and no play make jill a dull girl')},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+ // group[1] is the "a" group
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 10);
+ for (const l of groups[2].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[3].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[3].lines[0].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[3].lines[0].contextGroups[0].lines.length, 90);
+ for (const l of groups[3].lines[0].contextGroups[0].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ });
+ });
- assert.equal(groups[3].type, GrDiffGroup.Type.CONTEXT_CONTROL);
- assert.instanceOf(groups[3].lines[0].contextGroup, GrDiffGroup);
- assert.equal(groups[3].lines[0].contextGroup.lines.length, 30);
- for (const l of groups[3].lines[0].contextGroup.lines) {
- assert.equal(l.text, content[1].ab[0]);
- }
+ test('at the end, smaller than context', () => {
+ element.context = 10;
+ const content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: new Array(5)
+ .fill('all work and no play make jill a dull girl')},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+ // group[1] is the "a" group
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 5);
+ for (const l of groups[2].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ });
+ });
- assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
- assert.equal(groups[4].lines.length, context);
- for (const l of groups[4].lines) {
- assert.equal(l.text, content[1].ab[0]);
- }
+ test('for interleaved ab and common: true chunks', () => {
+ element.context = 10;
+ const content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: new Array(3)
+ .fill('all work and no play make jill a dull girl')},
+ {
+ a: new Array(3).fill(
+ 'all work and no play make jill a dull girl'),
+ b: new Array(3).fill(
+ ' all work and no play make jill a dull girl'),
+ common: true,
+ },
+ {ab: new Array(3)
+ .fill('all work and no play make jill a dull girl')},
+ {
+ a: new Array(3).fill(
+ 'all work and no play make jill a dull girl'),
+ b: new Array(3).fill(
+ ' all work and no play make jill a dull girl'),
+ common: true,
+ },
+ {ab: new Array(3)
+ .fill('all work and no play make jill a dull girl')},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+ // group[1] is the "a" group
+
+ // The first three interleaved chunks are completely shown because
+ // they are part of the context (3 * 3 <= 10)
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 3);
+ for (const l of groups[2].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[3].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[3].lines.length, 6);
+ assert.equal(groups[3].adds.length, 3);
+ assert.equal(groups[3].removes.length, 3);
+ for (const l of groups[3].removes) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[3].adds) {
+ assert.equal(
+ l.text, ' all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[4].lines.length, 3);
+ for (const l of groups[4].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+
+ // The next chunk is partially shown, so it results in two groups
+
+ assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
+ assert.equal(groups[5].lines.length, 2);
+ assert.equal(groups[5].adds.length, 1);
+ assert.equal(groups[5].removes.length, 1);
+ for (const l of groups[5].removes) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[5].adds) {
+ assert.equal(
+ l.text, ' all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[6].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.equal(groups[6].lines[0].contextGroups.length, 2);
+
+ assert.equal(groups[6].lines[0].contextGroups[0].lines.length, 4);
+ assert.equal(groups[6].lines[0].contextGroups[0].removes.length, 2);
+ assert.equal(groups[6].lines[0].contextGroups[0].adds.length, 2);
+ for (const l of groups[6].lines[0].contextGroups[0].removes) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[6].lines[0].contextGroups[0].adds) {
+ assert.equal(
+ l.text, ' all work and no play make jill a dull girl');
+ }
+
+ // The final chunk is completely hidden
+ assert.equal(
+ groups[6].lines[0].contextGroups[1].type,
+ GrDiffGroup.Type.BOTH);
+ assert.equal(groups[6].lines[0].contextGroups[1].lines.length, 3);
+ for (const l of groups[6].lines[0].contextGroups[1].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ });
+ });
- assert.equal(groups[5].type, GrDiffGroup.Type.DELTA);
- assert.equal(groups[5].lines.length, 1);
- assert.equal(groups[5].adds.length, 1);
- assert.equal(groups[5].adds[0].text, 'elgoog elgoog elgoog');
+ test('in the middle, larger than context', () => {
+ element.context = 10;
+ const content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: new Array(100)
+ .fill('all work and no play make jill a dull girl')},
+ {a: ['all work and no play make andybons a dull boy']},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+ // group[1] is the "a" group
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 10);
+ for (const l of groups[2].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[3].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.instanceOf(groups[3].lines[0].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[3].lines[0].contextGroups[0].lines.length, 80);
+ for (const l of groups[3].lines[0].contextGroups[0].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+
+ assert.equal(groups[4].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[4].lines.length, 10);
+ for (const l of groups[4].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ });
+ });
- done();
+ test('in the middle, smaller than context', () => {
+ element.context = 10;
+ const content = [
+ {a: ['all work and no play make andybons a dull boy']},
+ {ab: new Array(5)
+ .fill('all work and no play make jill a dull girl')},
+ {a: ['all work and no play make andybons a dull boy']},
+ ];
+
+ return element.process(content).then(() => {
+ const groups = element.groups;
+
+ // group[0] is the file group
+ // group[1] is the "a" group
+
+ assert.equal(groups[2].type, GrDiffGroup.Type.BOTH);
+ assert.equal(groups[2].lines.length, 5);
+ for (const l of groups[2].lines) {
+ assert.equal(
+ l.text, 'all work and no play make jill a dull girl');
+ }
+ });
});
});
@@ -303,10 +460,11 @@ limitations under the License.
},
];
const result =
- element._splitUnchangedChunksWithComments(content);
+ element._splitCommonChunksWithKeyLocations(content);
assert.deepEqual(result, [
{
ab: ['Copyright (C) 2015 The Android Open Source Project'],
+ keyLocation: true,
},
{
ab: [
@@ -320,10 +478,12 @@ limitations under the License.
'',
'Unless required by applicable law or agreed to in writing, ',
],
+ keyLocation: false,
},
{
ab: [
'software distributed under the License is distributed on an '],
+ keyLocation: true,
},
{
ab: [
@@ -332,6 +492,7 @@ limitations under the License.
'language governing permissions and limitations under the ' +
'License.',
],
+ keyLocation: false,
},
]);
});
@@ -348,14 +509,16 @@ limitations under the License.
assert.deepEqual(result[1].ab, content[0].ab.slice(120));
});
- test('does not break-down shared chunks w/ context', () => {
+ test('does not break-down common chunks w/ context', () => {
const content = [{
ab: _.times(75, () => { return `${Math.random()}`; }),
}];
element.context = 4;
const result =
- element._splitUnchangedChunksWithComments(content);
- assert.deepEqual(result, content);
+ element._splitCommonChunksWithKeyLocations(content);
+ assert.equal(result.length, 1);
+ assert.deepEqual(result[0].ab, content[0].ab);
+ assert.isFalse(result[0].keyLocation);
});
test('intraline normalization', () => {
@@ -371,7 +534,7 @@ limitations under the License.
[31, 34], [42, 26],
];
- let results = element._normalizeIntralineHighlights(content,
+ let results = element._convertIntralineInfos(content,
highlights);
assert.deepEqual(results, [
{
@@ -412,7 +575,7 @@ limitations under the License.
[12, 67],
[14, 29],
];
- results = element._normalizeIntralineHighlights(content, highlights);
+ results = element._convertIntralineInfos(content, highlights);
assert.deepEqual(results, [
{
contentIndex: 0,
@@ -453,10 +616,13 @@ limitations under the License.
sandbox.stub(element, 'async');
element._isScrolling = true;
element.process(content);
+ // Just the files group - no more processing during scrolling.
assert.equal(element.groups.length, 1);
+
element._isScrolling = false;
element.process(content);
- assert.equal(element.groups.length, 33);
+ // More groups have been processed. How many does not matter here.
+ assert.isAtLeast(element.groups.length, 2);
});
test('image diffs', () => {
@@ -475,98 +641,210 @@ limitations under the License.
assert.equal(element.groups[0].lines.length, 1);
});
-
- suite('gr-diff-processor helpers', () => {
+ suite('_processNext', () => {
let rows;
setup(() => {
rows = loremIpsum.split(' ');
});
- test('_sharedGroupsFromRows WHOLE_FILE', () => {
- const context = WHOLE_FILE;
- const lineNumbers = {left: 10, right: 100};
- const result = element._sharedGroupsFromRows(
- rows, context, lineNumbers.left, lineNumbers.right, null);
+ test('WHOLE_FILE', () => {
+ element.context = WHOLE_FILE;
+ const state = {
+ lineNums: {left: 10, right: 100},
+ chunkIndex: 1,
+ };
+ const chunks = [
+ {a: ['foo']},
+ {ab: rows},
+ {a: ['bar']},
+ ];
+ const result = element._processNext(state, chunks);
// Results in one, uncollapsed group with all rows.
- assert.equal(result.length, 1);
- assert.equal(result[0].type, GrDiffGroup.Type.BOTH);
- assert.equal(result[0].lines.length, rows.length);
+ assert.equal(result.groups.length, 1);
+ assert.equal(result.groups[0].type, GrDiffGroup.Type.BOTH);
+ assert.equal(result.groups[0].lines.length, rows.length);
// Line numbers are set correctly.
- assert.equal(result[0].lines[0].beforeNumber, lineNumbers.left + 1);
- assert.equal(result[0].lines[0].afterNumber, lineNumbers.right + 1);
-
- assert.equal(result[0].lines[rows.length - 1].beforeNumber,
- lineNumbers.left + rows.length);
- assert.equal(result[0].lines[rows.length - 1].afterNumber,
- lineNumbers.right + rows.length);
+ assert.equal(
+ result.groups[0].lines[0].beforeNumber,
+ state.lineNums.left + 1);
+ assert.equal(
+ result.groups[0].lines[0].afterNumber,
+ state.lineNums.right + 1);
+
+ assert.equal(result.groups[0].lines[rows.length - 1].beforeNumber,
+ state.lineNums.left + rows.length);
+ assert.equal(result.groups[0].lines[rows.length - 1].afterNumber,
+ state.lineNums.right + rows.length);
});
- test('_sharedGroupsFromRows context', () => {
- const context = 10;
- const result = element._sharedGroupsFromRows(
- rows, context, 10, 100, null);
- const expectedCollapseSize = rows.length - 2 * context;
-
- assert.equal(result.length, 3, 'Results in three groups');
+ test('with context', () => {
+ element.context = 10;
+ const state = {
+ lineNums: {left: 10, right: 100},
+ chunkIndex: 1,
+ };
+ const chunks = [
+ {a: ['foo']},
+ {ab: rows},
+ {a: ['bar']},
+ ];
+ const result = element._processNext(state, chunks);
+ const expectedCollapseSize = rows.length - 2 * element.context;
+
+ assert.equal(result.groups.length, 3, 'Results in three groups');
// The first and last are uncollapsed context, whereas the middle has
// a single context-control line.
- assert.equal(result[0].lines.length, context);
- assert.equal(result[1].lines.length, 1);
- assert.equal(result[2].lines.length, context);
+ assert.equal(result.groups[0].lines.length, element.context);
+ assert.equal(result.groups[1].lines.length, 1);
+ assert.equal(result.groups[2].lines.length, element.context);
// The collapsed group has the hidden lines as its context group.
- assert.equal(result[1].lines[0].contextGroup.lines.length,
+ assert.equal(result.groups[1].lines[0].contextGroups[0].lines.length,
expectedCollapseSize);
});
- test('_sharedGroupsFromRows first', () => {
- const context = 10;
- const result = element._sharedGroupsFromRows(
- rows, context, 10, 100, 'first');
- const expectedCollapseSize = rows.length - context;
-
- assert.equal(result.length, 2, 'Results in two groups');
+ test('first', () => {
+ element.context = 10;
+ const state = {
+ lineNums: {left: 10, right: 100},
+ chunkIndex: 0,
+ };
+ const chunks = [
+ {ab: rows},
+ {a: ['foo']},
+ {a: ['bar']},
+ ];
+ const result = element._processNext(state, chunks);
+ const expectedCollapseSize = rows.length - element.context;
+
+ assert.equal(result.groups.length, 2, 'Results in two groups');
// Only the first group is collapsed.
- assert.equal(result[0].lines.length, 1);
- assert.equal(result[1].lines.length, context);
+ assert.equal(result.groups[0].lines.length, 1);
+ assert.equal(result.groups[1].lines.length, element.context);
// The collapsed group has the hidden lines as its context group.
- assert.equal(result[0].lines[0].contextGroup.lines.length,
+ assert.equal(result.groups[0].lines[0].contextGroups[0].lines.length,
expectedCollapseSize);
});
- test('_sharedGroupsFromRows few-rows', () => {
+ test('few-rows', () => {
// Only ten rows.
rows = rows.slice(0, 10);
- const context = 10;
- const result = element._sharedGroupsFromRows(
- rows, context, 10, 100, 'first');
+ element.context = 10;
+ const state = {
+ lineNums: {left: 10, right: 100},
+ chunkIndex: 0,
+ };
+ const chunks = [
+ {ab: rows},
+ {a: ['foo']},
+ {a: ['bar']},
+ ];
+ const result = element._processNext(state, chunks);
// Results in one uncollapsed group with all rows.
- assert.equal(result.length, 1, 'Results in one group');
- assert.equal(result[0].lines.length, rows.length);
+ assert.equal(result.groups.length, 1, 'Results in one group');
+ assert.equal(result.groups[0].lines.length, rows.length);
});
- test('_sharedGroupsFromRows no single line collapse', () => {
+ test('no single line collapse', () => {
rows = rows.slice(0, 7);
- const context = 3;
- const result = element._sharedGroupsFromRows(
- rows, context, 10, 100);
+ element.context = 3;
+ const state = {
+ lineNums: {left: 10, right: 100},
+ chunkIndex: 1,
+ };
+ const chunks = [
+ {a: ['foo']},
+ {ab: rows},
+ {a: ['bar']},
+ ];
+ const result = element._processNext(state, chunks);
// Results in one uncollapsed group with all rows.
- assert.equal(result.length, 1, 'Results in one group');
- assert.equal(result[0].lines.length, rows.length);
+ assert.equal(result.groups.length, 1, 'Results in one group');
+ assert.equal(result.groups[0].lines.length, rows.length);
+ });
+
+ suite('with key location', () => {
+ let state;
+ let chunks;
+
+ setup(() => {
+ state = {
+ lineNums: {left: 10, right: 100},
+ };
+ element.context = 10;
+ chunks = [
+ {ab: rows},
+ {ab: ['foo'], keyLocation: true},
+ {ab: rows},
+ ];
+ });
+
+ test('context before', () => {
+ state.chunkIndex = 0;
+ const result = element._processNext(state, chunks);
+
+ // The first chunk is split into two groups:
+ // 1) A context-control, hiding everything but the context before
+ // the key location.
+ // 2) The context before the key location.
+ // The key location is not processed in this call to _processNext
+ assert.equal(result.groups.length, 2);
+ assert.equal(result.groups[0].lines.length, 1);
+ // The collapsed group has the hidden lines as its context group.
+ assert.equal(result.groups[0].lines[0].contextGroups[0].lines.length,
+ rows.length - element.context);
+ assert.equal(result.groups[1].lines.length, element.context);
+ });
+
+ test('key location itself', () => {
+ state.chunkIndex = 1;
+ const result = element._processNext(state, chunks);
+
+ // The second chunk results in a single group, that is just the
+ // line with the key location
+ assert.equal(result.groups.length, 1);
+ assert.equal(result.groups[0].lines.length, 1);
+ assert.equal(result.lineDelta.left, 1);
+ assert.equal(result.lineDelta.right, 1);
+ });
+
+ test('context after', () => {
+ state.chunkIndex = 2;
+ const result = element._processNext(state, chunks);
+
+ // The last chunk is split into two groups:
+ // 1) The context after the key location.
+ // 1) A context-control, hiding everything but the context after the
+ // key location.
+ assert.equal(result.groups.length, 2);
+ assert.equal(result.groups[0].lines.length, element.context);
+ assert.equal(result.groups[1].lines.length, 1);
+ // The collapsed group has the hidden lines as its context group.
+ assert.equal(result.groups[1].lines[0].contextGroups[0].lines.length,
+ rows.length - element.context);
+ });
+ });
+ });
+
+ suite('gr-diff-processor helpers', () => {
+ let rows;
+
+ setup(() => {
+ rows = loremIpsum.split(' ');
});
- test('_deltaLinesFromRows', () => {
+ test('_linesFromRows', () => {
const startLineNum = 10;
- let result = element._deltaLinesFromRows(GrDiffLine.Type.ADD, rows,
- startLineNum);
+ let result = element._linesFromRows(GrDiffLine.Type.ADD, rows,
+ startLineNum + 1);
assert.equal(result.length, rows.length);
assert.equal(result[0].type, GrDiffLine.Type.ADD);
@@ -576,8 +854,8 @@ limitations under the License.
startLineNum + rows.length);
assert.notOk(result[result.length - 1].beforeNumber);
- result = element._deltaLinesFromRows(GrDiffLine.Type.REMOVE, rows,
- startLineNum);
+ result = element._linesFromRows(GrDiffLine.Type.REMOVE, rows,
+ startLineNum + 1);
assert.equal(result.length, rows.length);
assert.equal(result[0].type, GrDiffLine.Type.REMOVE);
@@ -590,19 +868,19 @@ limitations under the License.
});
suite('_breakdown*', () => {
- test('_breakdownGroup breaks down additions', () => {
+ test('_breakdownChunk breaks down additions', () => {
sandbox.spy(element, '_breakdown');
const chunk = {b: ['blah', 'blah', 'blah']};
- const result = element._breakdownGroup(chunk);
+ const result = element._breakdownChunk(chunk);
assert.deepEqual(result, [chunk]);
assert.isTrue(element._breakdown.called);
});
- test('_breakdownGroup keeps due_to_rebase for broken down additions',
+ test('_breakdownChunk keeps due_to_rebase for broken down additions',
() => {
sandbox.spy(element, '_breakdown');
const chunk = {b: ['blah', 'blah', 'blah'], due_to_rebase: true};
- const result = element._breakdownGroup(chunk);
+ const result = element._breakdownChunk(chunk);
for (const subResult of result) {
assert.isTrue(subResult.due_to_rebase);
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
index f9822f23ef..cfa46a0240 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
@@ -14,36 +14,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/dom-util-behavior/dom-util-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<script src="../../../scripts/util.js"></script>
<dom-module id="gr-diff-selection">
<template>
- <style include="shared-styles">
- .contentWrapper ::content .content,
- .contentWrapper ::content .contextControl,
- .contentWrapper ::content .blame {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
-
- :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .side-by-side .left + .content .contentText,
- :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .side-by-side .right + .content .contentText,
- :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both) .contentText,
- :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .unified .right.lineNum ~ .content .contentText,
- :host-context(.selected-left.selected-comment) .contentWrapper ::content .side-by-side .left + .content .message,
- :host-context(.selected-right.selected-comment) .contentWrapper ::content .side-by-side .right + .content .message :not(.collapsedContent),
- :host-context(.selected-comment) .contentWrapper ::content .unified .message :not(.collapsedContent),
- :host-context(.selected-blame) .contentWrapper ::content .blame {
- -webkit-user-select: text;
- -moz-user-select: text;
- -ms-user-select: text;
- user-select: text;
- }
- </style>
<div class="contentWrapper">
<slot></slot>
</div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 3484693d9b..5caac74053 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -32,7 +32,6 @@
Polymer({
is: 'gr-diff-selection',
- _legacyUndefinedCheck: true,
properties: {
diff: Object,
@@ -73,7 +72,26 @@
this._linesCache = getNewCache();
},
+ _handleDownOnRangeComment(node) {
+ if (node &&
+ node.nodeName &&
+ node.nodeName.toLowerCase() === 'gr-comment-thread') {
+ this._setClasses([
+ SelectionClass.COMMENT,
+ node.commentSide === 'left' ?
+ SelectionClass.LEFT :
+ SelectionClass.RIGHT,
+ ]);
+ return true;
+ }
+ return false;
+ },
+
_handleDown(e) {
+ // Handle the down event on comment thread in Polymer 2
+ const handled = this._handleDownOnRangeComment(e.target);
+ if (handled) return;
+
const lineEl = this.diffBuilder.getLineElByChild(e.target);
const blameSelected = this._elementDescendedFromClass(e.target, 'blame');
if (!lineEl && !blameSelected) { return; }
@@ -161,7 +179,18 @@
},
/**
- * Get the text of the current window selection. If commentSelected is
+ * For Polymer 2, use shadowRoot.getSelection instead.
+ */
+ _getSelection() {
+ const diffHost = util.querySelector(document.body, 'gr-diff');
+ const selection = diffHost &&
+ diffHost.shadowRoot &&
+ diffHost.shadowRoot.getSelection();
+ return selection ? selection: window.getSelection();
+ },
+
+ /**
+ * Get the text of the current selection. If commentSelected is
* true, it returns only the text of comments within the selection.
* Otherwise it returns the text of the selected diff region.
*
@@ -170,7 +199,7 @@
* @return {string} The selected text.
*/
_getSelectedText(side, commentSelected) {
- const sel = window.getSelection();
+ const sel = this._getSelection();
if (sel.rangeCount != 1) {
return ''; // No multi-select support yet.
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
index 469a894ab5..0f5c6dde1a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-selection</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-diff-selection.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 5ebe73825f..17b8b4c5cf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -15,12 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
@@ -70,7 +72,7 @@ limitations under the License.
justify-content: space-between;
}
header {
- padding: .75em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
.patchRangeLeft {
align-items: center;
@@ -85,11 +87,11 @@ limitations under the License.
white-space: nowrap;
}
.navLink {
- padding: 0 .25em;
+ padding: 0 var(--spacing-xs);
}
.reviewed {
display: inline-block;
- margin: 0 .25em;
+ margin: 0 var(--spacing-xs);
vertical-align: .15em;
}
.jumpToFileContainer {
@@ -99,22 +101,19 @@ limitations under the License.
display: none;
}
gr-button {
- padding: .3em 0;
+ padding: var(--spacing-s) 0;
text-decoration: none;
}
.loading {
color: var(--deemphasized-text-color);
- font-size: 2rem;
+ font-size: var(--font-size-h1);
height: 100%;
- padding: 1em var(--default-horizontal-margin);
+ padding: var(--spacing-l);
text-align: center;
}
.subHeader {
flex-wrap: wrap;
- margin: 0 var(--default-horizontal-margin) .75em;
- }
- .subHeader > div {
- margin-top: .25em;
+ padding: 0 var(--spacing-l) var(--spacing-s);
}
.prefsButton {
text-align: right;
@@ -145,7 +144,7 @@ limitations under the License.
}
.diffModeSelector span,
.editButton span {
- margin-right: .2rem;
+ margin-right: var(--spacing-xs);
}
.diffModeSelector.hide,
.separator.hide {
@@ -161,7 +160,7 @@ limitations under the License.
}
@media screen and (max-width: 50em) {
header {
- padding: .5em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
.dash {
display: none;
@@ -172,23 +171,23 @@ limitations under the License.
.fileNav {
align-items: flex-start;
display: flex;
- margin: 0 .25em;
+ margin: 0 var(--spacing-xs);
}
.fullFileName {
display: block;
font-style: italic;
min-width: 50%;
- padding: 0 .1em;
+ padding: 0 var(--spacing-xxs);
text-align: center;
width: 100%;
word-wrap: break-word;
}
.reviewed {
- vertical-align: -.1em;
+ vertical-align: -1px;
}
.mobileNavLink {
color: var(--primary-text-color);
- font-size: 1.5rem;
+ font-size: var(--font-size-h2);
font-weight: var(--font-weight-bold);
text-decoration: none;
}
@@ -287,7 +286,8 @@ limitations under the License.
<gr-button
link
disabled="[[_isBlameLoading]]"
- on-tap="_toggleBlame">[[_computeBlameToggleLabel(_isBlameLoaded, _isBlameLoading)]]</gr-button>
+ on-click="_toggleBlame">[[_computeBlameToggleLabel(_isBlameLoaded, _isBlameLoading)]]</gr-button>
+ <span class="separator"></span>
</span>
<template is="dom-if" if="[[_computeCanEdit(_loggedIn, _change.*)]]">
<span class="separator"></span>
@@ -314,13 +314,15 @@ limitations under the License.
class="prefsButton"
has-tooltip
title="Diff preferences"
- on-tap="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
+ on-click="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
</span>
</span>
<gr-endpoint-decorator name="annotation-toggler">
<span hidden id="annotation-span">
<label for="annotation-checkbox" id="annotation-label"></label>
- <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+ <iron-input type="checkbox" disabled>
+ <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+ </iron-input>
</span>
</gr-endpoint-decorator>
</div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index d73042e5a9..bd0486feca 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -35,7 +35,6 @@
Polymer({
is: 'gr-diff-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -102,14 +101,24 @@
// element for selected a file to view.
_formattedFiles: {
type: Array,
- computed: '_formatFilesForDropdown(_fileList, _patchRange.patchNum, ' +
+ computed: '_formatFilesForDropdown(_files, _patchRange.patchNum, ' +
'_changeComments)',
},
// An sorted array of files, as returned by the rest API.
_fileList: {
type: Array,
- value() { return []; },
+ computed: '_getSortedFileList(_files)',
},
+ /**
+ * Contains information about files as returned by the rest API.
+ *
+ * @type {{ sortedFileList: Array<string>, changeFilesByPath: Object }}
+ */
+ _files: {
+ type: Object,
+ value() { return {sortedFileList: [], changeFilesByPath: {}}; },
+ },
+
_path: {
type: String,
observer: '_pathChanged',
@@ -181,6 +190,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.PathListBehavior,
@@ -189,7 +199,7 @@
observers: [
'_getProjectConfig(_change.project)',
- '_getFiles(_changeNum, _patchRange.*)',
+ '_getFiles(_changeNum, _patchRange.*, _changeComments)',
'_setReviewedObserver(_loggedIn, params.*, _prefs)',
],
@@ -262,11 +272,31 @@
return this.$.restAPI.getChangeEdit(this._changeNum);
},
- _getFiles(changeNum, patchRangeRecord) {
+ _getSortedFileList(files) {
+ return files.sortedFileList;
+ },
+
+ _getFiles(changeNum, patchRangeRecord, changeComments) {
+ // Polymer 2: check for undefined
+ if ([changeNum, patchRangeRecord, patchRangeRecord.base, changeComments]
+ .some(arg => arg === undefined)) {
+ return Promise.resolve();
+ }
+
const patchRange = patchRangeRecord.base;
- return this.$.restAPI.getChangeFilePathsAsSpeciallySortedArray(
- changeNum, patchRange).then(files => {
- this._fileList = files;
+ return this.$.restAPI.getChangeFiles(
+ changeNum, patchRange).then(changeFiles => {
+ if (!changeFiles) return;
+ const commentedPaths = changeComments.getPaths(patchRange);
+ const files = Object.assign({}, changeFiles);
+ Object.keys(commentedPaths).forEach(commentedPath => {
+ if (files.hasOwnProperty(commentedPath)) { return; }
+ files[commentedPath] = {status: 'U'};
+ });
+ this._files = {
+ sortedFileList: Object.keys(files).sort(this.specialFilePathCompare),
+ changeFilesByPath: files,
+ };
});
},
@@ -562,7 +592,7 @@
* @return {?Object}
*/
_getNavLinkPath(path, fileList, direction, opt_noUp) {
- if (!path || fileList.length === 0) { return null; }
+ if (!path || !fileList || fileList.length === 0) { return null; }
let idx = fileList.indexOf(path);
if (idx === -1) {
@@ -608,11 +638,11 @@
this._initCursor(this.params);
this._changeNum = value.changeNum;
+ this._path = value.path;
this._patchRange = {
patchNum: value.patchNum,
basePatchNum: value.basePatchNum || PARENT,
};
- this._path = value.path;
// NOTE: This may be called before attachment (e.g. while parentElement is
// null). Fire title-change in an async so that, if attachment to the DOM
@@ -640,22 +670,24 @@
promises.push(this._getChangeDetail(this._changeNum).then(change => {
let commit;
let baseCommit;
- for (const commitSha in change.revisions) {
- if (!change.revisions.hasOwnProperty(commitSha)) continue;
- const revision = change.revisions[commitSha];
- const patchNum = revision._number.toString();
- if (patchNum === this._patchRange.patchNum) {
- commit = commitSha;
- const commitObj = revision.commit || {};
- const parents = commitObj.parents || [];
- if (this._patchRange.basePatchNum === PARENT && parents.length) {
- baseCommit = parents[parents.length - 1].commit;
+ if (change) {
+ for (const commitSha in change.revisions) {
+ if (!change.revisions.hasOwnProperty(commitSha)) continue;
+ const revision = change.revisions[commitSha];
+ const patchNum = revision._number.toString();
+ if (patchNum === this._patchRange.patchNum) {
+ commit = commitSha;
+ const commitObj = revision.commit || {};
+ const parents = commitObj.parents || [];
+ if (this._patchRange.basePatchNum === PARENT && parents.length) {
+ baseCommit = parents[parents.length - 1].commit;
+ }
+ } else if (patchNum === this._patchRange.basePatchNum) {
+ baseCommit = commitSha;
}
- } else if (patchNum === this._patchRange.basePatchNum) {
- baseCommit = commitSha;
}
+ this._commitRange = {commit, baseCommit};
}
- this._commitRange = {commit, baseCommit};
}));
promises.push(this._loadComments());
@@ -674,8 +706,10 @@
}
this._loading = false;
this.$.diffHost.comments = this._commentsForDiff;
- return this.$.diffHost.reload();
+ return this.$.diffHost.reload(true);
}).then(() => {
+ this.$.reporting.diffViewFullyLoaded();
+ // If diff view displayed has not ended yet, it ends here.
this.$.reporting.diffViewDisplayed();
});
},
@@ -690,6 +724,11 @@
},
_setReviewedObserver(_loggedIn, paramsRecord, _prefs) {
+ // Polymer 2: check for undefined
+ if ([_loggedIn, paramsRecord, _prefs].some(arg => arg === undefined)) {
+ return;
+ }
+
const params = paramsRecord.base || {};
if (!_loggedIn) { return; }
@@ -741,6 +780,9 @@
},
_getDiffUrl(change, patchRange, path) {
+ if ([change, patchRange, path].some(arg => arg === undefined)) {
+ return '';
+ }
return Gerrit.Nav.getUrlForDiff(change, path, patchRange.patchNum,
patchRange.basePatchNum);
},
@@ -779,6 +821,9 @@
},
_getChangePath(change, patchRange, revisions) {
+ if ([change, patchRange].some(arg => arg === undefined)) {
+ return '';
+ }
const range = this._getChangeUrlRange(patchRange, revisions);
return Gerrit.Nav.getUrlForChange(change, range.patchNum,
range.basePatchNum);
@@ -793,22 +838,31 @@
return this._getChangePath(change, patchRangeRecord.base, revisions);
},
- _formatFilesForDropdown(fileList, patchNum, changeComments) {
- if (!fileList) { return; }
+ _formatFilesForDropdown(files, patchNum, changeComments) {
+ // Polymer 2: check for undefined
+ if ([
+ files,
+ patchNum,
+ changeComments,
+ ].some(arg => arg === undefined)) {
+ return;
+ }
+
+ if (!files) { return; }
const dropdownContent = [];
- for (const path of fileList) {
+ for (const path of files.sortedFileList) {
dropdownContent.push({
text: this.computeDisplayPath(path),
mobileText: this.computeTruncatedPath(path),
value: path,
bottomText: this._computeCommentString(changeComments, patchNum,
- path),
+ path, files.changeFilesByPath[path]),
});
}
return dropdownContent;
},
- _computeCommentString(changeComments, patchNum, path) {
+ _computeCommentString(changeComments, patchNum, path, changeFileInfo) {
const unresolvedCount = changeComments.computeUnresolvedNum(patchNum,
path);
const commentCount = changeComments.computeCommentCount(patchNum, path);
@@ -817,11 +871,13 @@
const unresolvedString = GrCountStringFormatter.computeString(
unresolvedCount, 'unresolved');
- return commentString +
- // Add a space if both comments and unresolved
- (commentString && unresolvedString ? ', ' : '') +
- // Add parentheses around unresolved if it exists.
- (unresolvedString ? `${unresolvedString}` : '');
+ const unmodifiedString = changeFileInfo.status === 'U' ? 'no changes': '';
+
+ return [
+ unmodifiedString,
+ commentString,
+ unresolvedString]
+ .filter(v => v && v.length > 0).join(', ');
},
_computePrefsButtonHidden(prefs, prefsDisabled) {
@@ -990,6 +1046,15 @@
},
_computeCommentSkips(commentMap, fileList, path) {
+ // Polymer 2: check for undefined
+ if ([
+ commentMap,
+ fileList,
+ path,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const skips = {previous: null, next: null};
if (!fileList.length) { return skips; }
const pathIndex = fileList.indexOf(path);
@@ -1070,6 +1135,11 @@
},
_computeFileNum(file, files) {
+ // Polymer 2: check for undefined
+ if ([file, files].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return files.findIndex(({value}) => value === file) + 1;
},
@@ -1109,13 +1179,12 @@
this._getDiffPreferences();
},
- _computeIsLoggedIn(loggedIn) {
- return loggedIn ? true : false;
- },
-
_computeCanEdit(loggedIn, changeChangeRecord) {
- return this._computeIsLoggedIn(loggedIn) &&
- this.changeIsOpen(changeChangeRecord.base.status);
+ if ([changeChangeRecord, changeChangeRecord.base]
+ .some(arg => arg === undefined)) {
+ return false;
+ }
+ return loggedIn && this.changeIsOpen(changeChangeRecord.base);
},
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index b33c54cf22..9c59577da0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-diff-view.html">
@@ -74,6 +76,17 @@ limitations under the License.
const PARENT = 'PARENT';
+ function getFilesFromFileList(fileList) {
+ const changeFilesByPath = fileList.reduce((files, path) => {
+ files[path] = {};
+ return files;
+ }, {});
+ return {
+ sortedFileList: fileList,
+ changeFilesByPath,
+ };
+ }
+
setup(() => {
sandbox = sinon.sandbox.create();
@@ -153,7 +166,8 @@ limitations under the License.
a: {_number: 10, commit: {parents: []}},
},
};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md']);
element._path = 'glados.txt';
element.changeViewState.selectedFileIndex = 1;
element._loggedIn = true;
@@ -260,7 +274,8 @@ limitations under the License.
b: {_number: 5, commit: {parents: []}},
},
};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md']);
element._path = 'glados.txt';
const diffNavStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff');
@@ -324,7 +339,8 @@ limitations under the License.
b: {_number: 2, commit: {parents: []}},
},
};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md']);
element._path = 'glados.txt';
const diffNavStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff');
@@ -517,13 +533,22 @@ limitations under the License.
unresolvedCountStub.withArgs(3, path).returns(2);
unresolvedCountStub.withArgs(4, path).returns(0);
- assert.equal(element._computeCommentString(comments, 1, path),
+ assert.equal(element._computeCommentString(comments, 1, path, {}),
'1 unresolved');
- assert.equal(element._computeCommentString(comments, 2, path),
+ assert.equal(
+ element._computeCommentString(comments, 2, path, {status: 'M'}),
'1 comment');
- assert.equal(element._computeCommentString(comments, 3, path),
+ assert.equal(
+ element._computeCommentString(comments, 2, path, {status: 'U'}),
+ 'no changes, 1 comment');
+ assert.equal(
+ element._computeCommentString(comments, 3, path, {status: 'A'}),
'2 comments, 2 unresolved');
- assert.equal(element._computeCommentString(comments, 4, path), '');
+ assert.equal(
+ element._computeCommentString(comments, 4, path, {status: 'M'}), '');
+ assert.equal(
+ element._computeCommentString(comments, 4, path, {status: 'U'}),
+ 'no changes');
done();
});
});
@@ -545,8 +570,9 @@ limitations under the License.
patchNum: '10',
};
element._change = {_number: 42};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md',
- '/COMMIT_MSG', '/MERGE_LIST'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md',
+ '/COMMIT_MSG', '/MERGE_LIST']);
element._path = 'glados.txt';
const expectedFormattedFiles = [
{
@@ -595,7 +621,8 @@ limitations under the License.
a: {_number: 10, commit: {parents: []}},
},
};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md']);
element._path = 'glados.txt';
flushAsynchronousOperations();
const linkEls = Polymer.dom(element.root).querySelectorAll('.navLink');
@@ -637,7 +664,8 @@ limitations under the License.
b: {_number: 10, commit: {parents: []}},
},
};
- element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._files = getFilesFromFileList(
+ ['chell.go', 'glados.txt', 'wheatley.md']);
element._path = 'glados.txt';
flushAsynchronousOperations();
const linkEls = Polymer.dom(element.root).querySelectorAll('.navLink');
@@ -1064,9 +1092,9 @@ limitations under the License.
setup(() => {
navToChangeStub = sandbox.stub(element, '_navToChangeView');
navToDiffStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff');
- element._fileList = [
+ element._files = getFilesFromFileList([
'path/one.jpg', 'path/two.m4v', 'path/three.wav',
- ];
+ ]);
element._patchRange = {patchNum: '2', basePatchNum: '1'};
});
@@ -1217,7 +1245,7 @@ limitations under the License.
});
test('shift+m navigates to next unreviewed file', () => {
- element._fileList = ['file1', 'file2', 'file3'];
+ element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
element._reviewedFiles = new Set(['file1', 'file2']);
element._path = 'file1';
const reviewedStub = sandbox.stub(element, '_setReviewed');
@@ -1233,6 +1261,39 @@ limitations under the License.
]);
});
+ test('File change should trigger navigateToDiff once', () => {
+ element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
+ sandbox.stub(element, '_getLineOfInterest');
+ sandbox.stub(element, '_initCursor');
+ sandbox.stub(Gerrit.Nav, 'navigateToDiff');
+
+ // Load file1
+ element._paramsChanged({
+ view: Gerrit.Nav.View.DIFF,
+ patchNum: 1,
+ changeNum: 101,
+ project: 'test-project',
+ path: 'file1',
+ });
+ assert.isTrue(Gerrit.Nav.navigateToDiff.notCalled);
+
+ // Switch to file2
+ element.$.dropdown.value = 'file2';
+ assert.isTrue(Gerrit.Nav.navigateToDiff.calledOnce);
+
+ // This is to mock the param change triggered by above navigate
+ element._paramsChanged({
+ view: Gerrit.Nav.View.DIFF,
+ patchNum: 1,
+ changeNum: 101,
+ project: 'test-project',
+ path: 'file2',
+ });
+
+ // No extra call
+ assert.isTrue(Gerrit.Nav.navigateToDiff.calledOnce);
+ });
+
test('_computeDownloadDropdownLinks', () => {
const downloadLinks = [
{
@@ -1328,4 +1389,54 @@ limitations under the License.
'/changes/test~12/revisions/1/patch?zip&path=index.php');
});
});
+
+ suite('gr-diff-view tests unmodified files with comments', () => {
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ const changedFiles = {
+ 'file1.txt': {},
+ 'a/b/test.c': {},
+ };
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({change: {}}); },
+ getLoggedIn() { return Promise.resolve(false); },
+ getProjectConfig() { return Promise.resolve({}); },
+ getDiffChangeDetail() { return Promise.resolve({}); },
+ getChangeFiles() { return Promise.resolve(changedFiles); },
+ saveFileReviewed() { return Promise.resolve(); },
+ getDiffComments() { return Promise.resolve({}); },
+ getDiffRobotComments() { return Promise.resolve({}); },
+ getDiffDrafts() { return Promise.resolve({}); },
+ getReviewedFiles() { return Promise.resolve([]); },
+ });
+ element = fixture('basic');
+ return element._loadComments();
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('_getFiles add files with comments without changes', () => {
+ const patchChangeRecord = {
+ base: {
+ basePatchNum: '5',
+ patchNum: '10',
+ },
+ };
+ const changeComments = {
+ getPaths: sandbox.stub().returns({'file2.txt': {}, 'file1.txt': {}}),
+ };
+ return element._getFiles(23, patchChangeRecord, changeComments).then(() => {
+ assert.deepEqual(element._files, {
+ sortedFileList: ['a/b/test.c', 'file1.txt', 'file2.txt'],
+ changeFilesByPath: {
+ 'file1.txt': {},
+ 'file2.txt': {status: 'U'},
+ 'a/b/test.c': {},
+ },
+ });
+ });
+ });
+ });
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
index dd69724096..a7e391a7aa 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
@@ -22,20 +22,42 @@
/**
* A chunk of the diff that should be rendered together.
+ *
+ * @param {!GrDiffGroup.Type} type
+ * @param {!Array<!GrDiffLine>=} opt_lines
*/
function GrDiffGroup(type, opt_lines) {
+ /** @type {!GrDiffGroup.Type} */
this.type = type;
- /** @type{!Array<!GrDiffLine>} */
+ /** @type {boolean} */
+ this.dueToRebase = false;
+
+ /**
+ * True means all changes in this line are whitespace changes that should
+ * not be highlighted as changed as per the user settings.
+ *
+ * @type{boolean}
+ */
+ this.ignoredWhitespaceOnly = false;
+
+ /**
+ * True means it should not be collapsed (because it was in the URL, or
+ * there is a comment on that line)
+ */
+ this.keyLocation = false;
+
+ /** @type {?HTMLElement} */
+ this.element = null;
+
+ /** @type {!Array<!GrDiffLine>} */
this.lines = [];
- /** @type{!Array<!GrDiffLine>} */
+ /** @type {!Array<!GrDiffLine>} */
this.adds = [];
- /** @type{!Array<!GrDiffLine>} */
+ /** @type {!Array<!GrDiffLine>} */
this.removes = [];
- /** @type{boolean|undefined} */
- this.dueToRebase = undefined;
-
+ /** Both start and end line are inclusive. */
this.lineRange = {
left: {start: null, end: null},
right: {start: null, end: null},
@@ -46,8 +68,7 @@
}
}
- GrDiffGroup.prototype.element = null;
-
+ /** @enum {string} */
GrDiffGroup.Type = {
/** Unchanged context. */
BOTH: 'both',
@@ -59,6 +80,139 @@
DELTA: 'delta',
};
+
+ /**
+ * Hides lines in the given range behind a context control group.
+ *
+ * Groups that would be partially visible are split into their visible and
+ * hidden parts, respectively.
+ * The groups need to be "common groups", meaning they have to have either
+ * originated from an `ab` chunk, or from an `a`+`b` chunk with
+ * `common: true`.
+ *
+ * If the hidden range is 1 line or less, nothing is hidden and no context
+ * control group is created.
+ *
+ * @param {!Array<!GrDiffGroup>} groups Common groups, ordered by their line
+ * ranges.
+ * @param {number} hiddenStart The first element to be hidden, as a
+ * non-negative line number offset relative to the first group's start
+ * line, left and right respectively.
+ * @param {number} hiddenEnd The first visible element after the hidden range,
+ * as a non-negative line number offset relative to the first group's
+ * start line, left and right respectively.
+ * @return {!Array<!GrDiffGroup>}
+ */
+ GrDiffGroup.hideInContextControl = function(groups, hiddenStart, hiddenEnd) {
+ if (groups.length === 0) return [];
+ // Clamp hiddenStart and hiddenEnd - inspired by e.g. substring
+ hiddenStart = Math.max(hiddenStart, 0);
+ hiddenEnd = Math.max(hiddenEnd, hiddenStart);
+
+ let before = [];
+ let hidden = groups;
+ let after = [];
+
+ const numHidden = hiddenEnd - hiddenStart;
+
+ // Only collapse if there is more than 1 line to be hidden.
+ if (numHidden > 1) {
+ if (hiddenStart) {
+ [before, hidden] = GrDiffGroup._splitCommonGroups(hidden, hiddenStart);
+ }
+ if (hiddenEnd) {
+ [hidden, after] = GrDiffGroup._splitCommonGroups(
+ hidden, hiddenEnd - hiddenStart);
+ }
+ } else {
+ [hidden, after] = [[], hidden];
+ }
+
+ const result = [...before];
+ if (hidden.length) {
+ const ctxLine = new GrDiffLine(GrDiffLine.Type.CONTEXT_CONTROL);
+ ctxLine.contextGroups = hidden;
+ const ctxGroup = new GrDiffGroup(
+ GrDiffGroup.Type.CONTEXT_CONTROL, [ctxLine]);
+ result.push(ctxGroup);
+ }
+ result.push(...after);
+ return result;
+ };
+
+ /**
+ * Splits a list of common groups into two lists of groups.
+ *
+ * Groups where all lines are before or all lines are after the split will be
+ * retained as is and put into the first or second list respectively. Groups
+ * with some lines before and some lines after the split will be split into
+ * two groups, which will be put into the first and second list.
+ *
+ * @param {!Array<!GrDiffGroup>} groups
+ * @param {number} split A line number offset relative to the first group's
+ * start line at which the groups should be split.
+ * @return {!Array<!Array<!GrDiffGroup>>} The outer array has 2 elements, the
+ * list of groups before and the list of groups after the split.
+ */
+ GrDiffGroup._splitCommonGroups = function(groups, split) {
+ if (groups.length === 0) return [[], []];
+ const leftSplit = groups[0].lineRange.left.start + split;
+ const rightSplit = groups[0].lineRange.right.start + split;
+
+ const beforeGroups = [];
+ const afterGroups = [];
+ for (const group of groups) {
+ if (group.lineRange.left.end < leftSplit ||
+ group.lineRange.right.end < rightSplit) {
+ beforeGroups.push(group);
+ continue;
+ }
+ if (leftSplit <= group.lineRange.left.start ||
+ rightSplit <= group.lineRange.right.start) {
+ afterGroups.push(group);
+ continue;
+ }
+
+ const before = [];
+ const after = [];
+ for (const line of group.lines) {
+ if ((line.beforeNumber && line.beforeNumber < leftSplit) ||
+ (line.afterNumber && line.afterNumber < rightSplit)) {
+ before.push(line);
+ } else {
+ after.push(line);
+ }
+ }
+
+ if (before.length) {
+ beforeGroups.push(before.length === group.lines.length ?
+ group : group.cloneWithLines(before));
+ }
+ if (after.length) {
+ afterGroups.push(after.length === group.lines.length ?
+ group : group.cloneWithLines(after));
+ }
+ }
+ return [beforeGroups, afterGroups];
+ };
+
+ /**
+ * Creates a new group with the same properties but different lines.
+ *
+ * The element property is not copied, because the original element is still a
+ * rendering of the old lines, so that would not make sense.
+ *
+ * @param {!Array<!GrDiffLine>} lines
+ * @return {!GrDiffGroup}
+ */
+ GrDiffGroup.prototype.cloneWithLines = function(lines) {
+ const group = new GrDiffGroup(this.type, lines);
+ group.dueToRebase = this.dueToRebase;
+ group.ignoredWhitespaceOnly = this.ignoredWhitespaceOnly;
+ return group;
+ };
+
+ /** @param {!GrDiffLine} line */
GrDiffGroup.prototype.addLine = function(line) {
this.lines.push(line);
@@ -77,6 +231,7 @@
this._updateRange(line);
};
+ /** @return {!Array<{left: GrDiffLine, right: GrDiffLine}>} */
GrDiffGroup.prototype.getSideBySidePairs = function() {
if (this.type === GrDiffGroup.Type.BOTH ||
this.type === GrDiffGroup.Type.CONTEXT_CONTROL) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
index 9dc5311700..16e8036ea5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
@@ -18,8 +18,10 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-group</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="gr-diff-line.js"></script>
<script src="gr-diff-group.js"></script>
@@ -28,12 +30,9 @@ limitations under the License.
suite('gr-diff-group tests', () => {
test('delta line pairs', () => {
let group = new GrDiffGroup(GrDiffGroup.Type.DELTA);
- const l1 = new GrDiffLine(GrDiffLine.Type.ADD);
- const l2 = new GrDiffLine(GrDiffLine.Type.ADD);
- const l3 = new GrDiffLine(GrDiffLine.Type.REMOVE);
- l1.afterNumber = 128;
- l2.afterNumber = 129;
- l3.beforeNumber = 64;
+ const l1 = new GrDiffLine(GrDiffLine.Type.ADD, 0, 128);
+ const l2 = new GrDiffLine(GrDiffLine.Type.ADD, 0, 129);
+ const l3 = new GrDiffLine(GrDiffLine.Type.REMOVE, 64, 0);
group.addLine(l1);
group.addLine(l2);
group.addLine(l3);
@@ -64,17 +63,9 @@ limitations under the License.
});
test('group/header line pairs', () => {
- const l1 = new GrDiffLine(GrDiffLine.Type.BOTH);
- l1.beforeNumber = 64;
- l1.afterNumber = 128;
-
- const l2 = new GrDiffLine(GrDiffLine.Type.BOTH);
- l2.beforeNumber = 65;
- l2.afterNumber = 129;
-
- const l3 = new GrDiffLine(GrDiffLine.Type.BOTH);
- l3.beforeNumber = 66;
- l3.afterNumber = 130;
+ const l1 = new GrDiffLine(GrDiffLine.Type.BOTH, 64, 128);
+ const l2 = new GrDiffLine(GrDiffLine.Type.BOTH, 65, 129);
+ const l3 = new GrDiffLine(GrDiffLine.Type.BOTH, 66, 130);
let group = new GrDiffGroup(GrDiffGroup.Type.BOTH, [l1, l2, l3]);
@@ -122,6 +113,97 @@ limitations under the License.
assert.throws(group.addLine.bind(group, l2));
assert.doesNotThrow(group.addLine.bind(group, l3));
});
+
+ suite('hideInContextControl', () => {
+ let groups;
+ setup(() => {
+ groups = [
+ new GrDiffGroup(GrDiffGroup.Type.BOTH, [
+ new GrDiffLine(GrDiffLine.Type.BOTH, 5, 7),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 6, 8),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 7, 9),
+ ]),
+ new GrDiffGroup(GrDiffGroup.Type.DELTA, [
+ new GrDiffLine(GrDiffLine.Type.REMOVE, 8),
+ new GrDiffLine(GrDiffLine.Type.ADD, 0, 10),
+ new GrDiffLine(GrDiffLine.Type.REMOVE, 9),
+ new GrDiffLine(GrDiffLine.Type.ADD, 0, 11),
+ new GrDiffLine(GrDiffLine.Type.REMOVE, 10),
+ new GrDiffLine(GrDiffLine.Type.ADD, 0, 12),
+ ]),
+ new GrDiffGroup(GrDiffGroup.Type.BOTH, [
+ new GrDiffLine(GrDiffLine.Type.BOTH, 11, 13),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 12, 14),
+ new GrDiffLine(GrDiffLine.Type.BOTH, 13, 15),
+ ]),
+ ];
+ });
+
+ test('hides hidden groups in context control', () => {
+ const collapsedGroups = GrDiffGroup.hideInContextControl(groups, 3, 6);
+ assert.equal(collapsedGroups.length, 3);
+
+ assert.equal(collapsedGroups[0], groups[0]);
+
+ assert.equal(collapsedGroups[1].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.equal(collapsedGroups[1].lines.length, 1);
+ assert.equal(
+ collapsedGroups[1].lines[0].type, GrDiffLine.Type.CONTEXT_CONTROL);
+ assert.equal(
+ collapsedGroups[1].lines[0].contextGroups.length, 1);
+ assert.equal(
+ collapsedGroups[1].lines[0].contextGroups[0], groups[1]);
+
+ assert.equal(collapsedGroups[2], groups[2]);
+ });
+
+ test('splits partially hidden groups', () => {
+ const collapsedGroups = GrDiffGroup.hideInContextControl(groups, 4, 7);
+ assert.equal(collapsedGroups.length, 4);
+ assert.equal(collapsedGroups[0], groups[0]);
+
+ assert.equal(collapsedGroups[1].type, GrDiffGroup.Type.DELTA);
+ assert.deepEqual(collapsedGroups[1].adds, [groups[1].adds[0]]);
+ assert.deepEqual(collapsedGroups[1].removes, [groups[1].removes[0]]);
+
+ assert.equal(collapsedGroups[2].type, GrDiffGroup.Type.CONTEXT_CONTROL);
+ assert.equal(collapsedGroups[2].lines.length, 1);
+ assert.equal(
+ collapsedGroups[2].lines[0].type, GrDiffLine.Type.CONTEXT_CONTROL);
+ assert.equal(
+ collapsedGroups[2].lines[0].contextGroups.length, 2);
+
+ assert.equal(
+ collapsedGroups[2].lines[0].contextGroups[0].type,
+ GrDiffGroup.Type.DELTA);
+ assert.deepEqual(
+ collapsedGroups[2].lines[0].contextGroups[0].adds,
+ groups[1].adds.slice(1));
+ assert.deepEqual(
+ collapsedGroups[2].lines[0].contextGroups[0].removes,
+ groups[1].removes.slice(1));
+
+ assert.equal(
+ collapsedGroups[2].lines[0].contextGroups[1].type,
+ GrDiffGroup.Type.BOTH);
+ assert.deepEqual(
+ collapsedGroups[2].lines[0].contextGroups[1].lines,
+ [groups[2].lines[0]]);
+
+ assert.equal(collapsedGroups[3].type, GrDiffGroup.Type.BOTH);
+ assert.deepEqual(collapsedGroups[3].lines, groups[2].lines.slice(1));
+ });
+
+ test('groups unchanged if the hidden range is empty', () => {
+ assert.deepEqual(
+ GrDiffGroup.hideInContextControl(groups, 0, 0), groups);
+ });
+
+ test('groups unchanged if there is only 1 line to hide', () => {
+ assert.deepEqual(
+ GrDiffGroup.hideInContextControl(groups, 3, 4), groups);
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.js
index 44bb52a929..a29529375d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.js
@@ -20,20 +20,31 @@
// Prevent redefinition.
if (window.GrDiffLine) { return; }
- function GrDiffLine(type) {
+ /**
+ * @param {GrDiffLine.Type} type
+ * @param {number|string=} opt_beforeLine
+ * @param {number|string=} opt_afterLine
+ */
+ function GrDiffLine(type, opt_beforeLine, opt_afterLine) {
this.type = type;
- this.highlights = [];
- }
- /** @type {number|string} */
- GrDiffLine.prototype.afterNumber = 0;
+ /** @type {number|string} */
+ this.beforeNumber = opt_beforeLine || 0;
- /** @type {number|string} */
- GrDiffLine.prototype.beforeNumber = 0;
+ /** @type {number|string} */
+ this.afterNumber = opt_afterLine || 0;
- GrDiffLine.prototype.contextGroup = null;
+ /** @type {boolean} */
+ this.hasIntralineInfo = false;
+
+ /** @type {Array<GrDiffLine.Highlights>} */
+ this.highlights = [];
- GrDiffLine.prototype.text = '';
+ /** @type {?Array<Object>} ?Array<!GrDiffGroup> */
+ this.contextGroups = null;
+
+ this.text = '';
+ }
GrDiffLine.Type = {
ADD: 'add',
@@ -43,6 +54,23 @@
REMOVE: 'remove',
};
+ /**
+ * A line highlight object consists of three fields:
+ * - contentIndex: The index of the chunk `content` field (the line
+ * being referred to).
+ * - startIndex: Index of the character where the highlight should begin.
+ * - endIndex: (optional) Index of the character where the highlight should
+ * end. If omitted, the highlight is meant to be a continuation onto the
+ * next line.
+ *
+ * @typedef {{
+ * contentIndex: number,
+ * startIndex: number,
+ * endIndex: number
+ * }}
+ */
+ GrDiffLine.Highlights;
+
GrDiffLine.FILE = 'FILE';
GrDiffLine.BLANK_LINE = new GrDiffLine(GrDiffLine.Type.BLANK);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 72fc1ee469..f6abb14211 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -23,19 +24,31 @@ limitations under the License.
<link rel="import" href="../gr-diff-highlight/gr-diff-highlight.html">
<link rel="import" href="../gr-diff-selection/gr-diff-selection.html">
<link rel="import" href="../gr-syntax-themes/gr-syntax-theme.html">
+<link rel="import" href="../gr-ranged-comment-themes/gr-ranged-comment-theme.html">
<script src="../../../scripts/hiddenscroll.js"></script>
<dom-module id="gr-diff">
<template>
<style include="shared-styles">
- :host(.no-left) .sideBySide ::content .left,
- :host(.no-left) .sideBySide ::content .left + td,
- :host(.no-left) .sideBySide ::content .right:not([data-value]),
- :host(.no-left) .sideBySide ::content .right:not([data-value]) + td {
+ :host(.no-left) .sideBySide .left,
+ :host(.no-left) .sideBySide .left + td,
+ :host(.no-left) .sideBySide .right:not([data-value]),
+ :host(.no-left) .sideBySide .right:not([data-value]) + td {
display: none;
}
- .thread-group, ::slotted(*) .thread-group {
+ ::slotted(*) .thread-group {
+ display: block;
+ max-width: var(--content-width, 80ch);
+ white-space: normal;
+ }
+ :host {
+ font-family: var(--monospace-font-family, ''), 'Roboto Mono';
+ font-size: var(--font-size, var(--font-size-code, 12px));
+ line-height: var(--line-height-code, 1.334);
+ }
+
+ .thread-group {
display: block;
max-width: var(--content-width, 80ch);
white-space: normal;
@@ -46,7 +59,7 @@ limitations under the License.
@apply --diff-container-styles;
}
.diffContainer.hiddenscroll {
- margin-bottom: .8em;
+ margin-bottom: var(--spacing-m);
}
table {
border-collapse: collapse;
@@ -80,10 +93,12 @@ limitations under the License.
background-color: var(--diff-selection-background-color);
color: var(--primary-text-color);
}
- .blank,
.content {
background-color: var(--view-background-color);
}
+ .blank {
+ background-color: var(--diff-blank-background-color);
+ }
.image-diff .content {
background-color: var(--table-header-background-color);
}
@@ -96,8 +111,6 @@ limitations under the License.
}
.lineNum,
.content {
- /* Set font size based the user's diff preference. */
- font-size: var(--font-size, var(--font-size-normal));
vertical-align: top;
white-space: pre;
}
@@ -109,7 +122,7 @@ limitations under the License.
user-select: none;
color: var(--deemphasized-text-color);
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
text-align: right;
}
.canComment .lineNum {
@@ -123,6 +136,8 @@ limitations under the License.
width: var(--content-width, 80ch);
}
.content.add .intraline,
+ /* If there are no intraline info, consider everything changed */
+ .content.add.no-intraline-info,
.delta.total .content.add {
background-color: var(--dark-add-highlight-color);
}
@@ -130,12 +145,16 @@ limitations under the License.
background-color: var(--light-add-highlight-color);
}
.content.remove .intraline,
+ /* If there are no intraline info, consider everything changed */
+ .content.remove.no-intraline-info,
.delta.total .content.remove {
background-color: var(--dark-remove-highlight-color);
}
.content.remove {
background-color: var(--light-remove-highlight-color);
}
+
+ /* dueToRebase */
.dueToRebase .content.add .intraline,
.delta.total.dueToRebase .content.add {
background-color: var(--dark-rebased-add-highlight-color);
@@ -150,20 +169,32 @@ limitations under the License.
.dueToRebase .content.remove {
background-color: var(--light-remove-add-highlight-color);
}
+
+ /* ignoredWhitespaceOnly */
+ .ignoredWhitespaceOnly .content.add .intraline,
+ .delta.total.ignoredWhitespaceOnly .content.add,
+ .ignoredWhitespaceOnly .content.add,
+ .ignoredWhitespaceOnly .content.remove .intraline,
+ .delta.total.ignoredWhitespaceOnly .content.remove,
+ .ignoredWhitespaceOnly .content.remove {
+ background: none;
+ }
+
.content .contentText:empty:after {
/* Newline, to ensure empty lines are one line-height tall. */
content: '\A';
}
.contextControl {
- background-color: var(--diff-context-control-color);
+ background-color: var(--diff-context-control-background-color);
border: 1px solid var(--diff-context-control-border-color);
+ color: var(--diff-context-control-color);
}
.contextControl gr-button {
display: inline-block;
text-decoration: none;
--gr-button: {
- color: var(--deemphasized-text-color);
- padding: .2em;
+ color: var(--diff-context-control-color);
+ padding: var(--spacing-xs);
}
}
.contextControl td:not(.lineNum) {
@@ -191,21 +222,19 @@ limitations under the License.
.content .trailing-whitespace,
.trailing-whitespace .intraline,
.content .trailing-whitespace .intraline {
- border-radius: .4em;
+ border-radius: var(--border-radius, 4px);
background-color: var(--diff-trailing-whitespace-indicator);
}
#diffHeader {
background-color: var(--table-header-background-color);
border-bottom: 1px solid var(--border-color);
color: var(--link-color);
- font-family: var(--monospace-font-family);
- font-size: var(--font-size, var(--font-size-normal));
- padding: 0.5em 0 0.5em 4em;
+ padding: var(--spacing-m) 0 var(--spacing-m) 48px;
}
#loadingError,
#sizeWarning {
display: none;
- margin: 1em auto;
+ margin: var(--spacing-l) auto;
max-width: 60em;
text-align: center;
}
@@ -213,7 +242,7 @@ limitations under the License.
color: var(--error-text-color);
}
#sizeWarning gr-button {
- margin: 1em;
+ margin: var(--spacing-l);
}
#loadingError.showError,
#sizeWarning.warn {
@@ -227,9 +256,7 @@ limitations under the License.
}
td.blame {
display: none;
- font-family: var(--font-family);
- font-size: var(--font-size, var(--font-size-normal));
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
white-space: pre;
}
:host(.showBlame) col.blame {
@@ -251,11 +278,6 @@ limitations under the License.
overflow: hidden;
width: 200px;
}
- /** Since the line limit position is determined by charachter size, blank
- lines also need to have the same font size as everything else */
- .full-width .blank {
- font-size: var(--font-size, var(--font-size-normal));
- }
/** Support the line length indicator **/
.full-width td.content,
.full-width td.blank {
@@ -280,8 +302,50 @@ limitations under the License.
.lineNum.PARTIALLY_COVERED {
background: linear-gradient(to right bottom, #FFD1A4 0%, #FFD1A4 50%, #E0F2F1 50%, #E0F2F1 100%);
}
+
+ /** BEGIN: Select and copy for Polymer 2 */
+ /** Below was copied and modified from the original css in gr-diff-selection.html */
+ .content,
+ .contextControl,
+ .blame {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ .selected-left:not(.selected-comment) .side-by-side .left + .content .contentText,
+ .selected-right:not(.selected-comment) .side-by-side .right + .content .contentText,
+ .selected-left:not(.selected-comment) .unified .left.lineNum ~ .content:not(.both) .contentText,
+ .selected-right:not(.selected-comment) .unified .right.lineNum ~ .content .contentText,
+ .selected-left.selected-comment .side-by-side .left + .content .message,
+ .selected-right.selected-comment .side-by-side .right + .content .message :not(.collapsedContent),
+ .selected-comment .unified .message :not(.collapsedContent),
+ .selected-blame .blame {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+
+ /** Make comments selectable when selected */
+ .selected-left.selected-comment ::slotted(gr-comment-thread[comment-side=left]),
+ .selected-right.selected-comment ::slotted(gr-comment-thread[comment-side=right]) {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+ /** END: Select and copy for Polymer 2 */
+
+ .whitespace-change-only-message {
+ background-color: var(--diff-context-control-background-color);
+ border: 1px solid var(--diff-context-control-border-color);
+ text-align: center;
+ }
</style>
<style include="gr-syntax-theme"></style>
+ <style include="gr-ranged-comment-theme"></style>
<div id="diffHeader" hidden$="[[_computeDiffHeaderHidden(_diffHeaderItems)]]">
<template
is="dom-repeat"
@@ -302,18 +366,26 @@ limitations under the License.
coverage-ranges="[[coverageRanges]]"
project-name="[[projectName]]"
diff="[[diff]]"
- diff-path="[[path]]"
+ path="[[path]]"
change-num="[[changeNum]]"
patch-num="[[patchRange.patchNum]]"
view-mode="[[viewMode]]"
line-wrapping="[[lineWrapping]]"
is-image-diff="[[isImageDiff]]"
base-image="[[baseImage]]"
+ layers="[[layers]]"
revision-image="[[revisionImage]]">
<table
id="diffTable"
class$="[[_diffTableClass]]"
role="presentation"></table>
+
+ <template is="dom-if" if="[[showNoChangeMessage(loading, prefs, _diffLength)]]">
+ <div class="whitespace-change-only-message">
+ This file only contains whitespace changes.
+ Modify the whitespace setting to see the changes.
+ </div>
+ </template>
</gr-diff-builder>
</gr-diff-highlight>
</gr-diff-selection>
@@ -329,15 +401,17 @@ limitations under the License.
Prevented render because "Whole file" is enabled and this diff is very
large (about [[_diffLength]] lines).
</p>
- <gr-button on-tap="_handleLimitedBypass">
+ <gr-button on-click="_handleLimitedBypass">
Render with limited context
</gr-button>
- <gr-button on-tap="_handleFullBypass">
+ <gr-button on-click="_handleFullBypass">
Render anyway (may be slow)
</gr-button>
</div>
</template>
<script src="gr-diff-line.js"></script>
<script src="gr-diff-group.js"></script>
+ <!-- gr-diff.js contains an 'import' statement, which is allowed only in modules -->
+ <script src="../../../scripts/shadow.js"></script>
<script src="gr-diff.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 4e023d4e85..c6b2e3a014 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -35,26 +35,10 @@
RIGHT: 'right',
};
- const Defs = {};
-
- /**
- * Special line number which should not be collapsed into a shared region.
- *
- * @typedef {{
- * number: number,
- * leftSide: boolean
- * }}
- */
- Defs.LineOfInterest;
-
const LARGE_DIFF_THRESHOLD_LINES = 10000;
const FULL_CONTEXT = -1;
const LIMITED_CONTEXT = 10;
- /** @typedef {{start_line: number, start_character: number,
- * end_line: number, end_character: number}} */
- Gerrit.Range;
-
/**
* Compare two ranges. Either argument may be falsy, but will only return
* true if both are falsy or if neither are falsy and have the same position
@@ -86,6 +70,9 @@
* implements the same behavior as the template parsing for imperative slots.
*/
Gerrit.slotToContent = function(slot) {
+ if (Polymer.Element) {
+ return slot;
+ }
const content = document.createElement('content');
content.name = slot.name;
content.setAttribute('select', `[slot='${slot.name}']`);
@@ -106,7 +93,6 @@
Polymer({
is: 'gr-diff',
- _legacyUndefinedCheck: true,
/**
* Fired when the user selects a line.
@@ -184,7 +170,7 @@
observer: '_viewModeObserver',
},
- /** @type {?Defs.LineOfInterest} */
+ /** @type {?Gerrit.LineOfInterest} */
lineOfInterest: Object,
loading: {
@@ -271,9 +257,11 @@
/** Set by Polymer. */
isAttached: Boolean,
+ layers: Array,
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PatchSetBehavior,
],
@@ -295,12 +283,23 @@
this._unobserveNodes();
},
+ showNoChangeMessage(loading, prefs, diffLength) {
+ return !loading &&
+ prefs && prefs.ignore_whitespace !== 'IGNORE_NONE'
+ && diffLength === 0;
+ },
+
_enableSelectionObserver(loggedIn, isAttached) {
+ // Polymer 2: check for undefined
+ if ([loggedIn, isAttached].some(arg => arg === undefined)) {
+ return;
+ }
+
if (loggedIn && isAttached) {
- this.listen(document, 'selectionchange', '_handleSelectionChange');
+ this.listen(document, '-shadow-selectionchange', '_handleSelectionChange');
this.listen(document, 'mouseup', '_handleMouseUp');
} else {
- this.unlisten(document, 'selectionchange', '_handleSelectionChange');
+ this.unlisten(document, '-shadow-selectionchange', '_handleSelectionChange');
this.unlisten(document, 'mouseup', '_handleMouseUp');
}
},
@@ -329,7 +328,8 @@
// This takes the shadow DOM selection if one exists.
return this.root.getSelection ?
this.root.getSelection() :
- document.getSelection();
+ // This is coming from shadow.js
+ getRange(this.root);
},
_observeNodes() {
@@ -361,7 +361,10 @@
});
this.splice('_commentRanges', i, 1);
}
- this.push('_commentRanges', ...addedCommentRanges);
+
+ if (addedCommentRanges && addedCommentRanges.length) {
+ this.push('_commentRanges', ...addedCommentRanges);
+ }
},
/**
@@ -384,7 +387,13 @@
const commentSide = threadEl.getAttribute('comment-side');
const lineNum = Number(threadEl.getAttribute('line-num')) ||
GrDiffLine.FILE;
+ const commentRange = threadEl.range || {};
keyLocations[commentSide][lineNum] = true;
+ // Add start_line as well if exists,
+ // the being and end of the range should not be collapsed.
+ if (commentRange.start_line) {
+ keyLocations[commentSide][commentRange.start_line] = true;
+ }
}
return keyLocations;
},
@@ -393,12 +402,12 @@
_redispatchHoverEvents(addedThreadEls) {
for (const threadEl of addedThreadEls) {
threadEl.addEventListener('mouseenter', () => {
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseenter', {bubbles: true}));
+ threadEl.dispatchEvent(new CustomEvent(
+ 'comment-thread-mouseenter', {bubbles: true, composed: true}));
});
threadEl.addEventListener('mouseleave', () => {
- threadEl.dispatchEvent(
- new CustomEvent('comment-thread-mouseleave', {bubbles: true}));
+ threadEl.dispatchEvent(new CustomEvent(
+ 'comment-thread-mouseleave', {bubbles: true, composed: true}));
});
}
},
@@ -415,7 +424,6 @@
return [];
}
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this.root).querySelectorAll('.diff-row'));
},
@@ -554,6 +562,7 @@
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
this.dispatchEvent(new CustomEvent('create-comment', {
bubbles: true,
+ composed: true,
detail: {
lineNum,
side,
@@ -713,7 +722,7 @@
_diffChanged(newValue) {
if (newValue) {
- this._diffLength = this.$.diffBuilder.getDiffLength();
+ this._diffLength = this.getDiffLength(newValue);
this._debounceRenderDiffTable();
}
},
@@ -736,14 +745,16 @@
_renderDiffTable() {
this._unobserveIncrementalNodes();
if (!this.prefs) {
- this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('render', {bubbles: true, composed: true}));
return;
}
if (this.prefs.context === -1 &&
this._diffLength >= LARGE_DIFF_THRESHOLD_LINES &&
this._safetyBypass === null) {
this._showWarning = true;
- this.dispatchEvent(new CustomEvent('render', {bubbles: true}));
+ this.dispatchEvent(
+ new CustomEvent('render', {bubbles: true, composed: true}));
return;
}
@@ -753,7 +764,11 @@
this.$.diffBuilder.render(keyLocations, this._getBypassPrefs())
.then(() => {
this.dispatchEvent(
- new CustomEvent('render', {bubbles: true}));
+ new CustomEvent('render', {
+ bubbles: true,
+ composed: true,
+ detail: {contentRendered: true},
+ }));
});
},
@@ -765,6 +780,7 @@
// not hurt. It's probably a bigger performance cost to remove them than
// to keep them around. Medium term we can even consider to add one slot
// for each line from the start.
+ let lastEl;
for (const threadEl of addedThreadEls) {
const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
const commentSide = threadEl.getAttribute('comment-side');
@@ -783,6 +799,14 @@
const slot = document.createElement('slot');
slot.name = threadEl.getAttribute('slot');
Polymer.dom(threadGroupEl).appendChild(Gerrit.slotToContent(slot));
+ lastEl = threadEl;
+ }
+
+ // Safari is not binding newly created comment-thread
+ // with the slot somehow, replace itself will rebind it
+ // @see Issue 11182
+ if (lastEl && lastEl.replaceWith) {
+ lastEl.replaceWith(lastEl);
}
});
},
@@ -886,7 +910,8 @@
!chunk.ab &&
// The chunk doesn't have the given side.
- ((leftSide && !chunk.a) || (!leftSide && !chunk.b)));
+ ((leftSide && (!chunk.a || !chunk.a.length)) ||
+ (!leftSide && (!chunk.b || !chunk.b.length))));
// If we reached the beginning of the diff and failed to find a chunk
// with the given side, return null.
@@ -944,5 +969,25 @@
if (loading || !warning) { return 'newlineWarning hidden'; }
return 'newlineWarning';
},
+
+ /**
+ * Get the approximate length of the diff as the sum of the maximum
+ * length of the chunks.
+ *
+ * @param {Object} diff object
+ * @return {number}
+ */
+ getDiffLength(diff) {
+ if (!diff) return 0;
+ return diff.content.reduce((sum, sec) => {
+ if (sec.hasOwnProperty('ab')) {
+ return sum + sec.ab.length;
+ } else {
+ return sum + Math.max(
+ sec.hasOwnProperty('a') ? sec.a.length : 0,
+ sec.hasOwnProperty('b') ? sec.b.length : 0);
+ }
+ }, 0);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 594d60eda3..b177bade0d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -18,12 +18,15 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<link rel="import" href="gr-diff.html">
@@ -85,14 +88,14 @@ limitations under the License.
element = fixture('basic');
element.prefs = Object.assign({}, MINIMAL_PREFS, {line_wrapping: true});
flushAsynchronousOperations();
- assert.equal(element.customStyle['--line-limit'], '80ch');
+ assert.equal(util.getComputedStyleValue('--line-limit', element), '80ch');
});
test('line limit without line_wrapping', () => {
element = fixture('basic');
element.prefs = Object.assign({}, MINIMAL_PREFS, {line_wrapping: false});
flushAsynchronousOperations();
- assert.isNotOk(element.customStyle['--line-limit']);
+ assert.isNotOk(util.getComputedStyleValue('--line-limit', element));
});
suite('_get{PatchNum|IsParentComment}ByLineAndContent', () => {
@@ -776,12 +779,12 @@ limitations under the License.
element = fixture('basic');
renderStub = sandbox.stub(element.$.diffBuilder, 'render',
() => {
- Promise.resolve();
element.$.diffBuilder.dispatchEvent(
- new CustomEvent('render', {bubbles: true}));
+ new CustomEvent('render', {bubbles: true, composed: true}));
+ return Promise.resolve({});
});
const mock = document.createElement('mock-diff-response');
- sandbox.stub(element.$.diffBuilder, 'getDiffLength').returns(10000);
+ sandbox.stub(element, 'getDiffLength').returns(10000);
element.diff = mock.diffResponse;
element.noRenderOnPrefsChange = true;
});
@@ -865,7 +868,7 @@ limitations under the License.
assert.equal(element._lastChunkForSide(diff, true), diff.content[3]);
});
- test('addition', () => {
+ test('addition with a undefined', () => {
const diff = {content: [
{b: ['foo', 'bar', 'baz']},
]};
@@ -873,7 +876,16 @@ limitations under the License.
assert.isNull(element._lastChunkForSide(diff, true));
});
- test('deletion', () => {
+ test('addition with a empty', () => {
+ const diff = {content: [
+ {a: [], b: ['foo', 'bar', 'baz']},
+ ]};
+ assert.equal(element._lastChunkForSide(diff, false), diff.content[0]);
+ assert.isNull(element._lastChunkForSide(diff, true));
+ });
+
+
+ test('deletion with b undefined', () => {
const diff = {content: [
{a: ['foo', 'bar', 'baz']},
]};
@@ -881,6 +893,14 @@ limitations under the License.
assert.equal(element._lastChunkForSide(diff, true), diff.content[0]);
});
+ test('deletion with b empty', () => {
+ const diff = {content: [
+ {a: ['foo', 'bar', 'baz'], b: []},
+ ]};
+ assert.isNull(element._lastChunkForSide(diff, false));
+ assert.equal(element._lastChunkForSide(diff, true), diff.content[0]);
+ });
+
test('empty', () => {
const diff = {content: []};
assert.isNull(element._lastChunkForSide(diff, false));
@@ -1039,6 +1059,117 @@ limitations under the License.
});
});
});
+
+ suite('whitespace changes only message', () => {
+ const setupDiff = function(ignore_whitespace, diffContent) {
+ element = fixture('basic');
+ element.prefs = {
+ ignore_whitespace,
+ auto_hide_diff_table_header: true,
+ context: 10,
+ cursor_blink_rate: 0,
+ font_size: 12,
+ intraline_difference: true,
+ line_length: 100,
+ line_wrapping: false,
+ show_line_endings: true,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
+
+ element.diff = {
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ diff_header: [
+ 'diff --git a/carrot.js b/carrot.js',
+ 'index 2adc47d..f9c2f2c 100644',
+ '--- a/carrot.js',
+ '+++ b/carrot.jjs',
+ 'file differ',
+ ],
+ content: diffContent,
+ binary: true,
+ };
+
+ element._renderDiffTable();
+ flushAsynchronousOperations();
+ };
+
+ test('show the message if ignore_whitespace is criteria matches', () => {
+ setupDiff('IGNORE_ALL', [{skip: 100}]);
+ assert.isTrue(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show the message if still loading', () => {
+ setupDiff('IGNORE_ALL', [{skip: 100}]);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ true,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show the message if contains valid changes', () => {
+ const content = [{
+ a: ['all work and no play make andybons a dull boy'],
+ b: ['elgoog elgoog elgoog'],
+ }, {
+ ab: [
+ 'Non eram nescius, Brute, cum, quae summis ingeniis ',
+ 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
+ ],
+ }];
+ setupDiff('IGNORE_ALL', content);
+ assert.equal(element._diffLength, 3);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show message if ignore whitespace is disabled', () => {
+ const content = [{
+ a: ['all work and no play make andybons a dull boy'],
+ b: ['elgoog elgoog elgoog'],
+ }, {
+ ab: [
+ 'Non eram nescius, Brute, cum, quae summis ingeniis ',
+ 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
+ ],
+ }];
+ setupDiff('IGNORE_NONE', content);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+ });
+
+ test('getDiffLength', () => {
+ const diff = document.createElement('mock-diff-response').diffResponse;
+ assert.equal(element.getDiffLength(diff), 52);
+ });
+
+ test('`render` event has contentRendered field in detail', done => {
+ element = fixture('basic');
+ element.prefs = {};
+ renderStub = sandbox.stub(element.$.diffBuilder, 'render')
+ .returns(Promise.resolve());
+ element.addEventListener('render', event => {
+ assert.isTrue(event.detail.contentRendered);
+ done();
+ });
+ element._renderDiffTable();
+ });
});
a11ySuite('basic');
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
index 3de4284e80..ee1f5365d9 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -34,7 +34,7 @@ limitations under the License.
}
.arrow {
color: var(--deemphasized-text-color);
- margin: 0 .5em;
+ margin: 0 var(--spacing-m);
}
gr-dropdown-list {
--trigger-style: {
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index f913080e30..c7bf4ec34f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -31,7 +31,6 @@
Polymer({
is: 'gr-patch-range-select',
- _legacyUndefinedCheck: true,
properties: {
availablePatches: Array,
@@ -68,6 +67,17 @@
_computeBaseDropdownContent(availablePatches, patchNum, _sortedRevisions,
changeComments, revisionInfo) {
+ // Polymer 2: check for undefined
+ if ([
+ availablePatches,
+ patchNum,
+ _sortedRevisions,
+ changeComments,
+ revisionInfo,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const parentCounts = revisionInfo.getParentCountMap();
const currentParentCount = parentCounts.hasOwnProperty(patchNum) ?
parentCounts[patchNum] : 1;
@@ -111,6 +121,16 @@
_computePatchDropdownContent(availablePatches, basePatchNum,
_sortedRevisions, changeComments) {
+ // Polymer 2: check for undefined
+ if ([
+ availablePatches,
+ basePatchNum,
+ _sortedRevisions,
+ changeComments,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
const dropdownContent = [];
for (const patch of availablePatches) {
const patchNum = patch.num;
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index f2b35a5300..ee893ee387 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-patch-range-select</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.html
index c9e9f50fc8..17a4866ae7 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-ranged-comment-layer">
<template>
</template>
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
index 2183e7df05..5a035792bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
@@ -17,17 +17,14 @@
(function() {
'use strict';
- const HOVER_PATH_PATTERN = /^commentRanges\.\#(\d+)\.hovering$/;
+ // Polymer 1 adds # before array's key, while Polymer 2 doesn't
+ const HOVER_PATH_PATTERN = /^(commentRanges\.\#?\d+)\.hovering$/;
- const RANGE_HIGHLIGHT = 'range';
- const HOVER_HIGHLIGHT = 'rangeHighlight';
-
- /** @typedef {{side: string, range: Gerrit.Range, hovering: boolean}} */
- Gerrit.HoveredRange;
+ const RANGE_HIGHLIGHT = 'style-scope gr-diff range';
+ const HOVER_HIGHLIGHT = 'style-scope gr-diff rangeHighlight';
Polymer({
is: 'gr-ranged-comment-layer',
- _legacyUndefinedCheck: true,
/**
* Fired when the range in a range comment was malformed and had to be
@@ -55,6 +52,10 @@
'_handleCommentRangesChange(commentRanges.*)',
],
+ get styleModuleName() {
+ return 'gr-ranged-comment-styles';
+ },
+
/**
* Layer method to add annotations to a line.
*
@@ -130,8 +131,10 @@
// If the change only changed the `hovering` property of a comment.
const match = record.path.match(HOVER_PATH_PATTERN);
if (match) {
- const commentRangesIndex = match[1];
- const {side, range, hovering} = this.commentRanges[commentRangesIndex];
+ // The #number indicates the key of that item in the array
+ // not the index, especially in polymer 1.
+ const {side, range, hovering} = this.get(match[1]);
+
this._updateRangesMap(
side, range, hovering, (forLine, start, end, hovering) => {
const index = forLine.findIndex(lineRange =>
@@ -192,6 +195,7 @@
range.end = line.text.length;
this.dispatchEvent(new CustomEvent('normalize-range', {
bubbles: true,
+ composed: true,
detail: {lineNum, side},
}));
}
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index 682c02633b..9d207a5a89 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-ranged-comment-layer</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../gr-diff/gr-diff-line.js"></script>
@@ -130,7 +132,7 @@ limitations under the License.
assert.equal(lastCall.args[0], el);
assert.equal(lastCall.args[1], expectedStart);
assert.equal(lastCall.args[2], expectedLength);
- assert.equal(lastCall.args[3], 'range');
+ assert.equal(lastCall.args[3], 'style-scope gr-diff range');
});
test('type=Remove has-comment hovering', () => {
@@ -148,7 +150,7 @@ limitations under the License.
assert.equal(lastCall.args[0], el);
assert.equal(lastCall.args[1], expectedStart);
assert.equal(lastCall.args[2], expectedLength);
- assert.equal(lastCall.args[3], 'rangeHighlight');
+ assert.equal(lastCall.args[3], 'style-scope gr-diff rangeHighlight');
});
test('type=Both has-comment', () => {
@@ -165,7 +167,7 @@ limitations under the License.
assert.equal(lastCall.args[0], el);
assert.equal(lastCall.args[1], expectedStart);
assert.equal(lastCall.args[2], expectedLength);
- assert.equal(lastCall.args[3], 'range');
+ assert.equal(lastCall.args[3], 'style-scope gr-diff range');
});
test('type=Both has-comment off side', () => {
@@ -193,7 +195,7 @@ limitations under the License.
assert.equal(lastCall.args[0], el);
assert.equal(lastCall.args[1], expectedStart);
assert.equal(lastCall.args[2], expectedLength);
- assert.equal(lastCall.args[3], 'range');
+ assert.equal(lastCall.args[3], 'style-scope gr-diff range');
});
});
@@ -207,6 +209,7 @@ limitations under the License.
test('_handleCommentRangesChange hovering', () => {
const notifyStub = sinon.stub();
element.addListener(notifyStub);
+ const updateRangesMapSpy = sandbox.spy(element, '_updateRangesMap');
element.set(['commentRanges', 1, 'hovering'], true);
@@ -215,6 +218,8 @@ limitations under the License.
assert.equal(lastCall.args[0], 10);
assert.equal(lastCall.args[1], 12);
assert.equal(lastCall.args[2], 'right');
+
+ assert.isTrue(updateRangesMapSpy.called);
});
test('_handleCommentRangesChange splice out', () => {
@@ -251,6 +256,31 @@ limitations under the License.
assert.equal(lastCall.args[2], 'left');
});
+ test('_handleCommentRangesChange mixed actions', () => {
+ const notifyStub = sinon.stub();
+ element.addListener(notifyStub);
+ const updateRangesMapSpy = sandbox.spy(element, '_updateRangesMap');
+
+ element.set(['commentRanges', 1, 'hovering'], true);
+ assert.isTrue(updateRangesMapSpy.callCount === 1);
+ element.splice('commentRanges', 1, 1);
+ assert.isTrue(updateRangesMapSpy.callCount === 2);
+ element.splice('commentRanges', 1, 1);
+ assert.isTrue(updateRangesMapSpy.callCount === 3);
+ element.splice('commentRanges', 1, 0, {
+ side: 'left',
+ range: {
+ end_character: 15,
+ end_line: 275,
+ start_character: 5,
+ start_line: 250,
+ },
+ });
+ assert.isTrue(updateRangesMapSpy.callCount === 4);
+ element.set(['commentRanges', 2, 'hovering'], true);
+ assert.isTrue(updateRangesMapSpy.callCount === 5);
+ });
+
test('_computeCommentMap creates maps correctly', () => {
// There is only one ranged comment on the left, but it spans ll.36-39.
const leftKeys = [];
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.html
new file mode 100644
index 0000000000..cefd241cc3
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.html
@@ -0,0 +1,30 @@
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<dom-module id="gr-ranged-comment-theme">
+ <template>
+ <style>
+ .range {
+ background-color: var(--diff-highlight-range-color);
+ display: inline;
+ }
+ .rangeHighlight {
+ background-color: var(--diff-highlight-range-hover-color);
+ display: inline;
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
index 633530f47f..aa4d2e1c34 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-tooltip/gr-tooltip.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index 26bf738ee1..b1b3e0f73a 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-selection-action-box',
- _legacyUndefinedCheck: true,
/**
* Fired when the comment creation action was taken (hotkey, click).
@@ -49,6 +48,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index dece36600f..b950e7b236 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-selection-action-box</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-selection-action-box.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html
index 67c32bb641..dd6bfeccc6 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-lib-loader/gr-lib-loader.html">
<dom-module id="gr-syntax-layer">
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 944fa494d2..50cf6b469b 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -134,7 +134,6 @@
Polymer({
is: 'gr-syntax-layer',
- _legacyUndefinedCheck: true,
properties: {
diff: {
@@ -179,6 +178,10 @@
this.push('_listeners', fn);
},
+ removeListener(fn) {
+ this._listeners = this._listeners.filter(f => f != fn);
+ },
+
/**
* Annotation layer method to add syntax annotations to the given element
* for the given line.
@@ -225,7 +228,7 @@
},
/**
- * Start processing symtax for the loaded diff and notify layer listeners
+ * Start processing syntax for the loaded diff and notify layer listeners
* as syntax info comes online.
*
* @return {Promise}
@@ -233,7 +236,7 @@
process() {
// Cancel any still running process() calls, because they append to the
// same _baseRanges and _revisionRanges fields.
- this.cancel();
+ this._cancel();
// Discard existing ranges.
this._baseRanges = [];
@@ -303,7 +306,7 @@
/**
* Cancel any asynchronous syntax processing jobs.
*/
- cancel() {
+ _cancel() {
if (this._processHandle != null) {
this.cancelAsync(this._processHandle);
this._processHandle = null;
@@ -314,7 +317,7 @@
},
_diffChanged() {
- this.cancel();
+ this._cancel();
this._baseRanges = [];
this._revisionRanges = [];
},
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index b63675aafb..472db21ce3 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-syntax-layer</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
<link rel="import" href="gr-syntax-layer.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html b/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
index 10710aeea4..e5ae06d4f8 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
@@ -41,7 +41,6 @@ limitations under the License.
.gr-syntax-keyword,
.gr-syntax-name {
color: var(--syntax-keyword-color);
- line-height: 1;
}
.gr-syntax-number {
color: var(--syntax-number-color);
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.html b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.html
index 720f353447..5072b9d0cc 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.html
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.html
@@ -14,11 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
index 995326f576..f850b9d2bb 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-documentation-search',
- _legacyUndefinedCheck: true,
properties: {
/**
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
index 84addb0da5..84298e20f3 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-documentation-search</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-documentation-search.html">
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.html b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.html
index 093e979f53..19a4e639a6 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.html
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-default-editor">
@@ -25,6 +25,8 @@ limitations under the License.
border: none;
box-sizing: border-box;
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-code);
min-height: 60vh;
resize: none;
white-space: pre;
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
index 01cd9df808..ed96bb23eb 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-default-editor',
- _legacyUndefinedCheck: true,
/**
* Fired when the content of the editor changes.
@@ -32,8 +31,9 @@
},
_handleTextareaInput(e) {
- this.dispatchEvent(new CustomEvent('content-change',
- {detail: {value: e.target.value}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'content-change',
+ {detail: {value: e.target.value}, bubbles: true, composed: true}));
},
});
})();
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
index b79cd9d66b..c986e7c9b7 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
@@ -17,9 +17,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-default-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-default-editor.html">
@@ -50,7 +52,7 @@ limitations under the License.
element.addEventListener('content-change', contentChangedHandler);
textarea.value = 'test';
textarea.dispatchEvent(new CustomEvent('input',
- {target: textarea, bubbles: true}));
+ {target: textarea, bubbles: true, composed: true}));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
index 81b3c076c3..52692a7431 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -43,7 +43,7 @@ limitations under the License.
display: none;
}
gr-button {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
text-decoration: none;
}
gr-dialog {
@@ -52,22 +52,23 @@ limitations under the License.
gr-dialog .main {
width: 100%;
}
+ gr-dialog .main > iron-input{
+ width: 100%;
+ }
gr-autocomplete {
--gr-autocomplete: {
border: 1px solid var(--border-color);
- border-radius: 2px;
- font-size: var(--font-size-normal);
+ border-radius: var(--border-radius);
height: 2em;
- padding: 0 .15em;
+ padding: 0 var(--spacing-xs);
}
}
input {
border: 1px solid var(--border-color);
- border-radius: 2px;
- font-size: var(--font-size-normal);
+ border-radius: var(--border-radius);
height: 2em;
- margin: .5em 0;
- padding: 0 .15em;
+ margin: var(--spacing-m) 0;
+ padding: 0 var(--spacing-xs);
width: 100%;
}
@media screen and (max-width: 50em) {
@@ -81,7 +82,7 @@ limitations under the License.
id$="[[action.id]]"
class$="[[_computeIsInvisible(action.id, hiddenActions)]]"
link
- on-tap="_handleTap">[[action.label]]</gr-button>
+ on-click="_handleTap">[[action.label]]</gr-button>
</template>
<gr-overlay id="overlay" with-backdrop>
<gr-dialog
@@ -132,11 +133,16 @@ limitations under the License.
placeholder="Enter an existing full file path."
query="[[_query]]"
text="{{_path}}"></gr-autocomplete>
- <input
- class="newPathInput"
- is="iron-input"
+ <iron-input
+ class="newPathIronInput"
bind-value="{{_newPath}}"
- placeholder="Enter the new path."/>
+ placeholder="Enter the new path.">
+ <input
+ class="newPathInput"
+ is="iron-input"
+ bind-value="{{_newPath}}"
+ placeholder="Enter the new path.">
+ </iron-input>
</div>
</gr-dialog>
<gr-dialog
@@ -148,10 +154,14 @@ limitations under the License.
on-cancel="_handleDialogCancel">
<div class="header" slot="header">Restore this file?</div>
<div class="main" slot="main">
- <input
- is="iron-input"
+ <iron-input
disabled
- bind-value="{{_path}}"/>
+ bind-value="{{_path}}">
+ <input
+ is="iron-input"
+ disabled
+ bind-value="{{_path}}">
+ </iron-input>
</div>
</gr-dialog>
</gr-overlay>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
index 3567d06f10..b7e12fe3b6 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-edit-controls',
- _legacyUndefinedCheck: true,
+
properties: {
change: Object,
patchNum: String,
@@ -170,7 +170,8 @@
// just make two separate queries.
dialog.querySelectorAll('gr-autocomplete')
.forEach(input => { input.text = ''; });
- dialog.querySelectorAll('input')
+
+ dialog.querySelectorAll('iron-input')
.forEach(input => { input.bindValue = ''; });
}
@@ -190,28 +191,33 @@
},
_handleDeleteConfirm(e) {
+ // Get the dialog before the api call as the event will change during bubbling
+ // which will make Polymer.dom(e).path an emtpy array in polymer 2
+ const dialog = this._getDialogFromEvent(e);
this.$.restAPI.deleteFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res.ok) { return; }
- this._closeDialog(this._getDialogFromEvent(e), true);
+ this._closeDialog(dialog, true);
Gerrit.Nav.navigateToChange(this.change);
});
},
_handleRestoreConfirm(e) {
+ const dialog = this._getDialogFromEvent(e);
this.$.restAPI.restoreFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res.ok) { return; }
- this._closeDialog(this._getDialogFromEvent(e), true);
+ this._closeDialog(dialog, true);
Gerrit.Nav.navigateToChange(this.change);
});
},
_handleRenameConfirm(e) {
+ const dialog = this._getDialogFromEvent(e);
return this.$.restAPI.renameFileInChangeEdit(this.change._number,
this._path, this._newPath).then(res => {
if (!res.ok) { return; }
- this._closeDialog(this._getDialogFromEvent(e), true);
+ this._closeDialog(dialog, true);
Gerrit.Nav.navigateToChange(this.change);
});
},
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index c67a2afdb8..dd8cb74841 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -17,9 +17,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-edit-controls</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-edit-controls.html">
@@ -189,6 +191,9 @@ suite('gr-edit-controls tests', () => {
let navStub;
let renameStub;
let renameAutocomplete;
+ const inputSelector = Polymer.Element ?
+ '.newPathIronInput' :
+ '.newPathInput';
setup(() => {
navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
@@ -208,7 +213,7 @@ suite('gr-edit-controls tests', () => {
assert.isTrue(queryStub.called);
assert.isTrue(element.$.renameDialog.disabled);
- element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ element.$.renameDialog.querySelector(inputSelector).bindValue =
'src/test.newPath';
assert.isFalse(element.$.renameDialog.disabled);
@@ -236,7 +241,7 @@ suite('gr-edit-controls tests', () => {
assert.isTrue(queryStub.called);
assert.isTrue(element.$.renameDialog.disabled);
- element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ element.$.renameDialog.querySelector(inputSelector).bindValue =
'src/test.newPath';
assert.isFalse(element.$.renameDialog.disabled);
@@ -258,7 +263,7 @@ suite('gr-edit-controls tests', () => {
assert.isTrue(element.$.renameDialog.disabled);
element.$.renameDialog.querySelector('gr-autocomplete').text =
'src/test.cpp';
- element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ element.$.renameDialog.querySelector(inputSelector).bindValue =
'src/test.newPath';
assert.isFalse(element.$.renameDialog.disabled);
MockInteractions.tap(element.$.renameDialog.$$('gr-button'));
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
index c57a147c12..f6c7803194 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
@@ -32,7 +32,7 @@ limitations under the License.
justify-content: flex-end;
}
#actions {
- margin-right: 1em;
+ margin-right: var(--spacing-l);
}
gr-button,
gr-dropdown {
@@ -45,7 +45,6 @@ limitations under the License.
background-color: transparent;
border: none;
color: var(--link-color);
- font-size: inherit;
text-transform: uppercase;
}
}
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
index 9407f18ca6..250816b970 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-edit-file-controls',
- _legacyUndefinedCheck: true,
/**
* Fired when an action in the overflow menu is tapped.
@@ -46,8 +45,9 @@
},
_dispatchFileAction(action, path) {
- this.dispatchEvent(new CustomEvent('file-action-tap',
- {detail: {action, path}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'file-action-tap',
+ {detail: {action, path}, bubbles: true, composed: true}));
},
_computeFileActions(actions) {
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
index 12d9e0b182..7979e57f33 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -17,9 +17,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-edit-file-controls</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../gr-edit-constants.html">
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
index b107221fed..ce90c69764 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
@@ -47,23 +48,23 @@ limitations under the License.
align-items: center;
display: flex;
justify-content: space-between;
- padding: .75em var(--default-horizontal-margin);
+ padding: var(--spacing-m) var(--spacing-l);
}
header gr-editable-label {
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
--label-style: {
text-overflow: initial;
white-space: initial;
word-break: break-all;
}
--input-style: {
- margin-top: 1em;
+ margin-top: var(--spacing-l);
}
}
.textareaWrapper {
border: 1px solid var(--border-color);
- border-radius: 3px;
- margin: var(--default-horizontal-margin);
+ border-radius: var(--border-radius);
+ margin: var(--spacing-l);
}
.textareaWrapper .editButtons {
display: none;
@@ -71,7 +72,7 @@ limitations under the License.
.controlGroup {
align-items: center;
display: flex;
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
}
.rightControls {
justify-content: flex-end;
@@ -101,13 +102,13 @@ limitations under the License.
<gr-button
id="close"
link
- on-tap="_handleCloseTap">Close</gr-button>
+ on-click="_handleCloseTap">Close</gr-button>
<gr-button
id="save"
disabled$="[[_saveDisabled]]"
primary
link
- on-tap="_saveEdit">Save</gr-button>
+ on-click="_saveEdit">Save</gr-button>
</span>
</header>
</gr-fixed-panel>
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index 3ed165e6b4..a21975d9b2 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -26,7 +26,6 @@
Polymer({
is: 'gr-editor-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -75,6 +74,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.PathListBehavior,
@@ -105,7 +105,9 @@
},
_paramsChanged(value) {
- if (value.view !== Gerrit.Nav.View.EDIT) { return; }
+ if (value.view !== Gerrit.Nav.View.EDIT) {
+ return;
+ }
this._changeNum = value.changeNum;
this._path = value.path;
@@ -136,7 +138,9 @@
_handlePathChanged(e) {
const path = e.detail;
- if (path === this._path) { return Promise.resolve(); }
+ if (path === this._path) {
+ return Promise.resolve();
+ }
return this.$.restAPI.renameFileInChangeEdit(this._changeNum,
this._path, path).then(res => {
if (!res.ok) { return; }
@@ -160,8 +164,11 @@
.then(res => {
if (storedContent && storedContent.message &&
storedContent.message !== res.content) {
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message: RESTORED_MESSAGE}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {message: RESTORED_MESSAGE},
+ bubbles: true,
+ composed: true,
+ }));
this._newContent = storedContent.message;
} else {
@@ -199,11 +206,23 @@
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message},
bubbles: true,
+ composed: true,
}));
},
_computeSaveDisabled(content, newContent, saving) {
- if (saving) { return true; }
+ // Polymer 2: check for undefined
+ if ([
+ content,
+ newContent,
+ saving,
+ ].some(arg => arg === undefined)) {
+ return true;
+ }
+
+ if (saving) {
+ return true;
+ }
return content === newContent;
},
@@ -226,7 +245,9 @@
_handleSaveShortcut(e) {
e.preventDefault();
- if (!this._saveDisabled) { this._saveEdit(); }
+ if (!this._saveDisabled) {
+ this._saveEdit();
+ }
},
});
})();
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 010ff4adec..226472fae3 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -17,9 +17,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-editor-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-editor-view.html">
@@ -121,7 +123,7 @@ suite('gr-editor-view tests', () => {
const storeStub = sandbox.spy(element.$.storage, 'setEditableContentItem');
element._newContent = 'test';
element.$.editorEndpoint.dispatchEvent(new CustomEvent('content-change', {
- bubbles: true,
+ bubbles: true, composed: true,
detail: {value: 'new content value'},
}));
element.flushDebouncer('store');
diff --git a/polygerrit-ui/app/elements/gr-app-element.html b/polygerrit-ui/app/elements/gr-app-element.html
new file mode 100644
index 0000000000..046e5ffd45
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-app-element.html
@@ -0,0 +1,238 @@
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<script src="/bower_components/moment/moment.js"></script>
+<script src="../scripts/util.js"></script>
+
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
+<link rel="import" href="../styles/shared-styles.html">
+<link rel="import" href="../styles/themes/app-theme.html">
+<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
+<link rel="import" href="./documentation/gr-documentation-search/gr-documentation-search.html">
+<link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html">
+<link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html">
+<link rel="import" href="./change/gr-change-view/gr-change-view.html">
+<link rel="import" href="./core/gr-error-manager/gr-error-manager.html">
+<link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html">
+<link rel="import" href="./core/gr-main-header/gr-main-header.html">
+<link rel="import" href="./core/gr-navigation/gr-navigation.html">
+<link rel="import" href="./core/gr-reporting/gr-reporting.html">
+<link rel="import" href="./core/gr-router/gr-router.html">
+<link rel="import" href="./core/gr-smart-search/gr-smart-search.html">
+<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
+<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="./plugins/gr-endpoint-param/gr-endpoint-param.html">
+<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
+<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
+<link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
+<link rel="import" href="./settings/gr-registration-dialog/gr-registration-dialog.html">
+<link rel="import" href="./settings/gr-settings-view/gr-settings-view.html">
+<link rel="import" href="./shared/gr-fixed-panel/gr-fixed-panel.html">
+<link rel="import" href="./shared/gr-lib-loader/gr-lib-loader.html">
+<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<dom-module id="gr-app-element">
+ <template>
+ <style include="shared-styles">
+ :host {
+ background-color: var(--view-background-color);
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+ }
+ gr-fixed-panel {
+ /**
+ * This one should be greater that the z-index in gr-diff-view
+ * because gr-main-header contains overlay.
+ */
+ z-index: 10;
+ }
+ gr-main-header,
+ footer {
+ color: var(--primary-text-color);
+ }
+ gr-main-header {
+ background: var(--header-background, var(--header-background-color, #eee));
+ padding: var(--header-padding);
+ border-bottom: var(--header-border-bottom);
+ border-image: var(--header-border-image);
+ border-right: 0;
+ border-left: 0;
+ border-top: 0;
+ box-shadow: var(--header-box-shadow);
+ }
+ footer {
+ background: var(--footer-background, var(--footer-background-color, #eee));
+ border-top: var(--footer-border-top);
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-m) var(--spacing-l);
+ z-index: 100;
+ }
+ main {
+ flex: 1;
+ padding-bottom: var(--spacing-xxl);
+ position: relative;
+ }
+ .errorView {
+ align-items: center;
+ display: none;
+ flex-direction: column;
+ justify-content: center;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+ .errorView.show {
+ display: flex;
+ }
+ .errorEmoji {
+ font-size: 2.6rem;
+ }
+ .errorText,
+ .errorMoreInfo {
+ margin-top: var(--spacing-m);
+ }
+ .errorText {
+ font-size: var(--font-size-h3);
+ }
+ .errorMoreInfo {
+ color: var(--deemphasized-text-color);
+ }
+ .feedback {
+ color: var(--error-text-color);
+ }
+ </style>
+ <gr-endpoint-decorator name="banner"></gr-endpoint-decorator>
+ <gr-fixed-panel id="header">
+ <gr-main-header
+ id="mainHeader"
+ search-query="{{params.query}}"
+ on-mobile-search="_mobileSearchToggle">
+ </gr-main-header>
+ </gr-fixed-panel>
+ <main>
+ <gr-smart-search
+ id="search"
+ search-query="{{params.query}}"
+ hidden="[[!mobileSearch]]">
+ </gr-smart-search>
+ <template is="dom-if" if="[[_showChangeListView]]" restamp="true">
+ <gr-change-list-view
+ params="[[params]]"
+ account="[[_account]]"
+ view-state="{{_viewState.changeListView}}"></gr-change-list-view>
+ </template>
+ <template is="dom-if" if="[[_showDashboardView]]" restamp="true">
+ <gr-dashboard-view
+ account="[[_account]]"
+ params="[[params]]"
+ view-state="{{_viewState.dashboardView}}"></gr-dashboard-view>
+ </template>
+ <template is="dom-if" if="[[_showChangeView]]" restamp="true">
+ <gr-change-view
+ params="[[params]]"
+ view-state="{{_viewState.changeView}}"
+ back-page="[[_lastSearchPage]]"></gr-change-view>
+ </template>
+ <template is="dom-if" if="[[_showEditorView]]" restamp="true">
+ <gr-editor-view
+ params="[[params]]"></gr-editor-view>
+ </template>
+ <template is="dom-if" if="[[_showDiffView]]" restamp="true">
+ <gr-diff-view
+ params="[[params]]"
+ change-view-state="{{_viewState.changeView}}"></gr-diff-view>
+ </template>
+ <template is="dom-if" if="[[_showSettingsView]]" restamp="true">
+ <gr-settings-view
+ params="[[params]]"
+ on-account-detail-update="_handleAccountDetailUpdate">
+ </gr-settings-view>
+ </template>
+ <template is="dom-if" if="[[_showAdminView]]" restamp="true">
+ <gr-admin-view path="[[_path]]"
+ params=[[params]]></gr-admin-view>
+ </template>
+ <template is="dom-if" if="[[_showPluginScreen]]" restamp="true">
+ <gr-endpoint-decorator name="[[_pluginScreenName]]">
+ <gr-endpoint-param name="token" value="[[params.screen]]"></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ </template>
+ <template is="dom-if" if="[[_showCLAView]]" restamp="true">
+ <gr-cla-view></gr-cla-view>
+ </template>
+ <template is="dom-if" if="[[_showDocumentationSearch]]" restamp="true">
+ <gr-documentation-search
+ params="[[params]]">
+ </gr-documentation-search>
+ </template>
+ <div id="errorView" class="errorView">
+ <div class="errorEmoji">[[_lastError.emoji]]</div>
+ <div class="errorText">[[_lastError.text]]</div>
+ <div class="errorMoreInfo">[[_lastError.moreInfo]]</div>
+ </div>
+ </main>
+ <footer r="contentinfo">
+ <div>
+ Powered by <a href="https://www.gerritcodereview.com/" rel="noopener"
+ target="_blank">Gerrit Code Review</a>
+ ([[_version]])
+ <gr-endpoint-decorator name="footer-left"></gr-endpoint-decorator>
+ </div>
+ <div>
+ <template is="dom-if" if="[[_feedbackUrl]]">
+ <a class="feedback"
+ href$="[[_feedbackUrl]]"
+ rel="noopener"
+ target="_blank">Report bug</a> |
+ </template>
+ Press &ldquo;?&rdquo; for keyboard shortcuts
+ <gr-endpoint-decorator name="footer-right"></gr-endpoint-decorator>
+ </div>
+ </footer>
+ <gr-overlay id="keyboardShortcuts" with-backdrop>
+ <gr-keyboard-shortcuts-dialog
+ view="[[params.view]]"
+ on-close="_handleKeyboardShortcutDialogClose"></gr-keyboard-shortcuts-dialog>
+ </gr-overlay>
+ <gr-overlay id="registrationOverlay" with-backdrop>
+ <gr-registration-dialog
+ id="registrationDialog"
+ settings-url="[[_settingsUrl]]"
+ on-account-detail-update="_handleAccountDetailUpdate"
+ on-close="_handleRegistrationDialogClose">
+ </gr-registration-dialog>
+ </gr-overlay>
+ <gr-endpoint-decorator name="plugin-overlay"></gr-endpoint-decorator>
+ <gr-error-manager id="errorManager"></gr-error-manager>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-reporting id="reporting"></gr-reporting>
+ <gr-router id="router"></gr-router>
+ <gr-plugin-host id="plugins"
+ config="[[_serverConfig]]">
+ </gr-plugin-host>
+ <gr-lib-loader id="libLoader"></gr-lib-loader>
+ <gr-external-style id="externalStyleForAll" name="app-theme"></gr-external-style>
+ <gr-external-style id="externalStyleForTheme" name="[[getThemeEndpoint()]]"></gr-external-style>
+ </template>
+ <script src="gr-app-element.js" crossorigin="anonymous"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
new file mode 100644
index 0000000000..ce6b98bedc
--- /dev/null
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -0,0 +1,468 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-app-element',
+
+ /**
+ * Fired when the URL location changes.
+ *
+ * @event location-change
+ */
+
+ properties: {
+ /**
+ * @type {{ query: string, view: string, screen: string }}
+ */
+ params: Object,
+ keyEventTarget: {
+ type: Object,
+ value() { return document.body; },
+ },
+
+ _account: {
+ type: Object,
+ observer: '_accountChanged',
+ },
+
+ /**
+ * The last time the g key was pressed in milliseconds (or a keydown event
+ * was handled if the key is held down).
+ *
+ * @type {number|null}
+ */
+ _lastGKeyPressTimestamp: {
+ type: Number,
+ value: null,
+ },
+
+ /**
+ * @type {{ plugin: Object }}
+ */
+ _serverConfig: Object,
+ _version: String,
+ _showChangeListView: Boolean,
+ _showDashboardView: Boolean,
+ _showChangeView: Boolean,
+ _showDiffView: Boolean,
+ _showSettingsView: Boolean,
+ _showAdminView: Boolean,
+ _showCLAView: Boolean,
+ _showEditorView: Boolean,
+ _showPluginScreen: Boolean,
+ _showDocumentationSearch: Boolean,
+ /** @type {?} */
+ _viewState: Object,
+ /** @type {?} */
+ _lastError: Object,
+ _lastSearchPage: String,
+ _path: String,
+ _pluginScreenName: {
+ type: String,
+ computed: '_computePluginScreenName(params)',
+ },
+ _settingsUrl: String,
+ _feedbackUrl: String,
+ // Used to allow searching on mobile
+ mobileSearch: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ listeners: {
+ 'page-error': '_handlePageError',
+ 'title-change': '_handleTitleChange',
+ 'location-change': '_handleLocationChange',
+ 'rpc-log': '_handleRpcLog',
+ },
+
+ observers: [
+ '_viewChanged(params.view)',
+ '_paramsChanged(params.*)',
+ ],
+
+ behaviors: [
+ Gerrit.BaseUrlBehavior,
+ Gerrit.KeyboardShortcutBehavior,
+ ],
+
+ keyboardShortcuts() {
+ return {
+ [this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG]: '_showKeyboardShortcuts',
+ [this.Shortcut.GO_TO_USER_DASHBOARD]: '_goToUserDashboard',
+ [this.Shortcut.GO_TO_OPENED_CHANGES]: '_goToOpenedChanges',
+ [this.Shortcut.GO_TO_MERGED_CHANGES]: '_goToMergedChanges',
+ [this.Shortcut.GO_TO_ABANDONED_CHANGES]: '_goToAbandonedChanges',
+ [this.Shortcut.GO_TO_WATCHED_CHANGES]: '_goToWatchedChanges',
+ };
+ },
+
+ created() {
+ this._bindKeyboardShortcuts();
+ },
+
+ ready() {
+ this.$.reporting.appStarted(document.visibilityState === 'hidden');
+ this.$.router.start();
+
+ this.$.restAPI.getAccount().then(account => {
+ this._account = account;
+ });
+ this.$.restAPI.getConfig().then(config => {
+ this._serverConfig = config;
+
+ if (config && config.gerrit && config.gerrit.report_bug_url) {
+ this._feedbackUrl = config.gerrit.report_bug_url;
+ }
+ });
+ this.$.restAPI.getVersion().then(version => {
+ this._version = version;
+ this._logWelcome();
+ });
+
+ if (window.localStorage.getItem('dark-theme')) {
+ // No need to add the style module to element again as it's imported
+ // by importHref already
+ this.$.libLoader.getDarkTheme();
+ }
+
+ // Note: this is evaluated here to ensure that it only happens after the
+ // router has been initialized. @see Issue 7837
+ this._settingsUrl = Gerrit.Nav.getUrlForSettings();
+
+ this._viewState = {
+ changeView: {
+ changeNum: null,
+ patchRange: null,
+ selectedFileIndex: 0,
+ showReplyDialog: false,
+ diffMode: null,
+ numFilesShown: null,
+ scrollTop: 0,
+ },
+ changeListView: {
+ query: null,
+ offset: 0,
+ selectedChangeIndex: 0,
+ },
+ dashboardView: {
+ selectedChangeIndex: 0,
+ },
+ };
+ },
+
+ _bindKeyboardShortcuts() {
+ this.bindShortcut(this.Shortcut.SEND_REPLY,
+ this.DOC_ONLY, 'ctrl+enter', 'meta+enter');
+ this.bindShortcut(this.Shortcut.EMOJI_DROPDOWN,
+ this.DOC_ONLY, ':');
+
+ this.bindShortcut(
+ this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG, '?');
+ this.bindShortcut(
+ this.Shortcut.GO_TO_USER_DASHBOARD, this.GO_KEY, 'i');
+ this.bindShortcut(
+ this.Shortcut.GO_TO_OPENED_CHANGES, this.GO_KEY, 'o');
+ this.bindShortcut(
+ this.Shortcut.GO_TO_MERGED_CHANGES, this.GO_KEY, 'm');
+ this.bindShortcut(
+ this.Shortcut.GO_TO_ABANDONED_CHANGES, this.GO_KEY, 'a');
+ this.bindShortcut(
+ this.Shortcut.GO_TO_WATCHED_CHANGES, this.GO_KEY, 'w');
+
+ this.bindShortcut(
+ this.Shortcut.CURSOR_NEXT_CHANGE, 'j');
+ this.bindShortcut(
+ this.Shortcut.CURSOR_PREV_CHANGE, 'k');
+ this.bindShortcut(
+ this.Shortcut.OPEN_CHANGE, 'o');
+ this.bindShortcut(
+ this.Shortcut.NEXT_PAGE, 'n', ']');
+ this.bindShortcut(
+ this.Shortcut.PREV_PAGE, 'p', '[');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_CHANGE_REVIEWED, 'r:keyup');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_CHANGE_STAR, 's:keyup');
+ this.bindShortcut(
+ this.Shortcut.REFRESH_CHANGE_LIST, 'shift+r:keyup');
+ this.bindShortcut(
+ this.Shortcut.EDIT_TOPIC, 't');
+
+ this.bindShortcut(
+ this.Shortcut.OPEN_REPLY_DIALOG, 'a');
+ this.bindShortcut(
+ this.Shortcut.OPEN_DOWNLOAD_DIALOG, 'd');
+ this.bindShortcut(
+ this.Shortcut.EXPAND_ALL_MESSAGES, 'x');
+ this.bindShortcut(
+ this.Shortcut.COLLAPSE_ALL_MESSAGES, 'z');
+ this.bindShortcut(
+ this.Shortcut.REFRESH_CHANGE, 'shift+r:keyup');
+ this.bindShortcut(
+ this.Shortcut.UP_TO_DASHBOARD, 'u');
+ this.bindShortcut(
+ this.Shortcut.UP_TO_CHANGE, 'u');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_DIFF_MODE, 'm:keyup');
+
+ this.bindShortcut(
+ this.Shortcut.NEXT_LINE, 'j', 'down');
+ this.bindShortcut(
+ this.Shortcut.PREV_LINE, 'k', 'up');
+ this.bindShortcut(
+ this.Shortcut.NEXT_CHUNK, 'n');
+ this.bindShortcut(
+ this.Shortcut.PREV_CHUNK, 'p');
+ this.bindShortcut(
+ this.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x');
+ this.bindShortcut(
+ this.Shortcut.NEXT_COMMENT_THREAD, 'shift+n');
+ this.bindShortcut(
+ this.Shortcut.PREV_COMMENT_THREAD, 'shift+p');
+ this.bindShortcut(
+ this.Shortcut.EXPAND_ALL_COMMENT_THREADS, this.DOC_ONLY, 'e');
+ this.bindShortcut(
+ this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS,
+ this.DOC_ONLY, 'shift+e');
+ this.bindShortcut(
+ this.Shortcut.LEFT_PANE, 'shift+left');
+ this.bindShortcut(
+ this.Shortcut.RIGHT_PANE, 'shift+right');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_LEFT_PANE, 'shift+a');
+ this.bindShortcut(
+ this.Shortcut.NEW_COMMENT, 'c');
+ this.bindShortcut(
+ this.Shortcut.SAVE_COMMENT,
+ 'ctrl+enter', 'meta+enter', 'ctrl+s', 'meta+s');
+ this.bindShortcut(
+ this.Shortcut.OPEN_DIFF_PREFS, ',');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_DIFF_REVIEWED, 'r:keyup');
+
+ this.bindShortcut(
+ this.Shortcut.NEXT_FILE, ']');
+ this.bindShortcut(
+ this.Shortcut.PREV_FILE, '[');
+ this.bindShortcut(
+ this.Shortcut.NEXT_FILE_WITH_COMMENTS, 'shift+j');
+ this.bindShortcut(
+ this.Shortcut.PREV_FILE_WITH_COMMENTS, 'shift+k');
+ this.bindShortcut(
+ this.Shortcut.CURSOR_NEXT_FILE, 'j', 'down');
+ this.bindShortcut(
+ this.Shortcut.CURSOR_PREV_FILE, 'k', 'up');
+ this.bindShortcut(
+ this.Shortcut.OPEN_FILE, 'o', 'enter');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_FILE_REVIEWED, 'r:keyup');
+ this.bindShortcut(
+ this.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_ALL_INLINE_DIFFS, 'shift+i:keyup');
+ this.bindShortcut(
+ this.Shortcut.TOGGLE_INLINE_DIFF, 'i:keyup');
+
+ this.bindShortcut(
+ this.Shortcut.OPEN_FIRST_FILE, ']');
+ this.bindShortcut(
+ this.Shortcut.OPEN_LAST_FILE, '[');
+
+ this.bindShortcut(
+ this.Shortcut.SEARCH, '/');
+ },
+
+ _accountChanged(account) {
+ if (!account) { return; }
+
+ // Preferences are cached when a user is logged in; warm them.
+ this.$.restAPI.getPreferences();
+ this.$.restAPI.getDiffPreferences();
+ this.$.restAPI.getEditPreferences();
+ this.$.errorManager.knownAccountId =
+ this._account && this._account._account_id || null;
+ },
+
+ _viewChanged(view) {
+ this.$.errorView.classList.remove('show');
+ this.set('_showChangeListView', view === Gerrit.Nav.View.SEARCH);
+ this.set('_showDashboardView', view === Gerrit.Nav.View.DASHBOARD);
+ this.set('_showChangeView', view === Gerrit.Nav.View.CHANGE);
+ this.set('_showDiffView', view === Gerrit.Nav.View.DIFF);
+ this.set('_showSettingsView', view === Gerrit.Nav.View.SETTINGS);
+ this.set('_showAdminView', view === Gerrit.Nav.View.ADMIN ||
+ view === Gerrit.Nav.View.GROUP || view === Gerrit.Nav.View.REPO);
+ this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
+ this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
+ const isPluginScreen = view === Gerrit.Nav.View.PLUGIN_SCREEN;
+ this.set('_showPluginScreen', false);
+ // Navigation within plugin screens does not restamp gr-endpoint-decorator
+ // because _showPluginScreen value does not change. To force restamp,
+ // change _showPluginScreen value between true and false.
+ if (isPluginScreen) {
+ this.async(() => this.set('_showPluginScreen', true), 1);
+ }
+ this.set('_showDocumentationSearch',
+ view === Gerrit.Nav.View.DOCUMENTATION_SEARCH);
+ if (this.params.justRegistered) {
+ this.$.registrationOverlay.open();
+ this.$.registrationDialog.loadData().then(() => {
+ this.$.registrationOverlay.refit();
+ });
+ }
+ this.$.header.unfloat();
+ },
+
+ _handlePageError(e) {
+ const props = [
+ '_showChangeListView',
+ '_showDashboardView',
+ '_showChangeView',
+ '_showDiffView',
+ '_showSettingsView',
+ '_showAdminView',
+ ];
+ for (const showProp of props) {
+ this.set(showProp, false);
+ }
+
+ this.$.errorView.classList.add('show');
+ const response = e.detail.response;
+ const err = {text: [response.status, response.statusText].join(' ')};
+ if (response.status === 404) {
+ err.emoji = '¯\\_(ツ)_/¯';
+ this._lastError = err;
+ } else {
+ err.emoji = 'o_O';
+ response.text().then(text => {
+ err.moreInfo = text;
+ this._lastError = err;
+ });
+ }
+ },
+
+ _handleLocationChange(e) {
+ const hash = e.detail.hash.substring(1);
+ let pathname = e.detail.pathname;
+ if (pathname.startsWith('/c/') && parseInt(hash, 10) > 0) {
+ pathname += '@' + hash;
+ }
+ this.set('_path', pathname);
+ },
+
+ _paramsChanged(paramsRecord) {
+ const params = paramsRecord.base;
+ const viewsToCheck = [Gerrit.Nav.View.SEARCH, Gerrit.Nav.View.DASHBOARD];
+ if (viewsToCheck.includes(params.view)) {
+ this.set('_lastSearchPage', location.pathname);
+ }
+ },
+
+ _handleTitleChange(e) {
+ if (e.detail.title) {
+ document.title = e.detail.title + ' · Gerrit Code Review';
+ } else {
+ document.title = '';
+ }
+ },
+
+ _showKeyboardShortcuts(e) {
+ if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+ this.$.keyboardShortcuts.open();
+ },
+
+ _handleKeyboardShortcutDialogClose() {
+ this.$.keyboardShortcuts.close();
+ },
+
+ _handleAccountDetailUpdate(e) {
+ this.$.mainHeader.reload();
+ if (this.params.view === Gerrit.Nav.View.SETTINGS) {
+ this.$$('gr-settings-view').reloadAccountDetail();
+ }
+ },
+
+ _handleRegistrationDialogClose(e) {
+ this.params.justRegistered = false;
+ this.$.registrationOverlay.close();
+ },
+
+ _goToOpenedChanges() {
+ Gerrit.Nav.navigateToStatusSearch('open');
+ },
+
+ _goToUserDashboard() {
+ Gerrit.Nav.navigateToUserDashboard();
+ },
+
+ _goToMergedChanges() {
+ Gerrit.Nav.navigateToStatusSearch('merged');
+ },
+
+ _goToAbandonedChanges() {
+ Gerrit.Nav.navigateToStatusSearch('abandoned');
+ },
+
+ _goToWatchedChanges() {
+ // The query is hardcoded, and doesn't respect custom menu entries
+ Gerrit.Nav.navigateToSearchQuery('is:watched is:open');
+ },
+
+ _computePluginScreenName({plugin, screen}) {
+ if (!plugin || !screen) return '';
+ return `${plugin}-screen-${screen}`;
+ },
+
+ _logWelcome() {
+ console.group('Runtime Info');
+ console.log('Gerrit UI (PolyGerrit)');
+ console.log(`Gerrit Server Version: ${this._version}`);
+ if (window.VERSION_INFO) {
+ console.log(`UI Version Info: ${window.VERSION_INFO}`);
+ }
+ if (this._feedbackUrl) {
+ console.log(`Please file bugs and feedback at: ${this._feedbackUrl}`);
+ }
+ console.groupEnd();
+ },
+
+ /**
+ * Intercept RPC log events emitted by REST API interfaces.
+ * Note: the REST API interface cannot use gr-reporting directly because
+ * that would create a cyclic dependency.
+ */
+ _handleRpcLog(e) {
+ this.$.reporting.reportRpcTiming(e.detail.anonymizedUrl,
+ e.detail.elapsed);
+ },
+
+ _mobileSearchToggle(e) {
+ this.mobileSearch = !this.mobileSearch;
+ },
+
+ getThemeEndpoint() {
+ // For now, we only have dark mode and light mode
+ return window.localStorage.getItem('dark-theme') ?
+ 'app-theme-dark' :
+ 'app-theme-light';
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 8c50358992..f49d8aaea0 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -15,28 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<script>
- if (!window.POLYMER2) {
- // This must be set prior to loading Polymer for the first time.
- if (localStorage.getItem('USE_SHADOW_DOM') === 'true') {
- window.Polymer = {
- dom: 'shadow',
- passiveTouchGestures: true,
- };
- } else if (!window.Polymer) {
- window.Polymer = {
- passiveTouchGestures: true,
- };
- }
+ if (!window.Polymer) {
+ window.Polymer = {
+ passiveTouchGestures: true,
+ lazyRegister: true,
+ };
}
- // Needed for JSCompiler to understand it's global.
- // eslint-disable-next-line no-unused-vars, prefer-const
- let Gerrit = window.Gerrit || {};
- window.Gerrit = Gerrit;
+ window.Gerrit = window.Gerrit || {};
</script>
-<link rel="import" href="../bower_components/polymer/polymer.html">
-<link rel="import" href="../bower_components/polymer-resin/standalone/polymer-resin.html">
-<link rel="import" href="../bower_components/polymer/lib/legacy/legacy-data-mixin.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer-resin/standalone/polymer-resin.html">
+<!-- TODO(taoalpha): Remove once all legacyUndefinedCheck removed. -->
<link rel="import" href="../behaviors/safe-types-behavior/safe-types-behavior.html">
<script>
security.polymer_resin.install({
@@ -45,228 +35,11 @@ limitations under the License.
safeTypesBridge: Gerrit.SafeTypes.safeTypesBridge,
});
</script>
-<script src="../bower_components/moment/moment.js"></script>
-
-<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../styles/shared-styles.html">
-<link rel="import" href="../styles/themes/app-theme.html">
-<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
-<link rel="import" href="./documentation/gr-documentation-search/gr-documentation-search.html">
-<link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html">
-<link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html">
-<link rel="import" href="./change/gr-change-view/gr-change-view.html">
-<link rel="import" href="./core/gr-error-manager/gr-error-manager.html">
-<link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html">
-<link rel="import" href="./core/gr-main-header/gr-main-header.html">
-<link rel="import" href="./core/gr-navigation/gr-navigation.html">
-<link rel="import" href="./core/gr-reporting/gr-reporting.html">
-<link rel="import" href="./core/gr-router/gr-router.html">
-<link rel="import" href="./core/gr-smart-search/gr-smart-search.html">
-<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
-<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
-<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
-<link rel="import" href="./plugins/gr-endpoint-param/gr-endpoint-param.html">
-<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
-<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
-<link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
-<link rel="import" href="./settings/gr-registration-dialog/gr-registration-dialog.html">
-<link rel="import" href="./settings/gr-settings-view/gr-settings-view.html">
-<link rel="import" href="./shared/gr-fixed-panel/gr-fixed-panel.html">
-<link rel="import" href="./shared/gr-lib-loader/gr-lib-loader.html">
-<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
-
-<script src="../scripts/util.js"></script>
+<link rel="import" href="./gr-app-element.html">
<dom-module id="gr-app">
<template>
- <style include="shared-styles">
- :host {
- background-color: var(--view-background-color);
- display: flex;
- flex-direction: column;
- min-height: 100%;
- }
- gr-fixed-panel {
- /**
- * This one should be greater that the z-index in gr-diff-view
- * because gr-main-header contains overlay.
- */
- z-index: 10;
- }
- gr-main-header,
- footer {
- color: var(--primary-text-color);
- }
- gr-main-header {
- background: var(--header-background, var(--header-background-color, #eee));
- padding: 0 var(--default-horizontal-margin);
- border-bottom: var(--header-border-bottom);
- border-image: var(--header-border-image);
- border-right: 0;
- border-left: 0;
- border-top: 0;
- }
- gr-main-header.shadow {
- /* Make it obvious for shadow dom testing */
- border-bottom: 1px solid pink;
- }
- footer {
- background: var(--footer-background, var(--footer-background-color, #eee));
- border-top: var(--footer-border-top);
- display: flex;
- justify-content: space-between;
- padding: .5rem var(--default-horizontal-margin);
- z-index: 100;
- }
- main {
- flex: 1;
- padding-bottom: 2em;
- position: relative;
- }
- .errorView {
- align-items: center;
- display: none;
- flex-direction: column;
- justify-content: center;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- }
- .errorView.show {
- display: flex;
- }
- .errorEmoji {
- font-size: 2.6rem;
- }
- .errorText,
- .errorMoreInfo {
- margin-top: .75em;
- }
- .errorText {
- font-size: 1.2rem;
- }
- .errorMoreInfo {
- color: var(--deemphasized-text-color);
- }
- .feedback {
- color: var(--error-text-color);
- }
- </style>
- <gr-endpoint-decorator name="banner"></gr-endpoint-decorator>
- <gr-fixed-panel id="header">
- <gr-main-header
- id="mainHeader"
- search-query="{{params.query}}"
- class$="[[_computeShadowClass(_isShadowDom)]]"
- on-mobile-search="_mobileSearchToggle">
- </gr-main-header>
- </gr-fixed-panel>
- <main>
- <gr-smart-search
- id="search"
- search-query="{{params.query}}"
- hidden="[[!mobileSearch]]">
- </gr-smart-search>
- <template is="dom-if" if="[[_showChangeListView]]" restamp="true">
- <gr-change-list-view
- params="[[params]]"
- account="[[_account]]"
- view-state="{{_viewState.changeListView}}"></gr-change-list-view>
- </template>
- <template is="dom-if" if="[[_showDashboardView]]" restamp="true">
- <gr-dashboard-view
- account="[[_account]]"
- params="[[params]]"
- view-state="{{_viewState.dashboardView}}"></gr-dashboard-view>
- </template>
- <template is="dom-if" if="[[_showChangeView]]" restamp="true">
- <gr-change-view
- params="[[params]]"
- view-state="{{_viewState.changeView}}"
- back-page="[[_lastSearchPage]]"></gr-change-view>
- </template>
- <template is="dom-if" if="[[_showEditorView]]" restamp="true">
- <gr-editor-view
- params="[[params]]"></gr-editor-view>
- </template>
- <template is="dom-if" if="[[_showDiffView]]" restamp="true">
- <gr-diff-view
- params="[[params]]"
- change-view-state="{{_viewState.changeView}}"></gr-diff-view>
- </template>
- <template is="dom-if" if="[[_showSettingsView]]" restamp="true">
- <gr-settings-view
- params="[[params]]"
- on-account-detail-update="_handleAccountDetailUpdate">
- </gr-settings-view>
- </template>
- <template is="dom-if" if="[[_showAdminView]]" restamp="true">
- <gr-admin-view path="[[_path]]"
- params=[[params]]></gr-admin-view>
- </template>
- <template is="dom-if" if="[[_showPluginScreen]]" restamp="true">
- <gr-endpoint-decorator name="[[_pluginScreenName]]">
- <gr-endpoint-param name="token" value="[[params.screen]]"></gr-endpoint-param>
- </gr-endpoint-decorator>
- </template>
- <template is="dom-if" if="[[_showCLAView]]" restamp="true">
- <gr-cla-view></gr-cla-view>
- </template>
- <template is="dom-if" if="[[_showDocumentationSearch]]" restamp="true">
- <gr-documentation-search
- params="[[params]]">
- </gr-documentation-search>
- </template>
- <div id="errorView" class="errorView">
- <div class="errorEmoji">[[_lastError.emoji]]</div>
- <div class="errorText">[[_lastError.text]]</div>
- <div class="errorMoreInfo">[[_lastError.moreInfo]]</div>
- </div>
- </main>
- <footer r="contentinfo" class$="[[_computeShadowClass(_isShadowDom)]]">
- <div>
- Powered by <a href="https://www.gerritcodereview.com/" rel="noopener"
- target="_blank">Gerrit Code Review</a>
- ([[_version]])
- <gr-endpoint-decorator name="footer-left"></gr-endpoint-decorator>
- </div>
- <div>
- <template is="dom-if" if="[[_feedbackUrl]]">
- <a class="feedback"
- href$="[[_feedbackUrl]]"
- rel="noopener"
- target="_blank">Send feedback</a> |
- </template>
- Press &ldquo;?&rdquo; for keyboard shortcuts
- <gr-endpoint-decorator name="footer-right"></gr-endpoint-decorator>
- </div>
- </footer>
- <gr-overlay id="keyboardShortcuts" with-backdrop>
- <gr-keyboard-shortcuts-dialog
- view="[[params.view]]"
- on-close="_handleKeyboardShortcutDialogClose"></gr-keyboard-shortcuts-dialog>
- </gr-overlay>
- <gr-overlay id="registrationOverlay" with-backdrop>
- <gr-registration-dialog
- id="registrationDialog"
- settings-url="[[_settingsUrl]]"
- on-account-detail-update="_handleAccountDetailUpdate"
- on-close="_handleRegistrationDialogClose">
- </gr-registration-dialog>
- </gr-overlay>
- <gr-endpoint-decorator name="plugin-overlay"></gr-endpoint-decorator>
- <gr-error-manager id="errorManager"></gr-error-manager>
- <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
- <gr-reporting id="reporting"></gr-reporting>
- <gr-router id="router"></gr-router>
- <gr-plugin-host id="plugins"
- config="[[_serverConfig]]">
- </gr-plugin-host>
- <gr-lib-loader id="libLoader"></gr-lib-loader>
- <gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
+ <gr-app-element id="app-element"></gr-app-element>
</template>
<script src="gr-app.js" crossorigin="anonymous"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index b89d90ede7..ac8ea1a260 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -17,461 +17,7 @@
(function() {
'use strict';
- // Eagerly render Polymer components when backgrounded. (Skips
- // requestAnimationFrame.)
- // @see https://github.com/Polymer/polymer/issues/3851
- // @see Issue 4699
- if (!window.POLYMER2) {
- Polymer.RenderStatus._makeReady();
- }
-
Polymer({
is: 'gr-app',
- _legacyUndefinedCheck: true,
-
- /**
- * Fired when the URL location changes.
- *
- * @event location-change
- */
-
- properties: {
- /**
- * @type {{ query: string, view: string, screen: string }}
- */
- params: Object,
- keyEventTarget: {
- type: Object,
- value() { return document.body; },
- },
-
- _account: {
- type: Object,
- observer: '_accountChanged',
- },
-
- /**
- * The last time the g key was pressed in milliseconds (or a keydown event
- * was handled if the key is held down).
- *
- * @type {number|null}
- */
- _lastGKeyPressTimestamp: {
- type: Number,
- value: null,
- },
-
- /**
- * @type {{ plugin: Object }}
- */
- _serverConfig: Object,
- _version: String,
- _showChangeListView: Boolean,
- _showDashboardView: Boolean,
- _showChangeView: Boolean,
- _showDiffView: Boolean,
- _showSettingsView: Boolean,
- _showAdminView: Boolean,
- _showCLAView: Boolean,
- _showEditorView: Boolean,
- _showPluginScreen: Boolean,
- _showDocumentationSearch: Boolean,
- /** @type {?} */
- _viewState: Object,
- /** @type {?} */
- _lastError: Object,
- _lastSearchPage: String,
- _path: String,
- _isShadowDom: Boolean,
- _pluginScreenName: {
- type: String,
- computed: '_computePluginScreenName(params)',
- },
- _settingsUrl: String,
- _feedbackUrl: String,
- // Used to allow searching on mobile
- mobileSearch: {
- type: Boolean,
- value: false,
- },
- },
-
- listeners: {
- 'page-error': '_handlePageError',
- 'title-change': '_handleTitleChange',
- 'location-change': '_handleLocationChange',
- 'rpc-log': '_handleRpcLog',
- },
-
- observers: [
- '_viewChanged(params.view)',
- '_paramsChanged(params.*)',
- ],
-
- behaviors: [
- Gerrit.BaseUrlBehavior,
- Gerrit.KeyboardShortcutBehavior,
- ],
-
- keyboardShortcuts() {
- return {
- [this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG]: '_showKeyboardShortcuts',
- [this.Shortcut.GO_TO_USER_DASHBOARD]: '_goToUserDashboard',
- [this.Shortcut.GO_TO_OPENED_CHANGES]: '_goToOpenedChanges',
- [this.Shortcut.GO_TO_MERGED_CHANGES]: '_goToMergedChanges',
- [this.Shortcut.GO_TO_ABANDONED_CHANGES]: '_goToAbandonedChanges',
- [this.Shortcut.GO_TO_WATCHED_CHANGES]: '_goToWatchedChanges',
- };
- },
-
- created() {
- this._bindKeyboardShortcuts();
- },
-
- ready() {
- this._isShadowDom = Polymer.Settings.useShadow;
- this.$.router.start();
-
- this.$.restAPI.getAccount().then(account => {
- this._account = account;
- });
- this.$.restAPI.getConfig().then(config => {
- this._serverConfig = config;
-
- if (config && config.gerrit && config.gerrit.report_bug_url) {
- this._feedbackUrl = config.gerrit.report_bug_url;
- }
- });
- this.$.restAPI.getVersion().then(version => {
- this._version = version;
- this._logWelcome();
- });
-
- if (window.localStorage.getItem('dark-theme')) {
- this.$.libLoader.getDarkTheme().then(module => {
- Polymer.dom(this.root).appendChild(module);
- });
- }
-
- // Note: this is evaluated here to ensure that it only happens after the
- // router has been initialized. @see Issue 7837
- this._settingsUrl = Gerrit.Nav.getUrlForSettings();
-
- this.$.reporting.appStarted(document.visibilityState === 'hidden');
-
- this._viewState = {
- changeView: {
- changeNum: null,
- patchRange: null,
- selectedFileIndex: 0,
- showReplyDialog: false,
- diffMode: null,
- numFilesShown: null,
- scrollTop: 0,
- },
- changeListView: {
- query: null,
- offset: 0,
- selectedChangeIndex: 0,
- },
- dashboardView: {
- selectedChangeIndex: 0,
- },
- };
- },
-
- _bindKeyboardShortcuts() {
- this.bindShortcut(this.Shortcut.SEND_REPLY,
- this.DOC_ONLY, 'ctrl+enter', 'meta+enter');
-
- this.bindShortcut(
- this.Shortcut.OPEN_SHORTCUT_HELP_DIALOG, '?');
- this.bindShortcut(
- this.Shortcut.GO_TO_USER_DASHBOARD, this.GO_KEY, 'i');
- this.bindShortcut(
- this.Shortcut.GO_TO_OPENED_CHANGES, this.GO_KEY, 'o');
- this.bindShortcut(
- this.Shortcut.GO_TO_MERGED_CHANGES, this.GO_KEY, 'm');
- this.bindShortcut(
- this.Shortcut.GO_TO_ABANDONED_CHANGES, this.GO_KEY, 'a');
- this.bindShortcut(
- this.Shortcut.GO_TO_WATCHED_CHANGES, this.GO_KEY, 'w');
-
- this.bindShortcut(
- this.Shortcut.CURSOR_NEXT_CHANGE, 'j');
- this.bindShortcut(
- this.Shortcut.CURSOR_PREV_CHANGE, 'k');
- this.bindShortcut(
- this.Shortcut.OPEN_CHANGE, 'o');
- this.bindShortcut(
- this.Shortcut.NEXT_PAGE, 'n', ']');
- this.bindShortcut(
- this.Shortcut.PREV_PAGE, 'p', '[');
- this.bindShortcut(
- this.Shortcut.TOGGLE_CHANGE_REVIEWED, 'r');
- this.bindShortcut(
- this.Shortcut.TOGGLE_CHANGE_STAR, 's');
- this.bindShortcut(
- this.Shortcut.REFRESH_CHANGE_LIST, 'shift+r');
- this.bindShortcut(
- this.Shortcut.EDIT_TOPIC, 't');
-
- this.bindShortcut(
- this.Shortcut.OPEN_REPLY_DIALOG, 'a');
- this.bindShortcut(
- this.Shortcut.OPEN_DOWNLOAD_DIALOG, 'd');
- this.bindShortcut(
- this.Shortcut.EXPAND_ALL_MESSAGES, 'x');
- this.bindShortcut(
- this.Shortcut.COLLAPSE_ALL_MESSAGES, 'z');
- this.bindShortcut(
- this.Shortcut.REFRESH_CHANGE, 'shift+r');
- this.bindShortcut(
- this.Shortcut.UP_TO_DASHBOARD, 'u');
- this.bindShortcut(
- this.Shortcut.UP_TO_CHANGE, 'u');
- this.bindShortcut(
- this.Shortcut.TOGGLE_DIFF_MODE, 'm');
-
- this.bindShortcut(
- this.Shortcut.NEXT_LINE, 'j', 'down');
- this.bindShortcut(
- this.Shortcut.PREV_LINE, 'k', 'up');
- this.bindShortcut(
- this.Shortcut.NEXT_CHUNK, 'n');
- this.bindShortcut(
- this.Shortcut.PREV_CHUNK, 'p');
- this.bindShortcut(
- this.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x');
- this.bindShortcut(
- this.Shortcut.NEXT_COMMENT_THREAD, 'shift+n');
- this.bindShortcut(
- this.Shortcut.PREV_COMMENT_THREAD, 'shift+p');
- this.bindShortcut(
- this.Shortcut.EXPAND_ALL_COMMENT_THREADS, this.DOC_ONLY, 'e');
- this.bindShortcut(
- this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS,
- this.DOC_ONLY, 'shift+e');
- this.bindShortcut(
- this.Shortcut.LEFT_PANE, 'shift+left');
- this.bindShortcut(
- this.Shortcut.RIGHT_PANE, 'shift+right');
- this.bindShortcut(
- this.Shortcut.TOGGLE_LEFT_PANE, 'shift+a');
- this.bindShortcut(
- this.Shortcut.NEW_COMMENT, 'c');
- this.bindShortcut(
- this.Shortcut.SAVE_COMMENT,
- 'ctrl+enter', 'meta+enter', 'ctrl+s', 'meta+s');
- this.bindShortcut(
- this.Shortcut.OPEN_DIFF_PREFS, ',');
- this.bindShortcut(
- this.Shortcut.TOGGLE_DIFF_REVIEWED, 'r');
-
- this.bindShortcut(
- this.Shortcut.NEXT_FILE, ']');
- this.bindShortcut(
- this.Shortcut.PREV_FILE, '[');
- this.bindShortcut(
- this.Shortcut.NEXT_FILE_WITH_COMMENTS, 'shift+j');
- this.bindShortcut(
- this.Shortcut.PREV_FILE_WITH_COMMENTS, 'shift+k');
- this.bindShortcut(
- this.Shortcut.CURSOR_NEXT_FILE, 'j', 'down');
- this.bindShortcut(
- this.Shortcut.CURSOR_PREV_FILE, 'k', 'up');
- this.bindShortcut(
- this.Shortcut.OPEN_FILE, 'o', 'enter');
- this.bindShortcut(
- this.Shortcut.TOGGLE_FILE_REVIEWED, 'r');
- this.bindShortcut(
- this.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
- this.bindShortcut(
- this.Shortcut.TOGGLE_ALL_INLINE_DIFFS, 'shift+i:keyup');
- this.bindShortcut(
- this.Shortcut.TOGGLE_INLINE_DIFF, 'i:keyup');
-
- this.bindShortcut(
- this.Shortcut.OPEN_FIRST_FILE, ']');
- this.bindShortcut(
- this.Shortcut.OPEN_LAST_FILE, '[');
-
- this.bindShortcut(
- this.Shortcut.SEARCH, '/');
- },
-
- _accountChanged(account) {
- if (!account) { return; }
-
- // Preferences are cached when a user is logged in; warm them.
- this.$.restAPI.getPreferences();
- this.$.restAPI.getDiffPreferences();
- this.$.restAPI.getEditPreferences();
- this.$.errorManager.knownAccountId =
- this._account && this._account._account_id || null;
- },
-
- _viewChanged(view) {
- this.$.errorView.classList.remove('show');
- this.set('_showChangeListView', view === Gerrit.Nav.View.SEARCH);
- this.set('_showDashboardView', view === Gerrit.Nav.View.DASHBOARD);
- this.set('_showChangeView', view === Gerrit.Nav.View.CHANGE);
- this.set('_showDiffView', view === Gerrit.Nav.View.DIFF);
- this.set('_showSettingsView', view === Gerrit.Nav.View.SETTINGS);
- this.set('_showAdminView', view === Gerrit.Nav.View.ADMIN ||
- view === Gerrit.Nav.View.GROUP || view === Gerrit.Nav.View.REPO);
- this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
- this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
- const isPluginScreen = view === Gerrit.Nav.View.PLUGIN_SCREEN;
- this.set('_showPluginScreen', false);
- // Navigation within plugin screens does not restamp gr-endpoint-decorator
- // because _showPluginScreen value does not change. To force restamp,
- // change _showPluginScreen value between true and false.
- if (isPluginScreen) {
- this.async(() => this.set('_showPluginScreen', true), 1);
- }
- this.set('_showDocumentationSearch',
- view === Gerrit.Nav.View.DOCUMENTATION_SEARCH);
- if (this.params.justRegistered) {
- this.$.registrationOverlay.open();
- this.$.registrationDialog.loadData().then(() => {
- this.$.registrationOverlay.refit();
- });
- }
- this.$.header.unfloat();
- },
-
- _handlePageError(e) {
- const props = [
- '_showChangeListView',
- '_showDashboardView',
- '_showChangeView',
- '_showDiffView',
- '_showSettingsView',
- '_showAdminView',
- ];
- for (const showProp of props) {
- this.set(showProp, false);
- }
-
- this.$.errorView.classList.add('show');
- const response = e.detail.response;
- const err = {text: [response.status, response.statusText].join(' ')};
- if (response.status === 404) {
- err.emoji = '¯\\_(ツ)_/¯';
- this._lastError = err;
- } else {
- err.emoji = 'o_O';
- response.text().then(text => {
- err.moreInfo = text;
- this._lastError = err;
- });
- }
- },
-
- _handleLocationChange(e) {
- const hash = e.detail.hash.substring(1);
- let pathname = e.detail.pathname;
- if (pathname.startsWith('/c/') && parseInt(hash, 10) > 0) {
- pathname += '@' + hash;
- }
- this.set('_path', pathname);
- },
-
- _paramsChanged(paramsRecord) {
- const params = paramsRecord.base;
- const viewsToCheck = [Gerrit.Nav.View.SEARCH, Gerrit.Nav.View.DASHBOARD];
- if (viewsToCheck.includes(params.view)) {
- this.set('_lastSearchPage', location.pathname);
- }
- },
-
- _handleTitleChange(e) {
- if (e.detail.title) {
- document.title = e.detail.title + ' · Gerrit Code Review';
- } else {
- document.title = '';
- }
- },
-
- _showKeyboardShortcuts(e) {
- if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- this.$.keyboardShortcuts.open();
- },
-
- _handleKeyboardShortcutDialogClose() {
- this.$.keyboardShortcuts.close();
- },
-
- _handleAccountDetailUpdate(e) {
- this.$.mainHeader.reload();
- if (this.params.view === Gerrit.Nav.View.SETTINGS) {
- this.$$('gr-settings-view').reloadAccountDetail();
- }
- },
-
- _handleRegistrationDialogClose(e) {
- this.params.justRegistered = false;
- this.$.registrationOverlay.close();
- },
-
- _computeShadowClass(isShadowDom) {
- return isShadowDom ? 'shadow' : '';
- },
-
- _goToUserDashboard() {
- Gerrit.Nav.navigateToUserDashboard();
- },
-
- _goToOpenedChanges() {
- Gerrit.Nav.navigateToStatusSearch('open');
- },
-
- _goToMergedChanges() {
- Gerrit.Nav.navigateToStatusSearch('merged');
- },
-
- _goToAbandonedChanges() {
- Gerrit.Nav.navigateToStatusSearch('abandoned');
- },
-
- _goToWatchedChanges() {
- // The query is hardcoded, and doesn't respect custom menu entries
- Gerrit.Nav.navigateToSearchQuery('is:watched is:open');
- },
-
- _computePluginScreenName({plugin, screen}) {
- return Gerrit._getPluginScreenName(plugin, screen);
- },
-
- _logWelcome() {
- console.group('Runtime Info');
- console.log('Gerrit UI (PolyGerrit)');
- console.log(`Gerrit Server Version: ${this._version}`);
- if (window.VERSION_INFO) {
- console.log(`UI Version Info: ${window.VERSION_INFO}`);
- }
- const renderTime = new Date(window.performance.timing.loadEventStart);
- console.log(`Document loaded at: ${renderTime}`);
- if (this._feedbackUrl) {
- console.log(`Please file bugs and feedback at: ${this._feedbackUrl}`);
- }
- console.groupEnd();
- },
-
- /**
- * Intercept RPC log events emitted by REST API interfaces.
- * Note: the REST API interface cannot use gr-reporting directly because
- * that would create a cyclic dependency.
- */
- _handleRpcLog(e) {
- this.$.reporting.reportRpcTiming(e.detail.anonymizedUrl,
- e.detail.elapsed);
- },
-
- _mobileSearchToggle(e) {
- this.mobileSearch = !this.mobileSearch;
- },
-
});
})();
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index 71ceab4750..9f1b7f88f8 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -18,11 +18,19 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-app</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../test/common-test-setup.html"/>
-<link rel="import" href="gr-app.html">
+
+<script>
+ const link = document.createElement('link');
+ link.setAttribute('rel', 'import');
+ link.setAttribute('href', 'gr-app.html');
+ document.head.appendChild(link);
+</script>
<script>void(0);</script>
@@ -45,12 +53,18 @@ limitations under the License.
stub('gr-account-dropdown', {
_getTopContent: sinon.stub(),
});
+ stub('gr-router', {
+ start: sandbox.stub(),
+ });
stub('gr-rest-api-interface', {
getAccount() { return Promise.resolve({}); },
getAccountCapabilities() { return Promise.resolve({}); },
getConfig() {
return Promise.resolve({
plugin: {},
+ auth: {
+ auth_type: undefined,
+ },
});
},
getPreferences() { return Promise.resolve({my: []}); },
@@ -68,21 +82,33 @@ limitations under the License.
sandbox.restore();
});
+ appElement = () => {
+ return element.$['app-element'];
+ };
+
test('reporting', () => {
- assert.isTrue(element.$.reporting.appStarted.calledOnce);
+ assert.isTrue(appElement().$.reporting.appStarted.calledOnce);
+ });
+
+ test('reporting called before router start', () => {
+ const element = appElement();
+ const appStartedStub = element.$.reporting.appStarted;
+ const routerStartStub = element.$.router.start;
+ sinon.assert.callOrder(appStartedStub, routerStartStub);
});
test('passes config to gr-plugin-host', () => {
- return element.$.restAPI.getConfig.lastCall.returnValue.then(config => {
- assert.deepEqual(element.$.plugins.config, config);
+ const config = appElement().$.restAPI.getConfig;
+ return config.lastCall.returnValue.then(config => {
+ assert.deepEqual(appElement().$.plugins.config, config);
});
});
test('_paramsChanged sets search page', () => {
- element._paramsChanged({base: {view: Gerrit.Nav.View.CHANGE}});
- assert.notOk(element._lastSearchPage);
- element._paramsChanged({base: {view: Gerrit.Nav.View.SEARCH}});
- assert.ok(element._lastSearchPage);
+ appElement()._paramsChanged({base: {view: Gerrit.Nav.View.CHANGE}});
+ assert.notOk(appElement()._lastSearchPage);
+ appElement()._paramsChanged({base: {view: Gerrit.Nav.View.SEARCH}});
+ assert.ok(appElement()._lastSearchPage);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 159f50a603..2537a37d9e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-admin-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="gr-admin-api.html">
@@ -37,7 +39,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
adminApi = plugin.admin();
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.html b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.html
index 208f1e8d24..ece8677c64 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.html
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-attribute-helper">
<script src="gr-attribute-helper.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
index 86238cf96f..0c4149c206 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-attribute-helper</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-attribute-helper.html"/>
@@ -30,7 +32,6 @@ limitations under the License.
<script>
Polymer({
is: 'some-element',
- _legacyUndefinedCheck: true,
properties: {
fooBar: {
type: Object,
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.html b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.html
index eddb52b9e0..dd532e1f8a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.html
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-change-metadata-api">
<script src="gr-change-metadata-api.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.html b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.html
index 252e8129ea..8b9000fe6b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.html
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.html
@@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-dom-hooks">
<script src="gr-dom-hooks.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
index c6d3e0f764..c0f111aff9 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
@@ -59,7 +59,6 @@
GrDomHook.prototype._createPlaceholder = function(hookName) {
Polymer({
is: hookName,
- _legacyUndefinedCheck: true,
properties: {
plugin: Object,
content: Object,
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
index 3dde458220..9e657fa200 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
@@ -18,11 +18,14 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-dom-hooks</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-dom-hooks.html"/>
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<script>void(0);</script>
@@ -116,7 +119,7 @@ limitations under the License.
hookInternal.handleInstanceAttached(el1);
hookInternal.handleInstanceAttached(el2);
assert.deepEqual([el1, el2], hook.getAllAttached());
- hookI.handleInstanceDetached(el1);
+ hookInternal.handleInstanceDetached(el1);
assert.deepEqual([el2], hook.getAllAttached());
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.html
index 50b80d5139..ab892ac958 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-endpoint-decorator">
@@ -23,4 +23,4 @@ limitations under the License.
<slot></slot>
</template>
<script src="gr-endpoint-decorator.js"></script>
-</dom-module>
+</dom-module> \ No newline at end of file
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
index f3d6eb4ac0..b38107eb81 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-endpoint-decorator',
- _legacyUndefinedCheck: true,
properties: {
name: String,
@@ -50,9 +49,12 @@
Gerrit._endpoints.onDetachedEndpoint(this.name, this._endpointCallBack);
},
+ /**
+ * @suppress {checkTypes}
+ */
_import(url) {
return new Promise((resolve, reject) => {
- this.importHref(url, resolve, reject);
+ (this.importHref || Polymer.importHref)(url, resolve, reject);
});
},
@@ -74,7 +76,6 @@
},
_getEndpointParams() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this).querySelectorAll('gr-endpoint-param'));
},
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
index 2e9b266f17..b0ad58565d 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-endpoint-decorator</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-endpoint-decorator.html">
<link rel="import" href="../gr-endpoint-param/gr-endpoint-param.html">
@@ -56,7 +58,7 @@ limitations under the License.
stub('gr-endpoint-decorator', {
_import: sandbox.stub().returns(Promise.resolve()),
});
- Gerrit._resetPlugins();
+ Gerrit._testOnly_resetPlugins();
container = fixture('basic');
Gerrit.install(p => plugin = p, '0.1', 'http://some/plugin/url.html');
// Decoration
@@ -65,7 +67,7 @@ limitations under the License.
replacementHook = plugin.registerCustomComponent(
'second', 'other-module', {replace: true});
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
flush(done);
});
@@ -86,7 +88,7 @@ limitations under the License.
test('decoration', () => {
const element =
container.querySelector('gr-endpoint-decorator[name="first"]');
- const modules = Polymer.dom(element.root).children.filter(
+ const modules = Array.from(Polymer.dom(element.root).children).filter(
element => element.nodeName === 'SOME-MODULE');
assert.equal(modules.length, 1);
const [module] = modules;
@@ -103,7 +105,7 @@ limitations under the License.
test('replacement', () => {
const element =
container.querySelector('gr-endpoint-decorator[name="second"]');
- const module = Polymer.dom(element.root).children.find(
+ const module = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'OTHER-MODULE');
assert.isOk(module);
assert.equal(module['someparam'], 'foofoo');
@@ -120,7 +122,7 @@ limitations under the License.
flush(() => {
const element =
container.querySelector('gr-endpoint-decorator[name="banana"]');
- const module = Polymer.dom(element.root).children.find(
+ const module = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'NOOB-NOOB');
assert.isOk(module);
done();
@@ -133,10 +135,10 @@ limitations under the License.
flush(() => {
const element =
container.querySelector('gr-endpoint-decorator[name="banana"]');
- const module1 = Polymer.dom(element.root).children.find(
+ const module1 = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'MOD-ONE');
assert.isOk(module1);
- const module2 = Polymer.dom(element.root).children.find(
+ const module2 = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'MOD-TWO');
assert.isOk(module2);
done();
@@ -150,14 +152,14 @@ limitations under the License.
param['value'] = undefined;
plugin.registerCustomComponent('banana', 'noob-noob');
flush(() => {
- let module = Polymer.dom(element.root).children.find(
+ let module = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'NOOB-NOOB');
// Module waits for param to be defined.
assert.isNotOk(module);
const value = {abc: 'def'};
param.value = value;
flush(() => {
- module = Polymer.dom(element.root).children.find(
+ module = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'NOOB-NOOB');
assert.isOk(module);
assert.strictEqual(module['someParam'], value);
@@ -175,7 +177,7 @@ limitations under the License.
param.value = value1;
plugin.registerCustomComponent('banana', 'noob-noob');
flush(() => {
- const module = Polymer.dom(element.root).children.find(
+ const module = Array.from(Polymer.dom(element.root).children).find(
element => element.nodeName === 'NOOB-NOOB');
assert.strictEqual(module['someParam'], value1);
param.value = value2;
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.html
index 9d28ac3acf..6a5b558486 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-endpoint-param">
<script src="gr-endpoint-param.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
index e21fc7294e..c7a2d9a0a0 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
@@ -19,13 +19,29 @@
Polymer({
is: 'gr-endpoint-param',
- _legacyUndefinedCheck: true,
+
properties: {
name: String,
value: {
type: Object,
notify: true,
+ observer: '_valueChanged',
},
},
+
+ _valueChanged(newValue, oldValue) {
+ /* In polymer 2 the following change was made:
+ "Property change notifications (property-changed events) aren't fired when
+ the value changes as a result of a binding from the host"
+ (see https://polymer-library.polymer-project.org/2.0/docs/about_20).
+ To workaround this problem, we fire the event from the observer.
+ In some cases this fire the event twice, but our code is
+ ready for it.
+ */
+ const detail = {
+ value: newValue,
+ };
+ this.dispatchEvent(new CustomEvent('value-changed', {detail}));
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.html
index d34bdef108..15db861fb1 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<dom-module id="gr-event-helper">
<script src="gr-event-helper.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
index 81b59b6a38..845c1e1794 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
@@ -35,17 +35,35 @@
};
/**
+ * Alias of onClick
+ *
+ * @see onClick
+ */
+ GrEventHelper.prototype.onTap = function(callback) {
+ return this._listen(this.element, callback);
+ };
+
+ /**
* Add a callback to element click or touch.
* The callback may return false to prevent event bubbling.
*
* @param {function(Event):boolean} callback
* @return {function()} Unsubscribe function.
*/
- GrEventHelper.prototype.onTap = function(callback) {
+ GrEventHelper.prototype.onClick = function(callback) {
return this._listen(this.element, callback);
};
/**
+ * Alias of captureClick
+ *
+ * @see captureClick
+ */
+ GrEventHelper.prototype.captureTap = function(callback) {
+ return this._listen(this.element.parentElement, callback, {capture: true});
+ };
+
+ /**
* Add a callback to element click or touch ahead of normal flow.
* Callback is installed on parent during capture phase.
* https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
@@ -54,13 +72,13 @@
* @param {function(Event):boolean} callback
* @return {function()} Unsubscribe function.
*/
- GrEventHelper.prototype.captureTap = function(callback) {
+ GrEventHelper.prototype.captureClick = function(callback) {
return this._listen(this.element.parentElement, callback, {capture: true});
};
GrEventHelper.prototype._listen = function(container, callback, opt_options) {
const capture = opt_options && opt_options.capture;
- const event = opt_options && opt_options.event || 'tap';
+ const event = opt_options && opt_options.event || 'click';
const handler = e => {
if (e.path.indexOf(this.element) !== -1) {
let mayContinue = true;
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index 47274f6297..bd76bd42e5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-event-helper</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-event-helper.html"/>
@@ -30,13 +32,17 @@ limitations under the License.
<script>
Polymer({
is: 'some-element',
- _legacyUndefinedCheck: true,
+
properties: {
fooBar: {
type: Object,
notify: true,
},
},
+
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
});
</script>
</dom-element>
@@ -67,14 +73,23 @@ limitations under the License.
instance.onTap(() => {
done();
});
- element.fire('tap');
+ MockInteractions.tap(element);
});
test('onTap() cancel', () => {
const tapStub = sandbox.stub();
- element.parentElement.addEventListener('tap', tapStub);
+ Polymer.Gestures.addListener(element.parentElement, 'tap', tapStub);
instance.onTap(() => false);
- element.fire('tap');
+ MockInteractions.tap(element);
+ flushAsynchronousOperations();
+ assert.isFalse(tapStub.called);
+ });
+
+ test('onClick() cancel', () => {
+ const tapStub = sandbox.stub();
+ element.parentElement.addEventListener('click', tapStub);
+ instance.onTap(() => false);
+ MockInteractions.tap(element);
flushAsynchronousOperations();
assert.isFalse(tapStub.called);
});
@@ -83,14 +98,30 @@ limitations under the License.
instance.captureTap(() => {
done();
});
- element.fire('tap');
+ MockInteractions.tap(element);
+ });
+
+ test('captureClick()', done => {
+ instance.captureClick(() => {
+ done();
+ });
+ MockInteractions.tap(element);
});
test('captureTap() cancels tap()', () => {
const tapStub = sandbox.stub();
- element.addEventListener('tap', tapStub);
+ Polymer.Gestures.addListener(element.parentElement, 'tap', tapStub);
+ instance.captureTap(() => false);
+ MockInteractions.tap(element);
+ flushAsynchronousOperations();
+ assert.isFalse(tapStub.called);
+ });
+
+ test('captureClick() cancels click()', () => {
+ const tapStub = sandbox.stub();
+ element.addEventListener('click', tapStub);
instance.captureTap(() => false);
- element.fire('tap');
+ MockInteractions.tap(element);
flushAsynchronousOperations();
assert.isFalse(tapStub.called);
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.html
index a83b2ab7b1..6a55349b92 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.html
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-external-style">
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
index 7924e27ca6..e90ff303f8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-external-style',
- _legacyUndefinedCheck: true,
properties: {
name: String,
@@ -33,20 +32,31 @@
},
},
+ /**
+ * @suppress {checkTypes}
+ */
_import(url) {
if (this._urlsImported.includes(url)) { return Promise.resolve(); }
this._urlsImported.push(url);
return new Promise((resolve, reject) => {
- this.importHref(url, resolve, reject);
+ (this.importHref || Polymer.importHref)(url, resolve, reject);
});
},
_applyStyle(name) {
if (this._stylesApplied.includes(name)) { return; }
this._stylesApplied.push(name);
+ // Hybrid custom-style syntax:
+ // https://polymer-library.polymer-project.org/2.0/docs/devguide/style-shadow-dom
const s = document.createElement('style', 'custom-style');
s.setAttribute('include', name);
- Polymer.dom(this.root).appendChild(s);
+ const cs = document.createElement('custom-style');
+ cs.appendChild(s);
+ // When using Shadow DOM <custom-style> must be added to the <body>.
+ // Within <gr-external-style> itself the styles would have no effect.
+ const topEl = document.getElementsByTagName('body')[0];
+ topEl.insertBefore(cs, topEl.firstChild);
+ Polymer.updateStyles();
},
_importAndApply() {
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
index ec2888d13f..956606774e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-external-style</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-external-style.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.html
index 8e106cc819..f2778991c3 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
index 65f2207689..6c66fbfc0e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-plugin-host',
- _legacyUndefinedCheck: true,
properties: {
config: {
@@ -28,33 +27,28 @@
},
},
- behaviors: [
- Gerrit.BaseUrlBehavior,
- ],
-
_configChanged(config) {
const plugins = config.plugin;
- const htmlPlugins = (plugins.html_resource_paths || [])
- .map(p => this._urlFor(p))
- .filter(p => !Gerrit._isPluginPreloaded(p));
+ const htmlPlugins = (plugins.html_resource_paths || []);
const jsPlugins =
- this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins)
- .map(p => this._urlFor(p))
- .filter(p => !Gerrit._isPluginPreloaded(p));
+ this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins);
const shouldLoadTheme = config.default_theme &&
!Gerrit._isPluginPreloaded('preloaded:gerrit-theme');
- const defaultTheme =
- shouldLoadTheme ? this._urlFor(config.default_theme) : null;
+ const themeToLoad =
+ shouldLoadTheme ? [config.default_theme] : [];
+
+ // Theme should be loaded first if has one to have better UX
const pluginsPending =
- [].concat(jsPlugins, htmlPlugins, defaultTheme || []);
- Gerrit._setPluginsPending(pluginsPending);
- if (defaultTheme) {
- // Make theme first to be first to load.
- // Load sync to work around rare theme loading race condition.
- this._importHtmlPlugins([defaultTheme], true);
+ themeToLoad.concat(jsPlugins, htmlPlugins);
+
+ const pluginOpts = {};
+
+ if (shouldLoadTheme) {
+ // Theme needs to be loaded synchronous.
+ pluginOpts[config.default_theme] = {sync: true};
}
- this._loadJsPlugins(jsPlugins);
- this._importHtmlPlugins(htmlPlugins);
+
+ Gerrit._loadPlugins(pluginsPending, pluginOpts);
},
/**
@@ -67,53 +61,5 @@
return !htmlPlugins.includes(counterpart);
});
},
-
- /**
- * @suppress {checkTypes}
- * States that it expects no more than 3 parameters, but that's not true.
- * @todo (beckysiegel) check Polymer annotations and submit change.
- * @param {Array} plugins
- * @param {boolean=} opt_sync
- */
- _importHtmlPlugins(plugins, opt_sync) {
- const async = !opt_sync;
- for (const url of plugins) {
- // onload (second param) needs to be a function. When null or undefined
- // were passed, plugins were not loaded correctly.
- this.importHref(
- this._urlFor(url), () => {},
- Gerrit._pluginInstallError.bind(null, `${url} import error`),
- async);
- }
- },
-
- _loadJsPlugins(plugins) {
- for (const url of plugins) {
- this._createScriptTag(this._urlFor(url));
- }
- },
-
- _createScriptTag(url) {
- const el = document.createElement('script');
- el.defer = true;
- el.src = url;
- el.onerror = Gerrit._pluginInstallError.bind(null, `${url} load error`);
- return document.body.appendChild(el);
- },
-
- _urlFor(pathOrUrl) {
- if (!pathOrUrl) {
- return pathOrUrl;
- }
- if (pathOrUrl.startsWith('preloaded:') ||
- pathOrUrl.startsWith('http')) {
- // Plugins are loaded from another domain or preloaded.
- return pathOrUrl;
- }
- if (!pathOrUrl.startsWith('/')) {
- pathOrUrl = '/' + pathOrUrl;
- }
- return window.location.origin + this.getBaseUrl() + pathOrUrl;
- },
});
})();
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 9901d9fe51..3a8e4d8cf5 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-host</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-plugin-host.html">
@@ -36,195 +38,57 @@ limitations under the License.
suite('gr-plugin-host tests', () => {
let element;
let sandbox;
- let url;
setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
sandbox.stub(document.body, 'appendChild');
sandbox.stub(element, 'importHref');
- url = window.location.origin;
});
teardown(() => {
sandbox.restore();
});
- test('counts plugins', () => {
- sandbox.stub(Gerrit, '_setPluginsCount');
+ test('load plugins should be called', () => {
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
plugin: {
html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
js_resource_paths: ['plugins/42'],
},
};
- assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([
+ 'plugins/42', 'plugins/foo/bar', 'plugins/baz',
+ ], {}));
});
- test('imports relative html plugins from config', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- element.config = {
- plugin: {html_resource_paths: ['foo/bar', 'baz']},
- };
- assert.equal(element.importHref.firstCall.args[0], url + '/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0], url + '/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
-
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('imports relative html plugins from config with a base url', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- sandbox.stub(element, 'getBaseUrl').returns('/the-base');
- element.config = {
- plugin: {html_resource_paths: ['foo/bar', 'baz']}};
- assert.equal(element.importHref.firstCall.args[0],
- url + '/the-base/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0],
- url + '/the-base/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('importHref is not called with null callback functions', () => {
- const plugins = ['path/to/plugin'];
- element._importHtmlPlugins(plugins);
- assert.isTrue(element.importHref.calledOnce);
- assert.isFunction(element.importHref.lastCall.args[1]);
- assert.isFunction(element.importHref.lastCall.args[2]);
- });
-
- test('imports absolute html plugins from config', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- element.config = {
- plugin: {
- html_resource_paths: [
- 'http://example.com/foo/bar',
- 'https://example.com/baz',
- ],
- },
- };
- assert.equal(element.importHref.firstCall.args[0],
- 'http://example.com/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0],
- 'https://example.com/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('adds js plugins from config to the body', () => {
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(document.body.appendChild.calledTwice);
- });
-
- test('imports relative js plugins from config', () => {
- sandbox.stub(element, '_createScriptTag');
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(element._createScriptTag.calledWith(url + '/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(url + '/baz'));
- });
-
- test('imports relative html plugins from config with a base url', () => {
- sandbox.stub(element, '_createScriptTag');
- sandbox.stub(element, 'getBaseUrl').returns('/the-base');
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(element._createScriptTag.calledWith(
- url + '/the-base/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(
- url + '/the-base/baz'));
- });
-
- test('imports absolute html plugins from config', () => {
- sandbox.stub(element, '_createScriptTag');
+ test('theme plugins should be loaded if enabled', () => {
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
+ default_theme: 'gerrit-theme.html',
plugin: {
- js_resource_paths: [
- 'http://example.com/foo/bar',
- 'https://example.com/baz',
- ],
- },
- };
- assert.isTrue(element._createScriptTag.calledWith(
- 'http://example.com/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(
- 'https://example.com/baz'));
- });
-
- test('default theme is loaded with html plugins', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- element.config = {
- default_theme: '/oof',
- plugin: {
- html_resource_paths: ['some'],
+ html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
+ js_resource_paths: ['plugins/42'],
},
};
- assert.equal(element.importHref.firstCall.args[0], url + '/oof');
- assert.isFalse(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0], url + '/some');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([
+ 'gerrit-theme.html', 'plugins/42', 'plugins/foo/bar', 'plugins/baz',
+ ], {'gerrit-theme.html': {sync: true}}));
});
- test('default theme is loaded with html plugins', () => {
- sandbox.stub(Gerrit, '_setPluginsPending');
- element.config = {
- default_theme: '/oof',
- plugin: {},
- };
- assert.isTrue(Gerrit._setPluginsPending.calledWith([url + '/oof']));
- });
-
- test('skips default theme loading if preloaded', () => {
+ test('skip theme if preloaded', () => {
sandbox.stub(Gerrit, '_isPluginPreloaded')
.withArgs('preloaded:gerrit-theme').returns(true);
- sandbox.stub(Gerrit, '_setPluginsPending');
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
default_theme: '/oof',
plugin: {},
};
- assert.isFalse(element.importHref.calledWith(url + '/oof'));
- });
-
- test('skips preloaded plugins', () => {
- sandbox.stub(Gerrit, '_isPluginPreloaded')
- .withArgs(url + '/plugins/foo/bar').returns(true)
- .withArgs(url + '/plugins/42').returns(true);
- sandbox.stub(Gerrit, '_setPluginsCount');
- sandbox.stub(Gerrit, '_setPluginsPending');
- sandbox.stub(element, '_createScriptTag');
- element.config = {
- plugin: {
- html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
- js_resource_paths: ['plugins/42'],
- },
- };
- assert.isTrue(
- Gerrit._setPluginsPending.calledWith([url + '/plugins/baz']));
- assert.equal(element._createScriptTag.callCount, 0);
- assert.isTrue(element.importHref.calledWith(url + '/plugins/baz'));
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([], {}));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html
index ce0bf1bc18..402d988d21 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<dom-module id="gr-plugin-popup">
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
index 3ef93e4467..2e7a2b7f27 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
@@ -16,15 +16,18 @@
*/
(function(window) {
'use strict';
+
Polymer({
is: 'gr-plugin-popup',
- _legacyUndefinedCheck: true,
+
get opened() {
return this.$.overlay.opened;
},
+
open() {
return this.$.overlay.open();
},
+
close() {
this.$.overlay.close();
},
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
index 91386b9717..1f1e81eaac 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-popup</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-plugin-popup.html"/>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html
index 2fdf28cee1..26ece3094f 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="gr-plugin-popup.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
index 983c795f3a..53370e29b8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-popup-interface</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-popup-interface.html"/>
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-plugin-repo-command.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-plugin-repo-command.html
index c9486aee6a..593c1e0e74 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-plugin-repo-command.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-plugin-repo-command.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../admin/gr-repo-command/gr-repo-command.html">
<dom-module id="gr-plugin-repo-command">
@@ -25,7 +25,6 @@ limitations under the License.
<script>
Polymer({
is: 'gr-plugin-repo-command',
- _legacyUndefinedCheck: true,
properties: {
title: String,
repoName: String,
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.html
index 34c9797e13..8e6c053bbb 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="gr-plugin-repo-command.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
index bb9ae87e0b..0b32f8a701 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="gr-repo-api.html">
@@ -44,7 +46,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
repoApi = plugin.project();
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.html
index 7c916dc370..20cc71be92 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../settings/gr-settings-view/gr-settings-item.html">
<link rel="import" href="../../settings/gr-settings-view/gr-settings-menu-item.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
index cabd26b3f7..cbc2de622f 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="gr-settings-api.html">
@@ -46,7 +48,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
settingsApi = plugin.settings();
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
new file mode 100644
index 0000000000..74b87c8241
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
@@ -0,0 +1,18 @@
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<script src="gr-styles-api.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
new file mode 100644
index 0000000000..d5647eae9a
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
@@ -0,0 +1,83 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ // Prevent redefinition.
+ if (window.GrStylesApi) { return; }
+
+ let styleObjectCount = 0;
+
+ function GrStyleObject(rulesStr) {
+ this._rulesStr = rulesStr;
+ this._className = `__pg_js_api_class_${styleObjectCount}`;
+ styleObjectCount++;
+ }
+
+ /**
+ * Creates a new unique CSS class and injects it in a root node of the element
+ * if it hasn't been added yet. A root node is an document or is the
+ * associated shadowRoot. This class can be added to any element with the same
+ * root node.
+ *
+ * @param {HTMLElement} element The element to get class name for.
+ * @return {string} Appropriate class name for the element is returned
+ */
+ GrStyleObject.prototype.getClassName = function(element) {
+ let rootNode = Polymer.Settings.useShadow
+ ? element.getRootNode() : document.body;
+ if (rootNode === document) {
+ rootNode = document.head;
+ }
+ if (!rootNode.__pg_js_api_style_tags) {
+ rootNode.__pg_js_api_style_tags = {};
+ }
+ if (!rootNode.__pg_js_api_style_tags[this._className]) {
+ const styleTag = document.createElement('style');
+ styleTag.innerHTML = `.${this._className} { ${this._rulesStr} }`;
+ rootNode.appendChild(styleTag);
+ rootNode.__pg_js_api_style_tags[this._className] = true;
+ }
+ return this._className;
+ };
+
+ /**
+ * Apply shared style to the element.
+ *
+ * @param {HTMLElement} element The element to apply style for
+ */
+ GrStyleObject.prototype.apply = function(element) {
+ element.classList.add(this.getClassName(element));
+ };
+
+
+ function GrStylesApi() {
+ }
+
+ /**
+ * Creates a new GrStyleObject with specified style properties.
+ *
+ * @param {string} String with style properties.
+ * @return {GrStyleObject}
+ */
+ GrStylesApi.prototype.css = function(ruleStr) {
+ return new GrStyleObject(ruleStr);
+ };
+
+
+ window.GrStylesApi = GrStylesApi;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
new file mode 100644
index 0000000000..46bda6db9d
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-admin-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="gr-styles-api.html">
+
+<script>void(0);</script>
+
+<dom-module id="gr-style-test-element">
+ <template>
+ <div id="wrapper"></div>
+ </template>
+ <script>Polymer({is: 'gr-style-test-element'});</script>
+</dom-module>
+
+<script>
+ suite('gr-styles-api tests', () => {
+ let sandbox;
+ let stylesApi;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ Gerrit._loadPlugins([]);
+ stylesApi = plugin.styles();
+ });
+
+ teardown(() => {
+ stylesApi = null;
+ sandbox.restore();
+ });
+
+ test('exists', () => {
+ assert.isOk(stylesApi);
+ });
+
+ test('css', () => {
+ const styleObject = stylesApi.css('background: red');
+ assert.isDefined(styleObject);
+ });
+ });
+
+ suite('GrStyleObject tests', () => {
+ let sandbox;
+ let stylesApi;
+ let displayInlineStyle;
+ let displayNoneStyle;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ Gerrit._loadPlugins([]);
+ stylesApi = plugin.styles();
+ displayInlineStyle = stylesApi.css('display: inline');
+ displayNoneStyle = stylesApi.css('display: none');
+ });
+
+ teardown(() => {
+ displayInlineStyle = null;
+ displayNoneStyle = null;
+ stylesApi = null;
+ sandbox.restore();
+ });
+
+ function createNestedElements(parentElement) {
+ /* parentElement
+ * |--- element1
+ * |--- element2
+ * |--- element3
+ **/
+ const element1 = document.createElement('div');
+ const element2 = document.createElement('div');
+ const element3 = document.createElement('div');
+ Polymer.dom(parentElement).appendChild(element1);
+ Polymer.dom(parentElement).appendChild(element2);
+ Polymer.dom(element2).appendChild(element3);
+
+ return [element1, element2, element3];
+ }
+
+
+ test('getClassName - body level elements', () => {
+ const bodyLevelElements = createNestedElements(document.body);
+
+ testGetClassName(bodyLevelElements);
+ });
+
+ test('getClassName - elements inside polymer element', () => {
+ const polymerElement = document.createElement('gr-style-test-element');
+ Polymer.dom(document.body).appendChild(polymerElement);
+ const contentElements = createNestedElements(polymerElement.$.wrapper);
+
+ testGetClassName(contentElements);
+ });
+
+ function testGetClassName(elements) {
+ assertAllElementsHaveDefaultStyle(elements);
+
+ const className1 = displayInlineStyle.getClassName(elements[0]);
+ const className2 = displayNoneStyle.getClassName(elements[1]);
+ const className3 = displayInlineStyle.getClassName(elements[2]);
+
+ assert.notEqual(className2, className1);
+ assert.equal(className3, className1);
+
+ assertAllElementsHaveDefaultStyle(elements);
+
+ elements[0].classList.add(className1);
+ elements[1].classList.add(className2);
+ elements[2].classList.add(className1);
+
+ assertDisplayPropertyValues(elements, ['inline', 'none', 'inline']);
+ }
+
+ test('apply - body level elements', () => {
+ const bodyLevelElements = createNestedElements(document.body);
+
+ testApply(bodyLevelElements);
+ });
+
+ test('apply - elements inside polymer element', () => {
+ const polymerElement = document.createElement('gr-style-test-element');
+ Polymer.dom(document.body).appendChild(polymerElement);
+ const contentElements = createNestedElements(polymerElement.$.wrapper);
+
+ testApply(contentElements);
+ });
+
+ function testApply(elements) {
+ assertAllElementsHaveDefaultStyle(elements);
+ displayInlineStyle.apply(elements[0]);
+ displayNoneStyle.apply(elements[1]);
+ displayInlineStyle.apply(elements[2]);
+ assertDisplayPropertyValues(elements, ['inline', 'none', 'inline']);
+ }
+
+
+ function assertAllElementsHaveDefaultStyle(elements) {
+ for (const element of elements) {
+ assert.equal(getComputedStyle(element).getPropertyValue('display'),
+ 'block');
+ }
+ }
+
+ function assertDisplayPropertyValues(elements, expectedDisplayValues) {
+ for (const key in elements) {
+ if (elements.hasOwnProperty(key)) {
+ assert.equal(
+ getComputedStyle(elements[key]).getPropertyValue('display'),
+ expectedDisplayValues[key]);
+ }
+ }
+ }
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.html
index 496d0e71df..f0eacd276d 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-custom-plugin-header">
<template>
@@ -26,7 +26,7 @@ limitations under the License.
vertical-align: middle;
}
.title {
- margin-left: .25em;
+ margin-left: var(--spacing-xs);
}
</style>
<span>
@@ -37,7 +37,6 @@ limitations under the License.
<script>
Polymer({
is: 'gr-custom-plugin-header',
- _legacyUndefinedCheck: true,
properties: {
logoUrl: String,
title: String,
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.html
index b84f5b9b27..d6e67fe337 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="gr-custom-plugin-header.html">
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
index 8d23ea2ccf..6332b9196e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-theme-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="gr-theme-api.html">
@@ -65,7 +67,7 @@ limitations under the License.
stub('gr-custom-plugin-header', {
ready() { customHeader = this; },
});
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
test('sets logo and title', done => {
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
index f534771228..662c6f1d76 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.html
@@ -15,8 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-avatar/gr-avatar.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -31,10 +33,10 @@ limitations under the License.
gr-avatar {
height: 120px;
width: 120px;
- margin-right: .15em;
+ margin-right: var(--spacing-xs);
vertical-align: -.25em;
}
- .hide {
+ div section.hide {
display: none;
}
</style>
@@ -79,12 +81,17 @@ limitations under the License.
<span
hidden$="[[!usernameMutable]]"
class="value">
- <input
- is="iron-input"
- id="usernameInput"
+ <iron-input
disabled="[[_saving]]"
on-keydown="_handleKeydown"
bind-value="{{_username}}">
+ <input
+ is="iron-input"
+ id="usernameInput"
+ disabled="[[_saving]]"
+ on-keydown="_handleKeydown"
+ bind-value="{{_username}}">
+ </iron-input>
</span>
</section>
<section id="nameSection">
@@ -95,24 +102,34 @@ limitations under the License.
<span
hidden$="[[!nameMutable]]"
class="value">
- <input
- is="iron-input"
- id="nameInput"
+ <iron-input
disabled="[[_saving]]"
on-keydown="_handleKeydown"
bind-value="{{_account.name}}">
+ <input
+ is="iron-input"
+ id="nameInput"
+ disabled="[[_saving]]"
+ on-keydown="_handleKeydown"
+ bind-value="{{_account.name}}">
+ </iron-input>
</span>
</section>
<section>
<span class="title">Status (e.g. "Vacation")</span>
<span class="value">
- <input
- is="iron-input"
- id="statusInput"
+ <iron-input
disabled="[[_saving]]"
on-keydown="_handleKeydown"
bind-value="{{_account.status}}">
- </span>
+ <input
+ is="iron-input"
+ id="statusInput"
+ disabled="[[_saving]]"
+ on-keydown="_handleKeydown"
+ bind-value="{{_account.status}}">
+ </iron-input>
+ </span>
</section>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index 51a22fc32d..3ba3a80a76 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-account-info',
- _legacyUndefinedCheck: true,
/**
* Fired when account details are changed.
@@ -69,6 +68,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
observers: [
'_nameChanged(_account.name)',
'_statusChanged(_account.status)',
@@ -145,6 +148,14 @@
},
_computeUsernameMutable(config, username) {
+ // Polymer 2: check for undefined
+ if ([
+ config,
+ username,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
// Username may not be changed once it is set.
return config.auth.editable_account_fields.includes('USER_NAME') &&
!username;
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index 75b9910772..a35c1f05e8 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-account-info</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-account-info.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
index 72ea503376..852161cb2a 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -16,7 +16,7 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
index fe36a86014..41595a9832 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-agreements-list',
- _legacyUndefinedCheck: true,
properties: {
_agreements: Array,
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
index 56122a99d4..14cf97c942 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-agreements-list.html">
@@ -45,7 +47,7 @@ limitations under the License.
}];
stub('gr-rest-api-interface', {
- getAccountGroups() { return Promise.resolve(agreements); },
+ getAccountAgreements() { return Promise.resolve(agreements); },
});
element = fixture('basic');
@@ -56,10 +58,10 @@ limitations under the License.
test('renders', () => {
const rows = Polymer.dom(element.root).querySelectorAll('tbody tr');
- assert.equal(rows.length, 3);
+ assert.equal(rows.length, 1);
- const nameCells = rows.map(row =>
- row.querySelectorAll('td')[0].textContent
+ const nameCells = Array.from(rows).map(row =>
+ row.querySelectorAll('td')[0].textContent.trim()
);
assert.equal(nameCells[0], 'Agreements 1');
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
index 4f695131db..88a53ee171 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
@@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../behaviors/gr-change-table-behavior/gr-change-table-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -56,11 +55,11 @@ limitations under the License.
<tr>
<td>Number</td>
<td class="checkboxContainer"
- on-tap="_handleCheckboxContainerTap">
+ on-click="_handleCheckboxContainerClick">
<input
type="checkbox"
name="number"
- on-tap="_handleNumberCheckboxTap"
+ on-click="_handleNumberCheckboxClick"
checked$="[[showNumber]]">
</td>
</tr>
@@ -68,11 +67,11 @@ limitations under the License.
<tr>
<td>[[item]]</td>
<td class="checkboxContainer"
- on-tap="_handleCheckboxContainerTap">
+ on-click="_handleCheckboxContainerClick">
<input
type="checkbox"
name="[[item]]"
- on-tap="_handleTargetTap"
+ on-click="_handleTargetClick"
checked$="[[!isColumnHidden(item, displayedColumns)]]">
</td>
</tr>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
index d660ee58bb..0fef3d625c 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-change-table-editor',
- _legacyUndefinedCheck: true,
properties: {
displayedColumns: {
@@ -43,7 +42,6 @@
* @return {!Array<string>}
*/
_getDisplayedColumns() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(Polymer.dom(this.root)
.querySelectorAll('.checkboxContainer input:not([name=number])'))
.filter(checkbox => checkbox.checked)
@@ -51,28 +49,28 @@
},
/**
- * Handle a tap on a checkbox container and relay the tap to the checkbox it
+ * Handle a click on a checkbox container and relay the click to the checkbox it
* contains.
*/
- _handleCheckboxContainerTap(e) {
+ _handleCheckboxContainerClick(e) {
const checkbox = Polymer.dom(e.target).querySelector('input');
if (!checkbox) { return; }
checkbox.click();
},
/**
- * Handle a tap on the number checkbox and update the showNumber property
+ * Handle a click on the number checkbox and update the showNumber property
* accordingly.
*/
- _handleNumberCheckboxTap(e) {
+ _handleNumberCheckboxClick(e) {
this.showNumber = Polymer.dom(e).rootTarget.checked;
},
/**
- * Handle a tap on a displayed column checkboxes (excluding number) and
+ * Handle a click on a displayed column checkboxes (excluding number) and
* update the displayedColumns property accordingly.
*/
- _handleTargetTap(e) {
+ _handleTargetClick(e) {
this.set('displayedColumns', this._getDisplayedColumns());
},
});
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 32fab9d364..29a70818d8 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-table-editor.html">
@@ -117,41 +119,41 @@ limitations under the License.
columns.filter(c => c !== 'Assignee'));
});
- test('_handleCheckboxContainerTap relayes taps to checkboxes', () => {
- sandbox.stub(element, '_handleNumberCheckboxTap');
- sandbox.stub(element, '_handleTargetTap');
+ test('_handleCheckboxContainerClick relayes taps to checkboxes', () => {
+ sandbox.stub(element, '_handleNumberCheckboxClick');
+ sandbox.stub(element, '_handleTargetClick');
MockInteractions.tap(
element.$$('table tr:first-of-type .checkboxContainer'));
- assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
- assert.isFalse(element._handleTargetTap.called);
+ assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
+ assert.isFalse(element._handleTargetClick.called);
MockInteractions.tap(
element.$$('table tr:last-of-type .checkboxContainer'));
- assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
- assert.isTrue(element._handleTargetTap.calledOnce);
+ assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
+ assert.isTrue(element._handleTargetClick.calledOnce);
});
- test('_handleNumberCheckboxTap', () => {
- sandbox.spy(element, '_handleNumberCheckboxTap');
+ test('_handleNumberCheckboxClick', () => {
+ sandbox.spy(element, '_handleNumberCheckboxClick');
MockInteractions
.tap(element.$$('.checkboxContainer input[name=number]'));
- assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
+ assert.isTrue(element._handleNumberCheckboxClick.calledOnce);
assert.isTrue(element.showNumber);
MockInteractions
.tap(element.$$('.checkboxContainer input[name=number]'));
- assert.isTrue(element._handleNumberCheckboxTap.calledTwice);
+ assert.isTrue(element._handleNumberCheckboxClick.calledTwice);
assert.isFalse(element.showNumber);
});
- test('_handleTargetTap', () => {
- sandbox.spy(element, '_handleTargetTap');
+ test('_handleTargetClick', () => {
+ sandbox.spy(element, '_handleTargetClick');
assert.include(element.displayedColumns, 'Assignee');
MockInteractions
.tap(element.$$('.checkboxContainer input[name=Assignee]'));
- assert.isTrue(element._handleTargetTap.calledOnce);
+ assert.isTrue(element._handleTargetClick.calledOnce);
assert.notInclude(element.displayedColumns, 'Assignee');
});
});
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
index 4a939e6433..c29153e7d6 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
@@ -16,8 +16,9 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -27,17 +28,17 @@ limitations under the License.
<template>
<style include="shared-styles">
h1 {
- margin-bottom: .6em;
+ margin-bottom: var(--spacing-m);
}
h3 {
- margin-bottom: .5em;
+ margin-bottom: var(--spacing-m);
}
.agreementsUrl {
- border: 0.1em solid #b0bdcc;
- margin-bottom: 1.25em;
- margin-left: 1.25em;
- margin-right: 1.25em;
- padding: 0.3em;
+ border: 1px solid #b0bdcc;
+ margin-bottom: var(--spacing-xl);
+ margin-left: var(--spacing-xl);
+ margin-right: var(--spacing-xl);
+ padding: var(--spacing-s);
}
#claNewAgreementsLabel {
font-weight: var(--font-weight-bold);
@@ -53,15 +54,15 @@ limitations under the License.
}
.alreadySubmittedText {
color: var(--error-text-color);
- margin: 0 2em;
- padding: .5em;
+ margin: 0 var(--spacing-xxl);
+ padding: var(--spacing-m);
}
.alreadySubmittedText.hide,
.hideAgreementsTextBox {
display: none;
}
main {
- margin: 2em auto;
+ margin: var(--spacing-xxl) auto;
max-width: 50em;
}
</style>
@@ -76,7 +77,7 @@ limitations under the License.
type="radio"
data-name$="[[item.name]]"
data-url$="[[item.url]]"
- on-tap="_handleShowAgreement"
+ on-click="_handleShowAgreement"
disabled$="[[_disableAgreements(item, _groups, _signedAgreements)]]">
<label id="claNewAgreementsLabel">[[item.name]]</label>
</span>
@@ -95,8 +96,14 @@ limitations under the License.
</div>
<div class$="agreementsTextBox [[_computeHideAgreementClass(_agreementName, _serverConfig.auth.contributor_agreements)]]">
<h3 class="smallHeading">Complete the agreement:</h3>
- <input id="input-agreements" is="iron-input" bind-value="{{_agreementsText}}" placeholder="Enter 'I agree' here" />
- <gr-button on-tap="_handleSaveAgreements" disabled="[[_disableAgreementsText(_agreementsText)]]">
+ <iron-input bind-value="{{_agreementsText}}"
+ placeholder="Enter 'I agree' here">
+ <input id="input-agreements"
+ is="iron-input"
+ bind-value="{{_agreementsText}}"
+ placeholder="Enter 'I agree' here">
+ </iron-input>
+ <gr-button on-click="_handleSaveAgreements" disabled="[[_disableAgreementsText(_agreementsText)]]">
Submit
</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
index 2a931558a0..cfd7b21ab5 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-cla-view',
- _legacyUndefinedCheck: true,
properties: {
_groups: Object,
@@ -37,6 +36,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
],
attached() {
@@ -66,7 +66,9 @@
_getAgreementsUrl(configUrl) {
let url;
- if (!configUrl) { return ''; }
+ if (!configUrl) {
+ return '';
+ }
if (configUrl.startsWith('http:') || configUrl.startsWith('https:')) {
url = configUrl;
} else {
@@ -100,8 +102,8 @@
},
_createToast(message) {
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent(
+ 'show-alert', {detail: {message}, bubbles: true, composed: true}));
},
_computeShowAgreementsClass(agreements) {
@@ -135,9 +137,13 @@
_computeHideAgreementClass(name, config) {
if (!config) return '';
for (const key in config) {
- if (!config.hasOwnProperty(key)) { continue; }
+ if (!config.hasOwnProperty(key)) {
+ continue;
+ }
for (const prop in config[key]) {
- if (!config[key].hasOwnProperty(prop)) { continue; }
+ if (!config[key].hasOwnProperty(prop)) {
+ continue;
+ }
if (name === config[key].name &&
!config[key].auto_verify_group) {
return 'hideAgreementsTextBox';
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
index aec52cb8f2..f1b65d97ba 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-cla-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-cla-view.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
index b3e6990572..53a30c3426 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -29,40 +30,64 @@ limitations under the License.
<section>
<span class="title">Tab width</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{editPrefs.tab_size}}"
on-keypress="_handleEditPrefsChanged"
on-change="_handleEditPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.tab_size}}"
+ on-keypress="_handleEditPrefsChanged"
+ on-change="_handleEditPrefsChanged">
+ </iron-input>
</span>
</section>
<section>
<span class="title">Columns</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{editPrefs.line_length}}"
on-keypress="_handleEditPrefsChanged"
on-change="_handleEditPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.line_length}}"
+ on-keypress="_handleEditPrefsChanged"
+ on-change="_handleEditPrefsChanged">
+ </iron-input>
</span>
</section>
<section>
<span class="title">Indent unit</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{editPrefs.indent_unit}}"
on-keypress="_handleEditPrefsChanged"
on-change="_handleEditPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.indent_unit}}"
+ on-keypress="_handleEditPrefsChanged"
+ on-change="_handleEditPrefsChanged">
+ </iron-input>
</span>
</section>
<section>
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
index 37bce08614..86350f9af1 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-edit-preferences',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
index 42171b782f..c1c5c52dd8 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-edit-preferences</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-edit-preferences.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.html b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.html
index 0a7433e966..caaf18b589 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -62,20 +62,28 @@ limitations under the License.
<template is="dom-repeat" items="[[_emails]]">
<tr>
<td class="emailColumn">[[item.email]]</td>
- <td class="preferredControl" on-tap="_handlePreferredControlTap">
- <input
- is="iron-input"
+ <td class="preferredControl" on-click="_handlePreferredControlClick">
+ <iron-input
class="preferredRadio"
type="radio"
on-change="_handlePreferredChange"
name="preferred"
- value="[[item.email]]"
+ bind-value="[[item.email]]"
checked$="[[item.preferred]]">
+ <input
+ is="iron-input"
+ class="preferredRadio"
+ type="radio"
+ on-change="_handlePreferredChange"
+ name="preferred"
+ value="[[item.email]]"
+ checked$="[[item.preferred]]">
+ </iron-input>
</td>
<td>
<gr-button
data-index$="[[index]]"
- on-tap="_handleDeleteButton"
+ on-click="_handleDeleteButton"
disabled="[[item.preferred]]"
class="remove-button">Delete</gr-button>
</td>
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
index 71d75cc3ea..8490b2641a 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-email-editor',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
@@ -74,7 +73,7 @@
this.hasUnsavedChanges = true;
},
- _handlePreferredControlTap(e) {
+ _handlePreferredControlClick(e) {
if (e.target.classList.contains('preferredControl')) {
e.target.firstElementChild.click();
}
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
index e937f8bdde..8d3f2d203f 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-email-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-email-editor.html">
@@ -49,7 +51,7 @@ limitations under the License.
element = fixture('basic');
- element.loadData().then(done);
+ element.loadData().then(flush(done));
});
test('renders', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.html b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.html
index 0c589c9980..cf73d99ba1 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
@@ -28,9 +28,6 @@ limitations under the License.
<template>
<style include="shared-styles"></style>
<style include="gr-form-styles">
- .statusHeader {
- width: 4em;
- }
.keyHeader {
width: 9em;
}
@@ -38,11 +35,13 @@ limitations under the License.
width: 15em;
}
#viewKeyOverlay {
- padding: 2em;
+ padding: var(--spacing-xxl);
width: 50em;
}
.publicKey {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
overflow-x: scroll;
overflow-wrap: break-word;
width: 30em;
@@ -53,11 +52,7 @@ limitations under the License.
right: 2em;
}
#existing {
- margin-bottom: 1em;
- }
- #existing .commentColumn {
- min-width: 27em;
- width: auto;
+ margin-bottom: var(--spacing-l);
}
</style>
<div class="gr-form-styles">
@@ -85,7 +80,7 @@ limitations under the License.
</td>
<td class="keyHeader">
<gr-button
- on-tap="_showKey"
+ on-click="_showKey"
data-index$="[[index]]"
link>Click to View</gr-button>
</td>
@@ -100,7 +95,7 @@ limitations under the License.
<td>
<gr-button
data-index$="[[index]]"
- on-tap="_handleDeleteKey">Delete</gr-button>
+ on-click="_handleDeleteKey">Delete</gr-button>
</td>
</tr>
</template>
@@ -119,10 +114,10 @@ limitations under the License.
</fieldset>
<gr-button
class="closeButton"
- on-tap="_closeOverlay">Close</gr-button>
+ on-click="_closeOverlay">Close</gr-button>
</gr-overlay>
<gr-button
- on-tap="save"
+ on-click="save"
disabled$="[[!hasUnsavedChanges]]">Save changes</gr-button>
</fieldset>
<fieldset>
@@ -139,7 +134,7 @@ limitations under the License.
<gr-button
id="addButton"
disabled$="[[_computeAddButtonDisabled(_newKey)]]"
- on-tap="_handleAddKey">Add new GPG key</gr-button>
+ on-click="_handleAddKey">Add new GPG key</gr-button>
</fieldset>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
index a50509cbda..890061e87b 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-gpg-editor',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
index aa53aca1bd..9cfbde5fed 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-gpg-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-gpg-editor.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.html b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.html
index 2c7afd3300..ca500c8732 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
index 4de24aa193..d62a241dee 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-group-list',
- _legacyUndefinedCheck: true,
properties: {
_groups: Array,
@@ -40,7 +39,9 @@
_computeGroupPath(group) {
if (!group || !group.id) { return; }
- return Gerrit.Nav.getUrlForGroup(group.id);
+ // Group ID is already encoded from the API
+ // Decode it here to match with our router encoding behavior
+ return Gerrit.Nav.getUrlForGroup(decodeURIComponent(group.id));
},
});
})();
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
index 3fa5a36ac8..0422d1b32c 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-group-list.html">
@@ -71,7 +73,8 @@ limitations under the License.
teardown(() => { sandbox.restore(); });
test('renders', () => {
- const rows = Polymer.dom(element.root).querySelectorAll('tbody tr');
+ const rows = Array.from(
+ Polymer.dom(element.root).querySelectorAll('tbody tr'));
assert.equal(rows.length, 3);
@@ -90,21 +93,30 @@ limitations under the License.
});
test('_computeGroupPath', () => {
- sandbox.stub(Gerrit.Nav, 'getUrlForGroup',
+ let urlStub = sandbox.stub(Gerrit.Nav, 'getUrlForGroup',
() => '/admin/groups/e2cd66f88a2db4d391ac068a92d987effbe872f5');
let group = {
id: 'e2cd66f88a2db4d391ac068a92d987effbe872f5',
};
-
assert.equal(element._computeGroupPath(group),
'/admin/groups/e2cd66f88a2db4d391ac068a92d987effbe872f5');
group = {
name: 'admin',
};
-
assert.isUndefined(element._computeGroupPath(group));
+
+ urlStub.restore();
+
+ urlStub = sandbox.stub(Gerrit.Nav, 'getUrlForGroup',
+ () => '/admin/groups/user/test');
+
+ group = {
+ id: 'user%2Ftest',
+ };
+ assert.equal(element._computeGroupPath(group),
+ '/admin/groups/user/test');
});
});
</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
index 54cfd655d4..0cb9695cf3 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
@@ -28,19 +28,23 @@ limitations under the License.
<style include="shared-styles">
.password {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
}
#generatedPasswordOverlay {
- padding: 2em;
+ padding: var(--spacing-xxl);
width: 50em;
}
#generatedPasswordDisplay {
- margin: 1em 0;
+ margin: var(--spacing-l) 0;
}
#generatedPasswordDisplay .title {
width: unset;
}
#generatedPasswordDisplay .value {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
}
#passwordWarning {
font-style: italic;
@@ -61,7 +65,7 @@ limitations under the License.
</section>
<gr-button
id="generateButton"
- on-tap="_handleGenerateTap">Generate new password</gr-button>
+ on-click="_handleGenerateTap">Generate new password</gr-button>
</div>
<span hidden$="[[!_passwordUrl]]">
<a href$="[[_passwordUrl]]" target="_blank" rel="noopener">
@@ -91,7 +95,7 @@ limitations under the License.
<gr-button
link
class="closeButton"
- on-tap="_closeOverlay">Close</gr-button>
+ on-click="_closeOverlay">Close</gr-button>
</div>
</gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
index 99f4504f13..003e47105f 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-http-password',
- _legacyUndefinedCheck: true,
properties: {
_username: String,
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index ca50b2bea6..8924058210 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-http-password.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
index 73aa65f52c..ee855cc54e 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
@@ -50,7 +50,7 @@ limitations under the License.
display: none;
}
.space {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
</style>
<div class="gr-form-styles">
@@ -75,7 +75,7 @@ limitations under the License.
<td class="deleteColumn">
<gr-button
class$="deleteButton [[_computeHideDeleteClass(item.can_delete)]]"
- on-tap="_handleDeleteItem">
+ on-click="_handleDeleteItem">
Delete
</gr-button>
</td>
@@ -84,13 +84,13 @@ limitations under the License.
</tbody>
</table>
</fieldset>
- <dom-if if="[[_showLinkAnotherIdentity]]">
+ <template is="dom-if" if="[[_showLinkAnotherIdentity]]">
<fieldset>
<a href$="[[_computeLinkAnotherIdentity()]]">
<gr-button id="linkAnotherIdentity" link>Link Another Identity</gr-button>
</a>
</fieldset>
- </dom-if>
+ </template>
</div>
<gr-overlay id="overlay" with-backdrop>
<gr-confirm-delete-item-dialog
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
index 4560a2e3b8..c927f1ee24 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
@@ -24,7 +24,6 @@
Polymer({
is: 'gr-identities',
- _legacyUndefinedCheck: true,
properties: {
_identities: Object,
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
index 5468d27079..12774246f5 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-identities</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-identities.html">
@@ -69,7 +71,8 @@ limitations under the License.
});
test('renders', () => {
- const rows = Polymer.dom(element.root).querySelectorAll('tbody tr');
+ const rows = Array.from(
+ Polymer.dom(element.root).querySelectorAll('tbody tr'));
assert.equal(rows.length, 2);
@@ -82,7 +85,8 @@ limitations under the License.
});
test('renders email', () => {
- const rows = Polymer.dom(element.root).querySelectorAll('tbody tr');
+ const rows = Array.from(
+ Polymer.dom(element.root).querySelectorAll('tbody tr'));
assert.equal(rows.length, 2);
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.html
index aa42623457..1485628511 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -61,22 +61,22 @@ limitations under the License.
<td class="buttonColumn">
<gr-button
link
- data-index="[[index]]"
- on-tap="_handleMoveUpButton"
+ data-index$="[[index]]"
+ on-click="_handleMoveUpButton"
class="moveUpButton">↑</gr-button>
</td>
<td class="buttonColumn">
<gr-button
link
- data-index="[[index]]"
- on-tap="_handleMoveDownButton"
+ data-index$="[[index]]"
+ on-click="_handleMoveDownButton"
class="moveDownButton">↓</gr-button>
</td>
<td>
<gr-button
link
- data-index="[[index]]"
- on-tap="_handleDeleteButton"
+ data-index$="[[index]]"
+ on-click="_handleDeleteButton"
class="remove-button">Delete</gr-button>
</td>
</tr>
@@ -85,19 +85,30 @@ limitations under the License.
<tfoot>
<tr>
<th>
- <input
- is="iron-input"
+ <iron-input
placeholder="New Title"
on-keydown="_handleInputKeydown"
bind-value="{{_newName}}">
+ <input
+ is="iron-input"
+ placeholder="New Title"
+ on-keydown="_handleInputKeydown"
+ bind-value="{{_newName}}">
+ </iron-input>
</th>
<th>
- <input
+ <iron-input
class="newUrlInput"
- is="iron-input"
placeholder="New URL"
on-keydown="_handleInputKeydown"
bind-value="{{_newUrl}}">
+ <input
+ class="newUrlInput"
+ is="iron-input"
+ placeholder="New URL"
+ on-keydown="_handleInputKeydown"
+ bind-value="{{_newUrl}}">
+ </iron-input>
</th>
<th></th>
<th></th>
@@ -105,7 +116,7 @@ limitations under the License.
<gr-button
link
disabled$="[[_computeAddDisabled(_newName, _newUrl)]]"
- on-tap="_handleAddButton">Add</gr-button>
+ on-click="_handleAddButton">Add</gr-button>
</th>
</tr>
</tfoot>
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
index 8587338dd7..4f3c0c793f 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-menu-editor',
- _legacyUndefinedCheck: true,
properties: {
menuItems: Array,
@@ -28,7 +27,7 @@
},
_handleMoveUpButton(e) {
- const index = Polymer.dom(e).localTarget.dataIndex;
+ const index = Number(Polymer.dom(e).localTarget.dataset.index);
if (index === 0) { return; }
const row = this.menuItems[index];
const prev = this.menuItems[index - 1];
@@ -36,7 +35,7 @@
},
_handleMoveDownButton(e) {
- const index = Polymer.dom(e).localTarget.dataIndex;
+ const index = Number(Polymer.dom(e).localTarget.dataset.index);
if (index === this.menuItems.length - 1) { return; }
const row = this.menuItems[index];
const next = this.menuItems[index + 1];
@@ -44,7 +43,7 @@
},
_handleDeleteButton(e) {
- const index = Polymer.dom(e).localTarget.dataIndex;
+ const index = Number(Polymer.dom(e).localTarget.dataset.index);
this.splice('menuItems', index, 1);
},
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
index c8a54b6e6f..134e018a37 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-menu-editor.html">
@@ -55,7 +57,7 @@ limitations under the License.
MockInteractions.tap(button);
}
- setup(() => {
+ setup(done => {
element = fixture('basic');
menu = [
{url: '/first/url', name: 'first name', target: '_blank'},
@@ -64,6 +66,7 @@ limitations under the License.
];
element.set('menuItems', menu);
Polymer.dom.flush();
+ flush(done);
});
test('renders', () => {
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
index 5f1794c398..f366d2a082 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
@@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -43,23 +45,23 @@ limitations under the License.
display: block;
}
hr {
- margin-top: 1em;
- margin-bottom: 1em;
+ margin-top: var(--spacing-l);
+ margin-bottom: var(--spacing-l);
}
header {
border-bottom: 1px solid var(--border-color);
font-weight: var(--font-weight-bold);
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
.container {
- padding: .5em 1.5em;
+ padding: var(--spacing-m) var(--spacing-xl);
}
footer {
display: flex;
justify-content: flex-end;
}
footer gr-button {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
input {
width: 20em;
@@ -81,19 +83,27 @@ limitations under the License.
<hr>
<section>
<div class="title">Full Name</div>
- <input
- is="iron-input"
- id="name"
+ <iron-input
bind-value="{{_account.name}}"
disabled="[[_saving]]">
+ <input
+ is="iron-input"
+ id="name"
+ bind-value="{{_account.name}}"
+ disabled="[[_saving]]">
+ </iron-input>
</section>
<section class$="[[_computeUsernameClass(_usernameMutable)]]">
<div class="title">Username</div>
- <input
- is="iron-input"
- id="username"
+ <iron-input
bind-value="{{_account.username}}"
disabled="[[_saving]]">
+ <input
+ is="iron-input"
+ id="username"
+ bind-value="{{_account.username}}"
+ disabled="[[_saving]]">
+ </iron-input>
</section>
<section>
<div class="title">Preferred Email</div>
@@ -109,7 +119,7 @@ limitations under the License.
<hr>
<p>
More configuration options for Gerrit may be found in the
- <a on-tap="close" href$="[[settingsUrl]]">settings</a>.
+ <a on-click="close" href$="[[settingsUrl]]">settings</a>.
</p>
</main>
<footer>
@@ -117,13 +127,13 @@ limitations under the License.
id="closeButton"
link
disabled="[[_saving]]"
- on-tap="_handleClose">Close</gr-button>
+ on-click="_handleClose">Close</gr-button>
<gr-button
id="saveButton"
primary
link
disabled="[[_computeSaveDisabled(_account.name, _account.email, _saving)]]"
- on-tap="_handleSave">Save</gr-button>
+ on-click="_handleSave">Save</gr-button>
</footer>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
index 6b4ee18559..063341663f 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-registration-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when account details are changed.
@@ -60,6 +59,10 @@
_serverConfig: Object,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
hostAttributes: {
role: 'dialog',
},
@@ -120,6 +123,14 @@
},
_computeUsernameMutable(config, username) {
+ // Polymer 2: check for undefined
+ if ([
+ config,
+ username,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
return config.auth.editable_account_fields.includes('USER_NAME') &&
!username;
},
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index 93a3188fe3..d1b5c803e8 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-registration-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-registration-dialog.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.html
index 30a3801a49..937ee7976b 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.html
@@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-settings-item">
<template>
<style>
:host {
display: block;
- margin-bottom: 2em;
+ margin-bottom: var(--spacing-xxl);
}
</style>
<h2 id="[[anchor]]">[[title]]</h2>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
index dc1aa936ca..dae3b68efd 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-settings-item',
- _legacyUndefinedCheck: true,
+
properties: {
anchor: String,
title: String,
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.html
index f64d898c4d..846f776a62 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-page-nav-styles.html">
<dom-module id="gr-settings-menu-item">
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
index 2a56b0935f..5db0031846 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-settings-menu-item',
- _legacyUndefinedCheck: true,
+
properties: {
href: String,
title: String,
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index f0c4d6ed15..74971cf482 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../behaviors/docs-url-behavior/docs-url-behavior.html">
-<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="/bower_components/paper-toggle-button/paper-toggle-button.html">
+
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
<link rel="import" href="../../../styles/gr-page-nav-styles.html">
@@ -49,18 +51,18 @@ limitations under the License.
:host {
color: var(--primary-text-color);
}
- #newEmailInput {
+ .newEmailInput {
width: 20em;
}
#email {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
main section.darkToggle {
display: block;
}
.filters p,
.darkToggle p {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
.queryExample em {
color: violet;
@@ -68,8 +70,8 @@ limitations under the License.
.toggle {
align-items: center;
display: flex;
- margin-bottom: 1rem;
- margin-right: 1rem;
+ margin-bottom: var(--spacing-l);
+ margin-right: var(--spacing-l);
}
</style>
<style include="gr-form-styles"></style>
@@ -132,7 +134,7 @@ limitations under the License.
mutable="{{_accountNameMutable}}"
has-unsaved-changes="{{_accountInfoChanged}}"></gr-account-info>
<gr-button
- on-tap="_handleSaveAccountInfo"
+ on-click="_handleSaveAccountInfo"
disabled="[[!_accountInfoChanged]]">Save changes</gr-button>
</fieldset>
<h2
@@ -278,7 +280,7 @@ limitations under the License.
</section>
<gr-button
id="savePrefs"
- on-tap="_handleSavePreferences"
+ on-click="_handleSavePreferences"
disabled="[[!_prefsChanged]]">Save changes</gr-button>
</fieldset>
<h2
@@ -292,7 +294,7 @@ limitations under the License.
has-unsaved-changes="{{_diffPrefsChanged}}"></gr-diff-preferences>
<gr-button
id="saveDiffPrefs"
- on-tap="_handleSaveDiffPreferences"
+ on-click="_handleSaveDiffPreferences"
disabled$="[[!_diffPrefsChanged]]">Save changes</gr-button>
</fieldset>
<h2
@@ -306,7 +308,7 @@ limitations under the License.
has-unsaved-changes="{{_editPrefsChanged}}"></gr-edit-preferences>
<gr-button
id="saveEditPrefs"
- on-tap="_handleSaveEditPreferences"
+ on-click="_handleSaveEditPreferences"
disabled$="[[!_editPrefsChanged]]">Save changes</gr-button>
</fieldset>
<h2 id="Menu" class$="[[_computeHeaderClass(_menuChanged)]]">Menu</h2>
@@ -315,12 +317,12 @@ limitations under the License.
menu-items="{{_localMenu}}"></gr-menu-editor>
<gr-button
id="saveMenu"
- on-tap="_handleSaveMenu"
+ on-click="_handleSaveMenu"
disabled="[[!_menuChanged]]">Save changes</gr-button>
<gr-button
id="resetMenu"
link
- on-tap="_handleResetMenuButton">Reset</gr-button>
+ on-click="_handleResetMenuButton">Reset</gr-button>
</fieldset>
<h2 id="ChangeTableColumns"
class$="[[_computeHeaderClass(_changeTableChanged)]]">
@@ -333,7 +335,7 @@ limitations under the License.
</gr-change-table-editor>
<gr-button
id="saveChangeTable"
- on-tap="_handleSaveChangeTable"
+ on-click="_handleSaveChangeTable"
disabled="[[!_changeTableChanged]]">Save changes</gr-button>
</fieldset>
<h2
@@ -346,7 +348,7 @@ limitations under the License.
has-unsaved-changes="{{_watchedProjectsChanged}}"
id="watchedProjectsEditor"></gr-watched-projects-editor>
<gr-button
- on-tap="_handleSaveWatchedProjects"
+ on-click="_handleSaveWatchedProjects"
disabled$="[[!_watchedProjectsChanged]]"
id="_handleSaveWatchedProjects">Save changes</gr-button>
</fieldset>
@@ -360,21 +362,29 @@ limitations under the License.
id="emailEditor"
has-unsaved-changes="{{_emailsChanged}}"></gr-email-editor>
<gr-button
- on-tap="_handleSaveEmails"
+ on-click="_handleSaveEmails"
disabled$="[[!_emailsChanged]]">Save changes</gr-button>
</fieldset>
<fieldset id="newEmail">
<section>
<span class="title">New email address</span>
<span class="value">
- <input
- id="newEmailInput"
+ <iron-input
+ class="newEmailInput"
bind-value="{{_newEmail}}"
- is="iron-input"
type="text"
disabled="[[_addingEmail]]"
on-keydown="_handleNewEmailKeydown"
placeholder="email@example.com">
+ <input
+ class="newEmailInput"
+ bind-value="{{_newEmail}}"
+ is="iron-input"
+ type="text"
+ disabled="[[_addingEmail]]"
+ on-keydown="_handleNewEmailKeydown"
+ placeholder="email@example.com">
+ </iron-input>
</span>
</section>
<section
@@ -387,7 +397,7 @@ limitations under the License.
</section>
<gr-button
disabled="[[!_computeAddEmailButtonEnabled(_newEmail, _addingEmail)]]"
- on-tap="_handleAddEmailButton">Send verification</gr-button>
+ on-click="_handleAddEmailButton">Send verification</gr-button>
</fieldset>
<template is="dom-if" if="[[_showHttpAuth(_serverConfig)]]">
<div>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index a66c38d8de..714faab262 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -47,7 +47,6 @@
Polymer({
is: 'gr-settings-view',
- _legacyUndefinedCheck: true,
/**
* Fired when the title of the page should change.
@@ -149,6 +148,7 @@
behaviors: [
Gerrit.DocsUrlBehavior,
Gerrit.ChangeTableBehavior,
+ Gerrit.FireBehavior,
],
observers: [
@@ -392,7 +392,7 @@
},
_isNewEmailValid(newEmail) {
- return newEmail.includes('@');
+ return newEmail && newEmail.includes('@');
},
_computeAddEmailButtonEnabled(newEmail, addingEmail) {
@@ -435,6 +435,7 @@
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message: RELOAD_MESSAGE},
bubbles: true,
+ composed: true,
}));
this.async(() => {
window.location.reload();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index 506c6af25b..6dcf124eb2 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-settings-view.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
index 57458f824b..2a27194b0d 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
@@ -35,11 +35,13 @@ limitations under the License.
width: 7.5em;
}
#viewKeyOverlay {
- padding: 2em;
+ padding: var(--spacing-xxl);
width: 50em;
}
.publicKey {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
overflow-x: scroll;
overflow-wrap: break-word;
width: 30em;
@@ -50,7 +52,7 @@ limitations under the License.
right: 2em;
}
#existing {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
#existing .commentColumn {
min-width: 27em;
@@ -77,7 +79,7 @@ limitations under the License.
<td>
<gr-button
link
- on-tap="_showKey"
+ on-click="_showKey"
data-index$="[[index]]"
link>Click to View</gr-button>
</td>
@@ -93,7 +95,7 @@ limitations under the License.
<gr-button
link
data-index$="[[index]]"
- on-tap="_handleDeleteKey">Delete</gr-button>
+ on-click="_handleDeleteKey">Delete</gr-button>
</td>
</tr>
</template>
@@ -116,10 +118,10 @@ limitations under the License.
</fieldset>
<gr-button
class="closeButton"
- on-tap="_closeOverlay">Close</gr-button>
+ on-click="_closeOverlay">Close</gr-button>
</gr-overlay>
<gr-button
- on-tap="save"
+ on-click="save"
disabled$="[[!hasUnsavedChanges]]">Save changes</gr-button>
</fieldset>
<fieldset>
@@ -137,7 +139,7 @@ limitations under the License.
id="addButton"
link
disabled$="[[_computeAddButtonDisabled(_newKey)]]"
- on-tap="_handleAddKey">Add new SSH key</gr-button>
+ on-click="_handleAddKey">Add new SSH key</gr-button>
</fieldset>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
index 4c423e8920..874173a4a7 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-ssh-editor',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
index 8ed07309aa..d313f5a50b 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-ssh-editor</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-ssh-editor.html">
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.html
index 85fe368bb3..360ea2d331 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.html
@@ -14,7 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -27,7 +28,7 @@ limitations under the License.
<style include="gr-form-styles">
#watchedProjects .notifType {
text-align: center;
- padding: 0 0.4em;
+ padding: 0 var(--spacing-s);
}
.notifControl {
cursor: pointer;
@@ -39,7 +40,7 @@ limitations under the License.
.projectFilter {
color: var(--deemphasized-text-color);
font-style: italic;
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
.newFilterInput {
width: 100%;
@@ -73,7 +74,7 @@ limitations under the License.
is="dom-repeat"
items="[[_getTypes()]]"
as="type">
- <td class="notifControl" on-tap="_handleNotifCellTap">
+ <td class="notifControl" on-click="_handleNotifCellClick">
<input
type="checkbox"
data-index$="[[projectIndex]]"
@@ -86,7 +87,7 @@ limitations under the License.
<gr-button
link
data-index$="[[projectIndex]]"
- on-tap="_handleRemoveProject">Delete</gr-button>
+ on-click="_handleRemoveProject">Delete</gr-button>
</td>
</tr>
</template>
@@ -103,14 +104,18 @@ limitations under the License.
placeholder="Repo"></gr-autocomplete>
</th>
<th colspan$="[[_getTypeCount()]]">
- <input
- id="newFilter"
+ <iron-input
class="newFilterInput"
- is="iron-input"
placeholder="branch:name, or other search expression">
+ <input
+ id="newFilter"
+ class="newFilterInput"
+ is="iron-input"
+ placeholder="branch:name, or other search expression">
+ </iron-input>
</th>
<th>
- <gr-button link on-tap="_handleAddProject">Add</gr-button>
+ <gr-button link on-click="_handleAddProject">Add</gr-button>
</th>
</tr>
</tfoot>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
index bd18456692..a40094d3be 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
@@ -27,7 +27,6 @@
Polymer({
is: 'gr-watched-projects-editor',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
@@ -170,7 +169,7 @@
this.hasUnsavedChanges = true;
},
- _handleNotifCellTap(e) {
+ _handleNotifCellClick(e) {
const checkbox = Polymer.dom(e.target).querySelector('input');
if (checkbox) { checkbox.click(); }
},
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
index 9022bcc579..7a238ec42b 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-settings-view</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-watched-projects-editor.html">
@@ -187,6 +189,7 @@ limitations under the License.
element.$.newProject.value = {id: 'project b'};
element.$.newProject.setText('project b');
element.$.newFilter.bindValue = 'filter 1';
+ element.$.newFilter.value = 'filter 1';
element._handleAddProject();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
index 543ed85de2..5a095a4142 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../gr-account-link/gr-account-link.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../gr-icons/gr-icons.html">
@@ -34,28 +35,34 @@ limitations under the License.
background: var(--chip-background-color);
border-radius: .75em;
display: inline-flex;
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
}
:host([show-avatar]) .container {
padding-left: 0;
}
+ gr-button.remove {
+ --gr-remove-button-style: {
+ border: 0;
+ color: var(--deemphasized-text-color);
+ font-weight: normal;
+ height: .6em;
+ line-height: 10px;
+ margin-left: var(--spacing-xs);
+ padding: 0;
+ text-decoration: none;
+ }
+ }
+
gr-button.remove:hover,
gr-button.remove:focus {
--gr-button: {
+ @apply --gr-remove-button-style;
color: #333;
}
}
gr-button.remove {
--gr-button: {
- border: 0;
- color: var(--deemphasized-text-color);
- font-size: 1.7rem;
- font-weight: normal;
- height: .6em;
- line-height: .6;
- margin-left: .15em;
- padding: 0;
- text-decoration: none;
+ @apply --gr-remove-button-style;
}
}
:host:focus {
@@ -93,7 +100,7 @@ limitations under the License.
tabindex="-1"
aria-label="Remove"
class$="remove [[_getBackgroundClass(transparentBackground)]]"
- on-tap="_handleRemoveTap">
+ on-click="_handleRemoveTap">
<iron-icon icon="gr-icons:close"></iron-icon>
</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
index 827e33b527..10c876dab4 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
@@ -20,7 +20,6 @@
Polymer({
is: 'gr-account-chip',
- _legacyUndefinedCheck: true,
/**
* Fired to indicate a key was pressed while this chip was focused.
@@ -57,6 +56,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
ready() {
this._getHasAvatars().then(hasAvatars => {
this.showAvatar = hasAvatars;
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
index 582c83bbe4..ae656fd21f 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-autocomplete/gr-autocomplete.html">
+<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-account-entry">
<template>
@@ -35,14 +35,13 @@ limitations under the License.
borderless="[[borderless]]"
placeholder="[[placeholder]]"
threshold="[[suggestFrom]]"
- query="[[query]]"
+ query="[[querySuggestions]]"
allow-non-suggested-values="[[allowAnyInput]]"
on-commit="_handleInputCommit"
clear-on-commit
warn-uncommitted
text="{{_inputText}}">
</gr-autocomplete>
- <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-account-entry.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
new file mode 100644
index 0000000000..b2e0973b9b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
@@ -0,0 +1,101 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+ 'use strict';
+
+ /**
+ * gr-account-entry is an element for entering account
+ * and/or group with autocomplete support.
+ */
+ Polymer({
+ is: 'gr-account-entry',
+
+ /**
+ * Fired when an account is entered.
+ *
+ * @event add
+ */
+
+ /**
+ * When allowAnyInput is true, account-text-changed is fired when input text
+ * changed. This is needed so that the reply dialog's save button can be
+ * enabled for arbitrary cc's, which don't need a 'commit'.
+ *
+ * @event account-text-changed
+ */
+ properties: {
+ allowAnyInput: Boolean,
+ borderless: Boolean,
+ placeholder: String,
+
+ // suggestFrom = 0 to enable default suggestions.
+ suggestFrom: {
+ type: Number,
+ value: 0,
+ },
+
+ /** @type {!function(string): !Promise<Array<{name, value}>>} */
+ querySuggestions: {
+ type: Function,
+ notify: true,
+ value() {
+ return input => Promise.resolve([]);
+ },
+ },
+
+ _config: Object,
+ /** The value of the autocomplete entry. */
+ _inputText: {
+ type: String,
+ observer: '_inputTextChanged',
+ },
+
+ },
+
+ get focusStart() {
+ return this.$.input.focusStart;
+ },
+
+ focus() {
+ this.$.input.focus();
+ },
+
+ clear() {
+ this.$.input.clear();
+ },
+
+ setText(text) {
+ this.$.input.setText(text);
+ },
+
+ getText() {
+ return this.$.input.text;
+ },
+
+ _handleInputCommit(e) {
+ this.fire('add', {value: e.detail.value});
+ this.$.input.focus();
+ },
+
+ _inputTextChanged(text) {
+ if (text.length && this.allowAnyInput) {
+ this.dispatchEvent(new CustomEvent(
+ 'account-text-changed', {bubbles: true, composed: true}));
+ }
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
new file mode 100644
index 0000000000..6896af9537
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-account-entry</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<script src="../../../scripts/util.js"></script>
+
+<link rel="import" href="gr-account-entry.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-account-entry></gr-account-entry>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-account-entry tests', () => {
+ let sandbox;
+
+ const suggestion1 = {
+ email: 'email1@example.com',
+ _account_id: 1,
+ some_property: 'value',
+ };
+ const suggestion2 = {
+ email: 'email2@example.com',
+ _account_id: 2,
+ };
+ const suggestion3 = {
+ email: 'email25@example.com',
+ _account_id: 25,
+ some_other_property: 'other value',
+ };
+
+ setup(done => {
+ element = fixture('basic');
+ sandbox = sinon.sandbox.create();
+ return flush(done);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ suite('stubbed values for querySuggestions', () => {
+ setup(() => {
+ element.querySuggestions = input => {
+ return Promise.resolve([
+ suggestion1,
+ suggestion2,
+ suggestion3,
+ ]);
+ };
+ });
+ });
+
+ test('account-text-changed fired when input text changed and allowAnyInput',
+ () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const changeStub = sandbox.stub();
+ element.allowAnyInput = true;
+ element.querySuggestions = input => Promise.resolve([]);
+ element.addEventListener('account-text-changed', changeStub);
+ element.$.input.text = 'a';
+ assert.isTrue(changeStub.calledOnce);
+ element.$.input.text = 'ab';
+ assert.isTrue(changeStub.calledTwice);
+ });
+
+ test('account-text-changed not fired when input text changed without ' +
+ 'allowAnyInput', () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const changeStub = sandbox.stub();
+ element.querySuggestions = input => Promise.resolve([]);
+ element.addEventListener('account-text-changed', changeStub);
+ element.$.input.text = 'a';
+ assert.isFalse(changeStub.called);
+ });
+
+ test('setText', () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const suggestSpy = sandbox.spy(element.$.input, 'query');
+ element.setText('test text');
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.input.$.input.value, 'test text');
+ assert.isFalse(suggestSpy.called);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index bdf37bfa04..7ed79624b0 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../gr-avatar/gr-avatar.html">
<link rel="import" href="../gr-limited-text/gr-limited-text.html">
@@ -35,7 +35,7 @@ limitations under the License.
gr-avatar {
height: 1.3em;
width: 1.3em;
- margin-right: .15em;
+ margin-right: var(--spacing-xs);
vertical-align: -.25em;
}
.text {
@@ -64,7 +64,11 @@ limitations under the License.
[[_computeEmailStr(account)]]
</span>
<template is="dom-if" if="[[account.status]]">
- (<gr-limited-text limit="10" text="[[account.status]]"></gr-limited-text>)
+ (<gr-limited-text
+ disable-tooltip="true"
+ limit="[[_computeStatusTextLength(account, _serverConfig)]]"
+ text="[[account.status]]">
+ </gr-limited-text>)
</template>
</span>
</span>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 7983fad80e..418d2eac30 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-account-label',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -52,7 +51,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
Gerrit.TooltipBehavior,
],
@@ -66,18 +65,38 @@
return this.getUserName(config, account, false);
},
+ _computeStatusTextLength(account, config) {
+ // 35 as the max length of the name + status
+ return Math.max(10, 35 - this._computeName(account, config).length);
+ },
+
_computeAccountTitle(account, tooltip) {
+ // Polymer 2: check for undefined
+ if ([
+ account,
+ tooltip,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (!account) { return; }
let result = '';
if (this._computeName(account, this._serverConfig)) {
result += this._computeName(account, this._serverConfig);
}
if (account.email) {
- result += ' <' + account.email + '>';
+ result += ` <${account.email}>`;
}
if (this.additionalText) {
- return result + ' ' + this.additionalText;
+ result += ` ${this.additionalText}`;
}
+
+ // Show status in the label tooltip instead of
+ // in a separate tooltip on status
+ if (account.status) {
+ result += ` (${account.status})`;
+ }
+
return result;
},
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index cd8e194e03..45545fe211 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-account-label</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -66,32 +68,35 @@ limitations under the License.
{
name: 'Andrew Bonventre',
email: 'andybons+gerrit@gmail.com',
- }),
+ }, /* additionalText= */ ''),
'Andrew Bonventre <andybons+gerrit@gmail.com>');
assert.equal(element._computeAccountTitle(
- {name: 'Andrew Bonventre'}),
+ {name: 'Andrew Bonventre'}, /* additionalText= */ ''),
'Andrew Bonventre');
assert.equal(element._computeAccountTitle(
{
email: 'andybons+gerrit@gmail.com',
- }),
+ }, /* additionalText= */ ''),
'Anonymous <andybons+gerrit@gmail.com>');
assert.equal(element._computeShowEmailClass(
{
name: 'Andrew Bonventre',
email: 'andybons+gerrit@gmail.com',
- }), '');
+ }, /* additionalText= */ ''), '');
assert.equal(element._computeShowEmailClass(
{
email: 'andybons+gerrit@gmail.com',
- }), 'showEmail');
+ }, /* additionalText= */ ''), 'showEmail');
- assert.equal(element._computeShowEmailClass({name: 'Andrew Bonventre'}),
- '');
+ assert.equal(element._computeShowEmailClass(
+ {name: 'Andrew Bonventre'},
+ /* additionalText= */ ''
+ ),
+ '');
assert.equal(element._computeShowEmailClass(undefined), '');
@@ -134,5 +139,44 @@ limitations under the License.
'TestAnon');
});
});
+
+ suite('status in tooltip', () => {
+ setup(() => {
+ element = fixture('basic');
+ element.account = {
+ name: 'test',
+ email: 'test@google.com',
+ status: 'OOO until Aug 10th',
+ };
+ element._config = {
+ user: {
+ anonymous_coward_name: 'Anonymous Coward',
+ },
+ };
+ });
+
+ test('tooltip should contain status text', () => {
+ assert.deepEqual(element.title,
+ 'test <test@google.com> (OOO until Aug 10th)');
+ });
+
+ test('status text should not have tooltip', () => {
+ flushAsynchronousOperations();
+ assert.deepEqual(element.$$('gr-limited-text').title, '');
+ });
+
+ test('status text should honor the name length and total length', () => {
+ assert.deepEqual(
+ element._computeStatusTextLength(element.account, element._config),
+ 31
+ );
+ assert.deepEqual(
+ element._computeStatusTextLength({
+ name: 'a very long long long long name',
+ }, element._config),
+ 10
+ );
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
index 34b0de63bd..d3575b29f9 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.html
@@ -16,7 +16,7 @@ limitations under the License.
-->
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../gr-account-label/gr-account-label.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
index 03967f10ea..faaf9c3ddd 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-account-link',
- _legacyUndefinedCheck: true,
properties: {
additionalText: String,
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index 6d1831e7a8..134c579ec9 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-account-link</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-account-link.html">
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
index 1bfc5eba5e..2ce608be8f 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+<link rel="import" href="../gr-account-chip/gr-account-chip.html">
<link rel="import" href="../gr-account-entry/gr-account-entry.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -25,7 +26,7 @@ limitations under the License.
<style include="shared-styles">
gr-account-chip {
display: inline-block;
- margin: .2em .2em .2em 0;
+ margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
}
gr-account-entry {
display: flex;
@@ -55,7 +56,7 @@ limitations under the License.
account="[[account]]"
class$="[[_computeChipClass(account)]]"
data-account-id$="[[account._account_id]]"
- removable="[[_computeRemovable(account)]]"
+ removable="[[_computeRemovable(account, readonly)]]"
on-keydown="_handleChipKeydown"
tabindex="-1">
</gr-account-chip>
@@ -66,13 +67,13 @@ limitations under the License.
hidden$="[[_computeEntryHidden(maxCount, accounts.*, readonly)]]"
id="entry"
change="[[change]]"
- filter="[[filter]]"
placeholder="[[placeholder]]"
on-add="_handleAdd"
on-input-keydown="_handleInputKeydown"
allow-any-input="[[allowAnyInput]]"
- allow-any-user="[[allowAnyUser]]">
+ query-suggestions="[[_querySuggestions]]">
</gr-account-entry>
+ <slot></slot>
</template>
<script src="gr-account-list.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index 7cdffc8846..de66c501d7 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-account-list',
- _legacyUndefinedCheck: true,
/**
* Fired when user inputs an invalid email address.
@@ -38,6 +37,20 @@
change: Object,
filter: Function,
placeholder: String,
+ disabled: {
+ type: Function,
+ value: false,
+ },
+
+ /**
+ * Returns suggestions and convert them to list item
+ *
+ * @type {Gerrit.GrSuggestionsProvider}
+ */
+ suggestionsProvider: {
+ type: Object,
+ },
+
/**
* Needed for template checking since value is initially set to null.
*
@@ -51,21 +64,6 @@
type: Boolean,
value: false,
},
-
- /**
- * When true, the account-entry autocomplete uses the account suggest API
- * endpoint, which suggests any account in that Gerrit instance (and does
- * not suggest groups).
- *
- * When false/undefined, account-entry uses the suggest_reviewers API
- * endpoint, which suggests any account or group in that Gerrit instance
- * that is not already a reviewer (or is not CCed) on that change.
- */
- allowAnyUser: {
- type: Boolean,
- value: false,
- },
-
/**
* When true, allows for non-suggested inputs to be added.
*/
@@ -83,14 +81,29 @@
type: Number,
value: 0,
},
+
+ /** Returns suggestion items
+ *
+ * @type {!function(string): Promise<Array<Gerrit.GrSuggestionItem>>}
+ */
+ _querySuggestions: {
+ type: Function,
+ value() {
+ return this._getSuggestions.bind(this);
+ },
+ },
},
+ behaviors: [
+ // Used in the tests for gr-account-list and other elements tests.
+ Gerrit.FireBehavior,
+ ],
+
listeners: {
remove: '_handleRemove',
},
get accountChips() {
- // Polymer2: querySelectorAll returns NodeList instead of Array.
return Array.from(
Polymer.dom(this.root).querySelectorAll('gr-account-chip'));
},
@@ -99,36 +112,54 @@
return this.$.entry.focusStart;
},
+ _getSuggestions(input) {
+ const provider = this.suggestionsProvider;
+ if (!provider) {
+ return Promise.resolve([]);
+ }
+ return provider.getSuggestions(input).then(suggestions => {
+ if (!suggestions) { return []; }
+ if (this.filter) {
+ suggestions = suggestions.filter(this.filter);
+ }
+ return suggestions.map(suggestion =>
+ provider.makeSuggestionItem(suggestion));
+ });
+ },
+
_handleAdd(e) {
- this._addReviewer(e.detail.value);
+ this._addAccountItem(e.detail.value);
},
- _addReviewer(reviewer) {
+ _addAccountItem(item) {
// Append new account or group to the accounts property. We add our own
// internal properties to the account/group here, so we clone the object
// to avoid cluttering up the shared change object.
- if (reviewer.account) {
+ if (item.account) {
const account =
- Object.assign({}, reviewer.account, {_pendingAdd: true});
+ Object.assign({}, item.account, {_pendingAdd: true});
this.push('accounts', account);
- } else if (reviewer.group) {
- if (reviewer.confirm) {
- this.pendingConfirmation = reviewer;
+ } else if (item.group) {
+ if (item.confirm) {
+ this.pendingConfirmation = item;
return;
}
- const group = Object.assign({}, reviewer.group,
+ const group = Object.assign({}, item.group,
{_pendingAdd: true, _group: true});
this.push('accounts', group);
} else if (this.allowAnyInput) {
- if (!reviewer.includes('@')) {
+ if (!item.includes('@')) {
// Repopulate the input with what the user tried to enter and have
// a toast tell them why they can't enter it.
- this.$.entry.setText(reviewer);
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message: VALID_EMAIL_ALERT}, bubbles: true}));
+ this.$.entry.setText(item);
+ this.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {message: VALID_EMAIL_ALERT},
+ bubbles: true,
+ composed: true,
+ }));
return false;
} else {
- const account = {email: reviewer, _pendingAdd: true};
+ const account = {email: item, _pendingAdd: true};
this.push('accounts', account);
}
}
@@ -166,8 +197,8 @@
return a === b;
},
- _computeRemovable(account) {
- if (this.readonly) { return false; }
+ _computeRemovable(account, readonly) {
+ if (readonly) { return false; }
if (this.removableValues) {
for (let i = 0; i < this.removableValues.length; i++) {
if (this._accountMatches(this.removableValues[i], account)) {
@@ -186,7 +217,9 @@
},
_removeAccount(toRemove) {
- if (!toRemove || !this._computeRemovable(toRemove)) { return; }
+ if (!toRemove || !this._computeRemovable(toRemove, this.readonly)) {
+ return;
+ }
for (let i = 0; i < this.accounts.length; i++) {
let matches;
const account = this.accounts[i];
@@ -203,8 +236,13 @@
console.warn('received remove event for missing account', toRemove);
},
+ _getNativeInput(paperInput) {
+ // In Polymer 2 inputElement isn't nativeInput anymore
+ return paperInput.$.nativeInput || paperInput.inputElement;
+ },
+
_handleInputKeydown(e) {
- const input = e.detail.input.inputElement;
+ const input = this._getNativeInput(e.detail.input);
if (input.selectionStart !== input.selectionEnd ||
input.selectionStart !== 0) {
return;
@@ -270,7 +308,7 @@
submitEntryText() {
const text = this.$.entry.getText();
if (!text.length) { return true; }
- const wasSubmitted = this._addReviewer(text);
+ const wasSubmitted = this._addAccountItem(text);
if (wasSubmitted) { this.$.entry.clear(); }
return wasSubmitted;
},
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 544238b7d7..f931a69ec2 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-account-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-account-list.html">
@@ -33,6 +35,16 @@ limitations under the License.
</test-fixture>
<script>
+ class MockSuggestionsProvider {
+ getSuggestions(input) {
+ return Promise.resolve([]);
+ }
+
+ makeSuggestionItem(item) {
+ return item;
+ }
+ }
+
suite('gr-account-list tests', () => {
let _nextAccountId = 0;
const makeAccount = function() {
@@ -49,10 +61,11 @@ limitations under the License.
};
};
- let existingReviewer1;
- let existingReviewer2;
+ let existingAccount1;
+ let existingAccount2;
let sandbox;
let element;
+ let suggestionsProvider;
function getChips() {
return Polymer.dom(element.root).querySelectorAll('gr-account-chip');
@@ -60,14 +73,16 @@ limitations under the License.
setup(() => {
sandbox = sinon.sandbox.create();
- existingReviewer1 = makeAccount();
- existingReviewer2 = makeAccount();
+ existingAccount1 = makeAccount();
+ existingAccount2 = makeAccount();
stub('gr-rest-api-interface', {
getConfig() { return Promise.resolve({}); },
});
element = fixture('basic');
- element.accounts = [existingReviewer1, existingReviewer2];
+ element.accounts = [existingAccount1, existingAccount2];
+ suggestionsProvider = new MockSuggestionsProvider();
+ element.suggestionsProvider = suggestionsProvider;
});
teardown(() => {
@@ -107,7 +122,7 @@ limitations under the License.
assert.isTrue(chips[2].classList.contains('pendingAdd'));
// Removed accounts are taken out of the list.
- element.fire('remove', {account: existingReviewer1});
+ element.fire('remove', {account: existingAccount1});
flushAsynchronousOperations();
chips = getChips();
assert.equal(chips.length, 2);
@@ -115,7 +130,7 @@ limitations under the License.
assert.isTrue(chips[1].classList.contains('pendingAdd'));
// Invalid remove is ignored.
- element.fire('remove', {account: existingReviewer1});
+ element.fire('remove', {account: existingAccount1});
element.fire('remove', {account: newAccount});
flushAsynchronousOperations();
chips = getChips();
@@ -145,6 +160,52 @@ limitations under the License.
assert.isFalse(chips[0].classList.contains('pendingAdd'));
});
+ test('_getSuggestions uses filter correctly', done => {
+ const originalSuggestions = [
+ {
+ email: 'abc@example.com',
+ text: 'abcd',
+ _account_id: 3,
+ },
+ {
+ email: 'qwe@example.com',
+ text: 'qwer',
+ _account_id: 1,
+ },
+ {
+ email: 'xyz@example.com',
+ text: 'aaaaa',
+ _account_id: 25,
+ },
+ ];
+ sandbox.stub(suggestionsProvider, 'getSuggestions')
+ .returns(Promise.resolve(originalSuggestions));
+ sandbox.stub(suggestionsProvider, 'makeSuggestionItem', suggestion => {
+ return {
+ name: suggestion.email,
+ value: suggestion._account_id,
+ };
+ });
+
+
+ element._getSuggestions().then(suggestions => {
+ // Default is no filtering.
+ assert.equal(suggestions.length, 3);
+
+ // Set up filter that only accepts suggestion1.
+ const accountId = originalSuggestions[0]._account_id;
+ element.filter = function(suggestion) {
+ return suggestion._account_id === accountId;
+ };
+
+ element._getSuggestions().then(suggestions => {
+ assert.deepEqual(suggestions,
+ [{name: originalSuggestions[0].email,
+ value: originalSuggestions[0]._account_id}]);
+ }).then(done);
+ });
+ });
+
test('_computeChipClass', () => {
const account = makeAccount();
assert.equal(element._computeChipClass(account), '');
@@ -161,18 +222,18 @@ limitations under the License.
newAccount._pendingAdd = true;
element.readonly = false;
element.removableValues = [];
- assert.isFalse(element._computeRemovable(existingReviewer1));
- assert.isTrue(element._computeRemovable(newAccount));
+ assert.isFalse(element._computeRemovable(existingAccount1, false));
+ assert.isTrue(element._computeRemovable(newAccount, false));
- element.removableValues = [existingReviewer1];
- assert.isTrue(element._computeRemovable(existingReviewer1));
- assert.isTrue(element._computeRemovable(newAccount));
- assert.isFalse(element._computeRemovable(existingReviewer2));
+ element.removableValues = [existingAccount1];
+ assert.isTrue(element._computeRemovable(existingAccount1, false));
+ assert.isTrue(element._computeRemovable(newAccount, false));
+ assert.isFalse(element._computeRemovable(existingAccount2, false));
element.readonly = true;
- assert.isFalse(element._computeRemovable(existingReviewer1));
- assert.isFalse(element._computeRemovable(newAccount));
+ assert.isFalse(element._computeRemovable(existingAccount1, true));
+ assert.isFalse(element._computeRemovable(newAccount, true));
});
test('submitEntryText', () => {
@@ -291,13 +352,40 @@ limitations under the License.
assert.isTrue(element.$.entry.hasAttribute('hidden'));
});
- suite('allowAnyInput', () => {
- let entry;
+ test('enter text calls suggestions provider', done => {
+ const suggestions = [
+ {
+ email: 'abc@example.com',
+ text: 'abcd',
+ },
+ {
+ email: 'qwe@example.com',
+ text: 'qwer',
+ },
+ ];
+ const getSuggestionsStub =
+ sandbox.stub(suggestionsProvider, 'getSuggestions')
+ .returns(Promise.resolve(suggestions));
+
+ const makeSuggestionItemStub =
+ sandbox.stub(suggestionsProvider, 'makeSuggestionItem', item => item);
+
+ const input = element.$.entry.$.input;
+ input.text = 'newTest';
+ MockInteractions.focus(input.$.input);
+ input.noDebounce = true;
+ flushAsynchronousOperations();
+ flush(() => {
+ assert.isTrue(getSuggestionsStub.calledOnce);
+ assert.equal(getSuggestionsStub.lastCall.args[0], 'newTest');
+ assert.equal(makeSuggestionItemStub.getCalls().length, 2);
+ done();
+ });
+ });
+
+ suite('allowAnyInput', () => {
setup(() => {
- entry = element.$.entry;
- sandbox.stub(entry, '_getReviewerSuggestions');
- sandbox.stub(entry.$.input, '_updateSuggestions');
element.allowAnyInput = true;
});
@@ -330,44 +418,51 @@ limitations under the License.
});
suite('keyboard interactions', () => {
- test('backspace at text input start removes last account', () => {
+ test('backspace at text input start removes last account', done => {
const input = element.$.entry.$.input;
- sandbox.stub(element.$.entry, '_getReviewerSuggestions');
sandbox.stub(input, '_updateSuggestions');
sandbox.stub(element, '_computeRemovable').returns(true);
- // Next line is a workaround for Firefix not moving cursor
- // on input field update
- assert.equal(input.$.input.inputElement.selectionStart, 0);
- input.text = 'test';
- MockInteractions.focus(input.$.input);
- flushAsynchronousOperations();
- assert.equal(element.accounts.length, 2);
- MockInteractions.pressAndReleaseKeyOn(
- input.$.input.inputElement, 8); // Backspace
- assert.equal(element.accounts.length, 2);
- input.text = '';
- MockInteractions.pressAndReleaseKeyOn(
- input.$.input.inputElement, 8); // Backspace
- assert.equal(element.accounts.length, 1);
+ flush(() => {
+ // Next line is a workaround for Firefix not moving cursor
+ // on input field update
+ assert.equal(
+ element._getNativeInput(input.$.input).selectionStart, 0);
+ input.text = 'test';
+ MockInteractions.focus(input.$.input);
+ flushAsynchronousOperations();
+ assert.equal(element.accounts.length, 2);
+ MockInteractions.pressAndReleaseKeyOn(
+ element._getNativeInput(input.$.input), 8); // Backspace
+ assert.equal(element.accounts.length, 2);
+ input.text = '';
+ MockInteractions.pressAndReleaseKeyOn(
+ element._getNativeInput(input.$.input), 8); // Backspace
+ flushAsynchronousOperations();
+ assert.equal(element.accounts.length, 1);
+ done();
+ });
});
- test('arrow key navigation', () => {
+ test('arrow key navigation', done => {
const input = element.$.entry.$.input;
input.text = '';
element.accounts = [makeAccount(), makeAccount()];
- MockInteractions.focus(input.$.input);
- flushAsynchronousOperations();
- const chips = element.accountChips;
- const chipsOneSpy = sandbox.spy(chips[1], 'focus');
- MockInteractions.pressAndReleaseKeyOn(input.$.input, 37); // Left
- assert.isTrue(chipsOneSpy.called);
- const chipsZeroSpy = sandbox.spy(chips[0], 'focus');
- MockInteractions.pressAndReleaseKeyOn(chips[1], 37); // Left
- assert.isTrue(chipsZeroSpy.called);
- MockInteractions.pressAndReleaseKeyOn(chips[0], 37); // Left
- assert.isTrue(chipsZeroSpy.calledOnce);
- MockInteractions.pressAndReleaseKeyOn(chips[0], 39); // Right
- assert.isTrue(chipsOneSpy.calledTwice);
+ flush(() => {
+ MockInteractions.focus(input.$.input);
+ flushAsynchronousOperations();
+ const chips = element.accountChips;
+ const chipsOneSpy = sandbox.spy(chips[1], 'focus');
+ MockInteractions.pressAndReleaseKeyOn(input.$.input, 37); // Left
+ assert.isTrue(chipsOneSpy.called);
+ const chipsZeroSpy = sandbox.spy(chips[0], 'focus');
+ MockInteractions.pressAndReleaseKeyOn(chips[1], 37); // Left
+ assert.isTrue(chipsZeroSpy.called);
+ MockInteractions.pressAndReleaseKeyOn(chips[0], 37); // Left
+ assert.isTrue(chipsZeroSpy.calledOnce);
+ MockInteractions.pressAndReleaseKeyOn(chips[0], 39); // Right
+ assert.isTrue(chipsOneSpy.calledTwice);
+ done();
+ });
});
test('delete', done => {
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
index b00fded1ab..b0018df74b 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -31,11 +31,10 @@ limitations under the License.
:host([toast]) {
background-color: var(--tooltip-background-color);
bottom: 1.25rem;
- border-radius: 3px;
+ border-radius: var(--border-radius);
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
color: var(--view-background-color);
left: 1.25rem;
- padding: 1em 1.5em;
position: fixed;
transform: translateY(5rem);
transition: transform var(--gr-alert-transition-duration, 80ms) ease-out;
@@ -44,6 +43,17 @@ limitations under the License.
:host([shown]) {
transform: translateY(0);
}
+ /**
+ * NOTE: To avoid style being overwritten by outside of the shadow DOM
+ * (as outside styles always win), .content-wrapper is introduced as a
+ * wrapper around main content to have better encapsulation, styles that
+ * may be affected by outside should be defined on it.
+ * In this case, `padding:0px` is defined in main.css for all elements
+ * with the universal selector: *.
+ */
+ .content-wrapper {
+ padding: var(--spacing-l) var(--spacing-xl);
+ }
.text {
color: var(--tooltip-text-color);
display: inline-block;
@@ -55,19 +65,21 @@ limitations under the License.
.action {
color: var(--link-color);
font-weight: var(--font-weight-bold);
- margin-left: 1em;
+ margin-left: var(--spacing-l);
text-decoration: none;
--gr-button: {
padding: 0;
}
}
</style>
- <span class="text">[[text]]</span>
- <gr-button
- link
- class="action"
- hidden$="[[_hideActionButton]]"
- on-tap="_handleActionTap">[[actionText]]</gr-button>
+ <div class="content-wrapper">
+ <span class="text">[[text]]</span>
+ <gr-button
+ link
+ class="action"
+ hidden$="[[_hideActionButton]]"
+ on-click="_handleActionTap">[[actionText]]</gr-button>
+ </div>
</template>
<script src="gr-alert.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
index ec7b6eb965..e7c8b2ce97 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-alert',
- _legacyUndefinedCheck: true,
/**
* Fired when the action button is pressed.
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
index 095e6407bd..2338d55247 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-alert</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-alert.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
index 7b82635462..92080682ab 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/iron-fit-behavior/iron-fit-behavior.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<script src="../../../scripts/rootElement.js"></script>
<link rel="import" href="../../../styles/shared-styles.html">
@@ -40,7 +42,7 @@ limitations under the License.
cursor: pointer;
display: flex;
justify-content: space-between;
- padding: .5em .75em;
+ padding: var(--spacing-m) var(--spacing-l);
}
li:last-of-type {
border: none;
@@ -67,7 +69,7 @@ limitations under the License.
}
.label {
color: var(--deemphasized-text-color);
- padding-left: 1em;
+ padding-left: var(--spacing-l);
}
.hide {
display: none;
@@ -86,7 +88,7 @@ limitations under the License.
aria-label$="[[item.name]]"
class="autocompleteOption"
role="option"
- on-tap="_handleTapItem">
+ on-click="_handleClickItem">
<span>[[item.text]]</span>
<span class$="label [[_computeLabelClass(item)]]">[[item.label]]</span>
</li>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index 1af629d246..b8c76ff1f8 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-autocomplete-dropdown',
- _legacyUndefinedCheck: true,
/**
* Fired when the dropdown is closed.
@@ -53,13 +52,11 @@
value: () => [],
observer: '_resetCursorStops',
},
- _suggestionEls: {
- type: Array,
- observer: '_resetCursorIndex',
- },
+ _suggestionEls: Array,
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Polymer.IronFitBehavior,
],
@@ -78,9 +75,9 @@
open() {
this.isHidden = false;
- this.refit();
this._resetCursorStops();
- this._resetCursorIndex();
+ // Refit should run after we call Polymer.flush inside _resetCursorStops
+ this.refit();
},
getCurrentText() {
@@ -138,7 +135,7 @@
this.close();
},
- _handleTapItem(e) {
+ _handleClickItem(e) {
e.preventDefault();
e.stopPropagation();
let selected = e.target;
@@ -147,7 +144,7 @@
selected = selected.parentElement;
}
this.fire('item-selected', {
- trigger: 'tap',
+ trigger: 'click',
selected,
});
},
@@ -162,10 +159,12 @@
_resetCursorStops() {
if (this.suggestions.length > 0) {
- Polymer.dom.flush();
- // Polymer2: querySelectorAll returns NodeList instead of Array.
- this._suggestionEls = Array.from(
- this.$.suggestions.querySelectorAll('li'));
+ if (!this.isHidden) {
+ Polymer.dom.flush();
+ this._suggestionEls = Array.from(
+ this.$.suggestions.querySelectorAll('li'));
+ this._resetCursorIndex();
+ }
} else {
this._suggestionEls = [];
}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
index d4d54ffcf6..a7b59d72ff 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-autocomplete-dropdown</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-autocomplete-dropdown.html">
@@ -126,7 +128,7 @@ limitations under the License.
MockInteractions.tap(element.$.suggestions.querySelectorAll('li')[1]);
flushAsynchronousOperations();
assert.deepEqual(itemSelectedStub.lastCall.args[0].detail, {
- trigger: 'tap',
+ trigger: 'click',
selected: element.$.suggestions.querySelectorAll('li')[1],
});
});
@@ -139,7 +141,7 @@ limitations under the License.
.lastElementChild);
flushAsynchronousOperations();
assert.deepEqual(itemSelectedStub.lastCall.args[0].detail, {
- trigger: 'tap',
+ trigger: 'click',
selected: element.$.suggestions.querySelectorAll('li')[0],
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index a878174469..c9d12ce81a 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -14,8 +14,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/paper-input/paper-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
@@ -32,21 +33,23 @@ limitations under the License.
display: inline-block;
}
iron-icon {
- margin: 0 .25em;
+ margin: 0 var(--spacing-xs);
+ vertical-align: top;
}
paper-input:not(.borderless) {
border: 1px solid var(--border-color);
}
paper-input {
- height: 100%;
+ height: var(--line-height-normal);
width: 100%;
@apply --gr-autocomplete;
--paper-input-container: {
padding: 0;
- }
+ };
--paper-input-container-input: {
font-size: var(--font-size-normal);
- }
+ line-height: var(--line-height-normal);
+ };
--paper-input-container-underline: {
display: none;
};
@@ -60,7 +63,7 @@ limitations under the License.
paper-input.warnUncommitted {
--paper-input-container-input: {
color: var(--error-text-color);
- font-size: var(--font-size-normal);
+ font-size: inherit;
}
}
</style>
@@ -75,14 +78,14 @@ limitations under the License.
on-focus="_onInputFocus"
on-blur="_onInputBlur"
autocomplete="off">
- <!-- slot is for future use (2.x) while prefix attribute is for 1.x
- (current) -->
- <iron-icon
+
+ <!-- prefix as attribute is required to for polymer 1 -->
+ <div slot="prefix" prefix>
+ <iron-icon
icon="gr-icons:search"
- slot="prefix"
- prefix
class$="searchIcon [[_computeShowSearchIconClass(showSearchIcon)]]">
- </iron-icon>
+ </iron-icon>
+ </div>
</paper-input>
<gr-autocomplete-dropdown
vertical-align="top"
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 9d16b9c63e..ee087cc6e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-autocomplete',
- _legacyUndefinedCheck: true,
/**
* Fired when a value is chosen.
@@ -168,6 +167,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -176,12 +176,17 @@
'_updateSuggestions(text, threshold, noDebounce)',
],
+ get _nativeInput() {
+ // In Polymer 2 inputElement isn't nativeInput anymore
+ return this.$.input.$.nativeInput || this.$.input.inputElement;
+ },
+
attached() {
- this.listen(document.body, 'tap', '_handleBodyTap');
+ this.listen(document.body, 'click', '_handleBodyClick');
},
detached() {
- this.unlisten(document.body, 'tap', '_handleBodyTap');
+ this.unlisten(document.body, 'click', '_handleBodyClick');
this.cancelDebouncer('update-suggestions');
},
@@ -190,11 +195,11 @@
},
focus() {
- this.$.input.focus();
+ this._nativeInput.focus();
},
selectAll() {
- const nativeInputElement = this.$.input.inputElement;
+ const nativeInputElement = this._nativeInput;
if (!this.$.input.value) { return; }
nativeInputElement.setSelectionRange(0, this.$.input.value.length);
},
@@ -205,7 +210,7 @@
_handleItemSelect(e) {
// Let _handleKeydown deal with keyboard interaction.
- if (e.detail.trigger !== 'tap') { return; }
+ if (e.detail.trigger !== 'click') { return; }
this._selected = e.detail.selected;
this._commit();
},
@@ -242,8 +247,13 @@
},
_updateSuggestions(text, threshold, noDebounce) {
+ // Polymer 2: check for undefined
+ if ([text, threshold, noDebounce].some(arg => arg === undefined)) {
+ return;
+ }
+
if (this._disableSuggestions) { return; }
- if (text === undefined || text.length < threshold) {
+ if (text.length < threshold) {
this._suggestions = [];
this.value = '';
return;
@@ -320,7 +330,7 @@
default:
// For any normal keypress, return focus to the input to allow for
// unbroken user input.
- this.$.input.inputElement.focus();
+ this.focus();
// Since this has been a normal keypress, the suggestions will have
// been based on a previous input. Clear them. This prevents an
@@ -365,7 +375,7 @@
}
},
- _handleBodyTap(e) {
+ _handleBodyClick(e) {
const eventPath = Polymer.dom(e).path;
for (let i = 0; i < eventPath.length; i++) {
if (eventPath[i] === this) {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 1a76f98260..ea1fd503b1 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reviewer-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-autocomplete.html">
@@ -80,16 +82,19 @@ limitations under the License.
});
});
- test('selectAll', () => {
- const nativeInput = element.$.input.inputElement;
- const selectionStub = sandbox.stub(nativeInput, 'setSelectionRange');
+ test('selectAll', done => {
+ flush(() => {
+ const nativeInput = element._nativeInput;
+ const selectionStub = sandbox.stub(nativeInput, 'setSelectionRange');
- element.selectAll();
- assert.isFalse(selectionStub.called);
+ element.selectAll();
+ assert.isFalse(selectionStub.called);
- element.$.input.value = 'test';
- element.selectAll();
- assert.isTrue(selectionStub.called);
+ element.$.input.value = 'test';
+ element.selectAll();
+ assert.isTrue(selectionStub.called);
+ done();
+ });
});
test('esc key behavior', done => {
@@ -318,12 +323,15 @@ limitations under the License.
});
});
- test('search icon shows with showSearchIcon property', () => {
- assert.equal(getComputedStyle(element.$$('iron-icon')).display,
- 'none');
- element.showSearchIcon = true;
- assert.notEqual(getComputedStyle(element.$$('iron-icon')).display,
- 'none');
+ test('search icon shows with showSearchIcon property', done => {
+ flush(() => {
+ assert.equal(getComputedStyle(element.$$('iron-icon')).display,
+ 'none');
+ element.showSearchIcon = true;
+ assert.notEqual(getComputedStyle(element.$$('iron-icon')).display,
+ 'none');
+ done();
+ });
});
test('vertical offset overridden by param if it exists', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
index bc63acf511..1daffa2323 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
@@ -28,7 +28,7 @@ limitations under the License.
display: inline-block;
border-radius: 50%;
background-size: cover;
- background-color: var(--background-color, #f1f2f3);
+ background-color: var(--avatar-background-color, #f1f2f3);
}
</style>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
index 2435e587bc..bf563823ac 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-avatar',
- _legacyUndefinedCheck: true,
properties: {
account: {
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 63718c533c..de05a5c041 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-avatar</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-avatar.html">
@@ -115,7 +117,7 @@ limitations under the License.
assert.strictEqual(element.style.backgroundImage, '');
// Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
Promise.all([
element.$.restAPI.getConfig(),
@@ -127,65 +129,82 @@ limitations under the License.
element.style.backgroundImage.includes('/accounts/123/avatar?s=64'));
});
});
+ });
- test('dom for non available account', () => {
- assert.isFalse(element.hasAttribute('hidden'));
+ suite('plugin has avatars', () => {
+ let element;
+ let sandbox;
- sandbox.stub(element, '_getConfig', () => {
- return Promise.resolve({plugin: {has_avatars: true}});
- });
+ setup(() => {
+ sandbox = sinon.sandbox.create();
- // Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ stub('gr-avatar', {
+ _getConfig: () => {
+ return Promise.resolve({plugin: {has_avatars: true}});
+ },
+ });
- return Promise.all([
- element.$.restAPI.getConfig(),
- Gerrit.awaitPluginsLoaded(),
- ]).then(() => {
- assert.isTrue(element.hasAttribute('hidden'));
+ element = fixture('basic');
+ });
- assert.strictEqual(element.style.backgroundImage, '');
- });
+ teardown(() => {
+ sandbox.restore();
});
- test('avatar config not set and account not set', () => {
+ test('dom for non available account', () => {
assert.isFalse(element.hasAttribute('hidden'));
- sandbox.stub(element, '_getConfig', () => {
- return Promise.resolve({});
- });
-
// Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
return Promise.all([
element.$.restAPI.getConfig(),
Gerrit.awaitPluginsLoaded(),
]).then(() => {
assert.isTrue(element.hasAttribute('hidden'));
+
+ assert.strictEqual(element.style.backgroundImage, '');
});
});
+ });
- test('avatar config not set and account set', () => {
- assert.isFalse(element.hasAttribute('hidden'));
+ suite('config not set', () => {
+ let element;
+ let sandbox;
- sandbox.stub(element, '_getConfig', () => {
- return Promise.resolve({});
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ stub('gr-avatar', {
+ _getConfig: () => {
+ return Promise.resolve({});
+ },
});
- element.imageSize = 64;
- element.account = {
- _account_id: 123,
- };
+ element = fixture('basic');
+ });
- // Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ teardown(() => {
+ sandbox.restore();
+ });
- return Promise.all([
- element.$.restAPI.getConfig(),
- Gerrit.awaitPluginsLoaded(),
- ]).then(() => {
- assert.isTrue(element.hasAttribute('hidden'));
+ test('avatar hidden when account set', () => {
+ flush(() => {
+ assert.isFalse(element.hasAttribute('hidden'));
+
+ element.imageSize = 64;
+ element.account = {
+ _account_id: 123,
+ };
+ // Emulate plugins loaded.
+ Gerrit._loadPlugins([]);
+
+ return Promise.all([
+ element.$.restAPI.getConfig(),
+ Gerrit.awaitPluginsLoaded(),
+ ]).then(() => {
+ assert.isTrue(element.hasAttribute('hidden'));
+ });
});
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index cdf617fbbd..87caf64522 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
+<link rel="import" href="/bower_components/paper-button/paper-button.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-button">
@@ -39,6 +39,37 @@ limitations under the License.
text-transform: none;
}
paper-button {
+ /* The next lines contains a copy of paper-button style.
+ Without a copy, the @apply works incorrectly with Polymer 2.
+ @apply is deprecated and is not recommended to use. It is expected
+ that @apply will be replaced with the ::part CSS pseudo-element.
+ After replacecment copied lines can be removed.
+ */
+ @apply --layout-inline;
+ @apply --layout-center-center;
+ position: relative;
+ box-sizing: border-box;
+ min-width: 5.14em;
+ margin: 0 0.29em;
+ background: transparent;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ -webkit-tap-highlight-color: transparent;
+ font: inherit;
+ text-transform: uppercase;
+ outline-width: 0;
+ border-radius: var(--border-radius);
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ cursor: pointer;
+ z-index: 0;
+ padding: var(--spacing-m);
+
+ @apply --paper-font-common-base;
+ @apply --paper-button;
+ /* End of copy*/
+
/* paper-button sets this to anti-aliased, which appears different than
bold font elsewhere on macOS. */
-webkit-font-smoothing: initial;
@@ -53,6 +84,24 @@ limitations under the License.
padding: var(--padding, 4px 8px);
@apply --gr-button;
}
+ /* https://github.com/PolymerElements/paper-button/blob/2.x/paper-button.html */
+ /* BEGIN: Copy from paper-button */
+ paper-button[elevation="1"] {
+ @apply --paper-material-elevation-1;
+ }
+ paper-button[elevation="2"] {
+ @apply --paper-material-elevation-2;
+ }
+ paper-button[elevation="3"] {
+ @apply --paper-material-elevation-3;
+ }
+ paper-button[elevation="4"] {
+ @apply --paper-material-elevation-4;
+ }
+ paper-button[elevation="5"] {
+ @apply --paper-material-elevation-5;
+ }
+ /* END: Copy from paper-button */
paper-button:hover {
background: linear-gradient(
rgba(0, 0, 0, .12),
@@ -91,6 +140,8 @@ limitations under the License.
}
:host([disabled][link]) {
--background-color: transparent;
+ --text-color: var(--deemphasized-text-color);
+ cursor: default;
}
/* Styles for the optional down arrow */
@@ -101,8 +152,8 @@ limitations under the License.
border-top: .36em solid #ccc;
border-left: .36em solid transparent;
border-right: .36em solid transparent;
- margin-bottom: .05em;
- margin-left: .5em;
+ margin-bottom: var(--spacing-xxs);
+ margin-left: var(--spacing-m);
transition: border-top-color 200ms;
}
:host([down-arrow]) paper-button:hover .downArrow {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index 5988cdec88..afc6ba84d8 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-button',
- _legacyUndefinedCheck: true,
properties: {
tooltip: String,
@@ -53,7 +52,6 @@
},
listeners: {
- tap: '_handleAction',
click: '_handleAction',
keydown: '_handleKeydown',
},
@@ -81,7 +79,7 @@
_disabledChanged(disabled) {
if (disabled) {
- this._enabledTabindex = this.getAttribute('tabindex');
+ this._enabledTabindex = this.getAttribute('tabindex') || '0';
}
this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
this.updateStyles();
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
index ed0da2e4ca..ef593c02ee 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-button</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-button.html">
@@ -39,7 +41,11 @@ limitations under the License.
const addSpyOn = function(eventName) {
const spy = sandbox.spy();
- element.addEventListener(eventName, spy);
+ if (eventName == 'tap') {
+ Polymer.Gestures.addListener(element, eventName, spy);
+ } else {
+ element.addEventListener(eventName, spy);
+ }
return spy;
};
@@ -62,6 +68,23 @@ limitations under the License.
assert.isTrue(element.$$('paper-button').disabled);
});
+ test('tabindex should be -1 if disabled', () => {
+ element.disabled = true;
+ assert.isTrue(element.getAttribute('tabindex') === '-1');
+ });
+
+ // Regression tests for Issue: 11969
+ test('tabindex should be reset to 0 if enabled', () => {
+ element.disabled = false;
+ assert.isTrue(element.getAttribute('tabindex') === '0');
+ element.disabled = true;
+ assert.isTrue(element.getAttribute('tabindex') === '-1');
+ element.disabled = false;
+ assert.isTrue(element.getAttribute('tabindex') === '0');
+ });
+
+ // 'tap' event is tested so we don't loose backward compatibility with older
+ // plugins who didn't move to on-click which is faster and well supported.
for (const eventName of ['tap', 'click']) {
test('dispatches ' + eventName + ' event', () => {
const spy = addSpyOn(eventName);
@@ -72,16 +95,16 @@ limitations under the License.
// Keycodes: 32 for Space, 13 for Enter.
for (const key of [32, 13]) {
- test('dispatches tap event on keycode ' + key, () => {
+ test('dispatches click event on keycode ' + key, () => {
const tapSpy = sandbox.spy();
- element.addEventListener('tap', tapSpy);
+ element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key);
assert.isTrue(tapSpy.calledOnce);
});
- test('dispatches no tap event with modifier on keycode ' + key, () => {
+ test('dispatches no click event with modifier on keycode ' + key, () => {
const tapSpy = sandbox.spy();
- element.addEventListener('tap', tapSpy);
+ element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key, 'shift');
MockInteractions.pressAndReleaseKeyOn(element, key, 'ctrl');
MockInteractions.pressAndReleaseKeyOn(element, key, 'meta');
@@ -105,9 +128,9 @@ limitations under the License.
// Keycodes: 32 for Space, 13 for Enter.
for (const key of [32, 13]) {
- test('stops tap event on keycode ' + key, () => {
+ test('stops click event on keycode ' + key, () => {
const tapSpy = sandbox.spy();
- element.addEventListener('tap', tapSpy);
+ element.addEventListener('click', tapSpy);
MockInteractions.pressAndReleaseKeyOn(element, key);
assert.isFalse(tapSpy.called);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
index a14c652136..377452902b 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -30,7 +30,7 @@ limitations under the License.
fill: var(--link-color);
}
</style>
- <button aria-label="Change star" on-tap="toggleStar">
+ <button aria-label="Change star" on-click="toggleStar">
<iron-icon
class$="[[_computeStarClass(change.starred)]]"
icon$="[[_computeStarIcon(change.starred)]]"></iron-icon>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
index 44f8c00dbe..a83bc2bd4d 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-change-star',
- _legacyUndefinedCheck: true,
/**
* Fired when star state is toggled.
@@ -49,6 +48,7 @@
this.set('change.starred', newVal);
this.dispatchEvent(new CustomEvent('toggle-star', {
bubbles: true,
+ composed: true,
detail: {change: this.change, starred: newVal},
}));
},
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index 0ca936861c..7ee22a7536 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-star</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-star.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.html b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.html
index 99ddff1cc0..55623b36f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
@@ -25,11 +25,9 @@ limitations under the License.
<template>
<style include="shared-styles">
.chip {
- border-radius: 4px;
+ border-radius: var(--border-radius);
background-color: var(--chip-background-color);
- font-family: var(--font-family);
- font-size: var(--font-size-normal);
- padding: .1em .5em;
+ padding: var(--spacing-xxs) var(--spacing-m);
white-space: nowrap;
}
:host(.merged) .chip {
@@ -66,7 +64,7 @@ limitations under the License.
}
:host([flat]) .chip {
background-color: transparent;
- padding: .1em;
+ padding: var(--spacing-xxs);
}
:host(:not([flat])) .chip {
color: white;
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
index 70c5d729a2..e6f52c61a5 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
@@ -29,12 +29,15 @@
'It will not appear on dashboards unless you are CC\'ed or assigned, ' +
'and email notifications will be silenced until the review is started.';
+ const MERGE_CONFLICT_TOOLTIP = 'This change has merge conflicts. ' +
+ 'Download the patch and run "git rebase master". ' +
+ 'Upload a new patchset after resolving all merge conflicts.';
+
const PRIVATE_TOOLTIP = 'This change is only visible to its owner and ' +
'current reviewers (or anyone with "View Private Changes" permission).';
Polymer({
is: 'gr-change-status',
- _legacyUndefinedCheck: true,
properties: {
flat: {
@@ -76,6 +79,9 @@
case ChangeStates.PRIVATE:
this.tooltipText = PRIVATE_TOOLTIP;
break;
+ case ChangeStates.MERGE_CONFLICT:
+ this.tooltipText = MERGE_CONFLICT_TOOLTIP;
+ break;
default:
this.tooltipText = '';
break;
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
index f73fc02d04..421c6ab589 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-status</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-change-status.html">
@@ -33,6 +35,17 @@ limitations under the License.
</test-fixture>
<script>
+ const WIP_TOOLTIP = 'This change isn\'t ready to be reviewed or submitted. ' +
+ 'It will not appear on dashboards unless you are CC\'ed or assigned, ' +
+ 'and email notifications will be silenced until the review is started.';
+
+ const MERGE_CONFLICT_TOOLTIP = 'This change has merge conflicts. ' +
+ 'Download the patch and run "git rebase master". ' +
+ 'Upload a new patchset after resolving all merge conflicts.';
+
+ const PRIVATE_TOOLTIP = 'This change is only visible to its owner and ' +
+ 'current reviewers (or anyone with "View Private Changes" permission).';
+
suite('gr-change-status tests', () => {
let element;
let sandbox;
@@ -49,7 +62,7 @@ limitations under the License.
test('WIP', () => {
element.status = 'WIP';
assert.equal(element.$$('.chip').innerText, 'Work in Progress');
- assert.isDefined(element.tooltipText);
+ assert.equal(element.tooltipText, WIP_TOOLTIP);
assert.isTrue(element.classList.contains('wip'));
});
@@ -79,14 +92,14 @@ limitations under the License.
test('merge conflict', () => {
element.status = 'Merge Conflict';
assert.equal(element.$$('.chip').innerText, element.status);
- assert.equal(element.tooltipText, '');
+ assert.equal(element.tooltipText, MERGE_CONFLICT_TOOLTIP);
assert.isTrue(element.classList.contains('merge-conflict'));
});
test('private', () => {
element.status = 'Private';
assert.equal(element.$$('.chip').innerText, element.status);
- assert.isDefined(element.tooltipText);
+ assert.equal(element.tooltipText, PRIVATE_TOOLTIP);
assert.isTrue(element.classList.contains('private'));
});
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index 8c80b379a2..bbd7ddfd78 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
@@ -27,20 +28,32 @@ limitations under the License.
<dom-module id="gr-comment-thread">
<template>
<style include="shared-styles">
+ :host {
+ font-family: var(--font-family);
+ font-size: var(--font-size-normal);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-normal);
+ }
gr-button {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
#actions {
margin-left: auto;
- padding: .5em .7em;
+ padding: var(--spacing-m);
}
#container {
background-color: var(--comment-background-color);
- border: 1px solid var(--border-color);
color: var(--comment-text-color);
display: block;
- margin-bottom: 1px;
+ margin: 0 4px 4px 4px;
white-space: normal;
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+ border-radius: var(--border-radius);
+ /** This is required for firefox to continue the inheritance */
+ -webkit-user-select: inherit;
+ -moz-user-select: inherit;
+ -ms-user-select: inherit;
+ user-select: inherit;
}
#container.unresolved {
background-color: var(--unresolved-comment-background-color);
@@ -52,14 +65,14 @@ limitations under the License.
#unresolvedLabel {
font-family: var(--font-family);
margin: auto 0;
- padding: .5em .7em;
+ padding: var(--spacing-m);
}
.pathInfo {
display: flex;
align-items: baseline;
}
.descriptionText {
- margin-left: .5rem;
+ margin-left: var(--spacing-m);
font-style: italic;
}
</style>
@@ -96,25 +109,25 @@ limitations under the License.
link
secondary
class="action reply"
- on-tap="_handleCommentReply">Reply</gr-button>
+ on-click="_handleCommentReply">Reply</gr-button>
<gr-button
id="quoteBtn"
link
secondary
class="action quote"
- on-tap="_handleCommentQuote">Quote</gr-button>
+ on-click="_handleCommentQuote">Quote</gr-button>
<gr-button
id="ackBtn"
link
secondary
class="action ack"
- on-tap="_handleCommentAck">Ack</gr-button>
+ on-click="_handleCommentAck">Ack</gr-button>
<gr-button
id="doneBtn"
link
secondary
class="action done"
- on-tap="_handleCommentDone">Done</gr-button>
+ on-click="_handleCommentDone">Done</gr-button>
</div>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index 0b4f80604d..c220ecf2c8 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-comment-thread',
- _legacyUndefinedCheck: true,
/**
* Fired when the thread should be discarded.
@@ -127,6 +126,10 @@
},
behaviors: [
+ /**
+ * Not used in this element rather other elements tests
+ */
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PathListBehavior,
],
@@ -209,7 +212,8 @@
},
_hideActions(_showActions, _lastComment) {
- return !_showActions || !_lastComment || !!_lastComment.__draft;
+ return !_showActions || !_lastComment || !!_lastComment.__draft ||
+ !!_lastComment.robot_id;
},
_getLastComment() {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
index 74c6f5fb6a..d3946e66d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-comment-thread</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -159,6 +161,9 @@ limitations under the License.
showActions = true;
lastComment.__draft = true;
assert.equal(element._hideActions(showActions, lastComment), true);
+ const robotComment = {};
+ robotComment.robot_id = true;
+ assert.equal(element._hideActions(showActions, robotComment), true);
});
test('setting project name loads the project config', done => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index a470285fdc..8ad261c8ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
@@ -41,7 +42,7 @@ limitations under the License.
:host {
display: block;
font-family: var(--font-family);
- padding: .7em .7em;
+ padding: var(--spacing-m);
--iron-autogrow-textarea: {
box-sizing: border-box;
padding: 2px;
@@ -62,23 +63,17 @@ limitations under the License.
align-items: baseline;
cursor: pointer;
display: flex;
- font-family: 'Open Sans', sans-serif;
- margin: -.7em -.7em 0 -.7em;
- padding: .7em;
+ margin: calc(0px - var(--spacing-m)) calc(0px - var(--spacing-m)) 0 calc(0px - var(--spacing-m));
+ padding: var(--spacing-m);
}
.container.collapsed .header {
- margin-bottom: -.7em;
+ margin-bottom: calc(0 - var(--spacing-m));
}
.headerMiddle {
color: var(--deemphasized-text-color);
flex: 1;
overflow: hidden;
}
- .authorName,
- .draftLabel,
- .draftTooltip {
- font-weight: var(--font-weight-bold);
- }
.draftLabel,
.draftTooltip {
color: var(--deemphasized-text-color);
@@ -103,25 +98,33 @@ limitations under the License.
padding-top: 0;
}
.action {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
.robotActions {
display: flex;
justify-content: flex-start;
- padding-top: 0;
+ padding-top: var(--spacing-m);
+ border-top: 1px solid var(--border-color);
}
.robotActions .action {
/* Keep button text lined up with output text */
- margin-left: -.3rem;
- margin-right: 1em;
+ margin-left: -4px;
+ margin-right: var(--spacing-l);
}
.rightActions {
display: flex;
justify-content: flex-end;
}
+ .rightActions gr-button {
+ --gr-button: {
+ height: 20px;
+ padding: 0 var(--spacing-s);
+ color: var(--default-button-text-color);
+ }
+ }
.editMessage {
display: none;
- margin: .5em 0;
+ margin: var(--spacing-m) 0;
width: 100%;
}
.container:not(.draft) .actions .hideOnPublished {
@@ -155,38 +158,37 @@ limitations under the License.
display: block;
}
.show-hide {
- margin-left: .4em;
+ margin-left: var(--spacing-s);
}
.robotId {
color: var(--deemphasized-text-color);
- margin-bottom: .8em;
+ margin-bottom: var(--spacing-m);
margin-top: -.4em;
}
.robotIcon {
- margin-right: .2em;
+ margin-right: var(--spacing-xs);
/* because of the antenna of the robot, it looks off center even when it
is centered. artificially adjust margin to account for this. */
- margin-top: -.3em;
+ margin-top: -4px;
}
.runIdInformation {
- margin: .7em 0;
+ margin: var(--spacing-m) 0;
}
.robotRun {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
.robotRunLink {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
input.show-hide {
display: none;
}
label.show-hide {
- color: var(--comment-text-color);
cursor: pointer;
display: block;
- font-size: .8rem;
- height: 1.1em;
- margin-top: .1em;
+ }
+ label.show-hide iron-icon {
+ vertical-align: top;
}
#container .collapsedContent {
display: none;
@@ -231,9 +233,19 @@ limitations under the License.
#deleteBtn.showDeleteButtons {
display: block;
}
+
+ /** Disable select for the caret and actions */
+ .actions,
+ .show-hide {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
</style>
<div id="container" class="container">
- <div class="header" id="header" on-tap="_handleToggleCollapsed">
+ <div class="header" id="header" on-click="_handleToggleCollapsed">
<div class="headerLeft">
<span class="authorName">[[comment.author.name]]</span>
<span class="draftLabel">DRAFT</span>
@@ -251,10 +263,10 @@ limitations under the License.
link
secondary
class$="action delete [[_computeDeleteButtonClass(_isAdmin, draft)]]"
- on-tap="_handleCommentDelete">
+ on-click="_handleCommentDelete">
(Delete)
</gr-button>
- <span class="date" on-tap="_handleAnchorTap">
+ <span class="date" on-click="_handleAnchorClick">
<gr-date-formatter
has-tooltip
date-str="[[comment.updated]]"></gr-date-formatter>
@@ -264,7 +276,10 @@ limitations under the License.
<input type="checkbox" class="show-hide"
checked$="[[collapsed]]"
on-change="_handleToggleCollapsed">
- [[_computeShowHideText(collapsed)]]
+ <iron-icon
+ id="icon"
+ icon="[[_computeShowHideIcon(collapsed)]]">
+ </iron-icon>
</label>
</div>
</div>
@@ -280,7 +295,7 @@ limitations under the License.
id="editTextarea"
class="editMessage"
autocomplete="on"
- monospace
+ code
disabled="{{disabled}}"
rows="4"
text="{{_messageText}}"></gr-textarea>
@@ -320,23 +335,23 @@ limitations under the License.
link
secondary
class="action cancel hideOnPublished"
- on-tap="_handleCancel">Cancel</gr-button>
+ on-click="_handleCancel">Cancel</gr-button>
<gr-button
link
secondary
class="action discard hideOnPublished"
- on-tap="_handleDiscard">Discard</gr-button>
+ on-click="_handleDiscard">Discard</gr-button>
<gr-button
link
secondary
class="action edit hideOnPublished"
- on-tap="_handleEdit">Edit</gr-button>
+ on-click="_handleEdit">Edit</gr-button>
<gr-button
link
secondary
disabled$="[[_computeSaveDisabled(_messageText, comment, resolved)]]"
class="action save hideOnPublished"
- on-tap="_handleSave">Save</gr-button>
+ on-click="_handleSave">Save</gr-button>
</div>
</div>
<div class="robotActions" hidden$="[[!_showRobotActions]]">
@@ -345,7 +360,7 @@ limitations under the License.
link
secondary
class="action fix"
- on-tap="_handleFix"
+ on-click="_handleFix"
disabled="[[robotButtonDisabled]]">
Please Fix
</gr-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
index b699b8b37d..68819297ed 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
@@ -33,7 +33,6 @@
Polymer({
is: 'gr-comment',
- _legacyUndefinedCheck: true,
/**
* Fired when the create fix comment action is triggered.
@@ -128,7 +127,8 @@
_numPendingDraftRequests: {
type: Object,
- value: {number: 0}, // Intentional to share the object across instances.
+ value:
+ {number: 0}, // Intentional to share the object across instances.
},
_enableOverlay: {
@@ -156,6 +156,7 @@
],
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -204,11 +205,16 @@
return this._overlays.confirmDiscard;
},
- _computeShowHideText(collapsed) {
- return collapsed ? '◀' : '▼';
+ _computeShowHideIcon(collapsed) {
+ return collapsed ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
},
_calculateActionstoShow(showActions, isRobotComment) {
+ // Polymer 2: check for undefined
+ if ([showActions, isRobotComment].some(arg => arg === undefined)) {
+ return;
+ }
+
this._showHumanActions = showActions && !isRobotComment;
this._showRobotActions = showActions && isRobotComment;
},
@@ -230,7 +236,9 @@
*/
save(opt_comment) {
let comment = opt_comment;
- if (!comment) { comment = this.comment; }
+ if (!comment) {
+ comment = this.comment;
+ }
this.set('comment.message', this._messageText);
this.editing = false;
@@ -315,6 +323,11 @@
},
_editingChanged(editing, previousValue) {
+ // Polymer 2: observer fires when at least one property is defined.
+ // Do nothing to prevent comment.__editing being overwritten
+ // if previousValue is undefined
+ if (previousValue === undefined) return;
+
this.$.container.classList.toggle('editing', editing);
if (this.comment && this.comment.id) {
this.$$('.cancel').hidden = !editing;
@@ -340,7 +353,9 @@
_computeSaveDisabled(draft, comment, resolved) {
// If resolved state has changed and a msg exists, save should be enabled.
- if (comment.unresolved === resolved && draft) { return false; }
+ if (!comment || comment.unresolved === resolved && draft) {
+ return false;
+ }
return !draft || draft.trim() === '';
},
@@ -376,7 +391,9 @@
},
_messageTextChanged(newValue, oldValue) {
- if (!this.comment || (this.comment && this.comment.id)) { return; }
+ if (!this.comment || (this.comment && this.comment.id)) {
+ return;
+ }
this.debounce('store', () => {
const message = this._messageText;
@@ -398,11 +415,14 @@
}, STORAGE_DEBOUNCE_INTERVAL);
},
- _handleAnchorTap(e) {
+ _handleAnchorClick(e) {
e.preventDefault();
- if (!this.comment.line) { return; }
+ if (!this.comment.line) {
+ return;
+ }
this.dispatchEvent(new CustomEvent('comment-anchor-tap', {
bubbles: true,
+ composed: true,
detail: {
number: this.comment.line || FILE,
side: this.side,
@@ -421,7 +441,9 @@
e.preventDefault();
// Ignore saves started while already saving.
- if (this.disabled) { return; }
+ if (this.disabled) {
+ return;
+ }
const timingLabel = this.comment.id ?
REPORT_UPDATE_DRAFT : REPORT_CREATE_DRAFT;
const timer = this.$.reporting.getTimer(timingLabel);
@@ -450,6 +472,7 @@
_handleFix() {
this.dispatchEvent(new CustomEvent('create-fix-comment', {
bubbles: true,
+ composed: true,
detail: this._getEventPayload(),
}));
},
@@ -512,7 +535,9 @@
},
_getSavingMessage(numPending) {
- if (numPending === 0) { return SAVED_MESSAGE; }
+ if (numPending === 0) {
+ return SAVED_MESSAGE;
+ }
return [
SAVING_MESSAGE,
numPending,
@@ -544,8 +569,8 @@
// Note: the event is fired on the body rather than this element because
// this element may not be attached by the time this executes, in which
// case the event would not bubble.
- document.body.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message}, bubbles: true}));
+ document.body.dispatchEvent(new CustomEvent(
+ 'show-alert', {detail: {message}, bubbles: true, composed: true}));
}, TOAST_DEBOUNCE_INTERVAL);
},
@@ -580,6 +605,11 @@
},
_loadLocalDraft(changeNum, patchNum, comment) {
+ // Polymer 2: check for undefined
+ if ([changeNum, patchNum, comment].some(arg => arg === undefined)) {
+ return;
+ }
+
// Only apply local drafts to comments that haven't been saved
// remotely, and haven't been given a default message already.
//
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
index 7ca524291a..c829343bc2 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-comment</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/page/page.js"></script>
+<script src="/bower_components/page/page.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-comment.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
index 9decfa9563..62ab3072a1 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -42,6 +43,8 @@ limitations under the License.
}
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
padding: 0;
width: 73ch; /* Add a char to account for the border. */
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
index 4ac059dd2e..c2075f0025 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-confirm-delete-comment-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -37,17 +36,23 @@
message: String,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
resetFocus() {
this.$.messageInput.textarea.focus();
},
_handleConfirmTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', {reason: this.message}, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
});
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
index 32ca557345..f58db39ff4 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
@@ -31,36 +31,53 @@ limitations under the License.
}
.copyText {
flex-grow: 1;
- margin-right: .3em;
+ margin-right: var(--spacing-s);
}
.hideInput {
display: none;
}
- input {
+ input#input {
font-family: var(--monospace-font-family);
- font-size: inherit;
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
@apply --text-container-style;
+ width: 100%;
}
#icon {
height: 1.2em;
width: 1.2em;
}
+ gr-button {
+ --gr-button: {
+ padding: 1px 4px;
+ }
+ }
+
</style>
<div class="text">
- <input id="input" is="iron-input"
- class$="copyText [[_computeInputClass(hideInput)]]"
+ <iron-input
+ class="copyText"
+ type="text"
+ bind-value="[[text]]"
+ on-tap="_handleInputClick"
+ readonly>
+ <input
+ id="input"
+ is="iron-input"
+ class$="[[_computeInputClass(hideInput)]]"
type="text"
bind-value="[[text]]"
- on-tap="_handleInputTap"
+ on-click="_handleInputClick"
readonly>
- <gr-button id="button"
- link
- has-tooltip="[[hasTooltip]]"
- class="copyToClipboard"
- title="[[buttonTitle]]"
- on-tap="_copyToClipboard">
- <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
- </gr-button>
+ </iron-input>
+ <gr-button id="button"
+ link
+ has-tooltip="[[hasTooltip]]"
+ class="copyToClipboard"
+ title="[[buttonTitle]]"
+ on-click="_copyToClipboard">
+ <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
+ </gr-button>
</div>
</template>
<script src="gr-copy-clipboard.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
index 550f1df1e7..3e872028dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-copy-clipboard',
- _legacyUndefinedCheck: true,
properties: {
text: String,
@@ -44,7 +43,7 @@
return hideInput ? 'hideInput' : '';
},
- _handleInputTap(e) {
+ _handleInputClick(e) {
e.preventDefault();
Polymer.dom(e).rootTarget.select();
},
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index d6e9dca4d8..9cec20ecae 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-copy-clipboard</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-copy-clipboard.html">
@@ -37,12 +39,13 @@ limitations under the License.
let element;
let sandbox;
- setup(() => {
+ setup(done => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element.text = `git fetch http://gerrit@localhost:8080/a/test-project
refs/changes/05/5/1 && git checkout FETCH_HEAD`;
flushAsynchronousOperations();
+ flush(done);
});
teardown(() => {
@@ -62,7 +65,7 @@ limitations under the License.
element.$$('.copyToClipboard'));
});
- test('_handleInputTap', () => {
+ test('_handleInputClick', () => {
const inputElement = element.$$('input');
MockInteractions.tap(inputElement);
assert.equal(inputElement.selectionStart, 0);
diff --git a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
index e4d896bd4d..d061ac2bae 100644
--- a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-count-string-formatter</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-count-string-formatter.html"/>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html
index d619b1883b..94d7aaa6c9 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-cursor-manager">
<template></template>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 766ac7fe84..b97726e9ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -24,7 +24,6 @@
Polymer({
is: 'gr-cursor-manager',
- _legacyUndefinedCheck: true,
properties: {
stops: {
@@ -92,8 +91,22 @@
this.unsetCursor();
},
- next(opt_condition, opt_getTargetHeight) {
- this._moveCursor(1, opt_condition, opt_getTargetHeight);
+ /**
+ * Move the cursor forward. Clipped to the ends of the stop list.
+ *
+ * @param {!Function=} opt_condition Optional stop condition. If a condition
+ * is passed the cursor will continue to move in the specified direction
+ * until the condition is met.
+ * @param {!Function=} opt_getTargetHeight Optional function to calculate the
+ * height of the target's 'section'. The height of the target itself is
+ * sometimes different, used by the diff cursor.
+ * @param {boolean=} opt_clipToTop When none of the next indices match, move
+ * back to first instead of to last.
+ * @private
+ */
+
+ next(opt_condition, opt_getTargetHeight, opt_clipToTop) {
+ this._moveCursor(1, opt_condition, opt_getTargetHeight, opt_clipToTop);
},
previous(opt_condition) {
@@ -148,8 +161,8 @@
},
/**
- * Move the cursor forward or backward by delta. Noop if moving past either
- * end of the stop list.
+ * Move the cursor forward or backward by delta. Clipped to the beginning or
+ * end of stop list.
*
* @param {number} delta either -1 or 1.
* @param {!Function=} opt_condition Optional stop condition. If a condition
@@ -158,9 +171,11 @@
* @param {!Function=} opt_getTargetHeight Optional function to calculate the
* height of the target's 'section'. The height of the target itself is
* sometimes different, used by the diff cursor.
+ * @param {boolean=} opt_clipToTop When none of the next indices match, move
+ * back to first instead of to last.
* @private
*/
- _moveCursor(delta, opt_condition, opt_getTargetHeight) {
+ _moveCursor(delta, opt_condition, opt_getTargetHeight, opt_clipToTop) {
if (!this.stops.length) {
this.unsetCursor();
return;
@@ -168,7 +183,7 @@
this._unDecorateTarget();
- const newIndex = this._getNextindex(delta, opt_condition);
+ const newIndex = this._getNextindex(delta, opt_condition, opt_clipToTop);
let newTarget = null;
if (newIndex !== -1) {
@@ -208,10 +223,12 @@
*
* @param {number} delta either -1 or 1.
* @param {!Function=} opt_condition Optional stop condition.
+ * @param {boolean=} opt_clipToTop When none of the next indices match, move
+ * back to first instead of to last.
* @return {number} the new index.
* @private
*/
- _getNextindex(delta, opt_condition) {
+ _getNextindex(delta, opt_condition, opt_clipToTop) {
if (!this.stops.length || this.index === -1) {
return -1;
}
@@ -227,10 +244,10 @@
// If we failed to satisfy the condition:
if (opt_condition && !opt_condition(this.stops[newIndex])) {
- if (delta > 0) {
- return this.stops.length - 1;
- } else if (delta < 0) {
+ if (delta < 0 || opt_clipToTop) {
return 0;
+ } else if (delta > 0) {
+ return this.stops.length - 1;
}
return this.index;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
index adbe6181d3..0793ccde3a 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-cursor-manager</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-cursor-manager.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
index 481dd2f912..ae5a9451c6 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -31,7 +31,7 @@ limitations under the License.
}
</style>
<span>
- [[_computeDateStr(dateStr, _timeFormat, _relative, showDateAndTime)]]
+ [[_computeDateStr(dateStr, _timeFormat, _dateFormat, _relative, showDateAndTime)]]
</span>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
index a9ce4c9121..545a7c33d3 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -27,13 +27,33 @@
TIME_12_WITH_SEC: 'h:mm:ss A', // 2:14:00 PM
TIME_24: 'HH:mm', // 14:14
TIME_24_WITH_SEC: 'HH:mm:ss', // 14:14:00
- MONTH_DAY: 'MMM DD', // Aug 29
- MONTH_DAY_YEAR: 'MMM DD, YYYY', // Aug 29, 1997
+ };
+
+ const DateFormats = {
+ STD: {
+ short: 'MMM DD', // Aug 29
+ full: 'MMM DD, YYYY', // Aug 29, 1997
+ },
+ US: {
+ short: 'MM/DD', // 08/29
+ full: 'MM/DD/YY', // 08/29/97
+ },
+ ISO: {
+ short: 'MM-DD', // 08-29
+ full: 'YYYY-MM-DD', // 1997-08-29
+ },
+ EURO: {
+ short: 'DD. MMM', // 29. Aug
+ full: 'DD.MM.YYYY', // 29.08.1997
+ },
+ UK: {
+ short: 'DD/MM', // 29/08
+ full: 'DD/MM/YYYY', // 29/08/1997
+ },
};
Polymer({
is: 'gr-date-formatter',
- _legacyUndefinedCheck: true,
properties: {
dateStr: {
@@ -58,9 +78,11 @@
title: {
type: String,
reflectToAttribute: true,
- computed: '_computeFullDateStr(dateStr, _timeFormat)',
+ computed: '_computeFullDateStr(dateStr, _timeFormat, _dateFormat)',
},
+ /** @type {?{short: string, full: string}} */
+ _dateFormat: Object,
_timeFormat: String, // No default value to prevent flickering.
_relative: Boolean, // No default value to prevent flickering.
},
@@ -81,6 +103,7 @@
return this._getLoggedIn().then(loggedIn => {
if (!loggedIn) {
this._timeFormat = TimeFormats.TIME_24;
+ this._dateFormat = DateFormats.STD;
this._relative = false;
return;
}
@@ -94,19 +117,47 @@
_loadTimeFormat() {
return this._getPreferences().then(preferences => {
const timeFormat = preferences && preferences.time_format;
- switch (timeFormat) {
- case 'HHMM_12':
- this._timeFormat = TimeFormats.TIME_12;
- break;
- case 'HHMM_24':
- this._timeFormat = TimeFormats.TIME_24;
- break;
- default:
- throw Error('Invalid time format: ' + timeFormat);
- }
+ const dateFormat = preferences && preferences.date_format;
+ this._decideTimeFormat(timeFormat);
+ this._decideDateFormat(dateFormat);
});
},
+ _decideTimeFormat(timeFormat) {
+ switch (timeFormat) {
+ case 'HHMM_12':
+ this._timeFormat = TimeFormats.TIME_12;
+ break;
+ case 'HHMM_24':
+ this._timeFormat = TimeFormats.TIME_24;
+ break;
+ default:
+ throw Error('Invalid time format: ' + timeFormat);
+ }
+ },
+
+ _decideDateFormat(dateFormat) {
+ switch (dateFormat) {
+ case 'STD':
+ this._dateFormat = DateFormats.STD;
+ break;
+ case 'US':
+ this._dateFormat = DateFormats.US;
+ break;
+ case 'ISO':
+ this._dateFormat = DateFormats.ISO;
+ break;
+ case 'EURO':
+ this._dateFormat = DateFormats.EURO;
+ break;
+ case 'UK':
+ this._dateFormat = DateFormats.UK;
+ break;
+ default:
+ throw Error('Invalid date format: ' + dateFormat);
+ }
+ },
+
_loadRelative() {
return this._getPreferences().then(prefs => {
// prefs.relative_date_in_change_table is not set when false.
@@ -139,8 +190,10 @@
diff < 180 * Duration.DAY;
},
- _computeDateStr(dateStr, timeFormat, relative, showDateAndTime) {
- if (!dateStr) { return ''; }
+ _computeDateStr(
+ dateStr, timeFormat, dateFormat, relative, showDateAndTime
+ ) {
+ if (!dateStr || !timeFormat || !dateFormat) { return ''; }
const date = moment(util.parseDate(dateStr));
if (!date.isValid()) { return ''; }
if (relative) {
@@ -152,12 +205,12 @@
}
}
const now = new Date();
- let format = TimeFormats.MONTH_DAY_YEAR;
+ let format = dateFormat.full;
if (this._isWithinDay(now, date)) {
format = timeFormat;
} else {
if (this._isWithinHalfYear(now, date)) {
- format = TimeFormats.MONTH_DAY;
+ format = dateFormat.short;
}
if (this.showDateAndTime) {
format = `${format} ${timeFormat}`;
@@ -172,11 +225,20 @@
TimeFormats.TIME_24_WITH_SEC;
},
- _computeFullDateStr(dateStr, timeFormat) {
+ _computeFullDateStr(dateStr, timeFormat, dateFormat) {
+ // Polymer 2: check for undefined
+ if ([
+ dateStr,
+ timeFormat,
+ dateFormat,
+ ].some(arg => arg === undefined)) {
+ return undefined;
+ }
+
if (!dateStr) { return ''; }
const date = moment(util.parseDate(dateStr));
if (!date.isValid()) { return ''; }
- let format = TimeFormats.MONTH_DAY_YEAR + ', ';
+ let format = dateFormat.full + ', ';
format += this._timeToSecondsFormat(timeFormat);
return date.format(format) + this._getUtcOffsetString();
},
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index 798aa68b1d..d51b5d574f 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-date-formatter</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -84,16 +86,16 @@ limitations under the License.
return Promise.all([loggedInPromise, preferencesPromise]);
}
- suite('24 hours time format preference', () => {
- setup(() => {
- return stubRestAPI(
- {time_format: 'HHMM_24', relative_date_in_change_table: false}
- ).then(() => {
- element = fixture('basic');
- sandbox.stub(element, '_getUtcOffsetString').returns('');
- return element._loadPreferences();
- });
- });
+ suite('STD + 24 hours time format preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_24',
+ date_format: 'STD',
+ relative_date_in_change_table: false,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
test('invalid dates are quietly rejected', () => {
assert.notOk((new Date('foo')).valueOf());
@@ -133,17 +135,161 @@ limitations under the License.
});
});
- suite('12 hours time format preference', () => {
- setup(() => {
+ suite('US + 24 hours time format preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_24',
+ date_format: 'US',
+ relative_date_in_change_table: false,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '15:34',
+ '15:34',
+ '07/29/15, 15:34:14', done);
+ });
+
+ test('Within 24 hours on different days', done => {
+ testDates('2015-07-29 03:34:14.985000000',
+ '2015-07-28 20:25:14.985000000',
+ '07/28',
+ '07/28 20:25',
+ '07/28/15, 20:25:14', done);
+ });
+
+ test('More than 24 hours but less than six months', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-06-15 03:25:14.985000000',
+ '06/15',
+ '06/15 03:25',
+ '06/15/15, 03:25:14', done);
+ });
+ });
+
+ suite('ISO + 24 hours time format preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_24',
+ date_format: 'ISO',
+ relative_date_in_change_table: false,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '15:34',
+ '15:34',
+ '2015-07-29, 15:34:14', done);
+ });
+
+ test('Within 24 hours on different days', done => {
+ testDates('2015-07-29 03:34:14.985000000',
+ '2015-07-28 20:25:14.985000000',
+ '07-28',
+ '07-28 20:25',
+ '2015-07-28, 20:25:14', done);
+ });
+
+ test('More than 24 hours but less than six months', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-06-15 03:25:14.985000000',
+ '06-15',
+ '06-15 03:25',
+ '2015-06-15, 03:25:14', done);
+ });
+ });
+
+ suite('EURO + 24 hours time format preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_24',
+ date_format: 'EURO',
+ relative_date_in_change_table: false,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '15:34',
+ '15:34',
+ '29.07.2015, 15:34:14', done);
+ });
+
+ test('Within 24 hours on different days', done => {
+ testDates('2015-07-29 03:34:14.985000000',
+ '2015-07-28 20:25:14.985000000',
+ '28. Jul',
+ '28. Jul 20:25',
+ '28.07.2015, 20:25:14', done);
+ });
+
+ test('More than 24 hours but less than six months', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-06-15 03:25:14.985000000',
+ '15. Jun',
+ '15. Jun 03:25',
+ '15.06.2015, 03:25:14', done);
+ });
+ });
+
+ suite('UK + 24 hours time format preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_24',
+ date_format: 'UK',
+ relative_date_in_change_table: false,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '15:34',
+ '15:34',
+ '29/07/2015, 15:34:14', done);
+ });
+
+ test('Within 24 hours on different days', done => {
+ testDates('2015-07-29 03:34:14.985000000',
+ '2015-07-28 20:25:14.985000000',
+ '28/07',
+ '28/07 20:25',
+ '28/07/2015, 20:25:14', done);
+ });
+
+ test('More than 24 hours but less than six months', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-06-15 03:25:14.985000000',
+ '15/06',
+ '15/06 03:25',
+ '15/06/2015, 03:25:14', done);
+ });
+ });
+
+ suite('STD + 12 hours time format preference', () => {
+ setup(() =>
// relative_date_in_change_table is not set when false.
- return stubRestAPI(
- {time_format: 'HHMM_12'}
+ stubRestAPI(
+ {time_format: 'HHMM_12', date_format: 'STD'}
).then(() => {
element = fixture('basic');
sandbox.stub(element, '_getUtcOffsetString').returns('');
return element._loadPreferences();
- });
- });
+ })
+ );
test('Within 24 hours on same day', done => {
testDates('2015-07-29 20:34:14.985000000',
@@ -154,16 +300,100 @@ limitations under the License.
});
});
- suite('relative date preference', () => {
- setup(() => {
- return stubRestAPI(
- {time_format: 'HHMM_12', relative_date_in_change_table: true}
+ suite('US + 12 hours time format preference', () => {
+ setup(() =>
+ // relative_date_in_change_table is not set when false.
+ stubRestAPI(
+ {time_format: 'HHMM_12', date_format: 'US'}
).then(() => {
element = fixture('basic');
sandbox.stub(element, '_getUtcOffsetString').returns('');
return element._loadPreferences();
- });
+ })
+ );
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '3:34 PM',
+ '3:34 PM',
+ '07/29/15, 3:34:14 PM', done);
});
+ });
+
+ suite('ISO + 12 hours time format preference', () => {
+ setup(() =>
+ // relative_date_in_change_table is not set when false.
+ stubRestAPI(
+ {time_format: 'HHMM_12', date_format: 'ISO'}
+ ).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ })
+ );
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '3:34 PM',
+ '3:34 PM',
+ '2015-07-29, 3:34:14 PM', done);
+ });
+ });
+
+ suite('EURO + 12 hours time format preference', () => {
+ setup(() =>
+ // relative_date_in_change_table is not set when false.
+ stubRestAPI(
+ {time_format: 'HHMM_12', date_format: 'EURO'}
+ ).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ })
+ );
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '3:34 PM',
+ '3:34 PM',
+ '29.07.2015, 3:34:14 PM', done);
+ });
+ });
+
+ suite('UK + 12 hours time format preference', () => {
+ setup(() =>
+ // relative_date_in_change_table is not set when false.
+ stubRestAPI(
+ {time_format: 'HHMM_12', date_format: 'UK'}
+ ).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ })
+ );
+
+ test('Within 24 hours on same day', done => {
+ testDates('2015-07-29 20:34:14.985000000',
+ '2015-07-29 15:34:14.985000000',
+ '3:34 PM',
+ '3:34 PM',
+ '29/07/2015, 3:34:14 PM', done);
+ });
+ });
+
+ suite('relative date preference', () => {
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_12',
+ date_format: 'STD',
+ relative_date_in_change_table: true,
+ }).then(() => {
+ element = fixture('basic');
+ sandbox.stub(element, '_getUtcOffsetString').returns('');
+ return element._loadPreferences();
+ }));
test('Within 24 hours on same day', done => {
testDates('2015-07-29 20:34:14.985000000',
@@ -183,31 +413,33 @@ limitations under the License.
});
suite('logged in', () => {
- setup(() => {
- return stubRestAPI(
- {time_format: 'HHMM_12', relative_date_in_change_table: true}
- ).then(() => {
- element = fixture('basic');
- return element._loadPreferences();
- });
- });
+ setup(() => stubRestAPI({
+ time_format: 'HHMM_12',
+ date_format: 'US',
+ relative_date_in_change_table: true,
+ }).then(() => {
+ element = fixture('basic');
+ return element._loadPreferences();
+ }));
test('Preferences are respected', () => {
assert.equal(element._timeFormat, 'h:mm A');
+ assert.equal(element._dateFormat.short, 'MM/DD');
+ assert.equal(element._dateFormat.full, 'MM/DD/YY');
assert.isTrue(element._relative);
});
});
suite('logged out', () => {
- setup(() => {
- return stubRestAPI(null).then(() => {
- element = fixture('basic');
- return element._loadPreferences();
- });
- });
+ setup(() => stubRestAPI(null).then(() => {
+ element = fixture('basic');
+ return element._loadPreferences();
+ }));
test('Default preferences are respected', () => {
assert.equal(element._timeFormat, 'HH:mm');
+ assert.equal(element._dateFormat.short, 'MMM DD');
+ assert.equal(element._dateFormat.full, 'MMM DD, YYYY');
assert.isFalse(element._relative);
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
index 797c8eaa0d..2ef5539d00 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -45,10 +46,10 @@ limitations under the License.
header,
main,
footer {
- padding: .5em 1.5em;
+ padding: var(--spacing-m) var(--spacing-xl);
}
gr-button {
- margin-left: 1em;
+ margin-left: var(--spacing-l);
}
footer {
display: flex;
@@ -63,10 +64,10 @@ limitations under the License.
<header><slot name="header"></slot></header>
<main><slot name="main"></slot></main>
<footer>
- <gr-button id="cancel" class$="[[_computeCancelClass(cancelLabel)]]" link on-tap="_handleCancelTap">
+ <gr-button id="cancel" class$="[[_computeCancelClass(cancelLabel)]]" link on-click="_handleCancelTap">
[[cancelLabel]]
</gr-button>
- <gr-button id="confirm" link primary on-tap="_handleConfirm" disabled="[[disabled]]">
+ <gr-button id="confirm" link primary on-click="_handleConfirm" disabled="[[disabled]]">
[[confirmLabel]]
</gr-button>
</footer>
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
index b8b2af4a2e..68dc537d92 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-dialog',
- _legacyUndefinedCheck: true,
/**
* Fired when the confirm button is pressed.
@@ -53,6 +52,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
hostAttributes: {
role: 'dialog',
},
@@ -61,11 +64,13 @@
if (this.disabled) { return; }
e.preventDefault();
+ e.stopPropagation();
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
+ e.stopPropagation();
this.fire('cancel', null, {bubbles: false});
},
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
index 4a5a181a0c..1456e77066 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-dialog</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-dialog.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.html
index 1c9f469658..9d85d4484d 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.html
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
@@ -60,43 +60,67 @@ limitations under the License.
<section>
<span class="title">Diff width</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
- id="columnsInput"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{diffPrefs.line_length}}"
on-keypress="_handleDiffPrefsChanged"
on-change="_handleDiffPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ id="columnsInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{diffPrefs.line_length}}"
+ on-keypress="_handleDiffPrefsChanged"
+ on-change="_handleDiffPrefsChanged">
+ </iron-input>
</span>
</section>
<section>
<span class="title">Tab width</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
- id="tabSizeInput"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{diffPrefs.tab_size}}"
on-keypress="_handleDiffPrefsChanged"
on-change="_handleDiffPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ id="tabSizeInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{diffPrefs.tab_size}}"
+ on-keypress="_handleDiffPrefsChanged"
+ on-change="_handleDiffPrefsChanged">
+ </iron-input>
</span>
</section>
<section hidden$="[[!diffPrefs.font_size]]">
<span class="title">Font size</span>
<span class="value">
- <input
- is="iron-input"
+ <iron-input
type="number"
- id="fontSizeInput"
prevent-invalid-input
allowed-pattern="[0-9]"
bind-value="{{diffPrefs.font_size}}"
on-keypress="_handleDiffPrefsChanged"
on-change="_handleDiffPrefsChanged">
+ <input
+ is="iron-input"
+ type="number"
+ id="fontSizeInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{diffPrefs.font_size}}"
+ on-keypress="_handleDiffPrefsChanged"
+ on-change="_handleDiffPrefsChanged">
+ </iron-input>
</span>
</section>
<section>
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
index 89c3d74eed..36fdf5b051 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-diff-preferences',
- _legacyUndefinedCheck: true,
properties: {
hasUnsavedChanges: {
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
index 055d7aa2c3..4d2a1a4fad 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-diff-preferences</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-diff-preferences.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
index 6aec5a6f8c..14a65b2e29 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.html
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/paper-tabs/paper-tabs.html">
+<link rel="import" href="/bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -29,7 +29,7 @@ limitations under the License.
<style include="shared-styles">
paper-tabs {
height: 3rem;
- margin-bottom: .5em;
+ margin-bottom: var(--spacing-m);
--paper-tabs-selection-bar-color: var(--link-color);
}
paper-tab {
@@ -54,7 +54,7 @@ limitations under the License.
}
gr-shell-command {
width: 60em;
- margin-bottom: .5em;
+ margin-bottom: var(--spacing-m);
}
.hidden {
display: none;
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
index ed7c2cc046..121aa3561b 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-download-commands',
- _legacyUndefinedCheck: true,
+
properties: {
commands: Array,
_loggedIn: {
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
index c59e56ad28..85a5b1ff3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-download-commands</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-download-commands.html">
@@ -65,12 +67,13 @@ limitations under the License.
});
suite('unauthenticated', () => {
- setup(() => {
+ setup(done => {
element = fixture('basic');
element.schemes = SCHEMES;
element.commands = COMMANDS;
element.selectedScheme = SELECTED_SCHEME;
flushAsynchronousOperations();
+ flush(done);
});
test('focusOnCopy', () => {
@@ -89,13 +92,13 @@ limitations under the License.
assert.isTrue(isHidden(element.$$('.commands')));
});
- test('tab selection', () => {
+ test('tab selection', done => {
assert.equal(element.$.downloadTabs.selected, '0');
MockInteractions.tap(element.$$('[data-scheme="ssh"]'));
flushAsynchronousOperations();
-
assert.equal(element.selectedScheme, 'ssh');
assert.equal(element.$.downloadTabs.selected, '2');
+ done();
});
test('loads scheme from preferences', done => {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index 42b6f941f9..98d7bf67c8 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -14,11 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
-<link rel="import" href="../../../bower_components/paper-item/paper-item.html">
-<link rel="import" href="../../../bower_components/paper-listbox/paper-listbox.html">
+<link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/paper-item/paper-item.html">
+<link rel="import" href="/bower_components/paper-listbox/paper-listbox.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -46,9 +46,9 @@ limitations under the License.
background-color: var(--dropdown-background-color);
box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
max-height: 70vh;
- margin-top: 2em;
+ margin-top: var(--spacing-xxl);
min-width: 266px;
- @apply --dropdown-content-style
+ @apply --dropdown-content-style;
}
paper-listbox {
--paper-listbox: {
@@ -58,7 +58,7 @@ limitations under the License.
paper-item {
cursor: pointer;
flex-direction: column;
- font-size: var(--font-size-normal);
+ font-size: inherit;
--paper-item: {
min-height: 0;
padding: 10px 16px;
@@ -78,20 +78,10 @@ limitations under the License.
}
.bottomContent {
color: var(--deemphasized-text-color);
- /*
- * Should be 16px when the base font size is 13px (browser default of
- * 16px.
- */
- line-height: 1.37rem;
}
.bottomContent,
.topContent {
display: flex;
- /*
- * Should be 16px when the base font size is 13px (browser default of
- * 16px.
- */
- line-height: 1.37rem;
justify-content: space-between;
flex-direction: row;
width: 100%;
@@ -103,7 +93,7 @@ limitations under the License.
}
gr-date-formatter {
color: var(--deemphasized-text-color);
- margin-left: 2em;
+ margin-left: var(--spacing-xxl);
white-space: nowrap;
}
gr-select {
@@ -135,11 +125,12 @@ limitations under the License.
}
</style>
<gr-button
+ disabled="[[disabled]]"
down-arrow
link
id="trigger"
class="dropdown-trigger"
- on-tap="_showDropdownTapHandler"
+ on-click="_showDropdownTapHandler"
slot="dropdown-trigger">
<span id="triggerText">[[text]]</span>
</gr-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
index 6b3905f5d2..efd1d0c42c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
@@ -47,7 +47,6 @@
Polymer({
is: 'gr-dropdown-list',
- _legacyUndefinedCheck: true,
/**
* Fired when the selected value changes
@@ -62,6 +61,10 @@
/** @type {!Array<!Defs.item>} */
items: Object,
text: String,
+ disabled: {
+ type: Boolean,
+ value: false,
+ },
value: {
type: String,
notify: true,
@@ -106,6 +109,11 @@
},
_handleValueChange(value, items) {
+ // Polymer 2: check for undefined
+ if ([value, items].some(arg => arg === undefined)) {
+ return;
+ }
+
if (!value) { return; }
const selectedObj = items.find(item => {
return item.value + '' === value + '';
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
index 87fd8de672..2b63d99f98 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-dropdown-list</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-dropdown-list.html">
@@ -66,7 +68,7 @@ limitations under the License.
assert.equal(element._computeMobileText(item), item.mobileText);
});
- test('options are selected and laid out correctly', () => {
+ test('options are selected and laid out correctly', done => {
element.value = 2;
element.items = [
{
@@ -92,77 +94,79 @@ limitations under the License.
];
assert.equal(element.$$('paper-listbox').selected, element.value);
assert.equal(element.text, 'Button Text 2');
- flushAsynchronousOperations();
- const items = Polymer.dom(element.root).querySelectorAll('paper-item');
- const mobileItems = Polymer.dom(element.root).querySelectorAll('option');
- assert.equal(items.length, 3);
- assert.equal(mobileItems.length, 3);
-
- // First Item
- // The first item should be disabled, has no bottom text, and no date.
- assert.isFalse(!!items[0].disabled);
- assert.isFalse(mobileItems[0].disabled);
- assert.isFalse(items[0].classList.contains('iron-selected'));
- assert.isFalse(mobileItems[0].selected);
-
- assert.isNotOk(Polymer.dom(items[0]).querySelector('gr-date-formatter'));
- assert.isNotOk(Polymer.dom(items[0]).querySelector('.bottomContent'));
- assert.equal(items[0].value, element.items[0].value);
- assert.equal(mobileItems[0].value, element.items[0].value);
- assert.equal(Polymer.dom(items[0]).querySelector('.topContent div')
- .innerText, element.items[0].text);
-
- // Since no mobile specific text, it should fall back to text.
- assert.equal(mobileItems[0].text, element.items[0].text);
-
-
- // Second Item
- // The second item should have top text, bottom text, and no date.
- assert.isFalse(!!items[1].disabled);
- assert.isFalse(mobileItems[1].disabled);
- assert.isTrue(items[1].classList.contains('iron-selected'));
- assert.isTrue(mobileItems[1].selected);
-
- assert.isNotOk(Polymer.dom(items[1]).querySelector('gr-date-formatter'));
- assert.isOk(Polymer.dom(items[1]).querySelector('.bottomContent'));
- assert.equal(items[1].value, element.items[1].value);
- assert.equal(mobileItems[1].value, element.items[1].value);
- assert.equal(Polymer.dom(items[1]).querySelector('.topContent div')
- .innerText, element.items[1].text);
-
- // Since there is mobile specific text, it should that.
- assert.equal(mobileItems[1].text, element.items[1].mobileText);
-
- // Since this item is selected, and it has triggerText defined, that
- // should be used.
- assert.equal(element.text, element.items[1].triggerText);
-
- // Third item
- // The third item should be disabled, and have a date, and bottom content.
- assert.isTrue(!!items[2].disabled);
- assert.isTrue(mobileItems[2].disabled);
- assert.isFalse(items[2].classList.contains('iron-selected'));
- assert.isFalse(mobileItems[2].selected);
-
- assert.isOk(Polymer.dom(items[2]).querySelector('gr-date-formatter'));
- assert.isOk(Polymer.dom(items[2]).querySelector('.bottomContent'));
- assert.equal(items[2].value, element.items[2].value);
- assert.equal(mobileItems[2].value, element.items[2].value);
- assert.equal(Polymer.dom(items[2]).querySelector('.topContent div')
- .innerText, element.items[2].text);
-
- // Since there is mobile specific text, it should that.
- assert.equal(mobileItems[2].text, element.items[2].mobileText);
-
- // Select a new item.
- MockInteractions.tap(items[0]);
- flushAsynchronousOperations();
- assert.equal(element.value, 1);
- assert.isTrue(items[0].classList.contains('iron-selected'));
- assert.isTrue(mobileItems[0].selected);
-
- // Since no triggerText, the fallback is used.
- assert.equal(element.text, element.items[0].text);
+ flush(() => {
+ const items = Polymer.dom(element.root).querySelectorAll('paper-item');
+ const mobileItems = Polymer.dom(element.root).querySelectorAll('option');
+ assert.equal(items.length, 3);
+ assert.equal(mobileItems.length, 3);
+
+ // First Item
+ // The first item should be disabled, has no bottom text, and no date.
+ assert.isFalse(!!items[0].disabled);
+ assert.isFalse(mobileItems[0].disabled);
+ assert.isFalse(items[0].classList.contains('iron-selected'));
+ assert.isFalse(mobileItems[0].selected);
+
+ assert.isNotOk(Polymer.dom(items[0]).querySelector('gr-date-formatter'));
+ assert.isNotOk(Polymer.dom(items[0]).querySelector('.bottomContent'));
+ assert.equal(items[0].value, element.items[0].value);
+ assert.equal(mobileItems[0].value, element.items[0].value);
+ assert.equal(Polymer.dom(items[0]).querySelector('.topContent div')
+ .innerText, element.items[0].text);
+
+ // Since no mobile specific text, it should fall back to text.
+ assert.equal(mobileItems[0].text, element.items[0].text);
+
+
+ // Second Item
+ // The second item should have top text, bottom text, and no date.
+ assert.isFalse(!!items[1].disabled);
+ assert.isFalse(mobileItems[1].disabled);
+ assert.isTrue(items[1].classList.contains('iron-selected'));
+ assert.isTrue(mobileItems[1].selected);
+
+ assert.isNotOk(Polymer.dom(items[1]).querySelector('gr-date-formatter'));
+ assert.isOk(Polymer.dom(items[1]).querySelector('.bottomContent'));
+ assert.equal(items[1].value, element.items[1].value);
+ assert.equal(mobileItems[1].value, element.items[1].value);
+ assert.equal(Polymer.dom(items[1]).querySelector('.topContent div')
+ .innerText, element.items[1].text);
+
+ // Since there is mobile specific text, it should that.
+ assert.equal(mobileItems[1].text, element.items[1].mobileText);
+
+ // Since this item is selected, and it has triggerText defined, that
+ // should be used.
+ assert.equal(element.text, element.items[1].triggerText);
+
+ // Third item
+ // The third item should be disabled, and have a date, and bottom content.
+ assert.isTrue(!!items[2].disabled);
+ assert.isTrue(mobileItems[2].disabled);
+ assert.isFalse(items[2].classList.contains('iron-selected'));
+ assert.isFalse(mobileItems[2].selected);
+
+ assert.isOk(Polymer.dom(items[2]).querySelector('gr-date-formatter'));
+ assert.isOk(Polymer.dom(items[2]).querySelector('.bottomContent'));
+ assert.equal(items[2].value, element.items[2].value);
+ assert.equal(mobileItems[2].value, element.items[2].value);
+ assert.equal(Polymer.dom(items[2]).querySelector('.topContent div')
+ .innerText, element.items[2].text);
+
+ // Since there is mobile specific text, it should that.
+ assert.equal(mobileItems[2].text, element.items[2].mobileText);
+
+ // Select a new item.
+ MockInteractions.tap(items[0]);
+ flushAsynchronousOperations();
+ assert.equal(element.value, 1);
+ assert.isTrue(items[0].classList.contains('iron-selected'));
+ assert.isTrue(mobileItems[0].selected);
+
+ // Since no triggerText, the fallback is used.
+ assert.equal(element.text, element.items[0].text);
+ done();
+ });
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index 373e77d9bc..d76721fc0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -17,8 +17,8 @@ limitations under the License.
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -63,7 +63,7 @@ limitations under the License.
li .itemAction {
cursor: pointer;
display: block;
- padding: .85em 1em;
+ padding: var(--spacing-m) var(--spacing-l);
}
li .itemAction {
@apply --gr-dropdown-item;
@@ -90,7 +90,7 @@ limitations under the License.
}
.topContent {
display: block;
- padding: .85em 1em;
+ padding: var(--spacing-m) var(--spacing-l);
@apply --gr-dropdown-item;
}
.bold-text {
@@ -101,7 +101,7 @@ limitations under the License.
link="[[link]]"
class="dropdown-trigger" id="trigger"
down-arrow="[[downArrow]]"
- on-tap="_dropdownTriggerTapHandler">
+ on-click="_dropdownTriggerTapHandler">
<slot></slot>
</gr-button>
<iron-dropdown id="dropdown"
@@ -139,7 +139,7 @@ limitations under the License.
<span
class$="itemAction [[_computeDisabledClass(link.id, disabledIds.*)]]"
data-id$="[[link.id]]"
- on-tap="_handleItemTap"
+ on-click="_handleItemTap"
hidden$="[[link.url]]"
tabindex="-1">[[link.name]]</span>
<a
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index b4b70875ab..11825c5641 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-dropdown',
- _legacyUndefinedCheck: true,
/**
* Fired when a non-link dropdown item with the given ID is tapped.
@@ -143,10 +142,10 @@
e.preventDefault();
e.stopPropagation();
if (this.$.dropdown.opened) {
- // TODO(kaspern): This solution will not work in Shadow DOM, and
- // is not particularly robust in general. Find a better solution
- // when page.js has been abstracted away from components.
- const el = this.$.cursor.target.querySelector(':not([hidden])');
+ // TODO(milutin): This solution is not particularly robust in general.
+ // Since gr-tooltip-content click on shadow dom is not propagated down,
+ // we have to target `a` inside it.
+ const el = this.$.cursor.target.querySelector(':not([hidden]) a');
if (el) { el.click(); }
} else {
this._open();
@@ -182,8 +181,8 @@
*/
_open() {
this.$.dropdown.open();
+ this._resetCursorStops();
this.$.cursor.setCursorAtIndex(0);
- Polymer.dom.flush();
this.$.cursor.target.focus();
},
@@ -295,10 +294,11 @@
* Recompute the stops for the dropdown item cursor.
*/
_resetCursorStops() {
- Polymer.dom.flush();
- // Polymer2: querySelectorAll returns NodeList instead of Array.
- this._listElements = Array.from(
- Polymer.dom(this.root).querySelectorAll('li'));
+ if (this.items && this.items.length > 0 && this.$.dropdown.opened) {
+ Polymer.dom.flush();
+ this._listElements = Array.from(
+ Polymer.dom(this.root).querySelectorAll('li'));
+ }
},
_computeHasTooltip(tooltip) {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index 7bb4dce5fe..295f746321 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-dropdown</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-dropdown.html">
@@ -194,7 +196,7 @@ limitations under the License.
MockInteractions.pressAndReleaseKeyOn(element, 32); // Space
assert.isTrue(element.$.dropdown.opened);
- const el = element.$.cursor.target.querySelector(':not([hidden])');
+ const el = element.$.cursor.target.querySelector(':not([hidden]) a');
const stub = sandbox.stub(el, 'click');
MockInteractions.pressAndReleaseKeyOn(element, 32); // Space
assert.isTrue(stub.called);
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 6cd87f52ff..45ddfc8369 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../gr-storage/gr-storage.html">
+<link rel="import" href="../gr-button/gr-button.html">
<dom-module id="gr-editable-content">
<template>
@@ -29,10 +31,18 @@ limitations under the License.
:host([disabled]) iron-autogrow-textarea {
opacity: .5;
}
- iron-autogrow-textarea {
+ .viewer {
+ background-color: var(--view-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ padding: var(--spacing-m);
+ }
+ .editor iron-autogrow-textarea {
+ background-color: var(--view-background-color);
width: 100%;
--iron-autogrow-textarea: {
+ padding: var(--spacing-m);
box-sizing: border-box;
overflow-y: hidden;
white-space: pre;
@@ -43,7 +53,7 @@ limitations under the License.
justify-content: space-between;
}
</style>
- <div hidden$="[[editing]]">
+ <div class="viewer" hidden$="[[editing]]">
<slot></slot>
</div>
<div class="editor" hidden$="[[!editing]]">
@@ -53,10 +63,10 @@ limitations under the License.
disabled="[[disabled]]"></iron-autogrow-textarea>
<div class="editButtons">
<gr-button primary
- on-tap="_handleSave"
+ on-click="_handleSave"
disabled="[[_saveDisabled]]">Save</gr-button>
<gr-button
- on-tap="_handleCancel"
+ on-click="_handleCancel"
disabled="[[disabled]]">Cancel</gr-button>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
index 5463564aa0..ee41103ae9 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-editable-content',
- _legacyUndefinedCheck: true,
/**
* Fired when the save button is pressed.
@@ -71,6 +70,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
focusTextarea() {
this.$$('iron-autogrow-textarea').textarea.focus();
},
@@ -117,8 +120,11 @@
this.$.storage.getEditableContentItem(this.storageKey);
if (storedContent && storedContent.message) {
content = storedContent.message;
- this.dispatchEvent(new CustomEvent('show-alert',
- {detail: {message: RESTORED_MESSAGE}, bubbles: true}));
+ this.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {message: RESTORED_MESSAGE},
+ bubbles: true,
+ composed: true,
+ }));
}
}
if (!content) {
@@ -132,6 +138,15 @@
},
_computeSaveDisabled(disabled, content, newContent) {
+ // Polymer 2: check for undefined
+ if ([
+ disabled,
+ content,
+ newContent,
+ ].some(arg => arg === undefined)) {
+ return true;
+ }
+
return disabled || !newContent || content === newContent;
},
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
index ca500cbd21..ee5adb0c6b 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-editable-content</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
+<script src="/bower_components/iron-test-helpers/mock-interactions.js"></script>
<link rel="import" href="gr-editable-content.html">
@@ -47,6 +49,7 @@ limitations under the License.
teardown(() => { sandbox.restore(); });
test('save event', done => {
+ element.content = '';
element._newContent = 'foo';
element.addEventListener('editable-content-save', e => {
assert.equal(e.detail.content, 'foo');
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index ddc35bf0aa..78465e188d 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -14,12 +14,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
-<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
+<link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="/bower_components/paper-input/paper-input.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../gr-button/gr-button.html">
<dom-module id="gr-editable-label">
<template>
@@ -35,13 +37,9 @@ limitations under the License.
label {
width: 100%;
}
- input {
- font: inherit;
- }
label {
color: var(--deemphasized-text-color);
display: inline-block;
- font-weight: var(--font-weight-bold);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -56,17 +54,17 @@ limitations under the License.
}
.inputContainer {
background-color: var(--dialog-background-color);
- padding: .8em;
+ padding: var(--spacing-m);
@apply --input-style;
}
.buttons {
display: flex;
justify-content: flex-end;
- padding-top: 1.2em;
+ padding-top: var(--spacing-l);
width: 100%;
}
.buttons gr-button {
- margin-left: .5em;
+ margin-left: var(--spacing-m);
}
paper-input {
--paper-input-container: {
@@ -74,7 +72,7 @@ limitations under the License.
min-width: 15em;
}
--paper-input-container-input: {
- font-size: var(--font-size-normal);
+ font-size: inherit;
}
--paper-input-container-focus-color: var(--link-color);
}
@@ -82,7 +80,7 @@ limitations under the License.
<label
class$="[[_computeLabelClass(readOnly, value, placeholder)]]"
title$="[[_computeLabel(value, placeholder)]]"
- on-tap="_showDropdown">[[_computeLabel(value, placeholder)]]</label>
+ on-click="_showDropdown">[[_computeLabel(value, placeholder)]]</label>
<iron-dropdown id="dropdown"
vertical-align="auto"
horizontal-align="auto"
@@ -97,8 +95,8 @@ limitations under the License.
maxlength="[[maxLength]]"
value="{{_inputText}}"></paper-input>
<div class="buttons">
- <gr-button link id="cancelBtn" on-tap="_cancel">cancel</gr-button>
- <gr-button link id="saveBtn" on-tap="_save">save</gr-button>
+ <gr-button link id="cancelBtn" on-click="_cancel">cancel</gr-button>
+ <gr-button link id="saveBtn" on-click="_save">save</gr-button>
</div>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
index f23afeae08..4485551a21 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-editable-label',
- _legacyUndefinedCheck: true,
/**
* Fired when the value is changed.
@@ -68,6 +67,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -94,15 +94,15 @@
_showDropdown() {
if (this.readOnly || this.editing) { return; }
return this._open().then(() => {
- this.$.input.$.input.focus();
+ this._nativeInput.focus();
if (!this.$.input.value) { return; }
- this.$.input.$.input.setSelectionRange(0, this.$.input.value.length);
+ this._nativeInput.setSelectionRange(0, this.$.input.value.length);
});
},
open() {
return this._open().then(() => {
- this.$.input.$.input.focus();
+ this._nativeInput.focus();
});
},
@@ -154,29 +154,25 @@
this._inputText = this.value;
},
- /**
- * @suppress {checkTypes}
- * Closure doesn't think 'e' is an Event.
- * TODO(beckysiegel) figure out why.
- */
+ get _nativeInput() {
+ // In Polymer 2, the namespace of nativeInput
+ // changed from input to nativeInput
+ return this.$.input.$.nativeInput || this.$.input.$.input;
+ },
+
_handleEnter(e) {
e = this.getKeyboardEvent(e);
const target = Polymer.dom(e).rootTarget;
- if (target === this.$.input.$.input) {
+ if (target === this._nativeInput) {
e.preventDefault();
this._save();
}
},
- /**
- * @suppress {checkTypes}
- * Closure doesn't think 'e' is an Event.
- * TODO(beckysiegel) figure out why.
- */
_handleEsc(e) {
e = this.getKeyboardEvent(e);
const target = Polymer.dom(e).rootTarget;
- if (target === this.$.input.$.input) {
+ if (target === this._nativeInput) {
e.preventDefault();
this._cancel();
}
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
index 68151734f4..7ff0a1411d 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-editable-label</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
+<script src="/bower_components/iron-test-helpers/mock-interactions.js"></script>
<link rel="import" href="gr-editable-label.html">
@@ -59,13 +61,17 @@ limitations under the License.
let label;
let sandbox;
- setup(() => {
+ setup(done => {
element = fixture('basic');
elementNoPlaceholder = fixture('no-placeholder');
- input = element.$.input.$.input;
label = element.$$('label');
sandbox = sinon.sandbox.create();
+ flush(() => {
+ // In Polymer 2 inputElement isn't nativeInput anymore
+ input = element.$.input.$.nativeInput || element.$.input.inputElement;
+ done();
+ });
});
teardown(() => {
@@ -77,7 +83,7 @@ limitations under the License.
assert.isFalse(element.$.dropdown.opened);
assert.isTrue(label.classList.contains('editable'));
assert.equal(label.textContent, 'value text');
- const focusSpy = sandbox.spy(element.$.input.$.input, 'focus');
+ const focusSpy = sandbox.spy(input, 'focus');
const showSpy = sandbox.spy(element, '_showDropdown');
MockInteractions.tap(label);
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.html b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.html
index 674ff977a6..226092fca4 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.html
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.html
@@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-fixed-panel">
<template>
<style include="shared-styles">
:host {
+ box-sizing: border-box;
display: block;
min-height: var(--header-height);
position: relative;
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
index 87cd4b4433..2c32709176 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-fixed-panel',
- _legacyUndefinedCheck: true,
properties: {
floatingDisabled: Boolean,
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
index 9eac7f74fb..75e9901f90 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-fixed-panel</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-fixed-panel.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
index 39955956c7..a98952c25c 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -29,7 +29,7 @@ limitations under the License.
ul,
blockquote,
gr-linked-text.pre {
- margin: 0 0 .8em 0;
+ margin: 0 0 var(--spacing-m) 0;
}
p,
ul,
@@ -44,14 +44,16 @@ limitations under the License.
}
blockquote {
border-left: 1px solid #aaa;
- padding: 0 .7em;
+ padding: 0 var(--spacing-m);
}
li {
list-style-type: disc;
- margin-left: 1.4em;
+ margin-left: var(--spacing-xl);
}
gr-linked-text.pre {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-code);
}
</style>
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
index d84c38e759..feae17314e 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-formatted-text',
- _legacyUndefinedCheck: true,
properties: {
content: {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
index ad036c58c1..801190a75d 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-editable-label</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-formatted-text.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.html
index 7e3246fcbb..c77ea811c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -34,10 +34,10 @@ limitations under the License.
visibility: visible;
opacity: 1;
}
- :host ::content #hovercard {
+ #hovercard {
background: var(--dialog-background-color);
box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
- padding: 1em;
+ padding: var(--spacing-l);
}
</style>
<div id="hovercard" role="tooltip" tabindex="-1">
@@ -46,4 +46,4 @@ limitations under the License.
</template>
<script src="../../../scripts/rootElement.js"></script>
<script src="gr-hovercard.js"></script>
-</dom-module> \ No newline at end of file
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
index f8ff549f95..5692b38896 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
@@ -27,7 +27,6 @@
Polymer({
is: 'gr-hovercard',
- _legacyUndefinedCheck: true,
properties: {
/**
@@ -98,7 +97,7 @@
this.listen(this._target, 'focus', 'show');
this.listen(this._target, 'mouseleave', 'hide');
this.listen(this._target, 'blur', 'hide');
- this.listen(this._target, 'tap', 'hide');
+ this.listen(this._target, 'click', 'hide');
},
ready() {
@@ -119,7 +118,7 @@
this.unlisten(this._target, 'focus', 'show');
this.unlisten(this._target, 'mouseleave', 'hide');
this.unlisten(this._target, 'blur', 'hide');
- this.unlisten(this._target, 'tap', 'hide');
+ this.unlisten(this._target, 'click', 'hide');
},
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
index e3e252fc56..8e79f65795 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-hovercard</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
+<script src="/bower_components/iron-test-helpers/mock-interactions.js"></script>
<link rel="import" href="gr-hovercard.html">
@@ -39,7 +41,8 @@ limitations under the License.
suite('gr-hovercard tests', () => {
let element;
let sandbox;
- const TRANSITION_TIME = 200;
+ // For css animations
+ const TRANSITION_TIME = 500;
setup(() => {
sandbox = sinon.sandbox.create();
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 1f885afd51..7bd6f48a4d 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -14,8 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
-<link rel="import" href="../../../bower_components/iron-iconset-svg/iron-iconset-svg.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="/bower_components/iron-iconset-svg/iron-iconset-svg.html">
<iron-iconset-svg name="gr-icons" size="24">
<svg>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
index 2581ff985d..708e5b68f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
@@ -45,26 +45,27 @@
*
* @param {number} offset The char offset where the update starts.
* @param {number} length The number of chars that the update covers.
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateRange = function(
- offset, length, cssClass, side) {
+ offset, length, styleObject, side) {
if (this._contentEl && this._contentEl.getAttribute('data-side') == side) {
- GrAnnotation.annotateElement(this._contentEl, offset, length, cssClass);
+ GrAnnotation.annotateElement(this._contentEl, offset, length,
+ styleObject.getClassName(this._contentEl));
}
};
/**
* Method to add a CSS class to the line number TD element.
*
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateLineNumber = function(
- cssClass, side) {
+ styleObject, side) {
if (this._lineNumberEl && this._lineNumberEl.classList.contains(side)) {
- this._lineNumberEl.classList.add(cssClass);
+ styleObject.apply(this._lineNumberEl);
}
};
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index 03c8c5e384..3223636ef9 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-annotation-actions-context</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<script src="../../diff/gr-diff-highlight/gr-annotation.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
@@ -40,9 +42,13 @@ limitations under the License.
let sandbox;
let el;
let lineNumberEl;
+ let plugin;
setup(() => {
sandbox = sinon.sandbox.create();
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+
const str = 'lorem ipsum blah blah';
const line = {text: str};
el = document.createElement('div');
@@ -50,6 +56,7 @@ limitations under the License.
el.setAttribute('data-side', 'right');
lineNumberEl = document.createElement('td');
lineNumberEl.classList.add('right');
+ document.body.appendChild(el);
instance = new GrAnnotationActionsContext(
el, lineNumberEl, line, 'dummy/path', '123', '1');
});
@@ -62,32 +69,34 @@ limitations under the License.
annotateElementSpy = sandbox.spy(GrAnnotation, 'annotateElement');
const start = 0;
const end = 100;
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
// Assert annotateElement is not called when side is different.
- instance.annotateRange(start, end, cssClass, 'left');
+ instance.annotateRange(start, end, cssStyleObject, 'left');
assert.equal(annotateElementSpy.callCount, 0);
// Assert annotateElement is called once when side is the same.
- instance.annotateRange(start, end, cssClass, 'right');
+ instance.annotateRange(start, end, cssStyleObject, 'right');
assert.equal(annotateElementSpy.callCount, 1);
const args = annotateElementSpy.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], start);
assert.equal(args[2], end);
- assert.equal(args[3], cssClass);
+ assert.equal(args[3], cssStyleObject.getClassName(el));
});
test('test annotateLineNumber', () => {
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
+
+ const className = cssStyleObject.getClassName(lineNumberEl);
// Assert that css class is *not* applied when side is different.
- instance.annotateLineNumber(cssClass, 'left');
- assert.isFalse(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'left');
+ assert.isFalse(lineNumberEl.classList.contains(className));
// Assert that css class is applied when side is the same.
- instance.annotateLineNumber(cssClass, 'right');
- assert.isTrue(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'right');
+ assert.isTrue(lineNumberEl.classList.contains(className));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
index 3f91a826c5..4ba9820599 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
@@ -67,10 +67,8 @@
* providers are not supported. A second call will just overwrite the
* provider of the first call.
*
- * TODO(brohlfs): Replace Array<Object> type by Array<Gerrit.CoverageRange>.
- *
* @param {function(changeNum, path, basePatchNum, patchNum):
- * !Promise<!Array<Object>>} coverageProvider
+ * !Promise<!Array<!Gerrit.CoverageRange>>} coverageProvider
* @return {GrAnnotationActionsInterface}
*/
GrAnnotationActionsInterface.prototype.setCoverageProvider = function(
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
index bf7c2cb939..987b551c0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-annotation-actions-js-api-js-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../change/gr-change-actions/gr-change-actions.html">
@@ -28,7 +30,9 @@ limitations under the License.
<template>
<span hidden id="annotation-span">
<label for="annotation-checkbox" id="annotation-label"></label>
- <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+ <iron-input type="checkbox" disabled>
+ <input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
+ </iron-input>
</span>
</template>
</test-fixture>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
new file mode 100644
index 0000000000..2925736755
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
@@ -0,0 +1,112 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function(window) {
+ 'use strict';
+
+ const PRELOADED_PROTOCOL = 'preloaded:';
+ const PLUGIN_LOADING_TIMEOUT_MS = 10000;
+
+ let _restAPI;
+ function getRestAPI() {
+ if (!_restAPI) {
+ _restAPI = document.createElement('gr-rest-api-interface');
+ }
+ return _restAPI;
+ }
+
+ function getBaseUrl() {
+ return Gerrit.BaseUrlBehavior.getBaseUrl();
+ }
+
+ /**
+ * Retrieves the name of the plugin base on the url.
+ *
+ * @param {string|URL} url
+ */
+ function getPluginNameFromUrl(url) {
+ if (!(url instanceof URL)) {
+ try {
+ url = new URL(url);
+ } catch (e) {
+ console.warn(e);
+ return null;
+ }
+ }
+ if (url.protocol === PRELOADED_PROTOCOL) {
+ return url.pathname;
+ }
+ const base = Gerrit.BaseUrlBehavior.getBaseUrl();
+ const pathname = url.pathname.replace(base, '');
+ // Site theme is server from predefined path.
+ if (pathname === '/static/gerrit-theme.html') {
+ return 'gerrit-theme';
+ } else if (!pathname.startsWith('/plugins')) {
+ console.warn('Plugin not being loaded from /plugins base path:',
+ url.href, '— Unable to determine name.');
+ return null;
+ }
+
+ // Pathname should normally look like this:
+ // /plugins/PLUGINNAME/static/SCRIPTNAME.html
+ // Or, for app/samples:
+ // /plugins/PLUGINNAME.html
+ // TODO(taoalpha): guard with a regex
+ return pathname.split('/')[2].split('.')[0];
+ }
+
+ // TODO(taoalpha): to be deprecated.
+ function send(method, url, opt_callback, opt_payload) {
+ return getRestAPI().send(method, url, opt_payload).then(response => {
+ if (response.status < 200 || response.status >= 300) {
+ return response.text().then(text => {
+ if (text) {
+ return Promise.reject(text);
+ } else {
+ return Promise.reject(response.status);
+ }
+ });
+ } else {
+ return getRestAPI().getResponseObject(response);
+ }
+ }).then(response => {
+ if (opt_callback) {
+ opt_callback(response);
+ }
+ return response;
+ });
+ }
+
+
+ // TEST only methods / properties
+
+ function testOnly_resetInternalState() {
+ _restAPI = undefined;
+ }
+
+ window._apiUtils = {
+ getPluginNameFromUrl,
+ send,
+ getRestAPI,
+ getBaseUrl,
+ PRELOADED_PROTOCOL,
+ PLUGIN_LOADING_TIMEOUT_MS,
+
+ // TEST only methods
+ testOnly_resetInternalState,
+ };
+})(window); \ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
new file mode 100644
index 0000000000..c407aa816d
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-api-interface</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-js-api-interface.html">
+
+<script>void(0);</script>
+
+<script>
+
+ const PRELOADED_PROTOCOL = 'preloaded:';
+
+ suite('gr-api-utils tests', () => {
+ suite('test getPluginNameFromUrl', () => {
+ const {getPluginNameFromUrl} = window._apiUtils;
+
+ test('with empty string', () => {
+ assert.equal(getPluginNameFromUrl(''), null);
+ });
+
+ test('with invalid url', () => {
+ assert.equal(getPluginNameFromUrl('test'), null);
+ });
+
+ test('with random invalid url', () => {
+ assert.equal(getPluginNameFromUrl('http://example.com'), null);
+ assert.equal(
+ getPluginNameFromUrl('http://example.com/static/a.html'),
+ null
+ );
+ });
+
+ test('with valid urls', () => {
+ assert.equal(
+ getPluginNameFromUrl('http://example.com/plugins/a.html'),
+ 'a'
+ );
+ assert.equal(
+ getPluginNameFromUrl('http://example.com/plugins/a/static/t.html'),
+ 'a'
+ );
+ });
+
+ test('with preloaded urls', () => {
+ assert.equal(getPluginNameFromUrl(`${PRELOADED_PROTOCOL}a`), 'a');
+ });
+
+ test('with gerrit-theme override', () => {
+ assert.equal(
+ getPluginNameFromUrl('http://example.com/static/gerrit-theme.html'),
+ 'gerrit-theme'
+ );
+ });
+ });
+ });
+</script> \ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index fef4fc9df2..7332877748 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-actions-js-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<!--
This must refer to the element this interface is wrapping around. Otherwise
@@ -52,17 +54,18 @@ breaking changes to gr-change-actions won’t be noticed.
suite('early init', () => {
setup(() => {
- Gerrit._resetPlugins();
+ Gerrit._testOnly_resetPlugins();
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
changeActions = plugin.changeActions();
element = fixture('basic');
});
teardown(() => {
changeActions = null;
+ Gerrit._testOnly_resetPlugins();
});
test('does not throw', ()=> {
@@ -74,7 +77,7 @@ breaking changes to gr-change-actions won’t be noticed.
suite('normal init', () => {
setup(() => {
- Gerrit._resetPlugins();
+ Gerrit._testOnly_resetPlugins();
element = fixture('basic');
sinon.stub(element, '_editStatusChanged');
element.change = {};
@@ -83,11 +86,12 @@ breaking changes to gr-change-actions won’t be noticed.
'http://test.com/plugins/testplugin/static/test.js');
changeActions = plugin.changeActions();
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
teardown(() => {
changeActions = null;
+ Gerrit._testOnly_resetPlugins();
});
test('property existence', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
index 278f95a3f7..842a2fe855 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-change-reply-js-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<!--
This must refer to the element this interface is wrapping around. Otherwise
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
new file mode 100644
index 0000000000..73a248063e
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
@@ -0,0 +1,196 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This defines the Gerrit instance. All methods directly attached to Gerrit
+ * should be defined or linked here.
+ */
+
+(function(window) {
+ 'use strict';
+
+ // Import utils methods
+ const {
+ send,
+ getRestAPI,
+ } = window._apiUtils;
+
+ /**
+ * Trigger the preinstalls for bundled plugins.
+ * This needs to happen before Gerrit as plugin bundle overrides the Gerrit.
+ */
+ function flushPreinstalls() {
+ if (window.Gerrit.flushPreinstalls) {
+ window.Gerrit.flushPreinstalls();
+ }
+ }
+ flushPreinstalls();
+
+ window.Gerrit = window.Gerrit || {};
+ const Gerrit = window.Gerrit;
+ Gerrit._pluginLoader = new PluginLoader();
+
+ Gerrit._endpoints = new GrPluginEndpoints();
+
+ // Provide reset plugins function to clear installed plugins between tests.
+ const app = document.querySelector('#app');
+ if (!app) {
+ // No gr-app found (running tests)
+ const {
+ testOnly_resetInternalState,
+ } = window._apiUtils;
+ Gerrit._testOnly_installPreloadedPlugins = (...args) => Gerrit._pluginLoader
+ .installPreloadedPlugins(...args);
+ Gerrit._testOnly_flushPreinstalls = flushPreinstalls;
+ Gerrit._testOnly_resetPlugins = () => {
+ testOnly_resetInternalState();
+ Gerrit._endpoints = new GrPluginEndpoints();
+ Gerrit._pluginLoader = new PluginLoader();
+ };
+ }
+
+ /**
+ * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
+ * the documentation how to replace it accordingly.
+ */
+ Gerrit.css = function(rulesStr) {
+ console.warn('Gerrit.css(rulesStr) is deprecated!',
+ 'Use plugin.styles().css(rulesStr)');
+ if (!Gerrit._customStyleSheet) {
+ const styleEl = document.createElement('style');
+ document.head.appendChild(styleEl);
+ Gerrit._customStyleSheet = styleEl.sheet;
+ }
+
+ const name = '__pg_js_api_class_' +
+ Gerrit._customStyleSheet.cssRules.length;
+ Gerrit._customStyleSheet.insertRule('.' + name + '{' + rulesStr + '}', 0);
+ return name;
+ };
+
+ Gerrit.install = function(callback, opt_version, opt_src) {
+ Gerrit._pluginLoader.install(callback, opt_version, opt_src);
+ };
+
+ Gerrit.getLoggedIn = function() {
+ console.warn('Gerrit.getLoggedIn() is deprecated! ' +
+ 'Use plugin.restApi().getLoggedIn()');
+ return document.createElement('gr-rest-api-interface').getLoggedIn();
+ };
+
+ Gerrit.get = function(url, callback) {
+ console.warn('.get() is deprecated! Use plugin.restApi().get()');
+ send('GET', url, callback);
+ };
+
+ Gerrit.post = function(url, payload, callback) {
+ console.warn('.post() is deprecated! Use plugin.restApi().post()');
+ send('POST', url, callback, payload);
+ };
+
+ Gerrit.put = function(url, payload, callback) {
+ console.warn('.put() is deprecated! Use plugin.restApi().put()');
+ send('PUT', url, callback, payload);
+ };
+
+ Gerrit.delete = function(url, opt_callback) {
+ console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
+ return getRestAPI().send('DELETE', url).then(response => {
+ if (response.status !== 204) {
+ return response.text().then(text => {
+ if (text) {
+ return Promise.reject(text);
+ } else {
+ return Promise.reject(response.status);
+ }
+ });
+ }
+ if (opt_callback) {
+ opt_callback(response);
+ }
+ return response;
+ });
+ };
+
+ Gerrit.awaitPluginsLoaded = function() {
+ return Gerrit._pluginLoader.awaitPluginsLoaded();
+ };
+
+ // TODO(taoalpha): consider removing these proxy methods
+ // and using _pluginLoader directly
+
+ Gerrit._loadPlugins = function(plugins, opt_option) {
+ Gerrit._pluginLoader.loadPlugins(plugins, opt_option);
+ };
+
+ Gerrit._arePluginsLoaded = function() {
+ return Gerrit._pluginLoader.arePluginsLoaded;
+ };
+
+ Gerrit._isPluginPreloaded = function(url) {
+ return Gerrit._pluginLoader.isPluginPreloaded(url);
+ };
+
+ Gerrit._isPluginEnabled = function(pathOrUrl) {
+ return Gerrit._pluginLoader.isPluginEnabled(pathOrUrl);
+ };
+
+ Gerrit._isPluginLoaded = function(pathOrUrl) {
+ return Gerrit._pluginLoader.isPluginLoaded(pathOrUrl);
+ };
+
+ // Preloaded plugins should be installed after Gerrit.install() is set,
+ // since plugin preloader substitutes Gerrit.install() temporarily.
+ Gerrit._pluginLoader.installPreloadedPlugins();
+
+ // TODO(taoalpha): List all internal supported event names.
+ // Also convert this to inherited class once we move Gerrit to class.
+ Gerrit._eventEmitter = new EventEmitter();
+ ['addListener',
+ 'dispatch',
+ 'emit',
+ 'off',
+ 'on',
+ 'once',
+ 'removeAllListeners',
+ 'removeListener',
+ ].forEach(method => {
+ /**
+ * Enabling EventEmitter interface on Gerrit.
+ *
+ * This will enable to signal across different parts of js code without relying on DOM,
+ * including core to core, plugin to plugin and also core to plugin.
+ *
+ * @example
+ *
+ * // Emit this event from pluginA
+ * Gerrit.install(pluginA => {
+ * fetch("some-api").then(() => {
+ * Gerrit.on("your-special-event", {plugin: pluginA});
+ * });
+ * });
+ *
+ * // Listen on your-special-event from pluignB
+ * Gerrit.install(pluginB => {
+ * Gerrit.on("your-special-event", ({plugin}) => {
+ * // do something, plugin is pluginA
+ * });
+ * });
+ */
+ Gerrit[method] = Gerrit._eventEmitter[method].bind(Gerrit._eventEmitter);
+ });
+})(window); \ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
new file mode 100644
index 0000000000..e81b8aad01
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-api-interface</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-js-api-interface.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-js-api-interface></gr-js-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-gerrit tests', () => {
+ let element;
+ let sandbox;
+ let sendStub;
+
+ setup(() => {
+ this.clock = sinon.useFakeTimers();
+ sandbox = sinon.sandbox.create();
+ sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
+ stub('gr-rest-api-interface', {
+ getAccount() {
+ return Promise.resolve({name: 'Judy Hopps'});
+ },
+ send(...args) {
+ return sendStub(...args);
+ },
+ });
+ element = fixture('basic');
+ });
+
+ teardown(() => {
+ this.clock.restore();
+ sandbox.restore();
+ element._removeEventCallbacks();
+ Gerrit._testOnly_resetPlugins();
+ });
+
+ suite('proxy methods', () => {
+ test('Gerrit._isPluginEnabled proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginEnabled',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginEnabled('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
+ });
+
+ test('Gerrit._isPluginLoaded proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginLoaded',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginLoaded('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
+ });
+
+ test('Gerrit._isPluginPreloaded proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginPreloaded',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginPreloaded('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index d95fd0afd6..72fc3d0fd4 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
@@ -26,10 +26,19 @@ limitations under the License.
<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
<link rel="import" href="../../plugins/gr-repo-api/gr-repo-api.html">
<link rel="import" href="../../plugins/gr-settings-api/gr-settings-api.html">
+<link rel="import" href="../../plugins/gr-styles-api/gr-styles-api.html">
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-js-api-interface">
+ <!--
+ Note: the order matters as files depend on each other.
+ 1. gr-api-utils will be used in multiple files below.
+ 2. gr-gerrit depends on gr-plugin-loader, gr-public-js-api and
+ also gr-plugin-endpoints
+ 3. gr-public-js-api depends on gr-plugin-rest-api
+ -->
+ <script src="gr-api-utils.js"></script>
<script src="../gr-event-interface/gr-event-interface.js"></script>
<script src="gr-annotation-actions-context.js"></script>
<script src="gr-annotation-actions-js-api.js"></script>
@@ -40,4 +49,6 @@ limitations under the License.
<script src="gr-plugin-action-context.js"></script>
<script src="gr-plugin-rest-api.js"></script>
<script src="gr-public-js-api.js"></script>
+ <script src="gr-plugin-loader.js"></script>
+ <script src="gr-gerrit.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
index ad318d7a74..2c00c89237 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
@@ -17,11 +17,13 @@
(function() {
'use strict';
+ // Note: for new events, naming convention should be: `a-b`
const EventType = {
HISTORY: 'history',
LABEL_CHANGE: 'labelchange',
SHOW_CHANGE: 'showchange',
SUBMIT_CHANGE: 'submitchange',
+ SHOW_REVISION_ACTIONS: 'show-revision-actions',
COMMIT_MSG_EDIT: 'commitmsgedit',
COMMENT: 'comment',
REVERT: 'revert',
@@ -38,7 +40,6 @@
Polymer({
is: 'gr-js-api-interface',
- _legacyUndefinedCheck: true,
properties: {
_elements: {
@@ -71,6 +72,9 @@
case EventType.LABEL_CHANGE:
this._handleLabelChange(detail);
break;
+ case EventType.SHOW_REVISION_ACTIONS:
+ this._handleShowRevisionActions(detail);
+ break;
case EventType.HIGHLIGHTJS_LOADED:
this._handleHighlightjsLoaded(detail);
break;
@@ -136,13 +140,16 @@
//
// This clone and getter can be removed after plugins migrate to use
// info.mergeable.
- const change = Object.assign({
+ //
+ // assign on getter with existing property will report error
+ // see Issue: 12286
+ const change = Object.assign({}, detail.change, {
get mergeable() {
console.warn('Accessing change.mergeable from SHOW_CHANGE is ' +
'deprecated! Use info.mergeable instead.');
- return detail.info.mergeable;
+ return detail.info && detail.info.mergeable;
},
- }, detail.change);
+ });
const patchNum = detail.patchNum;
const info = detail.info;
@@ -163,6 +170,22 @@
}
},
+ /**
+ * @param {!{change: !Object, revisionActions: !Object}} detail
+ */
+ _handleShowRevisionActions(detail) {
+ const registeredCallbacks = this._getEventCallbacks(
+ EventType.SHOW_REVISION_ACTIONS
+ );
+ for (const cb of registeredCallbacks) {
+ try {
+ cb(detail.revisionActions, detail.change);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ },
+
handleCommitMessage(change, msg) {
for (const cb of this._getEventCallbacks(EventType.COMMIT_MSG_EDIT)) {
try {
@@ -232,30 +255,13 @@
* Retrieves coverage data possibly provided by a plugin.
*
* Will wait for plugins to be loaded. If multiple plugins offer a coverage
- * provider, the first one is used. If no plugin offers a coverage provider,
- * will resolve to [].
- *
- * TODO(brohlfs): Replace Array<Object> type by Array<Gerrit.CoverageRange>.
- *
- * @param {string|number} changeNum
- * @param {string} path
- * @param {string|number} basePatchNum
- * @param {string|number} patchNum
- * @return {!Promise<!Array<Object>>}
+ * provider, the first one is returned. If no plugin offers a coverage provider,
+ * will resolve to null.
*/
- getCoverageRanges(changeNum, path, basePatchNum, patchNum) {
- return Gerrit.awaitPluginsLoaded().then(() => {
- for (const annotationApi of
- this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
- const provider = annotationApi.getCoverageProvider();
- // Only one coverage provider makes sense. If there are more, then we
- // simply ignore them.
- if (provider) {
- return provider(changeNum, path, basePatchNum, patchNum);
- }
- }
- return [];
- });
+ getCoverageAnnotationApi() {
+ return Gerrit.awaitPluginsLoaded()
+ .then(() => this._getEventCallbacks(EventType.ANNOTATE_DIFF)
+ .find(api => api.getCoverageProvider()));
},
getAdminMenuLinks() {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index f03aa09879..14928e611b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-api-interface</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-js-api-interface.html">
@@ -33,6 +35,7 @@ limitations under the License.
</test-fixture>
<script>
+ const {PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
suite('gr-js-api-interface tests', () => {
let element;
let plugin;
@@ -46,6 +49,7 @@ limitations under the License.
};
setup(() => {
+ this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
getResponseObjectStub = sandbox.stub().returns(Promise.resolve());
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
@@ -62,32 +66,16 @@ limitations under the License.
errorStub = sandbox.stub(console, 'error');
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
teardown(() => {
+ this.clock.restore();
sandbox.restore();
element._removeEventCallbacks();
plugin = null;
});
- test('reuse plugin for install calls', () => {
- let otherPlugin;
- Gerrit.install(p => { otherPlugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin/static/test.js');
- assert.strictEqual(plugin, otherPlugin);
- });
-
- test('flushes preinstalls if provided', () => {
- assert.doesNotThrow(() => {
- Gerrit._flushPreinstalls();
- });
- window.Gerrit.flushPreinstalls = sandbox.stub();
- Gerrit._flushPreinstalls();
- assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
- delete window.Gerrit.flushPreinstalls;
- });
-
test('url', () => {
assert.equal(plugin.url(), 'http://test.com/plugins/testplugin/');
assert.equal(plugin.url('/static/test.js'),
@@ -203,18 +191,37 @@ limitations under the License.
{change: testChange, patchNum: 1, info: {mergeable: false}});
});
+ test('show-revision-actions event', done => {
+ const testChange = {
+ _number: 42,
+ revisions: {def: {_number: 2}, abc: {_number: 1}},
+ };
+ plugin.on(element.EventType.SHOW_REVISION_ACTIONS, throwErrFn);
+ plugin.on(element.EventType.SHOW_REVISION_ACTIONS, (actions, change) => {
+ assert.deepEqual(change, testChange);
+ assert.deepEqual(actions, {test: {}});
+ assert.isTrue(errorStub.calledOnce);
+ done();
+ });
+ element.handleEvent(element.EventType.SHOW_REVISION_ACTIONS,
+ {change: testChange, revisionActions: {test: {}}});
+ });
+
test('handleEvent awaits plugins load', done => {
const testChange = {
_number: 42,
revisions: {def: {_number: 2}, abc: {_number: 1}},
};
const spy = sandbox.spy();
- Gerrit._setPluginsCount(1);
+ Gerrit._loadPlugins(['plugins/test.html']);
plugin.on(element.EventType.SHOW_CHANGE, spy);
element.handleEvent(element.EventType.SHOW_CHANGE,
{change: testChange, patchNum: 1});
assert.isFalse(spy.called);
- Gerrit._setPluginsCount(0);
+
+ // Timeout on loading plugins
+ this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
+
flush(() => {
assert.isTrue(spy.called);
done();
@@ -311,90 +318,13 @@ limitations under the License.
element.handleEvent(element.EventType.HIGHLIGHTJS_LOADED, {hljs: testHljs});
});
- test('versioning', () => {
- const callback = sandbox.spy();
- Gerrit.install(callback, '0.0pre-alpha');
- assert(callback.notCalled);
- });
-
test('getAccount', done => {
- Gerrit.getLoggedIn().then(loggedIn => {
+ plugin.restApi().getLoggedIn().then(loggedIn => {
assert.isTrue(loggedIn);
done();
});
});
- test('_setPluginsCount', done => {
- stub('gr-reporting', {
- pluginsLoaded() {
- done();
- },
- });
- Gerrit._setPluginsCount(0);
- });
-
- test('_arePluginsLoaded', () => {
- assert.isTrue(Gerrit._arePluginsLoaded());
- Gerrit._setPluginsCount(1);
- assert.isFalse(Gerrit._arePluginsLoaded());
- Gerrit._setPluginsCount(0);
- assert.isTrue(Gerrit._arePluginsLoaded());
- });
-
- test('_pluginInstalled', () => {
- const pluginsLoadedStub = sandbox.stub();
- stub('gr-reporting', {
- pluginsLoaded: (...args) => pluginsLoadedStub(...args),
- });
- const plugins = [
- 'http://test.com/plugins/foo/static/test.js',
- 'http://test.com/plugins/bar/static/test.js',
- ];
- Gerrit._setPluginsPending(plugins);
- Gerrit._pluginInstalled(plugins[0]);
- Gerrit._pluginInstalled(plugins[1]);
- assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
- });
-
- test('install calls _pluginInstalled', () => {
- sandbox.stub(Gerrit, '_pluginInstalled');
- Gerrit.install(p => { plugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin/static/test.js');
-
- // testplugin has already been installed once (in setup).
- assert.isFalse(Gerrit._pluginInstalled.called);
-
- // testplugin2 plugin has not yet been installed.
- Gerrit.install(p => { plugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin2/static/test.js');
- assert.isTrue(Gerrit._pluginInstalled.calledOnce);
- });
-
- test('plugin install errors mark plugins as loaded', () => {
- Gerrit._setPluginsCount(1);
- Gerrit.install(() => {}, '0.0pre-alpha');
- return Gerrit.awaitPluginsLoaded();
- });
-
- test('multiple ui plugins per java plugin', () => {
- const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
- const file2 = 'http://test.com/plugins/qaz/static/bar.js';
- Gerrit._setPluginsPending([file1, file2]);
- Gerrit.install(() => {}, '0.1', file1);
- Gerrit.install(() => {}, '0.1', file2);
- return Gerrit.awaitPluginsLoaded();
- });
-
- test('plugin install errors shows toasts', () => {
- const alertStub = sandbox.stub();
- document.addEventListener('show-alert', alertStub);
- Gerrit._setPluginsCount(1);
- Gerrit.install(() => {}, '0.0pre-alpha');
- return Gerrit.awaitPluginsLoaded().then(() => {
- assert.isTrue(alertStub.calledOnce);
- });
- });
-
test('attributeHelper', () => {
assert.isOk(plugin.attributeHelper());
});
@@ -420,40 +350,12 @@ limitations under the License.
element.EventType.ADMIN_MENU_LINKS);
});
- test('Gerrit._isPluginPreloaded', () => {
- Gerrit._preloadedPlugins = {baz: ()=>{}};
- assert.isFalse(Gerrit._isPluginPreloaded('plugins/foo/bar'));
- assert.isFalse(Gerrit._isPluginPreloaded('http://a.com/42'));
- assert.isTrue(Gerrit._isPluginPreloaded('preloaded:baz'));
- Gerrit._preloadedPlugins = null;
- });
-
- test('preloaded plugins are installed', () => {
- const installStub = sandbox.stub();
- Gerrit._preloadedPlugins = {foo: installStub};
- Gerrit._installPreloadedPlugins();
- assert.isTrue(installStub.called);
- const pluginApi = installStub.lastCall.args[0];
- assert.strictEqual(pluginApi.getPluginName(), 'foo');
- });
-
- test('installing preloaded plugin', () => {
- let plugin;
- window.ASSETS_PATH = 'http://blips.com/chitz';
- Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
- assert.strictEqual(plugin.getPluginName(), 'foo');
- assert.strictEqual(plugin.url('/some/thing.html'),
- 'http://blips.com/chitz/plugins/foo/some/thing.html');
- delete window.ASSETS_PATH;
- });
-
suite('test plugin with base url', () => {
let baseUrlPlugin;
setup(() => {
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { baseUrlPlugin = p; }, '0.1',
'http://test.com/r/plugins/baseurlplugin/static/test.js');
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index ca87956488..6da117f40e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-action-context</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-js-api-interface.html"/>
@@ -40,7 +42,6 @@ limitations under the License.
setup(() => {
sandbox = sinon.sandbox.create();
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginActionContext(plugin);
@@ -140,12 +141,12 @@ limitations under the License.
send: sendStub,
});
const errorStub = sandbox.stub();
- document.addEventListener('network-error', errorStub);
+ document.addEventListener('show-alert', errorStub);
instance.call();
flush(() => {
assert.isTrue(errorStub.calledOnce);
assert.equal(errorStub.args[0][0].detail.message,
- 'Plugin network error: boom');
+ 'Plugin network error: Error: boom');
done();
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
index b00b5acbcc..8ed7f14bcf 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-endpoints</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-js-api-interface.html"/>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
new file mode 100644
index 0000000000..201b68302b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
@@ -0,0 +1,397 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function(window) {
+ 'use strict';
+
+ // Import utils methods
+ const {
+ PLUGIN_LOADING_TIMEOUT_MS,
+ PRELOADED_PROTOCOL,
+ getPluginNameFromUrl,
+ getBaseUrl,
+ } = window._apiUtils;
+
+ /**
+ * @enum {string}
+ */
+ const PluginState = {
+ /**
+ * State that indicates the plugin is pending to be loaded.
+ */
+ PENDING: 'PENDING',
+
+ /**
+ * State that indicates the plugin is already loaded.
+ */
+ LOADED: 'LOADED',
+
+ /**
+ * State that indicates the plugin is already loaded.
+ */
+ PRE_LOADED: 'PRE_LOADED',
+
+ /**
+ * State that indicates the plugin failed to load.
+ */
+ LOAD_FAILED: 'LOAD_FAILED',
+ };
+
+ // Prefix for any unrecognized plugin urls.
+ // Url should match following patterns:
+ // /plugins/PLUGINNAME/static/SCRIPTNAME.(html|js)
+ // /plugins/PLUGINNAME.(js|html)
+ const UNKNOWN_PLUGIN_PREFIX = '__$$__';
+
+ // Current API version for Plugin,
+ // plugins with incompatible version will not be laoded.
+ const API_VERSION = '0.1';
+
+ /**
+ * PluginLoader, responsible for:
+ *
+ * Loading all plugins and handling errors etc.
+ * Recording plugin state.
+ * Reporting on plugin loading status.
+ * Retrieve plugin.
+ * Check plugin status and if all plugins loaded.
+ */
+ class PluginLoader {
+ constructor() {
+ this._pluginListLoaded = false;
+
+ /** @type {Map<string,PluginLoader.PluginObject>} */
+ this._plugins = new Map();
+
+ this._reporting = null;
+
+ // Promise that resolves when all plugins loaded
+ this._loadingPromise = null;
+
+ // Resolver to resolve _loadingPromise once all plugins loaded
+ this._loadingResolver = null;
+ }
+
+ _getReporting() {
+ if (!this._reporting) {
+ this._reporting = document.createElement('gr-reporting');
+ }
+ return this._reporting;
+ }
+
+ /**
+ * Use the plugin name or use the full url if not recognized.
+ *
+ * @see gr-api-utils#getPluginNameFromUrl
+ * @param {string|URL} url
+ */
+ _getPluginKeyFromUrl(url) {
+ return getPluginNameFromUrl(url) ||
+ `${UNKNOWN_PLUGIN_PREFIX}${url}`;
+ }
+
+ /**
+ * Load multiple plugins with certain options.
+ *
+ * @param {Array<string>} plugins
+ * @param {Object<string, PluginLoader.PluginOption>} opts
+ */
+ loadPlugins(plugins = [], opts = {}) {
+ this._pluginListLoaded = true;
+
+ plugins.forEach(path => {
+ const url = this._urlFor(path);
+ // Skip if preloaded, for bundling.
+ if (this.isPluginPreloaded(url)) return;
+
+ const pluginKey = this._getPluginKeyFromUrl(url);
+ // Skip if already installed.
+ if (this._plugins.has(pluginKey)) return;
+ this._plugins.set(pluginKey, {
+ name: pluginKey,
+ url,
+ state: PluginState.PENDING,
+ plugin: null,
+ });
+
+ if (this._isPathEndsWith(url, '.html')) {
+ this._importHtmlPlugin(url, opts && opts[path]);
+ } else if (this._isPathEndsWith(url, '.js')) {
+ this._loadJsPlugin(url);
+ } else {
+ this._failToLoad(`Unrecognized plugin url ${url}`, url);
+ }
+ });
+
+ this.awaitPluginsLoaded().then(() => {
+ console.info('Plugins loaded');
+ this._getReporting().pluginsLoaded(this._getAllInstalledPluginNames());
+ });
+ }
+
+ _isPathEndsWith(url, suffix) {
+ if (!(url instanceof URL)) {
+ try {
+ url = new URL(url);
+ } catch (e) {
+ console.warn(e);
+ return false;
+ }
+ }
+
+ return url.pathname && url.pathname.endsWith(suffix);
+ }
+
+ _getAllInstalledPluginNames() {
+ const installedPlugins = [];
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.LOADED) {
+ installedPlugins.push(plugin.name);
+ }
+ }
+ return installedPlugins;
+ }
+
+ install(callback, opt_version, opt_src) {
+ // HTML import polyfill adds __importElement pointing to the import tag.
+ const script = document.currentScript &&
+ (document.currentScript.__importElement || document.currentScript);
+ let src = opt_src || (script && script.src);
+ if (!src || src.startsWith('data:')) {
+ src = script && script.baseURI;
+ }
+
+ if (opt_version && opt_version !== API_VERSION) {
+ this._failToLoad(`Plugin ${src} install error: only version ` +
+ API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
+ ' was given.', src);
+ return;
+ }
+
+ const pluginObject = this.getPlugin(src);
+ let plugin = pluginObject && pluginObject.plugin;
+ if (!plugin) {
+ plugin = new Plugin(src);
+ }
+ try {
+ callback(plugin);
+ this._pluginInstalled(src, plugin);
+ } catch (e) {
+ this._failToLoad(`${e.name}: ${e.message}`, src);
+ }
+ }
+
+ get arePluginsLoaded() {
+ // As the size of plugins is relatively small,
+ // so the performance of this check should be reasonable
+ if (!this._pluginListLoaded) return false;
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.PENDING) return false;
+ }
+ return true;
+ }
+
+ _checkIfCompleted() {
+ if (this.arePluginsLoaded && this._loadingResolver) {
+ this._loadingResolver();
+ this._loadingResolver = null;
+ this._loadingPromise = null;
+ }
+ }
+
+ _timeout() {
+ const pendingPlugins = [];
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.PENDING) {
+ this._updatePluginState(plugin.url, PluginState.LOAD_FAILED);
+ this._checkIfCompleted();
+ pendingPlugins.push(plugin.url);
+ }
+ }
+ return `Timeout when loading plugins: ${pendingPlugins.join(',')}`;
+ }
+
+ _failToLoad(message, pluginUrl) {
+ // Show an alert with the error
+ document.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {
+ message: `Plugin install error: ${message} from ${pluginUrl}`,
+ },
+ }));
+ this._updatePluginState(pluginUrl, PluginState.LOAD_FAILED);
+ this._checkIfCompleted();
+ }
+
+ _updatePluginState(pluginUrl, state) {
+ const key = this._getPluginKeyFromUrl(pluginUrl);
+ if (this._plugins.has(key)) {
+ this._plugins.get(key).state = state;
+ } else {
+ // Plugin is not recorded for some reason.
+ console.warn(`Plugin loaded separately: ${pluginUrl}`);
+ this._plugins.set(key, {
+ name: key,
+ url: pluginUrl,
+ state,
+ plugin: null,
+ });
+ }
+ return this._plugins.get(key);
+ }
+
+ _pluginInstalled(url, plugin) {
+ const pluginObj = this._updatePluginState(url, PluginState.LOADED);
+ pluginObj.plugin = plugin;
+ this._getReporting().pluginLoaded(plugin.getPluginName() || url);
+ console.log(`Plugin ${plugin.getPluginName() || url} installed.`);
+ this._checkIfCompleted();
+ }
+
+ installPreloadedPlugins() {
+ if (!window.Gerrit || !window.Gerrit._preloadedPlugins) { return; }
+ const Gerrit = window.Gerrit;
+ for (const name in Gerrit._preloadedPlugins) {
+ if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
+ const callback = Gerrit._preloadedPlugins[name];
+ this.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
+ }
+ }
+
+ isPluginPreloaded(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ const name = getPluginNameFromUrl(url);
+ if (name && window.Gerrit._preloadedPlugins) {
+ return window.Gerrit._preloadedPlugins.hasOwnProperty(name);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if given plugin path/url is enabled or not.
+ *
+ * @param {string} pathOrUrl
+ */
+ isPluginEnabled(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ if (this.isPluginPreloaded(url)) return true;
+ const key = this._getPluginKeyFromUrl(url);
+ return this._plugins.has(key);
+ }
+
+ /**
+ * Returns the plugin object with a given url.
+ *
+ * @param {string} pathOrUrl
+ */
+ getPlugin(pathOrUrl) {
+ const key = this._getPluginKeyFromUrl(this._urlFor(pathOrUrl));
+ return this._plugins.get(key);
+ }
+
+ /**
+ * Checks if given plugin path/url is loaded or not.
+ *
+ * @param {string} pathOrUrl
+ */
+ isPluginLoaded(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ const key = this._getPluginKeyFromUrl(url);
+ return this._plugins.has(key) ?
+ this._plugins.get(key).state === PluginState.LOADED :
+ false;
+ }
+
+ _importHtmlPlugin(pluginUrl, opts = {}) {
+ // onload (second param) needs to be a function. When null or undefined
+ // were passed, plugins were not loaded correctly.
+ (Polymer.importHref || Polymer.Base.importHref)(
+ this._urlFor(pluginUrl), () => {},
+ () => this._failToLoad(`${pluginUrl} import error`, pluginUrl),
+ !opts.sync);
+ }
+
+ _loadJsPlugin(pluginUrl) {
+ this._createScriptTag(this._urlFor(pluginUrl));
+ }
+
+ _createScriptTag(url) {
+ const el = document.createElement('script');
+ el.defer = true;
+ el.src = url;
+ el.onerror = () => this._failToLoad(`${url} load error`, url);
+ return document.body.appendChild(el);
+ }
+
+ _urlFor(pathOrUrl) {
+ if (!pathOrUrl) {
+ return pathOrUrl;
+ }
+ if (pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
+ pathOrUrl.startsWith('http')) {
+ // Plugins are loaded from another domain or preloaded.
+ return pathOrUrl;
+ }
+ if (!pathOrUrl.startsWith('/')) {
+ pathOrUrl = '/' + pathOrUrl;
+ }
+ return window.location.origin + getBaseUrl() + pathOrUrl;
+ }
+
+ awaitPluginsLoaded() {
+ // Resolve if completed.
+ this._checkIfCompleted();
+
+ if (this.arePluginsLoaded) {
+ return Promise.resolve();
+ }
+ if (!this._loadingPromise) {
+ let timerId;
+ this._loadingPromise =
+ Promise.race([
+ new Promise(resolve => this._loadingResolver = resolve),
+ new Promise((_, reject) => timerId = setTimeout(
+ () => {
+ reject(this._timeout());
+ }, PLUGIN_LOADING_TIMEOUT_MS)),
+ ]).then(() => {
+ if (timerId) clearTimeout(timerId);
+ });
+ }
+ return this._loadingPromise;
+ }
+ }
+
+ /**
+ * @typedef {{
+ * name:string,
+ * url:string,
+ * state:PluginState,
+ * plugin:Object
+ * }}
+ */
+ PluginLoader.PluginObject;
+
+ /**
+ * @typedef {{
+ * sync:boolean,
+ * }}
+ */
+ PluginLoader.PluginOption;
+
+ window.PluginLoader = PluginLoader;
+})(window); \ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
new file mode 100644
index 0000000000..ee54319bf0
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
@@ -0,0 +1,502 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-plugin-host</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-js-api-interface.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-js-api-interface></gr-js-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ const {PRELOADED_PROTOCOL, PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
+ suite('gr-plugin-loader tests', () => {
+ let plugin;
+ let sandbox;
+ let url;
+ let sendStub;
+
+ setup(() => {
+ this.clock = sinon.useFakeTimers();
+ sandbox = sinon.sandbox.create();
+ sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
+ stub('gr-rest-api-interface', {
+ getAccount() {
+ return Promise.resolve({name: 'Judy Hopps'});
+ },
+ send(...args) {
+ return sendStub(...args);
+ },
+ });
+ sandbox.stub(document.body, 'appendChild');
+ fixture('basic');
+ url = window.location.origin;
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ this.clock.restore();
+ Gerrit._testOnly_resetPlugins();
+ });
+
+ test('reuse plugin for install calls', () => {
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+
+ let otherPlugin;
+ Gerrit.install(p => { otherPlugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ assert.strictEqual(plugin, otherPlugin);
+ });
+
+ test('flushes preinstalls if provided', () => {
+ assert.doesNotThrow(() => {
+ Gerrit._testOnly_flushPreinstalls();
+ });
+ window.Gerrit.flushPreinstalls = sandbox.stub();
+ Gerrit._testOnly_flushPreinstalls();
+ assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
+ delete window.Gerrit.flushPreinstalls;
+ });
+
+ test('versioning', () => {
+ const callback = sandbox.spy();
+ Gerrit.install(callback, '0.0pre-alpha');
+ assert(callback.notCalled);
+ });
+
+ test('report pluginsLoaded', done => {
+ stub('gr-reporting', {
+ pluginsLoaded() {
+ done();
+ },
+ });
+ Gerrit._loadPlugins([]);
+ });
+
+ test('arePluginsLoaded', done => {
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ Gerrit._loadPlugins(plugins);
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ // Timeout on loading plugins
+ this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
+
+ flush(() => {
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ test('plugins installed successfully', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ test('isPluginEnabled and isPluginLoaded', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ 'bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
+ );
+
+ flush(() => {
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginLoaded(plugin))
+ );
+
+ done();
+ });
+ });
+
+ test('plugins installed mixed result, 1 fail 1 succeed', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ if (url === plugins[0]) {
+ throw new Error('failed');
+ }
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ done();
+ });
+ });
+
+ test('isPluginEnabled and isPluginLoaded for mixed results', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ if (url === plugins[0]) {
+ throw new Error('failed');
+ }
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
+ );
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ assert.isTrue(Gerrit._pluginLoader.isPluginLoaded(plugins[1]));
+ assert.isFalse(Gerrit._pluginLoader.isPluginLoaded(plugins[0]));
+ done();
+ });
+ });
+
+ test('plugins installed all failed', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ throw new Error('failed');
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly([]));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledTwice);
+ done();
+ });
+ });
+
+ test('plugins installed failed becasue of wrong version', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ }, url === plugins[0] ? '' : 'alpha', url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ done();
+ });
+ });
+
+ test('multiple assets for same plugin installed successfully', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/foo/static/test2.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ suite('plugin path and url', () => {
+ let importHtmlPluginStub;
+ let loadJsPluginStub;
+ setup(() => {
+ importHtmlPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
+ importHtmlPluginStub(url);
+ });
+ loadJsPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ loadJsPluginStub(url);
+ });
+ });
+
+ test('invalid plugin path', () => {
+ const failToLoadStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_failToLoad', (...args) => {
+ failToLoadStub(...args);
+ });
+
+ Gerrit._loadPlugins([
+ 'foo/bar',
+ ]);
+
+ assert.isTrue(failToLoadStub.calledOnce);
+ assert.isTrue(failToLoadStub.calledWithExactly(
+ `Unrecognized plugin url ${url}/foo/bar`,
+ `${url}/foo/bar`
+ ));
+ });
+
+ test('relative path for plugins', () => {
+ Gerrit._loadPlugins([
+ 'foo/bar.js',
+ 'foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(`${url}/foo/bar.html`)
+ );
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`${url}/foo/bar.js`)
+ );
+ });
+
+
+ test('relative path should honor getBaseUrl', () => {
+ const testUrl = '/test';
+ sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl', () => {
+ return testUrl;
+ });
+
+ Gerrit._loadPlugins([
+ 'foo/bar.js',
+ 'foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(
+ `${url}${testUrl}/foo/bar.html`
+ )
+ );
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`${url}${testUrl}/foo/bar.js`)
+ );
+ });
+
+ test('absolute path for plugins', () => {
+ Gerrit._loadPlugins([
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`)
+ );
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`http://e.com/foo/bar.js`)
+ );
+ });
+ });
+
+ test('adds js plugins will call the body', () => {
+ Gerrit._loadPlugins([
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/bar/foo.js',
+ ]);
+ assert.isTrue(document.body.appendChild.calledTwice);
+ });
+
+ test('can call awaitPluginsLoaded multiple times', done => {
+ const plugins = [
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/bar/foo.js',
+ ];
+
+ let installed = false;
+ function pluginCallback(url) {
+ if (url === plugins[1]) {
+ installed = true;
+ }
+ }
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => pluginCallback(url), undefined, url);
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ Gerrit.awaitPluginsLoaded().then(() => {
+ assert.isTrue(installed);
+
+ Gerrit.awaitPluginsLoaded().then(() => {
+ done();
+ });
+ });
+ });
+
+ suite('preloaded plugins', () => {
+ test('skips preloaded plugins when load plugins', () => {
+ const importHtmlPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
+ importHtmlPluginStub(url);
+ });
+ const loadJsPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ loadJsPluginStub(url);
+ });
+
+ Gerrit._preloadedPlugins = {
+ foo: () => void 0,
+ bar: () => void 0,
+ };
+
+ Gerrit._loadPlugins([
+ 'http://e.com/plugins/foo.js',
+ 'plugins/bar.html',
+ 'http://e.com/plugins/test/foo.js',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.notCalled);
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ });
+
+ test('isPluginPreloaded', () => {
+ Gerrit._preloadedPlugins = {baz: ()=>{}};
+ assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('plugins/foo/bar'));
+ assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('http://a.com/42'));
+ assert.isTrue(
+ Gerrit._pluginLoader.isPluginPreloaded(PRELOADED_PROTOCOL + 'baz')
+ );
+ Gerrit._preloadedPlugins = null;
+ });
+
+ test('preloaded plugins are installed', () => {
+ const installStub = sandbox.stub();
+ Gerrit._preloadedPlugins = {foo: installStub};
+ Gerrit._pluginLoader.installPreloadedPlugins();
+ assert.isTrue(installStub.called);
+ const pluginApi = installStub.lastCall.args[0];
+ assert.strictEqual(pluginApi.getPluginName(), 'foo');
+ });
+
+ test('installing preloaded plugin', () => {
+ let plugin;
+ window.ASSETS_PATH = 'http://blips.com/chitz';
+ Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
+ assert.strictEqual(plugin.getPluginName(), 'foo');
+ assert.strictEqual(plugin.url('/some/thing.html'),
+ 'http://blips.com/chitz/plugins/foo/some/thing.html');
+ delete window.ASSETS_PATH;
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
index ce619a0190..ecfafb5d3d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
@@ -46,6 +46,19 @@
getRestApi().invalidateReposCache();
};
+ GrPluginRestApi.prototype.getAccount = function() {
+ return getRestApi().getAccount();
+ };
+
+ GrPluginRestApi.prototype.getAccountCapabilities = function(capabilities) {
+ return getRestApi().getAccountCapabilities(capabilities);
+ };
+
+ GrPluginRestApi.prototype.getRepos =
+ function(filter, reposPerPage, opt_offset) {
+ return getRestApi().getRepos(filter, reposPerPage, opt_offset);
+ };
+
/**
* Fetch and return native browser REST API Response.
*
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
index 7a59337771..bcbd961c00 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-rest-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-js-api-interface.html"/>
@@ -48,7 +50,6 @@ limitations under the License.
a[k] = (...args) => restApiStub[k](...args);
return a;
}, {}));
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginRestApi();
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 7376a1222f..b261a90a01 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -17,71 +17,18 @@
(function(window) {
'use strict';
- /**
- * Hash of loaded and installed plugins, name to Plugin object.
- */
- const _plugins = {};
-
- /**
- * Array of plugin URLs to be loaded, name to url.
- */
- let _pluginsPending = {};
-
- let _pluginsInstalled = [];
-
- let _pluginsPendingCount = -1;
-
const PRELOADED_PROTOCOL = 'preloaded:';
- const UNKNOWN_PLUGIN = 'unknown';
-
const PANEL_ENDPOINTS_MAPPING = {
CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
};
- const PLUGIN_LOADING_TIMEOUT_MS = 10000;
-
- let _restAPI;
-
- const getRestAPI = () => {
- if (!_restAPI) {
- _restAPI = document.createElement('gr-rest-api-interface');
- }
- return _restAPI;
- };
-
- let _reporting;
- const getReporting = () => {
- if (!_reporting) {
- _reporting = document.createElement('gr-reporting');
- }
- return _reporting;
- };
-
- // TODO (viktard): deprecate in favor of GrPluginRestApi.
- function send(method, url, opt_callback, opt_payload) {
- return getRestAPI().send(method, url, opt_payload).then(response => {
- if (response.status < 200 || response.status >= 300) {
- return response.text().then(text => {
- if (text) {
- return Promise.reject(text);
- } else {
- return Promise.reject(response.status);
- }
- });
- } else {
- return getRestAPI().getResponseObject(response);
- }
- }).then(response => {
- if (opt_callback) {
- opt_callback(response);
- }
- return response;
- });
- }
-
- const API_VERSION = '0.1';
+ // Import utils methods
+ const {
+ getPluginNameFromUrl,
+ send,
+ } = window._apiUtils;
/**
* Plugin-provided custom components can affect content in extension
@@ -99,50 +46,6 @@
STYLE: 'style',
};
- function flushPreinstalls() {
- if (window.Gerrit.flushPreinstalls) {
- window.Gerrit.flushPreinstalls();
- }
- }
-
- function installPreloadedPlugins() {
- if (!Gerrit._preloadedPlugins) { return; }
- for (const name in Gerrit._preloadedPlugins) {
- if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
- const callback = Gerrit._preloadedPlugins[name];
- Gerrit.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
- }
- }
-
- function getPluginNameFromUrl(url) {
- if (!(url instanceof URL)) {
- try {
- url = new URL(url);
- } catch (e) {
- console.warn(e);
- return null;
- }
- }
- if (url.protocol === PRELOADED_PROTOCOL) {
- return url.pathname;
- }
- const base = Gerrit.BaseUrlBehavior.getBaseUrl();
- const pathname = url.pathname.replace(base, '');
- // Site theme is server from predefined path.
- if (pathname === '/static/gerrit-theme.html') {
- return 'gerrit-theme';
- } else if (!pathname.startsWith('/plugins')) {
- console.warn('Plugin not being loaded from /plugins base path:',
- url.href, '— Unable to determine name.');
- return;
- }
- // Pathname should normally look like this:
- // /plugins/PLUGINNAME/static/SCRIPTNAME.html
- // Or, for app/samples:
- // /plugins/PLUGINNAME.html
- return pathname.split('/')[2].split('.')[0];
- }
-
function Plugin(opt_url) {
this._domHooks = new GrDomHooksManager(this);
@@ -315,6 +218,10 @@
return new GrSettingsApi(this);
};
+ Plugin.prototype.styles = function() {
+ return new GrStylesApi();
+ };
+
/**
* To make REST requests for plugin-provided endpoints, use
*
@@ -361,10 +268,14 @@
return;
}
return this.registerCustomComponent(
- Gerrit._getPluginScreenName(this.getPluginName(), screenName),
+ this._getScreenName(screenName),
opt_moduleName);
};
+ Plugin.prototype._getScreenName = function(screenName) {
+ return `${this.getPluginName()}-screen-${screenName}`;
+ };
+
const deprecatedAPI = {
_loadedGwt: ()=> {},
@@ -415,7 +326,7 @@
'Please use strings for patterns.');
return;
}
- this.hook(Gerrit._getPluginScreenName(this.getPluginName(), pattern))
+ this.hook(this._getScreenName(pattern))
.onAttached(el => {
el.style.display = 'none';
callback({
@@ -472,243 +383,5 @@
},
};
- flushPreinstalls();
-
- const Gerrit = window.Gerrit || {};
-
- let _resolveAllPluginsLoaded = null;
- let _allPluginsPromise = null;
-
- Gerrit._endpoints = new GrPluginEndpoints();
-
- // Provide reset plugins function to clear installed plugins between tests.
- const app = document.querySelector('#app');
- if (!app) {
- // No gr-app found (running tests)
- Gerrit._installPreloadedPlugins = installPreloadedPlugins;
- Gerrit._flushPreinstalls = flushPreinstalls;
- Gerrit._resetPlugins = () => {
- _allPluginsPromise = null;
- _pluginsInstalled = [];
- _pluginsPending = {};
- _pluginsPendingCount = -1;
- _reporting = null;
- _resolveAllPluginsLoaded = null;
- _restAPI = null;
- Gerrit._endpoints = new GrPluginEndpoints();
- for (const k of Object.keys(_plugins)) {
- delete _plugins[k];
- }
- };
- }
-
- Gerrit.getPluginName = function() {
- console.warn('Gerrit.getPluginName is not supported in PolyGerrit.',
- 'Please use plugin.getPluginName() instead.');
- };
-
- Gerrit.css = function(rulesStr) {
- if (!Gerrit._customStyleSheet) {
- const styleEl = document.createElement('style');
- document.head.appendChild(styleEl);
- Gerrit._customStyleSheet = styleEl.sheet;
- }
-
- const name = '__pg_js_api_class_' +
- Gerrit._customStyleSheet.cssRules.length;
- Gerrit._customStyleSheet.insertRule('.' + name + '{' + rulesStr + '}', 0);
- return name;
- };
-
- Gerrit.install = function(callback, opt_version, opt_src) {
- // HTML import polyfill adds __importElement pointing to the import tag.
- const script = document.currentScript &&
- (document.currentScript.__importElement || document.currentScript);
- const src = opt_src || (script && (script.src || script.baseURI));
- const name = getPluginNameFromUrl(src);
-
- if (opt_version && opt_version !== API_VERSION) {
- Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` +
- API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
- ' was given.');
- return;
- }
-
- const existingPlugin = _plugins[name];
- const plugin = existingPlugin || new Plugin(src);
- try {
- callback(plugin);
- if (name) {
- _plugins[name] = plugin;
- }
- if (!existingPlugin) {
- Gerrit._pluginInstalled(src);
- }
- } catch (e) {
- Gerrit._pluginInstallError(`${e.name}: ${e.message}`);
- }
- };
-
- Gerrit.getLoggedIn = function() {
- console.warn('Gerrit.getLoggedIn() is deprecated! ' +
- 'Use plugin.restApi().getLoggedIn()');
- return document.createElement('gr-rest-api-interface').getLoggedIn();
- };
-
- Gerrit.get = function(url, callback) {
- console.warn('.get() is deprecated! Use plugin.restApi().get()');
- send('GET', url, callback);
- };
-
- Gerrit.post = function(url, payload, callback) {
- console.warn('.post() is deprecated! Use plugin.restApi().post()');
- send('POST', url, callback, payload);
- };
-
- Gerrit.put = function(url, payload, callback) {
- console.warn('.put() is deprecated! Use plugin.restApi().put()');
- send('PUT', url, callback, payload);
- };
-
- Gerrit.delete = function(url, opt_callback) {
- console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
- return getRestAPI().send('DELETE', url).then(response => {
- if (response.status !== 204) {
- return response.text().then(text => {
- if (text) {
- return Promise.reject(text);
- } else {
- return Promise.reject(response.status);
- }
- });
- }
- if (opt_callback) {
- opt_callback(response);
- }
- return response;
- });
- };
-
- Gerrit.awaitPluginsLoaded = function() {
- if (!_allPluginsPromise) {
- if (Gerrit._arePluginsLoaded()) {
- _allPluginsPromise = Promise.resolve();
- } else {
- let timeoutId;
- _allPluginsPromise =
- Promise.race([
- new Promise(resolve => _resolveAllPluginsLoaded = resolve),
- new Promise(resolve => timeoutId = setTimeout(
- Gerrit._pluginLoadingTimeout, PLUGIN_LOADING_TIMEOUT_MS)),
- ]).then(() => clearTimeout(timeoutId));
- }
- }
- return _allPluginsPromise;
- };
-
- Gerrit._pluginLoadingTimeout = function() {
- console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
- Gerrit._setPluginsPending([]);
- };
-
- Gerrit._setPluginsPending = function(plugins) {
- _pluginsPending = plugins.reduce((o, url) => {
- // TODO(viktard): Remove guard (@see Issue 8962)
- o[getPluginNameFromUrl(url) || UNKNOWN_PLUGIN] = url;
- return o;
- }, {});
- Gerrit._setPluginsCount(Object.keys(_pluginsPending).length);
- };
-
- Gerrit._setPluginsCount = function(count) {
- _pluginsPendingCount = count;
- if (Gerrit._arePluginsLoaded()) {
- getReporting().pluginsLoaded(_pluginsInstalled);
- if (_resolveAllPluginsLoaded) {
- _resolveAllPluginsLoaded();
- }
- }
- };
-
- Gerrit._pluginInstallError = function(message) {
- document.dispatchEvent(new CustomEvent('show-alert', {
- detail: {
- message: `Plugin install error: ${message}`,
- },
- }));
- console.info(`Plugin install error: ${message}`);
- Gerrit._setPluginsCount(_pluginsPendingCount - 1);
- };
-
- Gerrit._pluginInstalled = function(url) {
- const name = getPluginNameFromUrl(url) || UNKNOWN_PLUGIN;
- if (!_pluginsPending[name]) {
- console.warn(`Unexpected plugin ${name} installed from ${url}.`);
- } else {
- delete _pluginsPending[name];
- _pluginsInstalled.push(name);
- Gerrit._setPluginsCount(_pluginsPendingCount - 1);
- console.log(`Plugin ${name} installed.`);
- }
- };
-
- Gerrit._arePluginsLoaded = function() {
- return _pluginsPendingCount === 0;
- };
-
- Gerrit._getPluginScreenName = function(pluginName, screenName) {
- return `${pluginName}-screen-${screenName}`;
- };
-
- Gerrit._isPluginPreloaded = function(url) {
- const name = getPluginNameFromUrl(url);
- if (name && Gerrit._preloadedPlugins) {
- return name in Gerrit._preloadedPlugins;
- } else {
- return false;
- }
- };
-
- // TODO(taoalpha): List all internal supported event names.
- // Also convert this to inherited class once we move Gerrit to class.
- Gerrit._eventEmitter = new EventEmitter();
- ['addListener',
- 'dispatch',
- 'emit',
- 'off',
- 'on',
- 'once',
- 'removeAllListeners',
- 'removeListener',
- ].forEach(method => {
- /**
- * Enabling EventEmitter interface on Gerrit.
- *
- * This will enable to signal across different parts of js code without relying on DOM,
- * including core to core, plugin to plugin and also core to plugin.
- *
- * @example
- *
- * // Emit this event from pluginA
- * Gerrit.install(pluginA => {
- * fetch("some-api").then(() => {
- * Gerrit.on("your-special-event", {plugin: pluginA});
- * });
- * });
- *
- * // Listen on your-special-event from pluignB
- * Gerrit.install(pluginB => {
- * Gerrit.on("your-special-event", ({plugin}) => {
- * // do something, plugin is pluginA
- * });
- * });
- */
- Gerrit[method] = Gerrit._eventEmitter[method].bind(Gerrit._eventEmitter);
- });
-
- window.Gerrit = Gerrit;
-
- // Preloaded plugins should be installed after Gerrit.install() is set,
- // since plugin preloader substitutes Gerrit.install() temporarily.
- installPreloadedPlugins();
+ window.Plugin = Plugin;
})(window);
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
index ca5c49f035..63a528e76d 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.html
@@ -15,11 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-voting-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../gr-account-label/gr-account-label.html">
+<link rel="import" href="../gr-account-chip/gr-account-chip.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../gr-icons/gr-icons.html">
<link rel="import" href="../gr-label/gr-label.html">
@@ -31,7 +32,7 @@ limitations under the License.
<style include="shared-styles">
.placeholder {
color: var(--deemphasized-text-color);
- padding-top: .2em;
+ padding-top: var(--spacing-xs);
}
.hidden {
display: none;
@@ -39,9 +40,10 @@ limitations under the License.
.voteChip {
display: flex;
justify-content: center;
- margin-right: .3em;
- padding: .05em .85em;
+ margin-right: var(--spacing-s);
+ padding: 0;
@apply --vote-chip-styles;
+ border-width: 0;
}
.max {
background-color: var(--vote-color-approved);
@@ -59,30 +61,31 @@ limitations under the License.
display: none;
}
td {
- vertical-align: middle;
+ vertical-align: top;
}
tr {
- min-height: 2.25em;
+ min-height: var(--line-height-normal);
}
gr-button {
+ vertical-align: top;
--gr-button: {
- height: 2em;
+ height: var(--line-height-normal);
+ width: var(--line-height-normal);
padding: 0;
- width: 2em;
}
}
gr-button[disabled] iron-icon {
color: var(--border-color);
}
gr-account-chip {
- margin-right: .25em;
+ margin-right: var(--spacing-xs);
}
iron-icon {
- height: 1.2em;
- width: 1.2em;
+ height: calc(var(--line-height-normal) - 2px);
+ width: calc(var(--line-height-normal) - 2px);
}
.labelValueContainer:not(:first-of-type) td {
- padding-top: .3em;
+ padding-top: var(--spacing-s);
}
</style>
<table>
@@ -111,7 +114,7 @@ limitations under the License.
<gr-button
link
aria-label="Remove"
- on-tap="_onDeleteVote"
+ on-click="_onDeleteVote"
tooltip="Remove vote"
data-account-id$="[[mappedLabel.account._account_id]]"
class$="deleteBtn [[_computeDeleteClass(mappedLabel.account, mutable, change)]]">
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
index 387fe96441..ed2dfdd23d 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-label-info',
- _legacyUndefinedCheck: true,
properties: {
labelInfo: Object,
@@ -38,7 +37,7 @@
*/
_mapLabelInfo(labelInfo, account, changeLabelsRecord) {
const result = [];
- if (!labelInfo) { return result; }
+ if (!labelInfo || !account) { return result; }
if (!labelInfo.values) {
if (labelInfo.rejected || labelInfo.approved) {
const ok = labelInfo.approved || !labelInfo.rejected;
@@ -149,10 +148,12 @@
* order to trigger computation when a label is removed from the change.
*/
_computeShowPlaceholder(labelInfo, changeLabelsRecord) {
- if (!labelInfo.values && (labelInfo.rejected || labelInfo.approved)) {
+ if (labelInfo &&
+ !labelInfo.values && (labelInfo.rejected || labelInfo.approved)) {
return 'hidden';
}
- if (labelInfo.all) {
+
+ if (labelInfo && labelInfo.all) {
for (const label of labelInfo.all) {
if (label.value && label.value != labelInfo.default_value) {
return 'hidden';
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
index e6e72d26c7..658a973380 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
@@ -17,9 +17,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-label-info</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-label-info.html">
@@ -98,7 +100,6 @@ limitations under the License.
assert.isTrue(button.disabled);
return deleteResponse.then(() => {
assert.isFalse(button.disabled);
- assert.notOk(element.change.labels.test.recommended);
assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.html b/polygerrit-ui/app/elements/shared/gr-label/gr-label.html
index fe290b759c..55ecc98848 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<dom-module id="gr-label">
<template strip-whitespace>
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.js b/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
index c437885465..0de0881130 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-label',
- _legacyUndefinedCheck: true,
behaviors: [
Gerrit.TooltipBehavior,
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
index c001ce7be1..da0b93f763 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -28,7 +28,7 @@ limitations under the License.
#container {
background: var(--chip-background-color);
border-radius: 1em;
- padding: .5em;
+ padding: var(--spacing-m);
}
#header {
color: var(--deemphasized-text-color);
@@ -48,7 +48,7 @@ limitations under the License.
border-left: 1px solid var(--deemphasized-text-color);
color: var(--deemphasized-text-color);
cursor: pointer;
- padding-left: .4em;
+ padding-left: var(--spacing-s);
}
#trigger:hover {
color: var(--primary-text-color);
@@ -64,7 +64,7 @@ limitations under the License.
disabled="[[disabled]]"
placeholder="[[placeholder]]"
borderless></gr-autocomplete>
- <div id="trigger" on-tap="_handleTriggerTap">▼</div>
+ <div id="trigger" on-click="_handleTriggerClick">▼</div>
</div>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
index a892522481..fd0f2285d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-labeled-autocomplete',
- _legacyUndefinedCheck: true,
/**
* Fired when a value is chosen.
@@ -59,7 +58,7 @@
},
},
- _handleTriggerTap(e) {
+ _handleTriggerClick(e) {
// Stop propagation here so we don't confuse gr-autocomplete, which
// listens for taps on body to try to determine when it's blurred.
e.stopPropagation();
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
index 6bcaa1873d..b257746d14 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-labeled-autocomplete</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-labeled-autocomplete.html">
@@ -47,7 +49,7 @@ limitations under the License.
const e = {stopPropagation: () => undefined};
sandbox.stub(e, 'stopPropagation');
sandbox.stub(element.$.autocomplete, 'focus');
- element._handleTriggerTap(e);
+ element._handleTriggerClick(e);
assert.isTrue(e.stopPropagation.calledOnce);
assert.isTrue(element.$.autocomplete.focus.calledOnce);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.html b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.html
index 4137485315..fb55c67354 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.html
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-lib-loader">
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
index ba86b66cef..5ea5dca99a 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-lib-loader',
- _legacyUndefinedCheck: true,
properties: {
_hljsState: {
@@ -68,14 +67,16 @@
* custom-style DOM element.
*
* @return {!Promise<Element>}
+ * @suppress {checkTypes}
*/
getDarkTheme() {
return new Promise((resolve, reject) => {
- this.importHref(this._getLibRoot() + DARK_THEME_PATH, () => {
- const module = document.createElement('style', 'custom-style');
- module.setAttribute('include', 'dark-theme');
- resolve(module);
- });
+ (this.importHref || Polymer.importHref)(
+ this._getLibRoot() + DARK_THEME_PATH, () => {
+ const module = document.createElement('style', 'custom-style');
+ module.setAttribute('include', 'dark-theme');
+ resolve(module);
+ });
});
},
@@ -139,7 +140,7 @@
return;
}
- script.src = src;
+ script.setAttribute('src', src);
script.onload = resolve;
script.onerror = reject;
Polymer.dom(document.head).appendChild(script);
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
index cf9a41cff1..10d16085a4 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-lib-loader</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-lib-loader.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html
index 91866e513c..d00416badd 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<dom-module id="gr-limited-text">
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
index 44a8791f10..048e4f5b77 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
@@ -26,7 +26,6 @@
Polymer({
is: 'gr-limited-text',
- _legacyUndefinedCheck: true,
properties: {
/** The un-truncated text to display. */
@@ -45,6 +44,15 @@
},
/**
+ * Disable the tooltip.
+ * When set to true, will not show tooltip even text is over limit
+ */
+ disableTooltip: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
* The maximum number of characters to display in the tooltop.
*/
tooltipLimit: {
@@ -66,8 +74,13 @@
* enabled.
*/
_updateTitle(text, limit, tooltipLimit) {
+ // Polymer 2: check for undefined
+ if ([text, limit, tooltipLimit].some(arg => arg === undefined)) {
+ return;
+ }
+
this.hasTooltip = !!limit && !!text && text.length > limit;
- if (this.hasTooltip) {
+ if (this.hasTooltip && !this.disableTooltip) {
this.setAttribute('title', text.substr(0, tooltipLimit));
} else {
this.removeAttribute('title');
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
index 16eb960227..7946bb6f95 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-limited-text</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-limited-text.html">
@@ -90,5 +92,14 @@ limitations under the License.
assert.equal(element._computeDisplayText('foo bar', 4), 'foo…');
assert.equal(element._computeDisplayText('foo bar', null), 'foo bar');
});
+
+ test('when disable tooltip', () => {
+ sandbox.spy(element, '_updateTitle');
+ element.text = 'abcdefghijklmn';
+ element.disableTooltip = true;
+ element.limit = 10;
+ flushAsynchronousOperations();
+ assert.equal(element.getAttribute('title'), null);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
index fab562a603..f3e0906416 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<link rel="import" href="../gr-button/gr-button.html">
<link rel="import" href="../gr-icons/gr-icons.html">
@@ -34,25 +35,31 @@ limitations under the License.
background: var(--chip-background-color);
border-radius: .75em;
display: inline-flex;
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
}
+ gr-button.remove {
+ --gr-remove-button-style: {
+ border: 0;
+ color: var(--deemphasized-text-color);
+ font-weight: normal;
+ height: .6em;
+ line-height: 10px;
+ margin-left: var(--spacing-xs);
+ padding: 0;
+ text-decoration: none;
+ }
+ }
+
gr-button.remove:hover,
gr-button.remove:focus {
--gr-button: {
+ @apply --gr-remove-button-style;
color: #333;
}
}
gr-button.remove {
--gr-button: {
- border: 0;
- color: var(--deemphasized-text-color);
- font-size: 1.7rem;
- font-weight: normal;
- height: .6em;
- line-height: .6;
- margin-left: .15em;
- padding: 0;
- text-decoration: none;
+ @apply --gr-remove-button-style;
}
}
.transparentBackground,
@@ -83,7 +90,7 @@ limitations under the License.
hidden$="[[!removable]]"
hidden
class$="remove [[_getBackgroundClass(transparentBackground)]]"
- on-tap="_handleRemoveTap">
+ on-click="_handleRemoveTap">
<iron-icon icon="gr-icons:close"></iron-icon>
</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
index 8388a079b5..33a9c25808 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-linked-chip',
- _legacyUndefinedCheck: true,
properties: {
href: String,
@@ -42,6 +41,10 @@
limit: Number,
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
_getBackgroundClass(transparent) {
return transparent ? 'transparentBackground' : '';
},
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
index eb57428f50..22a2eaf054 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
@@ -18,11 +18,13 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-linked-chip</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../bower_components/iron-test-helpers/mock-interactions.js"></script>
+<script src="/bower_components/iron-test-helpers/mock-interactions.js"></script>
<link rel="import" href="gr-linked-chip.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
index af70e374dd..61facc042e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
@@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
-<script src="../../../bower_components/ba-linkify/ba-linkify.js"></script>
+<script src="/bower_components/ba-linkify/ba-linkify.js"></script>
<script src="link-text-parser.js"></script>
<dom-module id="gr-linked-text">
<template>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 157ad5ed88..229fa1919b 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-linked-text',
- _legacyUndefinedCheck: true,
properties: {
removeZeroWidthSpace: Boolean,
@@ -62,6 +61,7 @@
* commentLink patterns
*/
_contentOrConfigChanged(content, config) {
+ if (!Gerrit.Nav || !Gerrit.Nav.mapCommentlinks) return;
config = Gerrit.Nav.mapCommentlinks(config);
const output = Polymer.dom(this.$.output);
output.textContent = '';
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index 9fc92b1511..0deff05f8e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-linked-text</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -276,16 +278,58 @@ limitations under the License.
let links = element.$.output.querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
+ assert.equal(links[0].innerHTML, 'mailto:test@google.com');
element.content = 'xx http://google.com yy';
links = element.$.output.querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'http://google.com');
+ assert.equal(links[0].innerHTML, 'http://google.com');
element.content = 'xx https://google.com yy';
links = element.$.output.querySelectorAll('a');
assert.equal(links.length, 1);
assert.equal(links[0].getAttribute('href'), 'https://google.com');
+ assert.equal(links[0].innerHTML, 'https://google.com');
+
+ element.content = 'xx ssh://google.com yy';
+ links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 0);
+
+ element.content = 'xx ftp://google.com yy';
+ links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 0);
+ });
+
+ test('links without leading whitespace are linkified', () => {
+ element.content = 'xx abcmailto:test@google.com yy';
+ assert.equal(element.$.output.innerHTML.substr(0, 6), 'xx abc');
+ let links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 1);
+ assert.equal(links[0].getAttribute('href'), 'mailto:test@google.com');
+ assert.equal(links[0].innerHTML, 'mailto:test@google.com');
+
+ element.content = 'xx defhttp://google.com yy';
+ assert.equal(element.$.output.innerHTML.substr(0, 6), 'xx def');
+ links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 1);
+ assert.equal(links[0].getAttribute('href'), 'http://google.com');
+ assert.equal(links[0].innerHTML, 'http://google.com');
+
+ element.content = 'xx qwehttps://google.com yy';
+ assert.equal(element.$.output.innerHTML.substr(0, 6), 'xx qwe');
+ links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 1);
+ assert.equal(links[0].getAttribute('href'), 'https://google.com');
+ assert.equal(links[0].innerHTML, 'https://google.com');
+
+ // Non-latin character
+ element.content = 'xx абвhttps://google.com yy';
+ assert.equal(element.$.output.innerHTML.substr(0, 6), 'xx абв');
+ links = element.$.output.querySelectorAll('a');
+ assert.equal(links.length, 1);
+ assert.equal(links[0].getAttribute('href'), 'https://google.com');
+ assert.equal(links[0].innerHTML, 'https://google.com');
element.content = 'xx ssh://google.com yy';
links = element.$.output.querySelectorAll('a');
@@ -328,26 +372,4 @@ limitations under the License.
assert.isTrue(contentConfigStub.called);
});
});
-
- suite('gr-linked-text with null config', () => {
- let element;
- let sandbox;
-
- setup(() => {
- element = fixture('basic');
- sandbox = sinon.sandbox.create();
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- test('_contentOrConfigChanged not called without config', () => {
- const contentStub = sandbox.stub(element, '_contentChanged');
- const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
- element.content = 'some text';
- assert.isTrue(contentStub.called);
- assert.isFalse(contentConfigStub.called);
- });
- });
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 027c632e12..fa38a66007 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -17,23 +17,12 @@
(function() {
'use strict';
- const Defs = {};
-
- /**
- * @typedef {{
- * html: Node,
- * position: number,
- * length: number,
- * }}
- */
- Defs.CommentLinkItem;
-
/**
* Pattern describing URLs with supported protocols.
*
* @type {RegExp}
*/
- const URL_PROTOCOL_PATTERN = /^(https?:\/\/|mailto:)/;
+ const URL_PROTOCOL_PATTERN = /^(.*)(https?:\/\/|mailto:)/;
/**
* Construct a parser for linkifying text. Will linkify plain URLs that appear
@@ -73,7 +62,7 @@
*
* @param {string} text The chuml of source text over which the outputArray
* items range.
- * @param {!Array<Defs.CommentLinkItem>} outputArray The list of items to add
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray The list of items to add
* resulting from commentlink matches.
*/
GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
@@ -109,7 +98,7 @@
* Sort the given array of CommentLinkItems such that the positions are in
* reverse order.
*
- * @param {!Array<Defs.CommentLinkItem>} outputArray
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray
*/
GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
outputArray.sort((a, b) => b.position - a.position);
@@ -132,7 +121,7 @@
* starts.
* @param {number} length The number of characters in the source text
* represented by the item.
- * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
* new item is to be appended.
*/
GrLinkTextParser.prototype.addItem =
@@ -174,7 +163,7 @@
* starts.
* @param {number} length The number of characters in the source text
* represented by the link.
- * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
* new item is to be appended.
*/
GrLinkTextParser.prototype.addLink =
@@ -196,7 +185,7 @@
* starts.
* @param {number} length The number of characters in the source text
* represented by the item.
- * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
* new item is to be appended.
*/
GrLinkTextParser.prototype.addHTML =
@@ -214,7 +203,7 @@
*
* @param {number} position
* @param {number} length
- * @param {!Array<Defs.CommentLinkItem>} outputArray
+ * @param {!Array<Gerrit.CommentLinkItem>} outputArray
*/
GrLinkTextParser.prototype.hasOverlap =
function(position, length, outputArray) {
@@ -238,9 +227,11 @@
* @param {string} text
*/
GrLinkTextParser.prototype.parse = function(text) {
- linkify(text, {
- callback: this.parseChunk.bind(this),
- });
+ if (text) {
+ linkify(text, {
+ callback: this.parseChunk.bind(this),
+ });
+ }
};
/**
@@ -265,13 +256,29 @@
// the source text does not include a protocol, the protocol will be added
// by ba-linkify. Create the link if the href is provided and its protocol
// matches the expected pattern.
- if (href && URL_PROTOCOL_PATTERN.test(href)) {
- this.addText(text, href);
- } else {
- // For the sections of text that lie between the links found by
- // ba-linkify, we search for the project-config-specified link patterns.
- this.parseLinks(text, this.linkConfig);
+ if (href) {
+ const result = URL_PROTOCOL_PATTERN.exec(href);
+ if (result) {
+ const prefixText = result[1];
+ if (prefixText.length > 0) {
+ // Fix for simple cases from
+ // https://bugs.chromium.org/p/gerrit/issues/detail?id=11697
+ // When leading whitespace is missed before link,
+ // linkify add this text before link as a schema name to href.
+ // We suppose, that prefixText just a single word
+ // before link and add this word as is, without processing
+ // any patterns in it.
+ this.parseLinks(prefixText, []);
+ text = text.substring(prefixText.length);
+ href = href.substring(prefixText.length);
+ }
+ this.addText(text, href);
+ return;
+ }
}
+ // For the sections of text that lie between the links found by
+ // ba-linkify, we search for the project-config-specified link patterns.
+ this.parseLinks(text, this.linkConfig);
};
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
index be02d403fd..3d41a7c51d 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
@@ -14,9 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-input/iron-input.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -25,7 +28,6 @@ limitations under the License.
<template>
<style include="shared-styles">
#filter {
- font-size: var(--font-size-normal);
max-width: 25em;
}
#filter:focus {
@@ -36,7 +38,7 @@ limitations under the License.
display: flex;
height: 3rem;
justify-content: space-between;
- margin: 0 1em;
+ margin: 0 var(--spacing-l);
}
#createNewContainer:not(.show) {
display: none;
@@ -68,20 +70,26 @@ limitations under the License.
<div id="topContainer">
<div class="filterContainer">
<label>Filter:</label>
- <input is="iron-input"
+ <iron-input
type="text"
- id="filter"
bind-value="{{filter}}">
+ <input
+ is="iron-input"
+ type="text"
+ id="filter"
+ bind-value="{{filter}}">
+ </iron-input>
</div>
<div id="createNewContainer"
class$="[[_computeCreateClass(createNew)]]">
- <gr-button primary link id="createNew" on-tap="_createNewItem">
+ <gr-button primary link id="createNew" on-click="_createNewItem">
Create New
</gr-button>
</div>
</div>
<slot></slot>
<nav>
+ Page [[_computePage(offset, itemsPerPage)]]
<a id="prevArrow"
href$="[[_computeNavLink(offset, -1, itemsPerPage, filter, path)]]"
hidden$="[[_hidePrevArrow(loading, offset)]]" hidden>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
index 53d05e1044..6840e97047 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
@@ -21,7 +21,6 @@
Polymer({
is: 'gr-list-view',
- _legacyUndefinedCheck: true,
properties: {
createNew: Boolean,
@@ -38,6 +37,7 @@
behaviors: [
Gerrit.BaseUrlBehavior,
+ Gerrit.FireBehavior,
Gerrit.URLEncodingBehavior,
],
@@ -90,11 +90,18 @@
},
_hideNextArrow(loading, items) {
- let lastPage = false;
- if (items.length < this.itemsPerPage + 1) {
- lastPage = true;
+ if (loading || !items || !items.length) {
+ return true;
}
- return loading || lastPage || !items || !items.length;
+ const lastPage = items.length < this.itemsPerPage + 1;
+ return lastPage;
+ },
+
+ // TODO: fix offset (including itemsPerPage)
+ // to either support a decimal or make it go to the nearest
+ // whole number (e.g 3).
+ _computePage(offset, itemsPerPage) {
+ return offset / itemsPerPage + 1;
},
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
index 09e68dda65..ea1dcbb8d1 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-list-view</title>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-list-view.html">
@@ -153,5 +155,10 @@ limitations under the License.
element.path = TAGS_PATH;
assert.equal(element._computeNavLink.lastCall.args[4], TAGS_PATH);
});
+
+ test('_computePage', () => {
+ assert.equal(element._computePage(0, 25), 1);
+ assert.equal(element._computePage(50, 25), 3);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
index e94b655f9e..2b4b982016 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-overlay">
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
index c167b3b54c..862345865b 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
@@ -23,7 +23,6 @@
Polymer({
is: 'gr-overlay',
- _legacyUndefinedCheck: true,
/**
* Fired when a fullscreen overlay is closed
@@ -45,6 +44,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Polymer.IronOverlayBehavior,
],
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
index ee05b699f7..08b749744c 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-overlay</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.html b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.html
index 3885497fde..f1c3a6f359 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.html
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-page-nav">
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
index 181c7bc751..2e056075c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-page-nav',
- _legacyUndefinedCheck: true,
properties: {
_headerHeight: Number,
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
index 428bab3294..b384b474d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-page-nav</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/page/page.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html
index d794dd648d..ce596f8a89 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html
@@ -14,8 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
@@ -33,7 +33,7 @@ limitations under the License.
display: inline-block;
}
iron-icon {
- margin-bottom: 1.2em;
+ margin-bottom: var(--spacing-l);
}
</style>
<div>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
index 2fccc8defa..e2298c3d37 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
@@ -22,7 +22,6 @@
Polymer({
is: 'gr-repo-branch-picker',
- _legacyUndefinedCheck: true,
properties: {
repo: {
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
index 989e838181..1ed9151383 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-repo-branch-picker</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-repo-branch-picker.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
index f20359466e..dc07d0f52b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-auth</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.html
index c5a0dfe80a..d3500d82a2 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-etag-decorator">
<script src="gr-etag-decorator.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
index 09ae1da341..76c8c2c336 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-etag-decorator</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="gr-etag-decorator.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
index 562980c313..7461ac475b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
@@ -15,19 +15,21 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="gr-etag-decorator.html">
<!-- NB: es6-promise Needed for IE11 and fetch polyfill support, see Issue 4308 -->
-<script src="../../../bower_components/es6-promise/dist/es6-promise.min.js"></script>
-<script src="../../../bower_components/fetch/fetch.js"></script>
+<script src="/bower_components/es6-promise/dist/es6-promise.min.js"></script>
+<script src="/bower_components/fetch/fetch.js"></script>
<dom-module id="gr-rest-api-interface">
<!-- NB: Order is important, because of namespaced classes. -->
+ <script src="gr-rest-apis/gr-rest-api-helper.js"></script>
<script src="gr-auth.js"></script>
<script src="gr-reviewer-updates-parser.js"></script>
<script src="gr-rest-api-interface.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 546d0045fa..8c34e33ac9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -17,113 +17,15 @@
(function() {
'use strict';
- const Defs = {};
-
- /**
- * @typedef {{
- * basePatchNum: (string|number),
- * patchNum: (number),
- * }}
- */
- Defs.patchRange;
-
- /**
- * @typedef {{
- * url: string,
- * fetchOptions: (Object|null|undefined),
- * anonymizedUrl: (string|undefined),
- * }}
- */
- Defs.FetchRequest;
-
- /**
- * Object to describe a request for passing into _fetchJSON or _fetchRawJSON.
- * - url is the URL for the request (excluding get params)
- * - errFn is a function to invoke when the request fails.
- * - cancelCondition is a function that, if provided and returns true, will
- * cancel the response after it resolves.
- * - params is a key-value hash to specify get params for the request URL.
- *
- * @typedef {{
- * url: string,
- * errFn: (function(?Response, string=)|null|undefined),
- * cancelCondition: (function()|null|undefined),
- * params: (Object|null|undefined),
- * fetchOptions: (Object|null|undefined),
- * anonymizedUrl: (string|undefined),
- * reportUrlAsIs: (boolean|undefined),
- * }}
- */
- Defs.FetchJSONRequest;
-
- /**
- * @typedef {{
- * changeNum: (string|number),
- * endpoint: string,
- * patchNum: (string|number|null|undefined),
- * errFn: (function(?Response, string=)|null|undefined),
- * params: (Object|null|undefined),
- * fetchOptions: (Object|null|undefined),
- * anonymizedEndpoint: (string|undefined),
- * reportEndpointAsIs: (boolean|undefined),
- * }}
- */
- Defs.ChangeFetchRequest;
-
- /**
- * Object to describe a request for passing into _send.
- * - method is the HTTP method to use in the request.
- * - url is the URL for the request
- * - body is a request payload.
- * TODO (beckysiegel) remove need for number at least.
- * - errFn is a function to invoke when the request fails.
- * - cancelCondition is a function that, if provided and returns true, will
- * cancel the response after it resolves.
- * - contentType is the content type of the body.
- * - headers is a key-value hash to describe HTTP headers for the request.
- * - parseResponse states whether the result should be parsed as a JSON
- * object using getResponseObject.
- *
- * @typedef {{
- * method: string,
- * url: string,
- * body: (string|number|Object|null|undefined),
- * errFn: (function(?Response, string=)|null|undefined),
- * contentType: (string|null|undefined),
- * headers: (Object|undefined),
- * parseResponse: (boolean|undefined),
- * anonymizedUrl: (string|undefined),
- * reportUrlAsIs: (boolean|undefined),
- * }}
- */
- Defs.SendRequest;
-
- /**
- * @typedef {{
- * changeNum: (string|number),
- * method: string,
- * patchNum: (string|number|undefined),
- * endpoint: string,
- * body: (string|number|Object|null|undefined),
- * errFn: (function(?Response, string=)|null|undefined),
- * contentType: (string|null|undefined),
- * headers: (Object|undefined),
- * parseResponse: (boolean|undefined),
- * anonymizedEndpoint: (string|undefined),
- * reportEndpointAsIs: (boolean|undefined),
- * }}
- */
- Defs.ChangeSendRequest;
-
const DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
UNIFIED: 'UNIFIED_DIFF',
};
const JSON_PREFIX = ')]}\'';
const MAX_PROJECT_RESULTS = 25;
- const MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX = 900;
+ // This value is somewhat arbitrary and not based on research or calculations.
+ const MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX = 850;
const PARENT_PATCH_NUM = 'PARENT';
- const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
const Requests = {
SEND_DIFF_DRAFT: 'sendDiffDraft',
@@ -137,57 +39,11 @@
const ANONYMIZED_REVISION_BASE_URL = ANONYMIZED_CHANGE_BASE_URL +
'/revisions/*';
- /**
- * Wrapper around Map for caching server responses. Site-based so that
- * changes to CANONICAL_PATH will result in a different cache going into
- * effect.
- */
- class SiteBasedCache {
- constructor() {
- // Container of per-canonical-path caches.
- this._data = new Map();
- }
-
- // Returns the cache for the current canonical path.
- _cache() {
- if (!this._data.has(window.CANONICAL_PATH)) {
- this._data.set(window.CANONICAL_PATH, new Map());
- }
- return this._data.get(window.CANONICAL_PATH);
- }
-
- has(key) {
- return this._cache().has(key);
- }
-
- get(key) {
- return this._cache().get(key);
- }
-
- set(key, value) {
- this._cache().set(key, value);
- }
-
- delete(key) {
- this._cache().delete(key);
- }
-
- invalidatePrefix(prefix) {
- const newMap = new Map();
- for (const [key, value] of this._cache().entries()) {
- if (!key.startsWith(prefix)) {
- newMap.set(key, value);
- }
- }
- this._data.set(window.CANONICAL_PATH, newMap);
- }
- }
-
Polymer({
is: 'gr-rest-api-interface',
- _legacyUndefinedCheck: true,
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.PathListBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
@@ -228,7 +84,7 @@
},
_sharedFetchPromises: {
type: Object,
- value: {}, // Intentional to share the object across instances.
+ value: new FetchPromisesCache(), // Shared across instances.
},
_pendingRequests: {
type: Object,
@@ -253,164 +109,56 @@
JSON_PREFIX,
- /**
- * Wraps calls to the underlying authenticated fetch function (_auth.fetch)
- * with timing and logging.
- *
- * @param {Defs.FetchRequest} req
- */
- _fetch(req) {
- const start = Date.now();
- const xhr = this._auth.fetch(req.url, req.fetchOptions);
-
- // Log the call after it completes.
- xhr.then(res => this._logCall(req, start, res.status));
-
- // Return the XHR directly (without the log).
- return xhr;
- },
-
- /**
- * Log information about a REST call. Because the elapsed time is determined
- * by this method, it should be called immediately after the request
- * finishes.
- *
- * @param {Defs.FetchRequest} req
- * @param {number} startTime the time that the request was started.
- * @param {number} status the HTTP status of the response. The status value
- * is used here rather than the response object so there is no way this
- * method can read the body stream.
- */
- _logCall(req, startTime, status) {
- const method = (req.fetchOptions && req.fetchOptions.method) ?
- req.fetchOptions.method : 'GET';
- const elapsed = (Date.now() - startTime);
- console.log([
- 'HTTP',
- status,
- method,
- elapsed + 'ms',
- req.anonymizedUrl || req.url,
- ].join(' '));
- if (req.anonymizedUrl) {
- this.fire('rpc-log',
- {status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
+ created() {
+ /* Polymer 1 and Polymer 2 have slightly different lifecycle.
+ * Differences are not very well documented (see
+ * https://github.com/Polymer/old-docs-site/issues/2322).
+ * In Polymer 1, created() is called when properties values is not set
+ * and ready() is always called later, even if element is not added
+ * to a DOM. I.e. in Polymer 1 _cache and other properties are undefined,
+ * while in Polymer 2 they are set to default values.
+ * In Polymer 2, created() is called after properties values set and
+ * ready() is called only after element is attached to a DOM.
+ * There are several places in the code, where element is created with
+ * document.createElement('gr-rest-api-interface') and is not added
+ * to a DOM.
+ * In such cases, Polymer 1 calls both created() and ready() methods,
+ * but Polymer 2 calls only created() method.
+ * To workaround these differences, we should try to create _restApiHelper
+ * in both methods.
+ */
+ //
+
+ this._initRestApiHelper();
+ },
+
+ ready() {
+ // See comments in created()
+ this._initRestApiHelper();
+ },
+
+ _initRestApiHelper() {
+ if (this._restApiHelper) {
+ return;
}
- },
-
- /**
- * Fetch JSON from url provided.
- * Returns a Promise that resolves to a native Response.
- * Doesn't do error checking. Supports cancel condition. Performs auth.
- * Validates auth expiry errors.
- *
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchRawJSON(req) {
- const urlWithParams = this._urlWithParams(req.url, req.params);
- const fetchReq = {
- url: urlWithParams,
- fetchOptions: req.fetchOptions,
- anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
- };
- return this._fetch(fetchReq).then(res => {
- if (req.cancelCondition && req.cancelCondition()) {
- res.body.cancel();
- return;
- }
- return res;
- }).catch(err => {
- const isLoggedIn = !!this._cache.get('/accounts/self/detail');
- if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) {
- this.checkCredentials();
- return;
- }
- if (req.errFn) {
- req.errFn.call(undefined, null, err);
- } else {
- this.fire('network-error', {error: err});
- }
- throw err;
- });
- },
-
- /**
- * Fetch JSON from url provided.
- * Returns a Promise that resolves to a parsed response.
- * Same as {@link _fetchRawJSON}, plus error handling.
- *
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchJSON(req) {
- return this._fetchRawJSON(req).then(response => {
- if (!response) {
- return;
- }
- if (!response.ok) {
- if (req.errFn) {
- req.errFn.call(null, response);
- return;
- }
- this.fire('server-error', {request: req, response});
- return;
- }
- return response && this.getResponseObject(response);
- });
- },
-
- /**
- * @param {string} url
- * @param {?Object|string=} opt_params URL params, key-value hash.
- * @return {string}
- */
- _urlWithParams(url, opt_params) {
- if (!opt_params) { return this.getBaseUrl() + url; }
-
- const params = [];
- for (const p in opt_params) {
- if (!opt_params.hasOwnProperty(p)) { continue; }
- if (opt_params[p] == null) {
- params.push(encodeURIComponent(p));
- continue;
- }
- for (const value of [].concat(opt_params[p])) {
- params.push(`${encodeURIComponent(p)}=${encodeURIComponent(value)}`);
- }
+ if (this._cache && this._auth && this._sharedFetchPromises
+ && this._credentialCheck) {
+ this._restApiHelper = new GrRestApiHelper(this._cache, this._auth,
+ this._sharedFetchPromises, this._credentialCheck, this);
}
- return this.getBaseUrl() + url + '?' + params.join('&');
},
- /**
- * @param {!Object} response
- * @return {?}
- */
- getResponseObject(response) {
- return this._readResponsePayload(response)
- .then(payload => payload.parsed);
+ _fetchSharedCacheURL(req) {
+ // Cache is shared across instances
+ return this._restApiHelper.fetchCacheURL(req);
},
/**
* @param {!Object} response
- * @return {!Object}
- */
- _readResponsePayload(response) {
- return response.text().then(text => {
- let result;
- try {
- result = this._parsePrefixedJSON(text);
- } catch (_) {
- result = null;
- }
- return {parsed: result, raw: text};
- });
- },
-
- /**
- * @param {string} source
* @return {?}
*/
- _parsePrefixedJSON(source) {
- return JSON.parse(source.substring(JSON_PREFIX.length));
+ getResponseObject(response) {
+ return this._restApiHelper.getResponseObject(response);
},
getConfig(noCache) {
@@ -421,7 +169,7 @@
});
}
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/config/server/info',
reportUrlAsIs: true,
});
@@ -471,7 +219,7 @@
// supports it.
const url = `/projects/${encodeURIComponent(repo)}/config`;
this._cache.delete(url);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url,
body: config,
@@ -485,7 +233,7 @@
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
const encodeName = encodeURIComponent(repo);
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: `/projects/${encodeName}/gc`,
body: '',
@@ -503,7 +251,7 @@
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
const encodeName = encodeURIComponent(config.name);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}`,
body: config,
@@ -519,7 +267,7 @@
createGroup(config, opt_errFn) {
if (!config.name) { return ''; }
const encodeName = encodeURIComponent(config.name);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeName}`,
body: config,
@@ -529,7 +277,7 @@
},
getGroupConfig(group, opt_errFn) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeURIComponent(group)}/detail`,
errFn: opt_errFn,
anonymizedUrl: '/groups/*/detail',
@@ -547,7 +295,7 @@
// supports it.
const encodeName = encodeURIComponent(repo);
const encodeRef = encodeURIComponent(ref);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/projects/${encodeName}/branches/${encodeRef}`,
body: '',
@@ -567,7 +315,7 @@
// supports it.
const encodeName = encodeURIComponent(repo);
const encodeRef = encodeURIComponent(ref);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/projects/${encodeName}/tags/${encodeRef}`,
body: '',
@@ -588,7 +336,7 @@
// supports it.
const encodeName = encodeURIComponent(name);
const encodeBranch = encodeURIComponent(branch);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}/branches/${encodeBranch}`,
body: revision,
@@ -609,7 +357,7 @@
// supports it.
const encodeName = encodeURIComponent(name);
const encodeTag = encodeURIComponent(tag);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}/tags/${encodeTag}`,
body: revision,
@@ -625,8 +373,8 @@
getIsGroupOwner(groupName) {
const encodeName = encodeURIComponent(groupName);
const req = {
- url: `/groups/?owned&q=${encodeName}`,
- anonymizedUrl: '/groups/owned&q=*',
+ url: `/groups/?owned&g=${encodeName}`,
+ anonymizedUrl: '/groups/owned&g=*',
};
return this._fetchSharedCacheURL(req)
.then(configs => configs.hasOwnProperty(groupName));
@@ -634,7 +382,7 @@
getGroupMembers(groupName, opt_errFn) {
const encodeName = encodeURIComponent(groupName);
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeName}/members/`,
errFn: opt_errFn,
anonymizedUrl: '/groups/*/members',
@@ -642,7 +390,7 @@
},
getIncludedGroup(groupName) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeURIComponent(groupName)}/groups/`,
anonymizedUrl: '/groups/*/groups',
});
@@ -650,7 +398,7 @@
saveGroupName(groupId, name) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/name`,
body: {name},
@@ -660,7 +408,7 @@
saveGroupOwner(groupId, ownerId) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/owner`,
body: {owner: ownerId},
@@ -670,7 +418,7 @@
saveGroupDescription(groupId, description) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/description`,
body: {description},
@@ -680,7 +428,7 @@
saveGroupOptions(groupId, options) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/options`,
body: options,
@@ -699,7 +447,7 @@
saveGroupMembers(groupName, groupMembers) {
const encodeName = encodeURIComponent(groupName);
const encodeMember = encodeURIComponent(groupMembers);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeName}/members/${encodeMember}`,
parseResponse: true,
@@ -716,7 +464,7 @@
errFn: opt_errFn,
anonymizedUrl: '/groups/*/groups/*',
};
- return this._send(req).then(response => {
+ return this._restApiHelper.send(req).then(response => {
if (response.ok) {
return this.getResponseObject(response);
}
@@ -726,7 +474,7 @@
deleteGroupMembers(groupName, groupMembers) {
const encodeName = encodeURIComponent(groupName);
const encodeMember = encodeURIComponent(groupMembers);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/groups/${encodeName}/members/${encodeMember}`,
anonymizedUrl: '/groups/*/members/*',
@@ -736,7 +484,7 @@
deleteIncludedGroup(groupName, includedGroup) {
const encodeName = encodeURIComponent(groupName);
const encodeIncludedGroup = encodeURIComponent(includedGroup);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/groups/${encodeName}/groups/${encodeIncludedGroup}`,
anonymizedUrl: '/groups/*/groups/*',
@@ -823,7 +571,7 @@
prefs.download_scheme = prefs.download_scheme.toLowerCase();
}
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences',
body: prefs,
@@ -839,7 +587,7 @@
saveDiffPreferences(prefs, opt_errFn) {
// Invalidate the cache.
this._cache.delete('/accounts/self/preferences.diff');
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences.diff',
body: prefs,
@@ -855,7 +603,7 @@
saveEditPreferences(prefs, opt_errFn) {
// Invalidate the cache.
this._cache.delete('/accounts/self/preferences.edit');
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences.edit',
body: prefs,
@@ -889,14 +637,14 @@
},
getExternalIds() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/external.ids',
reportUrlAsIs: true,
});
},
deleteAccountIdentity(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/external.ids:delete',
body: id,
@@ -910,7 +658,7 @@
* @return {!Promise<!Object>}
*/
getAccountDetails(userId) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/detail`,
anonymizedUrl: '/accounts/*/detail',
});
@@ -928,7 +676,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
addAccountEmail(email, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
@@ -941,7 +689,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
deleteAccountEmail(email, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
@@ -961,7 +709,7 @@
errFn: opt_errFn,
anonymizedUrl: '/accounts/self/emails/*/preferred',
};
- return this._send(req).then(() => {
+ return this._restApiHelper.send(req).then(() => {
// If result of getAccountEmails is in cache, update it in the cache
// so we don't have to invalidate it.
const cachedEmails = this._cache.get('/accounts/self/emails');
@@ -1005,7 +753,7 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newName => this._updateCachedAccount({name: newName}));
},
@@ -1022,7 +770,7 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newName => this._updateCachedAccount({username: newName}));
},
@@ -1039,33 +787,33 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newStatus => this._updateCachedAccount({status: newStatus}));
},
getAccountStatus(userId) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/status`,
anonymizedUrl: '/accounts/*/status',
});
},
getAccountGroups() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/groups',
reportUrlAsIs: true,
});
},
getAccountAgreements() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/agreements',
reportUrlAsIs: true,
});
},
saveAccountAgreement(name) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/agreements',
body: name,
@@ -1092,6 +840,8 @@
getLoggedIn() {
return this.getAccount().then(account => {
return account != null;
+ }).catch(() => {
+ return false;
});
},
@@ -1108,29 +858,7 @@
},
checkCredentials() {
- if (this._credentialCheck.checking) {
- return;
- }
- this._credentialCheck.checking = true;
- const req = {url: '/accounts/self/detail', reportUrlAsIs: true};
- // Skip the REST response cache.
- return this._fetchRawJSON(req).then(res => {
- if (!res) { return; }
- if (res.status === 403) {
- this.fire('auth-error');
- this._cache.delete('/accounts/self/detail');
- } else if (res.ok) {
- return this.getResponseObject(res);
- }
- }).then(res => {
- this._credentialCheck.checking = false;
- if (res) {
- this._cache.delete('/accounts/self/detail');
- }
- return res;
- }).catch(err => {
- this._credentialCheck.checking = false;
- });
+ return this._restApiHelper.checkCredentials();
},
getDefaultPreferences() {
@@ -1146,6 +874,8 @@
const req = {url: '/accounts/self/preferences', reportUrlAsIs: true};
return this._fetchSharedCacheURL(req).then(res => {
if (this._isNarrowScreen()) {
+ // Note that this can be problematic, because the diff will stay
+ // unified even after increasing the window width.
res.default_diff_view = DiffViewMode.UNIFIED;
} else {
res.default_diff_view = res.diff_view;
@@ -1176,7 +906,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
saveWatchedProjects(projects, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/watched.projects',
body: projects,
@@ -1191,7 +921,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
deleteWatchedProjects(projects, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/watched.projects:delete',
body: projects,
@@ -1200,45 +930,6 @@
});
},
- /**
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchSharedCacheURL(req) {
- if (this._sharedFetchPromises[req.url]) {
- return this._sharedFetchPromises[req.url];
- }
- // TODO(andybons): Periodic cache invalidation.
- if (this._cache.has(req.url)) {
- return Promise.resolve(this._cache.get(req.url));
- }
- this._sharedFetchPromises[req.url] = this._fetchJSON(req)
- .then(response => {
- if (response !== undefined) {
- this._cache.set(req.url, response);
- }
- this._sharedFetchPromises[req.url] = undefined;
- return response;
- }).catch(err => {
- this._sharedFetchPromises[req.url] = undefined;
- throw err;
- });
- return this._sharedFetchPromises[req.url];
- },
-
- /**
- * @param {string} prefix
- */
- _invalidateSharedFetchPromisesPrefix(prefix) {
- const newObject = {};
- Object.entries(this._sharedFetchPromises).forEach(([key, value]) => {
- if (!key.startsWith(prefix)) {
- newObject[key] = value;
- }
- });
- this._sharedFetchPromises = newObject;
- this._cache.invalidatePrefix(prefix);
- },
-
_isNarrowScreen() {
return window.innerWidth < MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX;
},
@@ -1280,7 +971,7 @@
params,
reportUrlAsIs: true,
};
- return this._fetchJSON(req).then(response => {
+ return this._restApiHelper.fetchJSON(req).then(response => {
// Response may be an array of changes OR an array of arrays of
// changes.
if (opt_query instanceof Array) {
@@ -1336,13 +1027,13 @@
this.ListChangesOption.ALL_COMMITS,
this.ListChangesOption.ALL_REVISIONS,
this.ListChangesOption.CHANGE_ACTIONS,
- this.ListChangesOption.CURRENT_ACTIONS,
this.ListChangesOption.DETAILED_LABELS,
this.ListChangesOption.DOWNLOAD_COMMANDS,
this.ListChangesOption.MESSAGES,
this.ListChangesOption.SUBMITTABLE,
this.ListChangesOption.WEB_LINKS,
this.ListChangesOption.SKIP_MERGEABLE,
+ this.ListChangesOption.SKIP_DIFFSTAT,
];
return this.getConfig(false).then(config => {
if (config.receive && config.receive.enable_signed_push) {
@@ -1364,7 +1055,8 @@
const optionsHex = this.listChangesOptionsToHex(
this.ListChangesOption.ALL_COMMITS,
this.ListChangesOption.ALL_REVISIONS,
- this.ListChangesOption.SKIP_MERGEABLE
+ this.ListChangesOption.SKIP_MERGEABLE,
+ this.ListChangesOption.SKIP_DIFFSTAT
);
return this._getChangeDetail(changeNum, optionsHex, opt_errFn,
opt_cancelCondition);
@@ -1378,9 +1070,10 @@
*/
_getChangeDetail(changeNum, optionsHex, opt_errFn, opt_cancelCondition) {
return this.getChangeActionURL(changeNum, null, '/detail').then(url => {
- const urlWithParams = this._urlWithParams(url, optionsHex);
+ const urlWithParams = this._restApiHelper
+ .urlWithParams(url, optionsHex);
const params = {O: optionsHex};
- const req = {
+ let req = {
url,
errFn: opt_errFn,
cancelCondition: opt_cancelCondition,
@@ -1388,9 +1081,10 @@
fetchOptions: this._etags.getOptions(urlWithParams),
anonymizedUrl: '/changes/*~*/detail?O=' + optionsHex,
};
- return this._fetchRawJSON(req).then(response => {
+ req = this._restApiHelper.addAcceptJsonHeader(req);
+ return this._restApiHelper.fetchRawJSON(req).then(response => {
if (response && response.status === 304) {
- return Promise.resolve(this._parsePrefixedJSON(
+ return Promise.resolve(this._restApiHelper.parsePrefixedJSON(
this._etags.getCachedPayload(urlWithParams)));
}
@@ -1404,7 +1098,7 @@
}
const payloadPromise = response ?
- this._readResponsePayload(response) :
+ this._restApiHelper.readResponsePayload(response) :
Promise.resolve(null);
return payloadPromise.then(payload => {
@@ -1433,7 +1127,7 @@
/**
* @param {number|string} changeNum
- * @param {Defs.patchRange} patchRange
+ * @param {Gerrit.PatchRange} patchRange
* @param {number=} opt_parentIndex
*/
getChangeFiles(changeNum, patchRange, opt_parentIndex) {
@@ -1454,7 +1148,7 @@
/**
* @param {number|string} changeNum
- * @param {Defs.patchRange} patchRange
+ * @param {Gerrit.PatchRange} patchRange
*/
getChangeEditFiles(changeNum, patchRange) {
let endpoint = '/edit?list';
@@ -1487,7 +1181,7 @@
/**
* @param {number|string} changeNum
- * @param {Defs.patchRange} patchRange
+ * @param {Gerrit.PatchRange} patchRange
* @return {!Promise<!Array<!Object>>}
*/
getChangeOrEditFiles(changeNum, patchRange) {
@@ -1498,18 +1192,6 @@
return this.getChangeFiles(changeNum, patchRange);
},
- /**
- * The closure compiler doesn't realize this.specialFilePathCompare is
- * valid.
- *
- * @suppress {checkTypes}
- */
- getChangeFilePathsAsSpeciallySortedArray(changeNum, patchRange) {
- return this.getChangeFiles(changeNum, patchRange).then(files => {
- return Object.keys(files).sort(this.specialFilePathCompare);
- });
- },
-
getChangeRevisionActions(changeNum, patchNum) {
const req = {
changeNum,
@@ -1517,15 +1199,7 @@
patchNum,
reportEndpointAsIs: true,
};
- return this._getChangeURLAndFetch(req).then(revisionActions => {
- // The rebase button on change screen is always enabled.
- if (revisionActions.rebase) {
- revisionActions.rebase.rebaseOnCurrent =
- !!revisionActions.rebase.enabled;
- revisionActions.rebase.enabled = true;
- }
- return revisionActions;
- });
+ return this._getChangeURLAndFetch(req);
},
/**
@@ -1534,9 +1208,24 @@
* @param {function(?Response, string=)=} opt_errFn
*/
getChangeSuggestedReviewers(changeNum, inputVal, opt_errFn) {
+ return this._getChangeSuggestedGroup('REVIEWER', changeNum, inputVal,
+ opt_errFn);
+ },
+
+ /**
+ * @param {number|string} changeNum
+ * @param {string} inputVal
+ * @param {function(?Response, string=)=} opt_errFn
+ */
+ getChangeSuggestedCCs(changeNum, inputVal, opt_errFn) {
+ return this._getChangeSuggestedGroup('CC', changeNum, inputVal,
+ opt_errFn);
+ },
+
+ _getChangeSuggestedGroup(reviewerState, changeNum, inputVal, opt_errFn) {
// More suggestions may obscure content underneath in the reply dialog,
// see issue 10793.
- const params = {n: 6};
+ const params = {'n': 6, 'reviewer-state': reviewerState};
if (inputVal) { params.q = inputVal; }
return this._getChangeURLAndFetch({
changeNum,
@@ -1618,11 +1307,11 @@
},
invalidateGroupsCache() {
- this._invalidateSharedFetchPromisesPrefix('/groups/?');
+ this._restApiHelper.invalidateFetchPromisesPrefix('/groups/?');
},
invalidateReposCache() {
- this._invalidateSharedFetchPromisesPrefix('/projects/?');
+ this._restApiHelper.invalidateFetchPromisesPrefix('/projects/?');
},
/**
@@ -1660,7 +1349,7 @@
setRepoHead(repo, ref) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeURIComponent(repo)}/HEAD`,
body: {ref},
@@ -1684,7 +1373,7 @@
const url = `/projects/${repo}/branches?n=${count}&S=${offset}${filter}`;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/branches?*',
@@ -1708,7 +1397,7 @@
encodedFilter;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/tags',
@@ -1727,7 +1416,7 @@
const encodedFilter = this._computeFilter(filter);
const n = pluginsPerPage + 1;
const url = `/plugins/?all&n=${n}&S=${offset}${encodedFilter}`;
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/plugins/?all',
@@ -1737,7 +1426,7 @@
getRepoAccessRights(repoName, opt_errFn) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/projects/${encodeURIComponent(repoName)}/access`,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/access',
@@ -1747,7 +1436,7 @@
setRepoAccessRights(repoName, repoInfo) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: `/projects/${encodeURIComponent(repoName)}/access`,
body: repoInfo,
@@ -1756,7 +1445,7 @@
},
setRepoAccessRightsForReview(projectName, projectInfo) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeURIComponent(projectName)}/access:review`,
body: projectInfo,
@@ -1773,7 +1462,7 @@
getSuggestedGroups(inputVal, opt_n, opt_errFn) {
const params = {s: inputVal};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/groups/',
errFn: opt_errFn,
params,
@@ -1793,7 +1482,7 @@
type: 'ALL',
};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/projects/',
errFn: opt_errFn,
params,
@@ -1812,7 +1501,7 @@
}
const params = {suggest: null, q: inputVal};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/',
errFn: opt_errFn,
params,
@@ -1843,7 +1532,7 @@
throw Error('Unsupported HTTP method: ' + method);
}
- return this._send({method, url, body});
+ return this._restApiHelper.send({method, url, body});
});
},
@@ -1873,7 +1562,7 @@
O: options,
q: 'status:open is:mergeable conflicts:' + changeNum,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/conflicts:*',
@@ -1895,7 +1584,7 @@
O: options,
q: query,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/change:*',
@@ -1918,7 +1607,7 @@
O: options,
q: query,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/topic:*',
@@ -1964,7 +1653,7 @@
this.getChangeActionURL(changeNum, patchNum, '/review'),
];
return Promise.all(promises).then(([, url]) => {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url,
body: review,
@@ -1998,7 +1687,7 @@
*/
createChange(project, branch, subject, opt_topic, opt_isPrivate,
opt_workInProgress, opt_baseChange, opt_baseCommit) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/changes/',
body: {
@@ -2175,7 +1864,7 @@
return this.getFromProjectLookup(changeNum).then(project => {
const url = '/accounts/self/starred.changes/' +
(project ? encodeURIComponent(project) + '~' : '') + changeNum;
- return this._send({
+ return this._restApiHelper.send({
method: starred ? 'PUT' : 'DELETE',
url,
anonymizedUrl: '/accounts/self/starred.changes/*',
@@ -2192,60 +1881,7 @@
},
/**
- * Send an XHR.
- *
- * @param {Defs.SendRequest} req
- * @return {Promise}
- */
- _send(req) {
- const options = {method: req.method};
- if (req.body) {
- options.headers = new Headers();
- options.headers.set(
- 'Content-Type', req.contentType || 'application/json');
- options.body = typeof req.body === 'string' ?
- req.body : JSON.stringify(req.body);
- }
- if (req.headers) {
- if (!options.headers) { options.headers = new Headers(); }
- for (const header in req.headers) {
- if (!req.headers.hasOwnProperty(header)) { continue; }
- options.headers.set(header, req.headers[header]);
- }
- }
- const url = req.url.startsWith('http') ?
- req.url : this.getBaseUrl() + req.url;
- const fetchReq = {
- url,
- fetchOptions: options,
- anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
- };
- const xhr = this._fetch(fetchReq).then(response => {
- if (!response.ok) {
- if (req.errFn) {
- return req.errFn.call(undefined, response);
- }
- this.fire('server-error', {request: fetchReq, response});
- }
- return response;
- }).catch(err => {
- this.fire('network-error', {error: err});
- if (req.errFn) {
- return req.errFn.call(undefined, null, err);
- } else {
- throw err;
- }
- });
-
- if (req.parseResponse) {
- return xhr.then(res => this.getResponseObject(res));
- }
-
- return xhr;
- },
-
- /**
- * Public version of the _send method preserved for plugins.
+ * Public version of the _restApiHelper.send method preserved for plugins.
*
* @param {string} method
* @param {string} url
@@ -2259,7 +1895,7 @@
*/
send(method, url, opt_body, opt_errFn, opt_contentType,
opt_headers) {
- return this._send({
+ return this._restApiHelper.send({
method,
url,
body: opt_body,
@@ -2524,7 +2160,7 @@
},
getCommitInfo(project, commit) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/projects/' + encodeURIComponent(project) +
'/commits/' + encodeURIComponent(commit),
anonymizedUrl: '/projects/*/comments/*',
@@ -2532,7 +2168,7 @@
},
_fetchB64File(url) {
- return this._fetch({url: this.getBaseUrl() + url})
+ return this._restApiHelper.fetch({url: this.getBaseUrl() + url})
.then(response => {
if (!response.ok) {
return Promise.reject(new Error(response.statusText));
@@ -2656,7 +2292,7 @@
},
deleteAccountHttpPassword() {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/password.http',
reportUrlAsIs: true,
@@ -2669,7 +2305,7 @@
* parameter.
*/
generateAccountHttpPassword() {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/password.http',
body: {generate: true},
@@ -2693,7 +2329,7 @@
contentType: 'text/plain',
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(response => {
if (response.status < 200 && response.status >= 300) {
return Promise.reject(new Error('error'));
@@ -2707,7 +2343,7 @@
},
deleteAccountSSHKey(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/sshkeys/' + id,
anonymizedUrl: '/accounts/self/sshkeys/*',
@@ -2715,7 +2351,7 @@
},
getAccountGPGKeys() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/gpgkeys',
reportUrlAsIs: true,
});
@@ -2728,7 +2364,7 @@
body: key,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(response => {
if (response.status < 200 && response.status >= 300) {
return Promise.reject(new Error('error'));
@@ -2742,7 +2378,7 @@
},
deleteAccountGPGKey(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/gpgkeys/' + id,
anonymizedUrl: '/accounts/self/gpgkeys/*',
@@ -2775,7 +2411,7 @@
body: {token},
reportUrlAsIs: true,
};
- return this._send(req).then(response => {
+ return this._restApiHelper.send(req).then(response => {
if (response.status === 204) {
return 'Email confirmed successfully.';
}
@@ -2784,7 +2420,7 @@
},
getCapabilities(opt_errFn) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/config/server/capabilities',
errFn: opt_errFn,
reportUrlAsIs: true,
@@ -2792,7 +2428,7 @@
},
getTopMenus(opt_errFn) {
- return this._fetchJSON({
+ return this._fetchSharedCacheURL({
url: '/config/server/top-menus',
errFn: opt_errFn,
reportUrlAsIs: true,
@@ -2890,7 +2526,7 @@
*/
getChange(changeNum, opt_errFn) {
// Cannot use _changeBaseURL, as this function is used by _projectLookup.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/changes/?q=change:${changeNum}`,
errFn: opt_errFn,
anonymizedUrl: '/changes/?q=change:*',
@@ -2941,7 +2577,7 @@
* Alias for _changeBaseURL.then(send).
*
* @todo(beckysiegel) clean up comments
- * @param {Defs.ChangeSendRequest} req
+ * @param {Gerrit.ChangeSendRequest} req
* @return {!Promise<!Object>}
*/
_getChangeURLAndSend(req) {
@@ -2951,7 +2587,7 @@
req.endpoint : req.anonymizedEndpoint;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
- return this._send({
+ return this._restApiHelper.send({
method: req.method,
url: url + req.endpoint,
body: req.body,
@@ -2968,7 +2604,7 @@
/**
* Alias for _changeBaseURL.then(_fetchJSON).
*
- * @param {Defs.ChangeFetchRequest} req
+ * @param {Gerrit.ChangeFetchRequest} req
* @return {!Promise<!Object>}
*/
_getChangeURLAndFetch(req) {
@@ -2977,7 +2613,7 @@
const anonymizedBaseUrl = req.patchNum ?
ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: url + req.endpoint,
errFn: req.errFn,
params: req.params,
@@ -3108,7 +2744,7 @@
},
deleteDraftComments(query) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/drafts:delete',
body: {query},
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index ef4e401e64..635e0f5065 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-rest-api-interface</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
@@ -61,93 +63,8 @@ limitations under the License.
sandbox.restore();
});
- test('JSON prefix is properly removed', done => {
- element._fetchJSON('/dummy/url').then(obj => {
- assert.deepEqual(obj, {hello: 'bonjour'});
- done();
- });
- });
-
- test('cached results', done => {
- let n = 0;
- sandbox.stub(element, '_fetchJSON', () => {
- return Promise.resolve(++n);
- });
- const promises = [];
- promises.push(element._fetchSharedCacheURL('/foo'));
- promises.push(element._fetchSharedCacheURL('/foo'));
- promises.push(element._fetchSharedCacheURL('/foo'));
-
- Promise.all(promises).then(results => {
- assert.deepEqual(results, [1, 1, 1]);
- element._fetchSharedCacheURL('/foo').then(foo => {
- assert.equal(foo, 1);
- done();
- });
- });
- });
-
- test('cached promise', done => {
- const promise = Promise.reject(new Error('foo'));
- element._cache.set('/foo', promise);
- element._fetchSharedCacheURL({url: '/foo'}).catch(p => {
- assert.equal(p.message, 'foo');
- done();
- });
- });
-
- test('cache invalidation', () => {
- element._cache.set('/foo/bar', 1);
- element._cache.set('/bar', 2);
- element._sharedFetchPromises['/foo/bar'] = 3;
- element._sharedFetchPromises['/bar'] = 4;
- element._invalidateSharedFetchPromisesPrefix('/foo/');
- assert.isFalse(element._cache.has('/foo/bar'));
- assert.isTrue(element._cache.has('/bar'));
- assert.isUndefined(element._sharedFetchPromises['/foo/bar']);
- assert.strictEqual(4, element._sharedFetchPromises['/bar']);
- });
-
- test('params are properly encoded', () => {
- let url = element._urlWithParams('/path/', {
- sp: 'hola',
- gr: 'guten tag',
- noval: null,
- });
- assert.equal(url,
- window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
-
- url = element._urlWithParams('/path/', {
- sp: 'hola',
- en: ['hey', 'hi'],
- });
- assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
-
- // Order must be maintained with array params.
- url = element._urlWithParams('/path/', {
- l: ['c', 'b', 'a'],
- });
- assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
- });
-
- test('request callbacks can be canceled', done => {
- let cancelCalled = false;
- window.fetch.returns(Promise.resolve({
- body: {
- cancel() { cancelCalled = true; },
- },
- }));
- const cancelCondition = () => { return true; };
- element._fetchJSON({url: '/dummy/url', cancelCondition}).then(
- obj => {
- assert.isUndefined(obj);
- assert.isTrue(cancelCalled);
- done();
- });
- });
-
test('parent diff comments are properly grouped', done => {
- sandbox.stub(element, '_fetchJSON', () => {
+ sandbox.stub(element._restApiHelper, 'fetchJSON', () => {
return Promise.resolve({
'/COMMIT_MSG': [],
'sieve.go': [
@@ -290,7 +207,7 @@ limitations under the License.
test('differing patch diff comments are properly grouped', done => {
sandbox.stub(element, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
- sandbox.stub(element, '_fetchJSON', request => {
+ sandbox.stub(element._restApiHelper, 'fetchJSON', request => {
const url = request.url;
if (url === '/changes/test~42/revisions/1') {
return Promise.resolve({
@@ -404,37 +321,6 @@ limitations under the License.
]);
});
- suite('rebase action', () => {
- let resolve_fetchJSON;
- setup(() => {
- sandbox.stub(element, '_fetchJSON').returns(
- new Promise(resolve => {
- resolve_fetchJSON = resolve;
- }));
- });
-
- test('no rebase on current', done => {
- element.getChangeRevisionActions('42', '1337').then(
- response => {
- assert.isTrue(response.rebase.enabled);
- assert.isFalse(response.rebase.rebaseOnCurrent);
- done();
- });
- resolve_fetchJSON({rebase: {}});
- });
-
- test('rebase on current', done => {
- element.getChangeRevisionActions('42', '1337').then(
- response => {
- assert.isTrue(response.rebase.enabled);
- assert.isTrue(response.rebase.rebaseOnCurrent);
- done();
- });
- resolve_fetchJSON({rebase: {enabled: true}});
- });
- });
-
-
test('server error', done => {
const getResponseObjectStub = sandbox.stub(element, 'getResponseObject');
window.fetch.returns(Promise.resolve({ok: false}));
@@ -442,7 +328,7 @@ limitations under the License.
element.addEventListener('server-error', resolve);
});
- element._fetchJSON({}).then(response => {
+ element._restApiHelper.fetchJSON({}).then(response => {
assert.isUndefined(response);
assert.isTrue(getResponseObjectStub.notCalled);
serverErrorEventPromise.then(() => done());
@@ -458,12 +344,31 @@ limitations under the License.
Promise.reject(new Error('Failed to fetch')));
window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse));
// Emulate logged in.
+ element._restApiHelper._cache.set('/accounts/self/detail', {});
+ const serverErrorStub = sandbox.stub();
+ element.addEventListener('server-error', serverErrorStub);
+ const authErrorStub = sandbox.stub();
+ element.addEventListener('auth-error', authErrorStub);
+ element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
+ flush(() => {
+ assert.isTrue(authErrorStub.called);
+ assert.isFalse(serverErrorStub.called);
+ assert.isFalse(element._cache.has('/accounts/self/detail'));
+ done();
+ });
+ });
+ });
+
+ test('auth failure - test all failed to fetch', done => {
+ window.fetch.returns(
+ Promise.reject(new Error('Failed to fetch')));
+ // Emulate logged in.
element._cache.set('/accounts/self/detail', {});
const serverErrorStub = sandbox.stub();
element.addEventListener('server-error', serverErrorStub);
const authErrorStub = sandbox.stub();
element.addEventListener('auth-error', authErrorStub);
- element._fetchJSON('/bar').then(r => {
+ element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
flush(() => {
assert.isTrue(authErrorStub.called);
assert.isFalse(serverErrorStub.called);
@@ -473,6 +378,15 @@ limitations under the License.
});
});
+ test('getLoggedIn returns false when network/auth failure', done => {
+ window.fetch.returns(
+ Promise.reject(new Error('Failed to fetch')));
+ element.getLoggedIn().then(isLoggedIn => {
+ assert.isFalse(isLoggedIn);
+ done();
+ });
+ });
+
test('checkCredentials', done => {
const responses = [
{
@@ -505,7 +419,8 @@ limitations under the License.
test('checkCredentials promise rejection', () => {
window.fetch.restore();
element._cache.set('/accounts/self/detail', true);
- sandbox.spy(element, 'checkCredentials');
+ const checkCredentialsSpy =
+ sandbox.spy(element._restApiHelper, 'checkCredentials');
sandbox.stub(window, 'fetch', url => {
return Promise.reject(new Error('Failed to fetch'));
});
@@ -517,13 +432,22 @@ limitations under the License.
// The second fetch call also fails, which leads to a second
// invocation of checkCredentials, which should immediately
// return instead of making further fetch calls.
- assert.isTrue(element.checkCredentials.calledTwice);
+ assert.isTrue(checkCredentialsSpy .calledTwice);
assert.isTrue(window.fetch.calledTwice);
});
});
+ test('checkCredentials accepts only json', () => {
+ const authFetchStub = sandbox.stub(element._auth, 'fetch')
+ .returns(Promise.resolve());
+ element.checkCredentials();
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
test('legacy n,z key in change url is replaced', () => {
- const stub = sandbox.stub(element, '_fetchJSON')
+ const stub = sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([]));
element.getChanges(1, null, 'n,z');
assert.equal(stub.lastCall.args[0].params.S, 0);
@@ -531,38 +455,38 @@ limitations under the License.
test('saveDiffPreferences invalidates cache line', () => {
const cacheKey = '/accounts/self/preferences.diff';
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element._cache.set(cacheKey, {tab_size: 4});
element.saveDiffPreferences({tab_size: 8});
- assert.isTrue(element._send.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(sendStub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
});
test('getAccount when resp is null does not add anything to the cache',
done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(() => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(stub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
done();
});
- element._cache.set(cacheKey, 'fake cache');
+ element._restApiHelper._cache.set(cacheKey, 'fake cache');
stub.lastCall.args[0].errFn();
});
test('getAccount does not add to the cache when resp.status is 403',
done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(() => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(stub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
done();
});
element._cache.set(cacheKey, 'fake cache');
@@ -571,15 +495,15 @@ limitations under the License.
test('getAccount when resp is successful', done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(response => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.equal(element._cache.get(cacheKey), 'fake cache');
+ assert.isTrue(stub.called);
+ assert.equal(element._restApiHelper._cache.get(cacheKey), 'fake cache');
done();
});
- element._cache.set(cacheKey, 'fake cache');
+ element._restApiHelper._cache.set(cacheKey, 'fake cache');
stub.lastCall.args[0].errFn({});
});
@@ -591,7 +515,7 @@ limitations under the License.
sandbox.stub(element, '_isNarrowScreen', () => {
return smallScreen;
});
- sandbox.stub(element, '_fetchSharedCacheURL', () => {
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL', () => {
return Promise.resolve(testJSON);
});
};
@@ -656,10 +580,10 @@ limitations under the License.
});
test('savPreferences normalizes download scheme', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.savePreferences({download_scheme: 'HTTP'});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.download_scheme, 'http');
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.download_scheme, 'http');
});
test('getDiffPreferences returns correct defaults', done => {
@@ -685,10 +609,10 @@ limitations under the License.
});
test('saveDiffPreferences set show_tabs to false', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.saveDiffPreferences({show_tabs: false});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
});
test('getEditPreferences returns correct defaults', done => {
@@ -718,34 +642,36 @@ limitations under the License.
});
test('saveEditPreferences set show_tabs to false', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.saveEditPreferences({show_tabs: false});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
});
test('confirmEmail', () => {
- sandbox.spy(element, '_send');
+ const sendStub = sandbox.spy(element._restApiHelper, 'send');
element.confirmEmail('foo');
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(sendStub.calledOnce);
+ assert.equal(sendStub.lastCall.args[0].method, 'PUT');
+ assert.equal(sendStub.lastCall.args[0].url,
'/config/server/email.confirm');
- assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
+ assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
});
test('setAccountStatus', () => {
- sandbox.stub(element, '_send').returns(Promise.resolve('OOO'));
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
+ .returns(Promise.resolve('OOO'));
element._cache.set('/accounts/self/detail', {});
return element.setAccountStatus('OOO').then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(sendStub.calledOnce);
+ assert.equal(sendStub.lastCall.args[0].method, 'PUT');
+ assert.equal(sendStub.lastCall.args[0].url,
'/accounts/self/status');
- assert.deepEqual(element._send.lastCall.args[0].body,
- {status: 'OOO'});
- assert.deepEqual(element._cache.get('/accounts/self/detail'),
+ assert.deepEqual(sendStub.lastCall.args[0].body,
{status: 'OOO'});
+ assert.deepEqual(element._restApiHelper
+ ._cache.get('/accounts/self/detail'),
+ {status: 'OOO'});
});
});
@@ -834,18 +760,20 @@ limitations under the License.
const change_num = '1';
const file_name = 'index.php';
const file_contents = '<?php';
- sandbox.stub(element, '_send').returns(
+ sandbox.stub(element._restApiHelper, 'send').returns(
Promise.resolve([change_num, file_name, file_contents]));
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, file_name, file_contents]));
element._cache.set('/changes/' + change_num + '/edit/' + file_name, {});
return element.saveChangeEdit(change_num, file_name, file_contents)
.then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(element._restApiHelper.send.calledOnce);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].method,
+ 'PUT');
+ assert.equal(element._restApiHelper.send.lastCall.args[0].url,
'/changes/test~1/edit/' + file_name);
- assert.equal(element._send.lastCall.args[0].body, file_contents);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].body,
+ file_contents);
});
});
@@ -853,17 +781,18 @@ limitations under the License.
element._projectLookup = {1: 'test'};
const change_num = '1';
const message = 'this is a commit message';
- sandbox.stub(element, '_send').returns(
+ sandbox.stub(element._restApiHelper, 'send').returns(
Promise.resolve([change_num, message]));
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, message]));
element._cache.set('/changes/' + change_num + '/message', {});
return element.putChangeCommitMessage(change_num, message).then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(element._restApiHelper.send.calledOnce);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].method, 'PUT');
+ assert.equal(element._restApiHelper.send.lastCall.args[0].url,
'/changes/test~1/message');
- assert.deepEqual(element._send.lastCall.args[0].body, {message});
+ assert.deepEqual(element._restApiHelper.send.lastCall.args[0].body,
+ {message});
});
});
@@ -919,7 +848,7 @@ limitations under the License.
});
test('createRepo encodes name', () => {
- const sendStub = sandbox.stub(element, '_send')
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
.returns(Promise.resolve());
return element.createRepo({name: 'x/y'}).then(() => {
assert.isTrue(sendStub.calledOnce);
@@ -965,64 +894,65 @@ limitations under the License.
suite('getRepos', () => {
const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
-
+ let fetchCacheURLStub;
setup(() => {
- sandbox.stub(element, '_fetchSharedCacheURL');
+ fetchCacheURLStub =
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL');
});
test('normal use', () => {
element.getRepos('test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=test');
element.getRepos(null, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
`/projects/?n=26&S=0&query=${defaultQuery}`);
element.getRepos('test', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=25&query=test');
});
test('with blank', () => {
element.getRepos('test/test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
});
test('with hyphen', () => {
element.getRepos('foo-bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with leading hyphen', () => {
element.getRepos('-bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Abar');
});
test('with trailing hyphen', () => {
element.getRepos('foo-bar-', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('hyphen only', () => {
element.getRepos('-', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
`/projects/?n=26&S=0&query=${defaultQuery}`);
});
});
@@ -1051,43 +981,45 @@ limitations under the License.
});
suite('getGroups', () => {
+ let fetchCacheURLStub;
setup(() => {
- sandbox.stub(element, '_fetchSharedCacheURL');
+ fetchCacheURLStub =
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL');
});
test('normal use', () => {
element.getGroups('test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&m=test');
element.getGroups(null, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0');
element.getGroups('test', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&m=test');
});
test('regex', () => {
element.getGroups('^test.*', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&r=%5Etest.*');
element.getGroups('^test.*', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&r=%5Etest.*');
});
});
test('gerrit auth is used', () => {
sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve());
- element._fetchJSON('foo');
+ element._restApiHelper.fetchJSON({url: 'foo'});
assert(Gerrit.Auth.fetch.called);
});
test('getSuggestedAccounts does not return _fetchJSON', () => {
- const _fetchJSONSpy = sandbox.spy(element, '_fetchJSON');
+ const _fetchJSONSpy = sandbox.spy(element._restApiHelper, 'fetchJSON');
return element.getSuggestedAccounts().then(accts => {
assert.isFalse(_fetchJSONSpy.called);
assert.equal(accts.length, 0);
@@ -1095,7 +1027,7 @@ limitations under the License.
});
test('_fetchJSON gets called by getSuggestedAccounts', () => {
- const _fetchJSONStub = sandbox.stub(element, '_fetchJSON',
+ const _fetchJSONStub = sandbox.stub(element._restApiHelper, 'fetchJSON',
() => Promise.resolve());
return element.getSuggestedAccounts('own').then(() => {
assert.deepEqual(_fetchJSONStub.lastCall.args[0].params, {
@@ -1167,24 +1099,35 @@ limitations under the License.
const errFn = sinon.stub();
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(''));
- sandbox.stub(element, '_fetchRawJSON')
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
.returns(Promise.resolve({ok: false, status: 500}));
return element._getChangeDetail(123, '516714', errFn).then(() => {
assert.isTrue(errFn.called);
});
});
+ test('_getChangeDetail accepts only json', () => {
+ const authFetchStub = sandbox.stub(element._auth, 'fetch')
+ .returns(Promise.resolve());
+ const errFn = sinon.stub();
+ element._getChangeDetail(123, '516714', errFn);
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
test('_getChangeDetail populates _projectLookup', () => {
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(''));
- sandbox.stub(element, '_fetchRawJSON')
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
.returns(Promise.resolve({ok: true}));
const mockResponse = {_number: 1, project: 'test'};
- sandbox.stub(element, '_readResponsePayload').returns(Promise.resolve({
- parsed: mockResponse,
- raw: JSON.stringify(mockResponse),
- }));
+ sandbox.stub(element._restApiHelper, 'readResponsePayload')
+ .returns(Promise.resolve({
+ parsed: mockResponse,
+ raw: JSON.stringify(mockResponse),
+ }));
return element._getChangeDetail(1, '516714').then(() => {
assert.equal(Object.keys(element._projectLookup).length, 1);
assert.equal(element._projectLookup[1], 'test');
@@ -1202,7 +1145,8 @@ limitations under the License.
const mockResponse = {foo: 'bar', baz: 42};
mockResponseSerial = element.JSON_PREFIX +
JSON.stringify(mockResponse);
- sandbox.stub(element, '_urlWithParams').returns(requestUrl);
+ sandbox.stub(element._restApiHelper, 'urlWithParams')
+ .returns(requestUrl);
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(requestUrl));
collectSpy = sandbox.spy(element._etags, 'collect');
@@ -1210,11 +1154,12 @@ limitations under the License.
});
test('contributes to cache', () => {
- sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
- text: () => Promise.resolve(mockResponseSerial),
- status: 200,
- ok: true,
- }));
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
+ .returns(Promise.resolve({
+ text: () => Promise.resolve(mockResponseSerial),
+ status: 200,
+ ok: true,
+ }));
return element._getChangeDetail(123, '516714').then(detail => {
assert.isFalse(getPayloadSpy.called);
@@ -1225,11 +1170,12 @@ limitations under the License.
});
test('uses cache on HTTP 304', () => {
- sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
- text: () => Promise.resolve(mockResponseSerial),
- status: 304,
- ok: true,
- }));
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
+ .returns(Promise.resolve({
+ text: () => Promise.resolve(mockResponseSerial),
+ status: 304,
+ ok: true,
+ }));
return element._getChangeDetail(123, {}).then(detail => {
assert.isFalse(collectSpy.called);
@@ -1274,7 +1220,7 @@ limitations under the License.
suite('getChanges populates _projectLookup', () => {
test('multiple queries', () => {
- sandbox.stub(element, '_fetchJSON')
+ sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([
[
{_number: 1, project: 'test'},
@@ -1294,7 +1240,7 @@ limitations under the License.
});
test('no query', () => {
- sandbox.stub(element, '_fetchJSON')
+ sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([
{_number: 1, project: 'test'},
{_number: 2, project: 'test'},
@@ -1314,7 +1260,7 @@ limitations under the License.
test('_getChangeURLAndFetch', () => {
element._projectLookup = {1: 'test'};
- const fetchStub = sandbox.stub(element, '_fetchJSON')
+ const fetchStub = sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve());
const req = {changeNum: 1, endpoint: '/test', patchNum: 1};
return element._getChangeURLAndFetch(req).then(() => {
@@ -1325,7 +1271,7 @@ limitations under the License.
test('_getChangeURLAndSend', () => {
element._projectLookup = {1: 'test'};
- const sendStub = sandbox.stub(element, '_send')
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
.returns(Promise.resolve());
const req = {
@@ -1347,16 +1293,17 @@ limitations under the License.
const mockObject = {foo: 'bar', baz: 'foo'};
const serial = element.JSON_PREFIX + JSON.stringify(mockObject);
const mockResponse = {text: () => Promise.resolve(serial)};
- return element._readResponsePayload(mockResponse).then(payload => {
- assert.deepEqual(payload.parsed, mockObject);
- assert.equal(payload.raw, serial);
- });
+ return element._restApiHelper.readResponsePayload(mockResponse)
+ .then(payload => {
+ assert.deepEqual(payload.parsed, mockObject);
+ assert.equal(payload.raw, serial);
+ });
});
test('_parsePrefixedJSON', () => {
const obj = {x: 3, y: {z: 4}, w: 23};
const serial = element.JSON_PREFIX + JSON.stringify(obj);
- const result = element._parsePrefixedJSON(serial);
+ const result = element._restApiHelper.parsePrefixedJSON(serial);
assert.deepEqual(result, obj);
});
});
@@ -1378,7 +1325,7 @@ limitations under the License.
});
test('generateAccountHttpPassword', () => {
- const sendSpy = sandbox.spy(element, '_send');
+ const sendSpy = sandbox.spy(element._restApiHelper, 'send');
return element.generateAccountHttpPassword().then(() => {
assert.isTrue(sendSpy.calledOnce);
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
@@ -1463,11 +1410,12 @@ limitations under the License.
});
test('getDashboard', () => {
- const fetchStub = sandbox.stub(element, '_fetchSharedCacheURL');
+ const fetchCacheURLStub = sandbox.stub(element._restApiHelper,
+ 'fetchCacheURL');
element.getDashboard('gerrit/project', 'default:main');
- assert.isTrue(fetchStub.calledOnce);
+ assert.isTrue(fetchCacheURLStub.calledOnce);
assert.equal(
- fetchStub.lastCall.args[0].url,
+ fetchCacheURLStub.lastCall.args[0].url,
'/projects/gerrit%2Fproject/dashboards/default%3Amain');
});
@@ -1535,7 +1483,7 @@ limitations under the License.
});
test('_fetch forwards request and logs', () => {
- const logStub = sandbox.stub(element, '_logCall');
+ const logStub = sandbox.stub(element._restApiHelper, '_logCall');
const response = {status: 404, text: sinon.stub()};
const url = 'my url';
const fetchOptions = {method: 'DELETE'};
@@ -1543,7 +1491,7 @@ limitations under the License.
const startTime = 123;
sandbox.stub(Date, 'now').returns(startTime);
const req = {url, fetchOptions};
- return element._fetch(req).then(() => {
+ return element._restApiHelper.fetch(req).then(() => {
assert.isTrue(logStub.calledOnce);
assert.isTrue(logStub.calledWith(req, startTime, response.status));
assert.isFalse(response.text.called);
@@ -1555,10 +1503,11 @@ limitations under the License.
const handler = sinon.stub();
element.addEventListener('rpc-log', handler);
- element._logCall({url: 'url'}, 100, 200);
+ element._restApiHelper._logCall({url: 'url'}, 100, 200);
assert.isFalse(handler.called);
- element._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
+ element._restApiHelper
+ ._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
flushAsynchronousOperations();
assert.isTrue(handler.calledOnce);
});
@@ -1567,7 +1516,7 @@ limitations under the License.
sandbox.stub(element, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
const sendStub =
- sandbox.stub(element, '_send').returns(Promise.resolve());
+ sandbox.stub(element._restApiHelper, 'send').returns(Promise.resolve());
await element.saveChangeStarred(123, true);
assert.isTrue(sendStub.calledOnce);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
new file mode 100644
index 0000000000..3908a001bb
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
@@ -0,0 +1,431 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ const JSON_PREFIX = ')]}\'';
+ const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
+
+ /**
+ * Wrapper around Map for caching server responses. Site-based so that
+ * changes to CANONICAL_PATH will result in a different cache going into
+ * effect.
+ */
+ class SiteBasedCache {
+ constructor() {
+ // Container of per-canonical-path caches.
+ this._data = new Map();
+ if (window.INITIAL_DATA != undefined) {
+ // Put all data shipped with index.html into the cache. This makes it
+ // so that we spare more round trips to the server when the app loads
+ // initially.
+ Object
+ .entries(window.INITIAL_DATA)
+ .forEach(e => this._cache().set(e[0], e[1]));
+ }
+ }
+
+ // Returns the cache for the current canonical path.
+ _cache() {
+ if (!this._data.has(window.CANONICAL_PATH)) {
+ this._data.set(window.CANONICAL_PATH, new Map());
+ }
+ return this._data.get(window.CANONICAL_PATH);
+ }
+
+ has(key) {
+ return this._cache().has(key);
+ }
+
+ get(key) {
+ return this._cache().get(key);
+ }
+
+ set(key, value) {
+ this._cache().set(key, value);
+ }
+
+ delete(key) {
+ this._cache().delete(key);
+ }
+
+ invalidatePrefix(prefix) {
+ const newMap = new Map();
+ for (const [key, value] of this._cache().entries()) {
+ if (!key.startsWith(prefix)) {
+ newMap.set(key, value);
+ }
+ }
+ this._data.set(window.CANONICAL_PATH, newMap);
+ }
+ }
+
+ class FetchPromisesCache {
+ constructor() {
+ this._data = {};
+ }
+
+ has(key) {
+ return !!this._data[key];
+ }
+
+ get(key) {
+ return this._data[key];
+ }
+
+ set(key, value) {
+ this._data[key] = value;
+ }
+
+ invalidatePrefix(prefix) {
+ const newData = {};
+ Object.entries(this._data).forEach(([key, value]) => {
+ if (!key.startsWith(prefix)) {
+ newData[key] = value;
+ }
+ });
+ this._data = newData;
+ }
+ }
+
+ class GrRestApiHelper {
+ /**
+ * @param {SiteBasedCache} cache
+ * @param {object} auth
+ * @param {FetchPromisesCache} fetchPromisesCache
+ * @param {object} credentialCheck
+ * @param {object} restApiInterface
+ */
+ constructor(cache, auth, fetchPromisesCache, credentialCheck,
+ restApiInterface) {
+ this._cache = cache;// TODO: make it public
+ this._auth = auth;
+ this._fetchPromisesCache = fetchPromisesCache;
+ this._credentialCheck = credentialCheck;
+ this._restApiInterface = restApiInterface;
+ }
+
+ /**
+ * Wraps calls to the underlying authenticated fetch function (_auth.fetch)
+ * with timing and logging.
+ *
+ * @param {Gerrit.FetchRequest} req
+ */
+ fetch(req) {
+ const start = Date.now();
+ const xhr = this._auth.fetch(req.url, req.fetchOptions);
+
+ // Log the call after it completes.
+ xhr.then(res => this._logCall(req, start, res ? res.status : null));
+
+ // Return the XHR directly (without the log).
+ return xhr;
+ }
+
+ /**
+ * Log information about a REST call. Because the elapsed time is determined
+ * by this method, it should be called immediately after the request
+ * finishes.
+ *
+ * @param {Gerrit.FetchRequest} req
+ * @param {number} startTime the time that the request was started.
+ * @param {number} status the HTTP status of the response. The status value
+ * is used here rather than the response object so there is no way this
+ * method can read the body stream.
+ */
+ _logCall(req, startTime, status) {
+ const method = (req.fetchOptions && req.fetchOptions.method) ?
+ req.fetchOptions.method : 'GET';
+ const endTime = Date.now();
+ const elapsed = (endTime - startTime);
+ const startAt = new Date(startTime);
+ const endAt = new Date(endTime);
+ console.log([
+ 'HTTP',
+ status,
+ method,
+ elapsed + 'ms',
+ req.anonymizedUrl || req.url,
+ `(${startAt.toISOString()}, ${endAt.toISOString()})`,
+ ].join(' '));
+ if (req.anonymizedUrl) {
+ this.fire('rpc-log',
+ {status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
+ }
+ }
+
+ /**
+ * Fetch JSON from url provided.
+ * Returns a Promise that resolves to a native Response.
+ * Doesn't do error checking. Supports cancel condition. Performs auth.
+ * Validates auth expiry errors.
+ *
+ * @param {Gerrit.FetchJSONRequest} req
+ */
+ fetchRawJSON(req) {
+ const urlWithParams = this.urlWithParams(req.url, req.params);
+ const fetchReq = {
+ url: urlWithParams,
+ fetchOptions: req.fetchOptions,
+ anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
+ };
+ return this.fetch(fetchReq).then(res => {
+ if (req.cancelCondition && req.cancelCondition()) {
+ res.body.cancel();
+ return;
+ }
+ return res;
+ }).catch(err => {
+ const isLoggedIn = !!this._cache.get('/accounts/self/detail');
+ if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) {
+ this.checkCredentials();
+ } else {
+ if (req.errFn) {
+ req.errFn.call(undefined, null, err);
+ } else {
+ this.fire('network-error', {error: err});
+ }
+ }
+ throw err;
+ });
+ }
+
+ /**
+ * Fetch JSON from url provided.
+ * Returns a Promise that resolves to a parsed response.
+ * Same as {@link fetchRawJSON}, plus error handling.
+ *
+ * @param {Gerrit.FetchJSONRequest} req
+ */
+ fetchJSON(req) {
+ req = this.addAcceptJsonHeader(req);
+ return this.fetchRawJSON(req).then(response => {
+ if (!response) {
+ return;
+ }
+ if (!response.ok) {
+ if (req.errFn) {
+ req.errFn.call(null, response);
+ return;
+ }
+ this.fire('server-error', {request: req, response});
+ return;
+ }
+ return response && this.getResponseObject(response);
+ });
+ }
+
+ /**
+ * @param {string} url
+ * @param {?Object|string=} opt_params URL params, key-value hash.
+ * @return {string}
+ */
+ urlWithParams(url, opt_params) {
+ if (!opt_params) { return this.getBaseUrl() + url; }
+
+ const params = [];
+ for (const p in opt_params) {
+ if (!opt_params.hasOwnProperty(p)) { continue; }
+ if (opt_params[p] == null) {
+ params.push(encodeURIComponent(p));
+ continue;
+ }
+ for (const value of [].concat(opt_params[p])) {
+ params.push(`${encodeURIComponent(p)}=${encodeURIComponent(value)}`);
+ }
+ }
+ return this.getBaseUrl() + url + '?' + params.join('&');
+ }
+
+ /**
+ * @param {!Object} response
+ * @return {?}
+ */
+ getResponseObject(response) {
+ return this.readResponsePayload(response)
+ .then(payload => payload.parsed);
+ }
+
+ /**
+ * @param {!Object} response
+ * @return {!Object}
+ */
+ readResponsePayload(response) {
+ return response.text().then(text => {
+ let result;
+ try {
+ result = this.parsePrefixedJSON(text);
+ } catch (_) {
+ result = null;
+ }
+ return {parsed: result, raw: text};
+ });
+ }
+
+ /**
+ * @param {string} source
+ * @return {?}
+ */
+ parsePrefixedJSON(source) {
+ return JSON.parse(source.substring(JSON_PREFIX.length));
+ }
+
+ /**
+ * @param {Gerrit.FetchJSONRequest} req
+ * @return {Gerrit.FetchJSONRequest}
+ */
+ addAcceptJsonHeader(req) {
+ if (!req.fetchOptions) req.fetchOptions = {};
+ if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
+ if (!req.fetchOptions.headers.has('Accept')) {
+ req.fetchOptions.headers.append('Accept', 'application/json');
+ }
+ return req;
+ }
+
+ getBaseUrl() {
+ return this._restApiInterface.getBaseUrl();
+ }
+
+ fire(type, detail, options) {
+ return this._restApiInterface.fire(type, detail, options);
+ }
+
+ /**
+ * @param {Gerrit.FetchJSONRequest} req
+ */
+ fetchCacheURL(req) {
+ if (this._fetchPromisesCache.has(req.url)) {
+ return this._fetchPromisesCache.get(req.url);
+ }
+ // TODO(andybons): Periodic cache invalidation.
+ if (this._cache.has(req.url)) {
+ return Promise.resolve(this._cache.get(req.url));
+ }
+ this._fetchPromisesCache.set(req.url,
+ this.fetchJSON(req).then(response => {
+ if (response !== undefined) {
+ this._cache.set(req.url, response);
+ }
+ this._fetchPromisesCache.set(req.url, undefined);
+ return response;
+ }).catch(err => {
+ this._fetchPromisesCache.set(req.url, undefined);
+ throw err;
+ })
+ );
+ return this._fetchPromisesCache.get(req.url);
+ }
+
+ /**
+ * Send an XHR.
+ *
+ * @param {Gerrit.SendRequest} req
+ * @return {Promise}
+ */
+ send(req) {
+ const options = {method: req.method};
+ if (req.body) {
+ options.headers = new Headers();
+ options.headers.set(
+ 'Content-Type', req.contentType || 'application/json');
+ options.body = typeof req.body === 'string' ?
+ req.body : JSON.stringify(req.body);
+ }
+ if (req.headers) {
+ if (!options.headers) { options.headers = new Headers(); }
+ for (const header in req.headers) {
+ if (!req.headers.hasOwnProperty(header)) { continue; }
+ options.headers.set(header, req.headers[header]);
+ }
+ }
+ const url = req.url.startsWith('http') ?
+ req.url : this.getBaseUrl() + req.url;
+ const fetchReq = {
+ url,
+ fetchOptions: options,
+ anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
+ };
+ const xhr = this.fetch(fetchReq).then(response => {
+ if (!response.ok) {
+ if (req.errFn) {
+ return req.errFn.call(undefined, response);
+ }
+ this.fire('server-error', {request: fetchReq, response});
+ }
+ return response;
+ }).catch(err => {
+ this.fire('network-error', {error: err});
+ if (req.errFn) {
+ return req.errFn.call(undefined, null, err);
+ } else {
+ throw err;
+ }
+ });
+
+ if (req.parseResponse) {
+ return xhr.then(res => this.getResponseObject(res));
+ }
+
+ return xhr;
+ }
+
+ checkCredentials() {
+ if (this._credentialCheck.checking) {
+ return;
+ }
+ this._credentialCheck.checking = true;
+ let req = {url: '/accounts/self/detail', reportUrlAsIs: true};
+ req = this.addAcceptJsonHeader(req);
+ // Skip the REST response cache.
+ return this.fetchRawJSON(req).then(res => {
+ if (!res) { return; }
+ if (res.status === 403) {
+ this.fire('auth-error');
+ this._cache.delete('/accounts/self/detail');
+ } else if (res.ok) {
+ return this.getResponseObject(res);
+ }
+ }).then(res => {
+ this._credentialCheck.checking = false;
+ if (res) {
+ this._cache.set('/accounts/self/detail', res);
+ }
+ return res;
+ }).catch(err => {
+ this._credentialCheck.checking = false;
+ if (err && err.message === FAILED_TO_FETCH_ERROR) {
+ this.fire('auth-error');
+ this._cache.delete('/accounts/self/detail');
+ }
+ });
+ }
+
+ /**
+ * @param {string} prefix
+ */
+ invalidateFetchPromisesPrefix(prefix) {
+ this._fetchPromisesCache.invalidatePrefix(prefix);
+ this._cache.invalidatePrefix(prefix);
+ }
+ }
+
+ window.SiteBasedCache = SiteBasedCache;
+ window.FetchPromisesCache = FetchPromisesCache;
+ window.GrRestApiHelper = GrRestApiHelper;
+})(window);
+
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
new file mode 100644
index 0000000000..4eaf1bcdcd
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-rest-api-helper</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../../test/common-test-setup.html"/>
+<script src="../../../../scripts/util.js"></script>
+<script src="../gr-auth.js"></script>
+<script src="gr-rest-api-helper.js"></script>
+
+<script>void(0);</script>
+
+<script>
+ suite('gr-rest-api-helper tests', () => {
+ let helper;
+ let sandbox;
+ let cache;
+ let fetchPromisesCache;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ cache = new SiteBasedCache();
+ fetchPromisesCache = new FetchPromisesCache();
+ const credentialCheck = {checking: false};
+
+ window.CANONICAL_PATH = 'testhelper';
+
+ const mockRestApiInterface = {
+ getBaseUrl: sinon.stub().returns(window.CANONICAL_PATH),
+ fire: sinon.stub(),
+ };
+
+ const testJSON = ')]}\'\n{"hello": "bonjour"}';
+ sandbox.stub(window, 'fetch').returns(Promise.resolve({
+ ok: true,
+ text() {
+ return Promise.resolve(testJSON);
+ },
+ }));
+
+ helper = new GrRestApiHelper(cache, Gerrit.Auth, fetchPromisesCache,
+ credentialCheck, mockRestApiInterface);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ suite('fetchJSON()', () => {
+ test('Sets header to accept application/json', () => {
+ const authFetchStub = sandbox.stub(helper._auth, 'fetch')
+ .returns(Promise.resolve());
+ helper.fetchJSON({url: '/dummy/url'});
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
+ test('Use header option accept when provided', () => {
+ const authFetchStub = sandbox.stub(helper._auth, 'fetch')
+ .returns(Promise.resolve());
+ const headers = new Headers();
+ headers.append('Accept', '*/*');
+ const fetchOptions = {headers};
+ helper.fetchJSON({url: '/dummy/url', fetchOptions});
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ '*/*');
+ });
+ });
+
+ test('JSON prefix is properly removed', done => {
+ helper.fetchJSON({url: '/dummy/url'}).then(obj => {
+ assert.deepEqual(obj, {hello: 'bonjour'});
+ done();
+ });
+ });
+
+ test('cached results', done => {
+ let n = 0;
+ sandbox.stub(helper, 'fetchJSON', () => {
+ return Promise.resolve(++n);
+ });
+ const promises = [];
+ promises.push(helper.fetchCacheURL('/foo'));
+ promises.push(helper.fetchCacheURL('/foo'));
+ promises.push(helper.fetchCacheURL('/foo'));
+
+ Promise.all(promises).then(results => {
+ assert.deepEqual(results, [1, 1, 1]);
+ helper.fetchCacheURL('/foo').then(foo => {
+ assert.equal(foo, 1);
+ done();
+ });
+ });
+ });
+
+ test('cached promise', done => {
+ const promise = Promise.reject(new Error('foo'));
+ cache.set('/foo', promise);
+ helper.fetchCacheURL({url: '/foo'}).catch(p => {
+ assert.equal(p.message, 'foo');
+ done();
+ });
+ });
+
+ test('cache invalidation', () => {
+ cache.set('/foo/bar', 1);
+ cache.set('/bar', 2);
+ fetchPromisesCache.set('/foo/bar', 3);
+ fetchPromisesCache.set('/bar', 4);
+ helper.invalidateFetchPromisesPrefix('/foo/');
+ assert.isFalse(cache.has('/foo/bar'));
+ assert.isTrue(cache.has('/bar'));
+ assert.isUndefined(fetchPromisesCache.get('/foo/bar'));
+ assert.strictEqual(4, fetchPromisesCache.get('/bar'));
+ });
+
+ test('params are properly encoded', () => {
+ let url = helper.urlWithParams('/path/', {
+ sp: 'hola',
+ gr: 'guten tag',
+ noval: null,
+ });
+ assert.equal(url,
+ window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
+
+ url = helper.urlWithParams('/path/', {
+ sp: 'hola',
+ en: ['hey', 'hi'],
+ });
+ assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
+
+ // Order must be maintained with array params.
+ url = helper.urlWithParams('/path/', {
+ l: ['c', 'b', 'a'],
+ });
+ assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
+ });
+
+ test('request callbacks can be canceled', done => {
+ let cancelCalled = false;
+ window.fetch.returns(Promise.resolve({
+ body: {
+ cancel() { cancelCalled = true; },
+ },
+ }));
+ const cancelCondition = () => { return true; };
+ helper.fetchJSON({url: '/dummy/url', cancelCondition}).then(
+ obj => {
+ assert.isUndefined(obj);
+ assert.isTrue(cancelCalled);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
index 8ddb255f90..d883ef6f5a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
@@ -21,7 +21,6 @@
if (window.GrReviewerUpdatesParser) { return; }
function GrReviewerUpdatesParser(change) {
- // TODO (viktard): Polyfill Object.assign for IE.
this.result = Object.assign({}, change);
this._lastState = {};
}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index 202c52a246..fdf79afcbc 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-reviewer-updates-parser</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<script src="../../../scripts/util.js"></script>
<script src="gr-reviewer-updates-parser.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
index 05c2ceee5b..015d71e7ad 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/mock-diff-response_test.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../test/common-test-setup.html"/>
<dom-module id="mock-diff-response">
<template></template>
@@ -153,7 +153,7 @@ limitations under the License.
Polymer({
is: 'mock-diff-response',
- _legacyUndefinedCheck: true,
+
properties: {
diffResponse: {
type: Object,
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.html b/polygerrit-ui/app/elements/shared/gr-select/gr-select.html
index e73d41cf9a..f1ef86ac4e 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.html
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.html
@@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+
<dom-module id="gr-select">
<slot></slot>
<script src="gr-select.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
index 85e1a616a1..05267bac75 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
@@ -19,7 +19,7 @@
Polymer({
is: 'gr-select',
- _legacyUndefinedCheck: true,
+
properties: {
bindValue: {
type: String,
@@ -28,6 +28,10 @@
},
},
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
listeners: {
'change': '_valueChanged',
'dom-change': '_updateValue',
@@ -55,9 +59,15 @@
this.bindValue = this.nativeSelect.value;
},
+ focus() {
+ this.nativeSelect.focus();
+ },
+
ready() {
// If not set via the property, set bind-value to the element value.
- if (!this.bindValue) { this.bindValue = this.nativeSelect.value; }
+ if (this.bindValue == undefined && this.nativeSelect.options.length > 0) {
+ this.bindValue = this.nativeSelect.value;
+ }
},
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
index 1748ec062a..b3abe5fc9b 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-select</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-select.html">
@@ -38,6 +40,15 @@ limitations under the License.
</template>
</test-fixture>
+<test-fixture id="noOptions">
+ <template>
+ <gr-select>
+ <select>
+ </select>
+ </gr-select>
+ </template>
+</test-fixture>
+
<script>
suite('gr-select tests', () => {
let element;
@@ -46,6 +57,10 @@ limitations under the License.
element = fixture('basic');
});
+ test('bindValue must be set to the first option value', () => {
+ assert.equal(element.bindValue, '1');
+ });
+
test('value of 0 should still trigger value updates', () => {
element.bindValue = 0;
assert.equal(element.nativeSelect.value, 0);
@@ -88,4 +103,16 @@ limitations under the License.
assert.isTrue(changeStub.called);
});
});
+
+ suite('gr-select no options tests', () => {
+ let element;
+
+ setup(() => {
+ element = fixture('noOptions');
+ });
+
+ test('bindValue must not be changed', () => {
+ assert.isUndefined(element.bindValue);
+ });
+ });
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html
index fe6ed88b4e..15e282f457 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
@@ -23,25 +23,30 @@ limitations under the License.
<template>
<style include="shared-styles">
.commandContainer {
- margin-bottom: .75em;
+ margin-bottom: var(--spacing-m);
}
.commandContainer {
background-color: var(--shell-command-background-color);
- padding: .5em .5em .5em 2.5em;
+ /* Should be spacing-m larger than the :before width. */
+ padding: var(--spacing-m) var(--spacing-m) var(--spacing-m) calc(3*var(--spacing-m) + 0.5em);
position: relative;
width: 100%;
}
.commandContainer:before {
- background: var(--shell-command-decoration-background-color);
- bottom: 0;
- box-sizing: border-box;
content: '$';
- display: block;
- left: 0;
- padding: .8em;
position: absolute;
+ display: block;
+ box-sizing: border-box;
+ background: var(--shell-command-decoration-background-color);
top: 0;
- width: 2em;
+ bottom: 0;
+ left: 0;
+ /* Should be spacing-m smaller than the .commandContainer padding-left. */
+ width: calc(2*var(--spacing-m) + 0.5em);
+ /* Should vertically match the padding of .commandContainer. */
+ padding: var(--spacing-m);
+ /* Should roughly match the height of .commandContainer without padding. */
+ line-height: 26px;
}
.commandContainer gr-copy-clipboard {
--text-container-style: {
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
index 901b8ce2c0..2c546cc250 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-shell-command',
- _legacyUndefinedCheck: true,
properties: {
command: String,
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
index a49f76fc1d..3f2f8bab4b 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-shell-command</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-shell-command.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
index 6fc2f3f411..7215b26939 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="gr-storage">
<script src="gr-storage.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index 83cd06cb81..f6ade6e4f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -30,7 +30,6 @@
Polymer({
is: 'gr-storage',
- _legacyUndefinedCheck: true,
properties: {
_lastCleanup: Number,
@@ -76,14 +75,6 @@
this._storage.removeItem(this._getEditableContentKey(key));
},
- getPreferences() {
- return this._getObject('localPrefs');
- },
-
- savePreferences(localPrefs) {
- this._setObject('localPrefs', localPrefs || null);
- },
-
_getDraftKey(location) {
const range = location.range ?
`${location.range.start_line}-${location.range.start_character}` +
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
index 6b89af2654..0482584fa3 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-storage</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-storage.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index 10c91112ac..6803eb93b1 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -14,25 +14,36 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
-<link rel="import" href="../../../bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<dom-module id="gr-textarea">
<template>
<style include="shared-styles">
:host {
- display: block;
+ display: flex;
position: relative;
}
:host(.monospace) {
font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ font-weight: var(--font-weight-normal);
+ }
+ :host(.code) {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-code);
+ font-weight: var(--font-weight-normal);
}
#emojiSuggestions {
font-family: var(--font-family);
@@ -54,6 +65,9 @@ limitations under the License.
iron-autogrow-textarea {
padding: 2px;
position: relative;
+
+ /** This is needed for firefox */
+ --iron-autogrow-textarea_-_white-space: pre-wrap;
}
#textarea.noBorder {
border: none;
@@ -92,6 +106,7 @@ limitations under the License.
max-rows="[[maxRows]]"
value="{{text}}"
on-bind-value-changed="_onValueChanged"></iron-autogrow-textarea>
+ <gr-reporting id="reporting"></gr-reporting>
</template>
<script src="gr-textarea.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index 7929fbec6d..4c4b0389ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -20,41 +20,40 @@
const MAX_ITEMS_DROPDOWN = 10;
const ALL_SUGGESTIONS = [
+ {value: '😊', match: 'smile :)'},
+ {value: '👍', match: 'thumbs up'},
+ {value: '😄', match: 'laugh :D'},
+ {value: '🎉', match: 'party'},
+ {value: '😞', match: 'sad :('},
+ {value: '😂', match: 'tears :\')'},
+ {value: '🙏', match: 'pray'},
+ {value: '😐', match: 'neutral :|'},
+ {value: '😮', match: 'shock :O'},
+ {value: '👎', match: 'thumbs down'},
+ {value: '😎', match: 'cool |;)'},
+ {value: '😕', match: 'confused'},
+ {value: '👌', match: 'ok'},
+ {value: '🔥', match: 'fire'},
+ {value: '👊', match: 'fistbump'},
{value: '💯', match: '100'},
{value: '💔', match: 'broken heart'},
{value: '🍺', match: 'beer'},
{value: '✔', match: 'check'},
- {value: '😎', match: 'cool'},
- {value: '😕', match: 'confused'},
- {value: '😭', match: 'crying'},
- {value: '🔥', match: 'fire'},
- {value: '👊', match: 'fistbump'},
+ {value: '😋', match: 'tongue'},
+ {value: '😭', match: 'crying :\'('},
{value: '🐨', match: 'koala'},
- {value: '😄', match: 'laugh'},
{value: '🤓', match: 'glasses'},
{value: '😆', match: 'grin'},
- {value: '😐', match: 'neutral'},
- {value: '👌', match: 'ok'},
- {value: '🎉', match: 'party'},
{value: '💩', match: 'poop'},
- {value: '🙏', match: 'pray'},
- {value: '😞', match: 'sad'},
- {value: '😮', match: 'shock'},
- {value: '😊', match: 'smile'},
{value: '😢', match: 'tear'},
- {value: '😂', match: 'tears'},
- {value: '😋', match: 'tongue'},
- {value: '👍', match: 'thumbs up'},
- {value: '👎', match: 'thumbs down'},
{value: '😒', match: 'unamused'},
- {value: '😉', match: 'wink'},
+ {value: '😉', match: 'wink ;)'},
{value: '🍷', match: 'wine'},
- {value: '😜', match: 'winking tongue'},
+ {value: '😜', match: 'winking tongue ;)'},
];
Polymer({
is: 'gr-textarea',
- _legacyUndefinedCheck: true,
/**
* @event bind-value-changed
@@ -75,15 +74,21 @@
type: Boolean,
value: false,
},
+ /** Text input should be rendered in monspace font. */
monospace: {
type: Boolean,
value: false,
},
+ /** Text input should be rendered in code font, which is smaller than the
+ standard monospace font. */
+ code: {
+ type: Boolean,
+ value: false,
+ },
/** @type(?number) */
_colonIndex: Number,
_currentSearchString: {
type: String,
- value: '',
observer: '_determineSuggestions',
},
_hideAutocomplete: {
@@ -101,6 +106,7 @@
},
behaviors: [
+ Gerrit.FireBehavior,
Gerrit.KeyboardShortcutBehavior,
],
@@ -113,10 +119,12 @@
},
ready() {
- this._resetEmojiDropdown();
if (this.monospace) {
this.classList.add('monospace');
}
+ if (this.code) {
+ this.classList.add('code');
+ }
if (this.hideBorder) {
this.$.textarea.classList.add('noBorder');
}
@@ -153,6 +161,7 @@
e.stopPropagation();
this.$.emojiSuggestions.cursorUp();
this.$.textarea.textarea.focus();
+ this.disableEnterKeyForSelectingEmoji = false;
},
_handleDownKey(e) {
@@ -161,24 +170,34 @@
e.stopPropagation();
this.$.emojiSuggestions.cursorDown();
this.$.textarea.textarea.focus();
+ this.disableEnterKeyForSelectingEmoji = false;
},
_handleEnterByKey(e) {
- if (this._hideAutocomplete) { return; }
+ if (this._hideAutocomplete || this.disableEnterKeyForSelectingEmoji) {
+ return;
+ }
e.preventDefault();
e.stopPropagation();
- this.text = this._getText(this.$.emojiSuggestions.getCurrentText());
- this._resetEmojiDropdown();
+ this._setEmoji(this.$.emojiSuggestions.getCurrentText());
},
_handleEmojiSelect(e) {
- this.text = this._getText(e.detail.selected.dataset.value);
+ this._setEmoji(e.detail.selected.dataset.value);
+ },
+
+ _setEmoji(text) {
+ const colonIndex = this._colonIndex;
+ this.text = this._getText(text);
+ this.$.textarea.selectionStart = colonIndex + 1;
+ this.$.textarea.selectionEnd = colonIndex + 1;
+ this.$.reporting.reportInteraction('select-emoji');
this._resetEmojiDropdown();
},
_getText(value) {
return this.text.substr(0, this._colonIndex || 0) +
- value + this.text.substr(this.$.textarea.selectionStart) + ' ';
+ value + this.text.substr(this.$.textarea.selectionStart);
},
/**
* Uses a hidden element with the same width and styling of the textarea and
@@ -218,41 +237,45 @@
// If cursor is not in textarea (just opened with colon as last char),
// Don't do anything.
if (!e.currentTarget.focused) { return; }
- const newChar = e.detail.value[this.$.textarea.selectionStart - 1];
- // When a colon is detected, set a colon index, but don't do anything else
- // yet.
- if (newChar === ':') {
- this._colonIndex = this.$.textarea.selectionStart - 1;
- // If the colon index exists, continue to determine what needs to be done
- // with the dropdown. It may be open or closed at this point.
- } else if (this._colonIndex !== null) {
- // The search string is a substring of the textarea's value from (1
- // position after) the colon index to the cursor position.
- this._currentSearchString = e.detail.value.substr(this._colonIndex + 1,
- this.$.textarea.selectionStart);
- // Under the following conditions, close and reset the dropdown:
- // - The cursor is no longer at the end of the current search string
- // - The search string is an space or new line
- // - The colon has been removed
- // - There are no suggestions that match the search string
- if (this.$.textarea.selectionStart !==
- this._currentSearchString.length + this._colonIndex + 1 ||
- this._currentSearchString === ' ' ||
- this._currentSearchString === '\n' ||
- !(e.detail.value[this._colonIndex] === ':') ||
- !this._suggestions.length) {
- this._resetEmojiDropdown();
- // Otherwise open the dropdown and set the position to be just below the
- // cursor.
- } else if (this.$.emojiSuggestions.isHidden) {
- this._updateCaratPosition();
+ const charAtCursor = e.detail && e.detail.value ?
+ e.detail.value[this.$.textarea.selectionStart - 1] : '';
+ if (charAtCursor !== ':' && this._colonIndex == null) { return; }
+
+ // When a colon is detected, set a colon index. We are interested only on
+ // colons after space or in beginning of textarea
+ if (charAtCursor === ':') {
+ if (this.$.textarea.selectionStart < 2 ||
+ e.detail.value[this.$.textarea.selectionStart - 2] === ' ') {
+ this._colonIndex = this.$.textarea.selectionStart - 1;
}
- this.$.textarea.textarea.focus();
}
+
+ this._currentSearchString = e.detail.value.substr(this._colonIndex + 1,
+ this.$.textarea.selectionStart - this._colonIndex - 1);
+ // Under the following conditions, close and reset the dropdown:
+ // - The cursor is no longer at the end of the current search string
+ // - The search string is an space or new line
+ // - The colon has been removed
+ // - There are no suggestions that match the search string
+ if (this.$.textarea.selectionStart !==
+ this._currentSearchString.length + this._colonIndex + 1 ||
+ this._currentSearchString === ' ' ||
+ this._currentSearchString === '\n' ||
+ !(e.detail.value[this._colonIndex] === ':') ||
+ !this._suggestions.length) {
+ this._resetEmojiDropdown();
+ // Otherwise open the dropdown and set the position to be just below the
+ // cursor.
+ } else if (this.$.emojiSuggestions.isHidden) {
+ this._updateCaratPosition();
+ }
+ this.$.textarea.textarea.focus();
},
+
_openEmojiDropdown() {
this.$.emojiSuggestions.open();
+ this.$.reporting.reportInteraction('open-emoji-dropdown');
},
_formatSuggestions(matchedSuggestions) {
@@ -268,11 +291,14 @@
_determineSuggestions(emojiText) {
if (!emojiText.length) {
this._formatSuggestions(ALL_SUGGESTIONS);
+ this.disableEnterKeyForSelectingEmoji = true;
+ } else {
+ const matches = ALL_SUGGESTIONS.filter(suggestion => {
+ return suggestion.match.includes(emojiText);
+ }).slice(0, MAX_ITEMS_DROPDOWN);
+ this._formatSuggestions(matches);
+ this.disableEnterKeyForSelectingEmoji = false;
}
- const matches = ALL_SUGGESTIONS.filter(suggestion => {
- return suggestion.match.includes(emojiText);
- }).splice(0, MAX_ITEMS_DROPDOWN);
- this._formatSuggestions(matches);
},
_resetEmojiDropdown() {
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index f010fb3234..b884ecd0da 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-textarea</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-textarea.html">
@@ -31,6 +33,18 @@ limitations under the License.
</template>
</test-fixture>
+<test-fixture id="monospace">
+ <template>
+ <gr-textarea monospace="true"></gr-textarea>
+ </template>
+</test-fixture>
+
+<test-fixture id="hideBorder">
+ <template>
+ <gr-textarea hide-border="true"></gr-textarea>
+ </template>
+</test-fixture>
+
<script>
suite('gr-textarea tests', () => {
let element;
@@ -39,6 +53,7 @@ limitations under the License.
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
+ sandbox.stub(element.$.reporting, 'reportInteraction');
});
teardown(() => {
@@ -47,16 +62,10 @@ limitations under the License.
test('monospace is set properly', () => {
assert.isFalse(element.classList.contains('monospace'));
- element.monospace = true;
- element.ready();
- assert.isTrue(element.classList.contains('monospace'));
});
test('hideBorder is set properly', () => {
assert.isFalse(element.$.textarea.classList.contains('noBorder'));
- element.hideBorder = true;
- element.ready();
- assert.isTrue(element.$.textarea.classList.contains('noBorder'));
});
test('emoji selector is not open with the textarea lacks focus', () => {
@@ -82,6 +91,49 @@ limitations under the License.
element.$.textarea.selectionStart = 1;
element.$.textarea.selectionEnd = 1;
element.text = ':';
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 0);
+ assert.isFalse(element._hideAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
+
+ test('emoji selector opens when a colon is typed after space',
+ () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 2;
+ element.$.textarea.selectionEnd = 2;
+ element.text = ' :';
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 1);
+ assert.isFalse(element._hideAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
+
+ test('emoji selector doesn\`t open when a colon is typed after character',
+ () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 5;
+ element.$.textarea.selectionEnd = 5;
+ element.text = 'test:';
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.emojiSuggestions.isHidden);
+ assert.isTrue(element._hideAutocomplete);
+ });
+
+ test('emoji selector opens when a colon is typed and some substring',
+ () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
element.$.textarea.selectionStart = 2;
element.$.textarea.selectionEnd = 2;
element.text = ':t';
@@ -92,6 +144,30 @@ limitations under the License.
assert.equal(element._currentSearchString, 't');
});
+ test('emoji selector opens when a colon is typed in middle of text',
+ () => {
+ MockInteractions.focus(element.$.textarea);
+ // Needed for Safari tests. selectionStart is not updated when text is
+ // updated.
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ // Since selectionStart is on Chrome set always on end of text, we
+ // stub it to 1
+ const text = ': hello';
+ sandbox.stub(element.$, 'textarea', {
+ selectionStart: 1,
+ value: text,
+ textarea: {
+ focus: () => {},
+ },
+ });
+ element.text = text;
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.emojiSuggestions.isHidden);
+ assert.equal(element._colonIndex, 0);
+ assert.isFalse(element._hideAutocomplete);
+ assert.equal(element._currentSearchString, '');
+ });
test('emoji selector closes when text changes before the colon', () => {
const resetStub = sandbox.stub(element, '_resetEmojiDropdown');
MockInteractions.focus(element.$.textarea);
@@ -131,8 +207,10 @@ limitations under the License.
element._determineSuggestions(emojiText);
assert.isTrue(formatSpy.called);
assert.isTrue(formatSpy.lastCall.calledWithExactly(
- [{dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
- {dataValue: '😂', value: '😂', match: 'tears', text: '😂 tears'}]));
+ [{dataValue: '😂', value: '😂', match: 'tears :\')',
+ text: '😂 tears :\')'},
+ {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
+ ]));
});
test('_formatSuggestions', () => {
@@ -153,7 +231,7 @@ limitations under the License.
const selectedItem = {dataset: {value: '😂'}};
const event = {detail: {selected: selectedItem}};
element._handleEmojiSelect(event);
- assert.equal(element.text, 'test test 😂 ');
+ assert.equal(element.text, 'test test 😂');
});
test('_updateCaratPosition', () => {
@@ -228,9 +306,70 @@ limitations under the License.
MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
assert.isTrue(enterSpy.called);
flushAsynchronousOperations();
- // A space is automatically added at the end.
- assert.equal(element.text, '💯 ');
+ assert.equal(element.text, '💯');
});
+
+ test('enter key - ignored on just colon without more information', () => {
+ const enterSpy = sandbox.spy(element.$.emojiSuggestions,
+ 'getCursorTarget');
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isFalse(enterSpy.called);
+ MockInteractions.focus(element.$.textarea);
+ element.$.textarea.selectionStart = 1;
+ element.$.textarea.selectionEnd = 1;
+ element.text = ':';
+ flushAsynchronousOperations();
+ MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
+ assert.isFalse(enterSpy.called);
+ });
+ });
+ });
+
+ suite('gr-textarea monospace', () => {
+ // gr-textarea set monospace class in the ready() method.
+ // In Polymer2, ready() is called from the fixture(...) method,
+ // If ready() is called again later, some nested elements doesn't
+ // handle it correctly. A separate test-fixture is used to set
+ // properties before ready() is called.
+
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('monospace');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('monospace is set properly', () => {
+ assert.isTrue(element.classList.contains('monospace'));
+ });
+ });
+
+ suite('gr-textarea hideBorder', () => {
+ // gr-textarea set noBorder class in the ready() method.
+ // In Polymer2, ready() is called from the fixture(...) method,
+ // If ready() is called again later, some nested elements doesn't
+ // handle it correctly. A separate test-fixture is used to set
+ // properties before ready() is called.
+
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('hideBorder');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('hideBorder is set properly', () => {
+ assert.isTrue(element.$.textarea.classList.contains('noBorder'));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
index 65f1fda83c..b4fefe1e12 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<dom-module id="gr-tooltip-content">
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
index b46cafb3b3..c5de8f4844 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-tooltip-content',
- _legacyUndefinedCheck: true,
properties: {
title: {
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
index 438d4360e1..f9350c670f 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-storage</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-tooltip-content.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
index 9947d61502..75d9c4bb14 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-tooltip">
@@ -34,7 +34,7 @@ limitations under the License.
max-width: var(--tooltip-max-width);
}
:host .tooltip {
- padding: .5em .85em;
+ padding: var(--spacing-m) var(--spacing-l);
}
:host .arrowPositionBelow,
:host([position-below]) .arrowPositionAbove {
@@ -54,11 +54,11 @@ limitations under the License.
}
.arrowPositionAbove {
border-top: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color);
- bottom: -var(--gr-tooltip-arrow-size);
+ bottom: calc(-1 * var(--gr-tooltip-arrow-size));
}
.arrowPositionBelow {
border-bottom: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color);
- top: -var(--gr-tooltip-arrow-size);
+ top: calc(-1 * var(--gr-tooltip-arrow-size));
}
</style>
<div class="tooltip">
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
index 3e16beb6f2..fb87b55831 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
@@ -19,7 +19,6 @@
Polymer({
is: 'gr-tooltip',
- _legacyUndefinedCheck: true,
properties: {
text: String,
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
index 3a472880e8..f59f6e106d 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
@@ -17,9 +17,11 @@ limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-storage</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-tooltip.html">
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
index 0846728b86..48e488a47f 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
@@ -36,6 +36,9 @@ limitations under the License.
* @return {Number}
*/
RevisionInfo.prototype.getMaxParents = function() {
+ if (!this._change || !this._change.revisions) {
+ return 0;
+ }
return Object.values(this._change.revisions)
.reduce((acc, rev) => Math.max(rev.commit.parents.length, acc), 0);
};
@@ -48,6 +51,9 @@ limitations under the License.
*/
RevisionInfo.prototype.getParentCountMap = function() {
const result = {};
+ if (!this._change || !this._change.revisions) {
+ return {};
+ }
Object.values(this._change.revisions)
.forEach(rev => { result[rev._number] = rev.commit.parents.length; });
return result;
@@ -75,9 +81,7 @@ limitations under the License.
return rev.commit.parents[parentIndex].commit;
};
- if (!window.Gerrit) {
- window.Gerrit = {};
- }
+ window.Gerrit = window.Gerrit || {};
window.Gerrit.RevisionInfo = RevisionInfo;
})();
</script>
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
index 433872db88..7e5810bc2c 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
@@ -18,9 +18,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>revision-info</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="revision-info.html">
diff --git a/polygerrit-ui/app/elements/test/plugin.html b/polygerrit-ui/app/elements/test/plugin.html
index bd29b9065e..ecd90075af 100644
--- a/polygerrit-ui/app/elements/test/plugin.html
+++ b/polygerrit-ui/app/elements/test/plugin.html
@@ -1,18 +1,44 @@
<dom-module id="my-plugin">
<script>
- Gerrit.install(plugin =>
- plugin.registerStyleModule('app-theme', 'myplugin-app-theme')
- );
+ Gerrit.install(plugin => {
+ plugin.registerStyleModule('app-theme', 'myplugin-app-theme');
+ plugin.registerStyleModule('app-theme-light', 'myplugin-app-theme-light');
+ plugin.registerStyleModule('app-theme-dark', 'myplugin-app-theme-dark');
+ });
</script>
</dom-module>
<dom-module id="myplugin-app-theme">
- <style>
- html {
- --primary-text-color: #F00BAA;
- --header-background-color: #F01BAA;
- --header-title-content: "MyGerrit";
- --footer-background-color: #F02BAA;
- }
- </style>
+ <template>
+ <style>
+ html {
+ --primary-text-color: #F00BAA;
+ }
+ </style>
+ </template>
+</dom-module>
+
+<dom-module id="myplugin-app-theme-light">
+ <template>
+ <style>
+ html {
+ --header-background-color: #F01BAA;
+ --header-title-content: "MyGerrit";
+ --footer-background-color: #F02BAA;
+ }
+ </style>
+ </template>
+</dom-module>
+
+<dom-module id="myplugin-app-theme-dark">
+ <template>
+ <style>
+ html {
+ --primary-text-color: red;
+ --header-background-color: black;
+ --header-title-content: "MyGerrit Dark";
+ --footer-background-color: yellow;
+ }
+ </style>
+ </template>
</dom-module>
diff --git a/polygerrit-ui/app/embed/embed.html b/polygerrit-ui/app/embed/embed.html
index 1b2f20fb23..64e0137182 100644
--- a/polygerrit-ui/app/embed/embed.html
+++ b/polygerrit-ui/app/embed/embed.html
@@ -15,12 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<script>
- // Needed for JSCompiler to understand it's global.
- // eslint-disable-next-line no-unused-vars, prefer-const
- let Gerrit = window.Gerrit || {};
- window.Gerrit = Gerrit;
+ window.Gerrit = window.Gerrit || {};
</script>
-<link rel="import" href="../bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../elements/change/gr-change-view/gr-change-view.html">
<link rel="import" href="../elements/core/gr-search-bar/gr-search-bar.html">
<link rel="import" href="../elements/diff/gr-diff-view/gr-diff-view.html">
diff --git a/polygerrit-ui/app/embed/embed_test.html b/polygerrit-ui/app/embed/embed_test.html
index 7ca75c9f8c..1e3f5d7f44 100644
--- a/polygerrit-ui/app/embed/embed_test.html
+++ b/polygerrit-ui/app/embed/embed_test.html
@@ -18,10 +18,12 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>embed_test</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../polygerrit_ui/elements/embed.html"/>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="embed.html"/>
<script>void(0);</script>
diff --git a/polygerrit-ui/app/embed/gr-diff.html b/polygerrit-ui/app/embed/gr-diff.html
index 6aa9370114..f5f74bde8c 100644
--- a/polygerrit-ui/app/embed/gr-diff.html
+++ b/polygerrit-ui/app/embed/gr-diff.html
@@ -15,11 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<script>
- // Needed for JSCompiler to understand it's global.
- // eslint-disable-next-line no-unused-vars, prefer-const
- let Gerrit = window.Gerrit || {};
- window.Gerrit = Gerrit;
+ window.Gerrit = window.Gerrit || {};
</script>
-<link rel="import" href="../styles/themes/app-theme.html">
<link rel="import" href="../elements/diff/gr-diff/gr-diff.html">
<link rel="import" href="../elements/diff/gr-diff-cursor/gr-diff-cursor.html">
diff --git a/polygerrit-ui/app/embed/test.html b/polygerrit-ui/app/embed/test.html
index eed2fefbfe..955eaeed1a 100644
--- a/polygerrit-ui/app/embed/test.html
+++ b/polygerrit-ui/app/embed/test.html
@@ -19,8 +19,8 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>Embed Test Runner</title>
<meta charset="utf-8">
-<script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<script>
- WCT.loadSuites(['embed_test.html']);
+ WCT.loadSuites(['../embed/embed_test.html']);
</script>
diff --git a/polygerrit-ui/app/embed_test.sh b/polygerrit-ui/app/embed_test.sh
index d48279665a..0d8f58ff28 100755
--- a/polygerrit-ui/app/embed_test.sh
+++ b/polygerrit-ui/app/embed_test.sh
@@ -4,20 +4,19 @@ set -ex
t=$(mktemp -d || mktemp -d -t wct-XXXXXXXXXX)
components=$TEST_SRCDIR/gerrit/polygerrit-ui/app/test_components.zip
-code=$TEST_SRCDIR/gerrit/polygerrit-ui/app/polygerrit_embed_ui.zip
-index=$TEST_SRCDIR/gerrit/polygerrit-ui/app/embed/test.html
-tests=$TEST_SRCDIR/gerrit/polygerrit-ui/app/embed/*_test.html
+code=$TEST_SRCDIR/gerrit/polygerrit-ui/app/pg_code.zip
+echo $t
unzip -qd $t $components
unzip -qd $t $code
+# Purge test/ directory contents coming from pg_code.zip.
+rm -rf $t/test
mkdir -p $t/test
-cp $index $t/test/
-cp $tests $t/test/
+cp $TEST_SRCDIR/gerrit/polygerrit-ui/app/embed/test.html $t/test/
if [ "${WCT_HEADLESS_MODE:-0}" != "0" ]; then
CHROME_OPTIONS=[\'start-maximized\',\'headless\',\'disable-gpu\',\'no-sandbox\']
- # TODO(paladox): Fix Firefox support for headless mode
- FIREFOX_OPTIONS=[\'\']
+ FIREFOX_OPTIONS=[\'-headless\']
else
CHROME_OPTIONS=[\'start-maximized\']
FIREFOX_OPTIONS=[\'\']
@@ -62,9 +61,9 @@ module.exports = {
};
EOF
-export PATH="$(dirname $WCT):$(dirname $NPM):$PATH"
+export PATH="$(dirname $NPM):$PATH"
cd $t
test -n "${WCT}"
-$(basename ${WCT}) ${WCT_ARGS}
+${WCT} ${WCT_ARGS}
diff --git a/polygerrit-ui/app/gr-diff/gr-diff-root.html b/polygerrit-ui/app/gr-diff/gr-diff-root.html
index 132654c14d..b3f0d34d93 100644
--- a/polygerrit-ui/app/gr-diff/gr-diff-root.html
+++ b/polygerrit-ui/app/gr-diff/gr-diff-root.html
@@ -1,7 +1,4 @@
<script>
- // Needed for JSCompiler to understand it's global.
- // eslint-disable-next-line no-unused-vars, prefer-const
- let Gerrit = window.Gerrit || {};
- window.Gerrit = Gerrit;
+ window.Gerrit = window.Gerrit || {};
</script>
<link rel="import" href="../elements/diff/gr-diff/gr-diff.html">
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index 98387a02c8..7ef0ee3850 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -14,17 +14,14 @@ def polygerrit_bundle(name, srcs, outs, app):
# See: https://github.com/google/closure-compiler/issues/2042
compilation_level = "WHITESPACE_ONLY",
defs = [
- "--polymer_version=1",
+ "--polymer_version=2",
"--jscomp_off=duplicate",
- "--force_inject_library=es6_runtime",
],
- language = "ECMASCRIPT5",
+ language = "ECMASCRIPT_2017",
deps = [name + "_closure_lib"],
dependency_mode = "PRUNE_LEGACY",
)
- # TODO(davido): Remove JSC_REFERENCE_BEFORE_DECLARE when this is fixed upstream:
- # https://github.com/Polymer/polymer-resin/issues/7
closure_js_library(
name = name + "_closure_lib",
srcs = [appName + ".js"],
@@ -33,9 +30,7 @@ def polygerrit_bundle(name, srcs, outs, app):
# and remove this supression
suppress = [
"JSC_JSDOC_MISSING_TYPE_WARNING",
- "JSC_REFERENCE_BEFORE_DECLARE",
"JSC_UNNECESSARY_ESCAPE",
- "JSC_UNUSED_LOCAL_ASSIGNMENT",
],
deps = [
"//lib/polymer_externs:polymer_closure",
diff --git a/polygerrit-ui/app/samples/bind-parameters.html b/polygerrit-ui/app/samples/bind-parameters.html
index a7eb39a542..a28c462bc0 100644
--- a/polygerrit-ui/app/samples/bind-parameters.html
+++ b/polygerrit-ui/app/samples/bind-parameters.html
@@ -15,7 +15,7 @@
<script>
Polymer({
is: 'my-bind-sample',
- _legacyUndefinedCheck: true,
+
properties: {
computedExample: {
type: String,
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index 0d38c63e71..d1d96a8736 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -32,6 +32,11 @@
const coverageData = {};
let displayCoverage = false;
const annotationApi = plugin.annotationApi();
+ const styleApi = plugin.styles();
+
+ const coverageStyle = styleApi.css('background-color: #EF9B9B !important');
+ const emptyStyle = styleApi.css('');
+
annotationApi.addLayer(context => {
if (Object.keys(coverageData).length === 0) {
// Coverage data is not ready yet.
@@ -41,16 +46,16 @@
const line = context.line;
// Highlight lines missing coverage with this background color if
// coverage should be displayed, else do nothing.
- const cssClass = displayCoverage
- ? Gerrit.css('background-color: #EF9B9B')
- : Gerrit.css('');
+ const annotationStyle = displayCoverage
+ ? coverageStyle
+ : emptyStyle;
if (coverageData[path] &&
coverageData[path].changeNum === context.changeNum &&
coverageData[path].patchNum === context.patchNum) {
const linesMissingCoverage = coverageData[path].linesMissingCoverage;
if (linesMissingCoverage.includes(line.afterNumber)) {
- context.annotateRange(0, line.text.length, cssClass, 'right');
- context.annotateLineNumber(cssClass, 'right');
+ context.annotateRange(0, line.text.length, annotationStyle, 'right');
+ context.annotateLineNumber(annotationStyle, 'right');
}
}
}).enableToggleCheckbox('Display Coverage', checkbox => {
diff --git a/polygerrit-ui/app/samples/repo-command.html b/polygerrit-ui/app/samples/repo-command.html
index 37aca04fc3..526d35051c 100644
--- a/polygerrit-ui/app/samples/repo-command.html
+++ b/polygerrit-ui/app/samples/repo-command.html
@@ -29,7 +29,7 @@
<script>
Polymer({
is: 'repo-command-low',
- _legacyUndefinedCheck: true,
+
attached() {
console.log(this.repoName);
console.log(this.config);
diff --git a/polygerrit-ui/app/samples/some-screen.html b/polygerrit-ui/app/samples/some-screen.html
index 527ebcebbd..da025a20fd 100644
--- a/polygerrit-ui/app/samples/some-screen.html
+++ b/polygerrit-ui/app/samples/some-screen.html
@@ -38,7 +38,7 @@
<script>
Polymer({
is: 'some-screen-main',
- _legacyUndefinedCheck: true,
+
properties: {
rootUrl: String,
},
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
new file mode 100644
index 0000000000..6e503c73b0
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
@@ -0,0 +1,71 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrDisplayNameUtils) {
+ return;
+ }
+
+ const ANONYMOUS_NAME = 'Anonymous';
+
+ class GrDisplayNameUtils {
+ /**
+ * enableEmail when true enables to fallback to using email if
+ * the account name is not avilable.
+ */
+ static getUserName(config, account, enableEmail) {
+ if (account && account.name) {
+ return account.name;
+ } else if (account && account.username) {
+ return account.username;
+ } else if (enableEmail && account && account.email) {
+ return account.email;
+ } else if (config && config.user &&
+ config.user.anonymous_coward_name !== 'Anonymous Coward') {
+ return config.user.anonymous_coward_name;
+ }
+
+ return ANONYMOUS_NAME;
+ }
+
+ static getAccountDisplayName(config, account, enableEmail) {
+ const reviewerName = this._accountOrAnon(config, account, enableEmail);
+ const reviewerEmail = this._accountEmail(account.email);
+ const reviewerStatus = account.status ? '(' + account.status + ')' : '';
+ return [reviewerName, reviewerEmail, reviewerStatus]
+ .filter(p => p.length > 0).join(' ');
+ }
+
+ static _accountOrAnon(config, reviewer, enableEmail) {
+ return this.getUserName(config, reviewer, !!enableEmail);
+ }
+
+ static _accountEmail(email) {
+ if (typeof email !== 'undefined') {
+ return '<' + email + '>';
+ }
+ return '';
+ }
+
+ static getGroupDisplayName(group) {
+ return group.name + ' (group)';
+ }
+ }
+
+ window.GrDisplayNameUtils = GrDisplayNameUtils;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
new file mode 100644
index 0000000000..25ca4c5b2c
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-display-name-utils</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
+<script src="gr-display-name-utils.js"></script>
+
+<script>
+ suite('gr-display-name-utils tests', () => {
+ // eslint-disable-next-line no-unused-vars
+ const config = {
+ user: {
+ anonymous_coward_name: 'Anonymous Coward',
+ },
+ };
+
+
+ test('getUserName name only', () => {
+ const account = {
+ name: 'test-name',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-name');
+ });
+
+ test('getUserName username only', () => {
+ const account = {
+ username: 'test-user',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-user');
+ });
+
+ test('getUserName email only', () => {
+ const account = {
+ email: 'test-user@test-url.com',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-user@test-url.com');
+ });
+
+ test('getUserName returns not Anonymous Coward as the anon name', () => {
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+ 'Anonymous');
+ });
+
+ test('getUserName for the config returning the anon name', () => {
+ const config = {
+ user: {
+ anonymous_coward_name: 'Test Anon',
+ },
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+ 'Test Anon');
+ });
+
+ test('getAccountDisplayName - account with name only', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {name: 'Some user name'}),
+ 'Some user name');
+ });
+
+ test('getAccountDisplayName - account with email only', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {email: 'my@example.com'}),
+ 'Anonymous <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with email only - allowEmail', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {email: 'my@example.com'}, true),
+ 'my@example.com <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with name and status', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ status: 'OOO',
+ }),
+ 'Some name (OOO)');
+ });
+
+ test('getAccountDisplayName - account with name and email', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ email: 'my@example.com',
+ }),
+ 'Some name <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with name, email and status', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ email: 'my@example.com',
+ status: 'OOO',
+ }),
+ 'Some name <my@example.com> (OOO)');
+ });
+
+ test('getGroupDisplayName', () => {
+ assert.equal(
+ GrDisplayNameUtils.getGroupDisplayName({name: 'Some user name'}),
+ 'Some user name (group)');
+ });
+
+ test('_accountEmail', () => {
+ assert.equal(
+ GrDisplayNameUtils._accountEmail('email@gerritreview.com'),
+ '<email@gerritreview.com>');
+ assert.equal(GrDisplayNameUtils._accountEmail(undefined), '');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
new file mode 100644
index 0000000000..67001d2941
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrEmailSuggestionsProvider) {
+ return;
+ }
+
+ class GrEmailSuggestionsProvider {
+ constructor(restAPI) {
+ this._restAPI = restAPI;
+ }
+
+ getSuggestions(input) {
+ return this._restAPI.getSuggestedAccounts(`${input}`)
+ .then(accounts => {
+ if (!accounts) { return []; }
+ return accounts;
+ });
+ }
+
+ makeSuggestionItem(account) {
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(null, account, true),
+ value: {account, count: 1},
+ };
+ }
+ }
+
+ window.GrEmailSuggestionsProvider = GrEmailSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
new file mode 100644
index 0000000000..0266ab91d9
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-email-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-email-suggestions-provider.js"></script>
+
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrEmailSuggestionsProvider tests', () => {
+ let sandbox;
+ let restAPI;
+ let provider;
+ const account1 = {
+ name: 'Some name',
+ email: 'some@example.com',
+ };
+ const account2 = {
+ email: 'other@example.com',
+ _account_id: 3,
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ });
+ restAPI = fixture('basic');
+ provider = new GrEmailSuggestionsProvider(restAPI);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('getSuggestions', done => {
+ const getSuggestedAccountsStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([account1, account2]));
+
+ provider.getSuggestions('Some input').then(res => {
+ assert.deepEqual(res, [account1, account2]);
+ assert.isTrue(getSuggestedAccountsStub.calledOnce);
+ assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
+ done();
+ });
+ });
+
+ test('makeSuggestionItem', () => {
+ assert.deepEqual(provider.makeSuggestionItem(account1), {
+ name: 'Some name <some@example.com>',
+ value: {
+ account: account1,
+ count: 1,
+ },
+ });
+
+ assert.deepEqual(provider.makeSuggestionItem(account2), {
+ name: 'other@example.com <other@example.com>',
+ value: {
+ account: account2,
+ count: 1,
+ },
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js
new file mode 100644
index 0000000000..a95670be9e
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js
@@ -0,0 +1,47 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrGroupSuggestionsProvider) {
+ return;
+ }
+
+ class GrGroupSuggestionsProvider {
+ constructor(restAPI) {
+ this._restAPI = restAPI;
+ }
+
+ getSuggestions(input) {
+ return this._restAPI.getSuggestedGroups(`${input}`)
+ .then(groups => {
+ if (!groups) { return []; }
+ const keys = Object.keys(groups);
+ return keys.map(key => {
+ return Object.assign({}, groups[key], {name: key});
+ });
+ });
+ }
+
+ makeSuggestionItem(suggestion) {
+ return {name: suggestion.name,
+ value: {group: {name: suggestion.name, id: suggestion.id}}};
+ }
+ }
+
+ window.GrGroupSuggestionsProvider = GrGroupSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
new file mode 100644
index 0000000000..b60aaa9538
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-group-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-group-suggestions-provider.js"></script>
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrGroupSuggestionsProvider tests', () => {
+ let sandbox;
+ let restAPI;
+ let provider;
+ const group1 = {
+ name: 'Some name',
+ id: 1,
+ };
+ const group2 = {
+ name: 'Other name',
+ id: 3,
+ url: 'abcd',
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ });
+ restAPI = fixture('basic');
+ provider = new GrGroupSuggestionsProvider(restAPI);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('getSuggestions', done => {
+ const getSuggestedAccountsStub =
+ sandbox.stub(restAPI, 'getSuggestedGroups')
+ .returns(Promise.resolve({
+ 'Some name': {id: 1},
+ 'Other name': {id: 3, url: 'abcd'},
+ }));
+
+ provider.getSuggestions('Some input').then(res => {
+ assert.deepEqual(res, [group1, group2]);
+ assert.isTrue(getSuggestedAccountsStub.calledOnce);
+ assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
+ done();
+ });
+ });
+
+ test('makeSuggestionItem', () => {
+ assert.deepEqual(provider.makeSuggestionItem(group1), {
+ name: 'Some name',
+ value: {
+ group: {
+ name: 'Some name',
+ id: 1,
+ },
+ },
+ });
+
+ assert.deepEqual(provider.makeSuggestionItem(group2), {
+ name: 'Other name',
+ value: {
+ group: {
+ name: 'Other name',
+ id: 3,
+ },
+ },
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
new file mode 100644
index 0000000000..ffcdba9846
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
@@ -0,0 +1,114 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrReviewerSuggestionsProvider) {
+ return;
+ }
+
+ /**
+ * @enum {string}
+ */
+ Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES = {
+ REVIEWER: 'reviewers',
+ CC: 'ccs',
+ ANY: 'any',
+ };
+
+ class GrReviewerSuggestionsProvider {
+ static create(restApi, changeNumber, usersType) {
+ switch (usersType) {
+ case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER:
+ return new GrReviewerSuggestionsProvider(restApi, changeNumber,
+ input => restApi.getChangeSuggestedReviewers(changeNumber,
+ input));
+ case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.CC:
+ return new GrReviewerSuggestionsProvider(restApi, changeNumber,
+ input => restApi.getChangeSuggestedCCs(changeNumber, input));
+ case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY:
+ return new GrReviewerSuggestionsProvider(restApi, changeNumber,
+ input => restApi.getSuggestedAccounts(
+ `cansee:${changeNumber} ${input}`));
+ default:
+ throw new Error(`Unknown users type: ${usersType}`);
+ }
+ }
+
+ constructor(restAPI, changeNumber, apiCall) {
+ this._changeNumber = changeNumber;
+ this._apiCall = apiCall;
+ this._restAPI = restAPI;
+ }
+
+ init() {
+ if (this._initPromise) {
+ return this._initPromise;
+ }
+ const getConfigPromise = this._restAPI.getConfig().then(cfg => {
+ this._config = cfg;
+ });
+ const getLoggedInPromise = this._restAPI.getLoggedIn().then(loggedIn => {
+ this._loggedIn = loggedIn;
+ });
+ this._initPromise = Promise.all([getConfigPromise, getLoggedInPromise])
+ .then(() => {
+ this._initialized = true;
+ });
+ return this._initPromise;
+ }
+
+ getSuggestions(input) {
+ if (!this._initialized || !this._loggedIn) {
+ return Promise.resolve([]);
+ }
+
+ return this._apiCall(input)
+ .then(reviewers => (reviewers || []));
+ }
+
+ makeSuggestionItem(suggestion) {
+ if (suggestion.account) {
+ // Reviewer is an account suggestion from getChangeSuggestedReviewers.
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(this._config,
+ suggestion.account, false),
+ value: suggestion,
+ };
+ }
+
+ if (suggestion.group) {
+ // Reviewer is a group suggestion from getChangeSuggestedReviewers.
+ return {
+ name: GrDisplayNameUtils.getGroupDisplayName(suggestion.group),
+ value: suggestion,
+ };
+ }
+
+ if (suggestion._account_id) {
+ // Reviewer is an account suggestion from getSuggestedAccounts.
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(this._config,
+ suggestion, false),
+ value: {account: suggestion, count: 1},
+ };
+ }
+ }
+ }
+
+ window.GrReviewerSuggestionsProvider = GrReviewerSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
new file mode 100644
index 0000000000..ca3c277761
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
@@ -0,0 +1,260 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-reviewer-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-reviewer-suggestions-provider.js"></script>
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrReviewerSuggestionsProvider tests', () => {
+ let sandbox;
+ let _nextAccountId = 0;
+ const makeAccount = function(opt_status) {
+ const accountId = ++_nextAccountId;
+ return {
+ _account_id: accountId,
+ name: 'name ' + accountId,
+ email: 'email ' + accountId,
+ status: opt_status,
+ };
+ };
+ let _nextAccountId2 = 0;
+ const makeAccount2 = function(opt_status) {
+ const accountId2 = ++_nextAccountId2;
+ return {
+ _account_id: accountId2,
+ name: 'name ' + accountId2,
+ status: opt_status,
+ };
+ };
+
+ let owner;
+ let existingReviewer1;
+ let existingReviewer2;
+ let suggestion1;
+ let suggestion2;
+ let suggestion3;
+ let restAPI;
+ let provider;
+
+ let redundantSuggestion1;
+ let redundantSuggestion2;
+ let redundantSuggestion3;
+ let change;
+
+ setup(done => {
+ owner = makeAccount();
+ existingReviewer1 = makeAccount();
+ existingReviewer2 = makeAccount();
+ suggestion1 = {account: makeAccount()};
+ suggestion2 = {account: makeAccount()};
+ suggestion3 = {
+ group: {
+ id: 'suggested group id',
+ name: 'suggested group',
+ },
+ };
+
+ stub('gr-rest-api-interface', {
+ getLoggedIn() { return Promise.resolve(true); },
+ getConfig() { return Promise.resolve({}); },
+ });
+
+ restAPI = fixture('basic');
+ change = {
+ _number: 42,
+ owner,
+ reviewers: {
+ CC: [existingReviewer1],
+ REVIEWER: [existingReviewer2],
+ },
+ };
+ sandbox = sinon.sandbox.create();
+ return flush(done);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+ suite('allowAnyUser set to false', () => {
+ setup(done => {
+ provider = GrReviewerSuggestionsProvider.create(restAPI, change._number,
+ Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER);
+ provider.init().then(done);
+ });
+ suite('stubbed values for _getReviewerSuggestions', () => {
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getChangeSuggestedReviewers() {
+ redundantSuggestion1 = {account: existingReviewer1};
+ redundantSuggestion2 = {account: existingReviewer2};
+ redundantSuggestion3 = {account: owner};
+ return Promise.resolve([redundantSuggestion1, redundantSuggestion2,
+ redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
+ },
+ });
+ });
+
+ test('makeSuggestionItem formats account or group accordingly', () => {
+ let account = makeAccount();
+ const account3 = makeAccount2();
+ let suggestion = provider.makeSuggestionItem({account});
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '>',
+ value: {account},
+ });
+
+ const group = {name: 'test'};
+ suggestion = provider.makeSuggestionItem({group});
+ assert.deepEqual(suggestion, {
+ name: group.name + ' (group)',
+ value: {group},
+ });
+
+ suggestion = provider.makeSuggestionItem(account);
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '>',
+ value: {account, count: 1},
+ });
+
+ suggestion = provider.makeSuggestionItem({account: {}});
+ assert.deepEqual(suggestion, {
+ name: 'Anonymous',
+ value: {account: {}},
+ });
+
+ provider._config = {
+ user: {
+ anonymous_coward_name: 'Anonymous Coward Name',
+ },
+ };
+
+ suggestion = provider.makeSuggestionItem({account: {}});
+ assert.deepEqual(suggestion, {
+ name: 'Anonymous Coward Name',
+ value: {account: {}},
+ });
+
+ account = makeAccount('OOO');
+
+ suggestion = provider.makeSuggestionItem({account});
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '> (OOO)',
+ value: {account},
+ });
+
+ suggestion = provider.makeSuggestionItem(account);
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '> (OOO)',
+ value: {account, count: 1},
+ });
+
+ sandbox.stub(GrDisplayNameUtils, '_accountEmail',
+ () => {
+ return '';
+ });
+
+ suggestion = provider.makeSuggestionItem(account3);
+ assert.deepEqual(suggestion, {
+ name: account3.name,
+ value: {account: account3, count: 1},
+ });
+ });
+
+ test('getSuggestions', done => {
+ provider.getSuggestions().then(reviewers => {
+ // Default is no filtering.
+ assert.equal(reviewers.length, 6);
+ assert.deepEqual(reviewers,
+ [redundantSuggestion1, redundantSuggestion2,
+ redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
+ }).then(done);
+ });
+
+ test('getSuggestions short circuits when logged out', () => {
+ // API call is already stubbed.
+ const xhrSpy = restAPI.getChangeSuggestedReviewers;
+ provider._loggedIn = false;
+ return provider.getSuggestions('').then(() => {
+ assert.isFalse(xhrSpy.called);
+ provider._loggedIn = true;
+ return provider.getSuggestions('').then(() => {
+ assert.isTrue(xhrSpy.called);
+ });
+ });
+ });
+ });
+
+ test('getChangeSuggestedReviewers is used', done => {
+ const suggestReviewerStub =
+ sandbox.stub(restAPI, 'getChangeSuggestedReviewers')
+ .returns(Promise.resolve([]));
+ const suggestAccountStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([]));
+
+ provider.getSuggestions('').then(() => {
+ assert.isTrue(suggestReviewerStub.calledOnce);
+ assert.isTrue(suggestReviewerStub.calledWith(42, ''));
+ assert.isFalse(suggestAccountStub.called);
+ done();
+ });
+ });
+ });
+
+ suite('allowAnyUser set to true', () => {
+ setup(done => {
+ provider = GrReviewerSuggestionsProvider.create(restAPI, change._number,
+ Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY);
+ provider.init().then(done);
+ });
+
+ test('getSuggestedAccounts is used', done => {
+ const suggestReviewerStub =
+ sandbox.stub(restAPI, 'getChangeSuggestedReviewers')
+ .returns(Promise.resolve([]));
+ const suggestAccountStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([]));
+
+ provider.getSuggestions('').then(() => {
+ assert.isFalse(suggestReviewerStub.called);
+ assert.isTrue(suggestAccountStub.calledOnce);
+ assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
+ done();
+ });
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/shadow.js b/polygerrit-ui/app/scripts/shadow.js
new file mode 100644
index 0000000000..df4376652a
--- /dev/null
+++ b/polygerrit-ui/app/scripts/shadow.js
@@ -0,0 +1,421 @@
+/**
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+ /* Downloaded from [1] and adapted to be used as HTML-imported JavaScript
+ * rather than import module.
+ * TODO: to be removed when merging to stable-3.2, where the NPM package
+ * management would allow to actually consume the artifact directly without
+ * adaptation.
+ *
+ * [1] https://raw.githubusercontent.com/GoogleChromeLabs/shadow-selection-polyfill/master/shadow.js
+ */
+
+const debug = false;
+
+const hasShadow = 'attachShadow' in Element.prototype && 'getRootNode' in Element.prototype;
+const hasSelection = !!(hasShadow && document.createElement('div').attachShadow({ mode: 'open' }).getSelection);
+const hasShady = window.ShadyDOM && window.ShadyDOM.inUse;
+const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) ||
+ /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
+const useDocument = !hasShadow || hasShady || (!hasSelection && !isSafari);
+
+const invalidPartialElements = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|script|source|style|template|track|wbr)$/;
+
+const eventName = '-shadow-selectionchange';
+
+
+const validNodeTypes = [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.DOCUMENT_FRAGMENT_NODE];
+function isValidNode(node) {
+ return validNodeTypes.includes(node.nodeType);
+}
+
+
+/**
+ * @param {!Selection} s selection to use
+ * @param {!Node} node to find caret position within a shadow root
+ * @return {!Node|!ShadowRoot}
+ */
+function findCaretFocus(s, node) {
+ const pending = [];
+ const pushAll = (nodeList) => {
+ for (let i = 0; i < nodeList.length; ++i) {
+ if (nodeList[i].shadowRoot) {
+ pending.push(nodeList[i].shadowRoot);
+ }
+ }
+ };
+
+ // We're told by Safari that a node containing a child with a Shadow Root is selected, but check
+ // the node directly too (just in case they change their mind later).
+ if (node.shadowRoot) {
+ pending.push(node.shadowRoot);
+ }
+ pushAll(node.childNodes);
+
+ while (pending.length) {
+ const root = pending.shift();
+
+ for (let i = 0; i < root.childNodes.length; ++i) {
+ if (s.containsNode(root.childNodes[i], true)) {
+ return root;
+ }
+ }
+
+ // The selection must be inside a further Shadow Root, but there's no good way to get a list of
+ // them. Safari won't tell you what regular node contains the root which has a selection. So,
+ // unfortunately if you stack them this will be slow(-ish).
+ pushAll(root.querySelectorAll('*'));
+ }
+
+ return null;
+}
+
+
+function findNode(s, parentNode, isLeft) {
+ const nodes = parentNode.childNodes || parentNode.children;
+ if (!nodes) {
+ return parentNode; // found it, probably text
+ }
+
+ for (let i = 0; i < nodes.length; ++i) {
+ const j = isLeft ? i : (nodes.length - 1 - i);
+ const childNode = nodes[j];
+ if (!isValidNode(childNode)) {
+ continue;
+ }
+
+ debug && console.debug('checking child', childNode, 'IsLeft', isLeft);
+ if (s.containsNode(childNode, true)) {
+ if (s.containsNode(childNode, false)) {
+ debug && console.info('found child', childNode);
+ return childNode;
+ }
+ // Special-case elements that cannot have feasible children.
+ if (!invalidPartialElements.exec(childNode.localName || '')) {
+ debug && console.info('descending child', childNode);
+ return findNode(s, childNode, isLeft);
+ }
+ }
+ debug && console.info(parentNode, 'does NOT contain', childNode);
+ }
+ return parentNode;
+}
+
+
+let recentCaretRange = {node: null, offset: -1};
+
+
+(function() {
+ if (hasSelection || useDocument) {
+ // getSelection exists or document API can be used
+ document.addEventListener('selectionchange', (ev) => {
+ document.dispatchEvent(new CustomEvent(eventName));
+ });
+ return () => {};
+ }
+
+ let withinInternals = false;
+
+ document.addEventListener('selectionchange', (ev) => {
+ if (withinInternals) {
+ return;
+ }
+
+ withinInternals = true;
+
+ const s = window.getSelection();
+ if (s.type === 'Caret') {
+ const root = findCaretFocus(s, s.anchorNode);
+ if (root instanceof window.ShadowRoot) {
+ const range = getRange(root);
+ if (range) {
+ const node = range.startContainer;
+ const offset = range.startOffset;
+ recentCaretRange = {node, offset};
+ }
+ }
+ }
+
+ document.dispatchEvent(new CustomEvent('-shadow-selectionchange'));
+ window.requestAnimationFrame(() => {
+ withinInternals = false;
+ });
+ });
+})();
+
+
+/**
+ * @param {!Selection} s the window selection to use
+ * @param {!Node} node the node to walk from
+ * @param {boolean} walkForward should this walk in natural direction
+ * @return {boolean} whether the selection contains the following node (even partially)
+ */
+function containsNextElement(s, node, walkForward) {
+ const start = node;
+ while (node = walkFromNode(node, walkForward)) {
+ // walking (left) can contain our own parent, which we don't want
+ if (!node.contains(start)) {
+ break;
+ }
+ }
+ if (!node) {
+ return false;
+ }
+ // we look for Element as .containsNode says true for _every_ text node, and we only care about
+ // elements themselves
+ return node instanceof Element && s.containsNode(node, true);
+}
+
+
+/**
+ * @param {!Selection} s the window selection to use
+ * @param {!Node} leftNode the left node
+ * @param {!Node} rightNode the right node
+ * @return {boolean|undefined} whether this has natural direction
+ */
+function getSelectionDirection(s, leftNode, rightNode) {
+ if (s.type !== 'Range') {
+ return undefined; // no direction
+ }
+ const measure = () => s.toString().length;
+
+ const initialSize = measure();
+ debug && console.info(`initial selection: "${s.toString()}"`)
+
+ let updatedSize;
+
+ // Try extending forward and seeing what happens.
+ s.modify('extend', 'forward', 'character');
+ updatedSize = measure();
+ debug && console.info(`forward selection: "${s.toString()}"`)
+
+ if (updatedSize > initialSize || containsNextElement(s, rightNode, true)) {
+ debug && console.info('got forward >, moving right')
+ s.modify('extend', 'backward', 'character');
+ return true;
+ } else if (updatedSize < initialSize || !s.containsNode(leftNode)) {
+ debug && console.info('got forward <, moving left')
+ s.modify('extend', 'backward', 'character');
+ return false;
+ }
+
+ // Maybe we were at the end of something. Extend backwards instead.
+ s.modify('extend', 'backward', 'character');
+ updatedSize = measure();
+ debug && console.info(`backward selection: "${s.toString()}"`)
+
+ if (updatedSize > initialSize || containsNextElement(s, leftNode, false)) {
+ debug && console.info('got backwards >, moving left')
+ s.modify('extend', 'forward', 'character');
+ return false;
+ } else if (updatedSize < initialSize || !s.containsNode(rightNode)) {
+ debug && console.info('got backwards <, moving right')
+ s.modify('extend', 'forward', 'character');
+ return true;
+ }
+
+ // This is likely a select-all.
+ return undefined;
+}
+
+/**
+ * Returns the next valid node (element or text). This is needed as Safari doesn't support
+ * TreeWalker inside Shadow DOM. Don't escape shadow roots.
+ *
+ * @param {!Node} node to start from
+ * @param {boolean} walkForward should this walk in natural direction
+ * @return {Node} node found, if any
+ */
+function walkFromNode(node, walkForward) {
+ if (!walkForward) {
+ return node.previousSibling || node.parentNode || null;
+ }
+ while (node) {
+ if (node.nextSibling) {
+ return node.nextSibling;
+ }
+ node = node.parentNode;
+ }
+ return null;
+}
+
+
+const cachedRange = new Map();
+function getRange(root) {
+ if (hasShady) {
+ const s = document.getSelection();
+ return s.rangeCount ? s.getRangeAt(0) : null;
+ } else if (useDocument) {
+ // Document pierces Shadow Root for selection, so actively filter it down to the right node.
+ // This is only for Firefox, which does not allow selection across Shadow Root boundaries.
+ const s = document.getSelection();
+ if (s.containsNode(root, true)) {
+ return s.getRangeAt(0);
+ }
+ return null;
+ } else if (hasSelection) {
+ const s = root.getSelection();
+ return s.rangeCount ? s.getRangeAt(0) : null;
+ }
+
+ const thisFrame = cachedRange.get(root);
+ if (thisFrame) {
+ return thisFrame;
+ }
+
+ const result = internalGetShadowSelection(root);
+
+ cachedRange.set(root, result.range);
+ window.setTimeout(() => {
+ cachedRange.delete(root);
+ }, 0);
+ debug && console.debug('getRange got', result);
+ return result.range;
+}
+
+
+function internalGetShadowSelection(root) {
+ // nb. We used to check whether the selection contained the host, but this broke in Safari 13.
+ // This is "nicely formatted" whitespace as per the browser's renderer. This is fine, and we only
+ // provide selection information at this granularity.
+ const s = window.getSelection();
+
+ if (s.type === 'None') {
+ return {range: null, type: 'none'};
+ } else if (!(s.type === 'Caret' || s.type === 'Range')) {
+ throw new TypeError('unexpected type: ' + s.type);
+ }
+
+ const leftNode = findNode(s, root, true);
+ if (leftNode === root) {
+ return {range: null, mode: 'none'};
+ }
+
+ const range = document.createRange();
+
+ let rightNode = null;
+ let isNaturalDirection = undefined;
+ if (s.type === 'Range') {
+ rightNode = findNode(s, root, false); // get right node here _before_ getSelectionDirection
+ isNaturalDirection = getSelectionDirection(s, leftNode, rightNode);
+
+ // isNaturalDirection means "going right"
+
+ if (isNaturalDirection === undefined) {
+ // This occurs when we can't move because we can't extend left or right to measure the
+ // direction we're moving in... because it's the entire range. Hooray!
+ range.setStart(leftNode, 0);
+ range.setEnd(rightNode, rightNode.length);
+ return {range, mode: 'all'};
+ }
+ }
+
+ const initialSize = s.toString().length;
+
+ // Dumbest possible approach: remove characters from left side until no more selection,
+ // re-add.
+
+ // Try right side first, as we can trim characters until selection gets shorter.
+
+ let leftOffset = 0;
+ let rightOffset = 0;
+
+ if (rightNode === null) {
+ // This is a caret selection, do nothing.
+ } else if (rightNode.nodeType === Node.TEXT_NODE) {
+ const rightText = rightNode.textContent;
+ const existingNextSibling = rightNode.nextSibling;
+
+ for (let i = rightText.length - 1; i >= 0; --i) {
+ rightNode.splitText(i);
+ const updatedSize = s.toString().length;
+ if (updatedSize !== initialSize) {
+ rightOffset = i + 1;
+ break;
+ }
+ }
+
+ // We don't use .normalize() here, as the user might already have a weird node arrangement
+ // they need to maintain.
+ rightNode.insertData(rightNode.length, rightText.substr(rightNode.length));
+ while (rightNode.nextSibling !== existingNextSibling) {
+ rightNode.nextSibling.remove();
+ }
+ }
+
+ if (leftNode.nodeType === Node.TEXT_NODE) {
+ if (leftNode !== rightNode) {
+ // If we're at the end of a text node, it's impossible to extend the selection, so add an
+ // extra character to select (that we delete later).
+ leftNode.appendData('?');
+ s.collapseToStart();
+ s.modify('extend', 'right', 'character');
+ }
+
+ const leftText = leftNode.textContent;
+ const existingNextSibling = leftNode.nextSibling;
+
+ const start = (leftNode === rightNode ? rightOffset : leftText.length - 1);
+
+ for (let i = start; i >= 0; --i) {
+ leftNode.splitText(i);
+ if (s.toString() === '') {
+ leftOffset = i;
+ break;
+ }
+ }
+
+ // As above, we don't want to use .normalize().
+ leftNode.insertData(leftNode.length, leftText.substr(leftNode.length));
+ while (leftNode.nextSibling !== existingNextSibling) {
+ leftNode.nextSibling.remove();
+ }
+
+ if (leftNode !== rightNode) {
+ leftNode.deleteData(leftNode.length - 1, 1);
+ }
+
+ if (rightNode === null) {
+ rightNode = leftNode;
+ rightOffset = leftOffset;
+ }
+
+ } else if (rightNode === null) {
+ rightNode = leftNode;
+ }
+
+ // Work around common browser bug. Single character selction is always seen as 'forward'. Check
+ // if it's actually supposed to be backward.
+ if (initialSize === 1 && recentCaretRange && recentCaretRange.node === leftNode) {
+ if (recentCaretRange.offset > leftOffset && isNaturalDirection) {
+ isNaturalDirection = false;
+ }
+ }
+
+ if (isNaturalDirection === true) {
+ s.collapse(leftNode, leftOffset);
+ s.extend(rightNode, rightOffset);
+ } else if (isNaturalDirection === false) {
+ s.collapse(rightNode, rightOffset);
+ s.extend(leftNode, leftOffset);
+ } else {
+ s.setPosition(leftNode, leftOffset);
+ }
+
+ range.setStart(leftNode, leftOffset);
+ range.setEnd(rightNode, rightOffset);
+ return {range, mode: 'normal'};
+}
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 624992b3e4..e26d6d99b8 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -75,5 +75,94 @@
return wrappedPromise;
};
+ /**
+ * Get computed style value.
+ *
+ * If ShadyCSS is provided, use ShadyCSS api.
+ * If `getComputedStyleValue` is provided on the elment, use it.
+ * Otherwise fallback to native method (in polymer 2).
+ *
+ */
+ util.getComputedStyleValue = (name, el) => {
+ let style;
+ if (window.ShadyCSS) {
+ style = ShadyCSS.getComputedStyleValue(el, name);
+ } else if (el.getComputedStyleValue) {
+ style = el.getComputedStyleValue(name);
+ } else {
+ style = getComputedStyle(el).getPropertyValue(name);
+ }
+ return style;
+ };
+
+ /**
+ * Query selector on a dom element.
+ *
+ * This is shadow DOM compatible, but only works when selector is within
+ * one shadow host, won't work if your selector is crossing
+ * multiple shadow hosts.
+ *
+ */
+ util.querySelector = (el, selector) => {
+ let nodes = [el];
+ let element = null;
+ while (nodes.length) {
+ const node = nodes.pop();
+
+ // Skip if it's an invalid node.
+ if (!node || !node.querySelector) continue;
+
+ // Try find it with native querySelector directly
+ element = node.querySelector(selector);
+
+ if (element) {
+ break;
+ } else if (node.shadowRoot) {
+ // If shadowHost detected, add the host and its children
+ nodes = nodes.concat(Array.from(node.children));
+ nodes.push(node.shadowRoot);
+ } else {
+ nodes = nodes.concat(Array.from(node.children));
+ }
+ }
+ return element;
+ };
+
+ /**
+ * Query selector all dom elements matching with certain selector.
+ *
+ * This is shadow DOM compatible, but only works when selector is within
+ * one shadow host, won't work if your selector is crossing
+ * multiple shadow hosts.
+ *
+ * Note: this can be very expensive, only use when have to.
+ */
+ util.querySelectorAll = (el, selector) => {
+ let nodes = [el];
+ const results = new Set();
+ while (nodes.length) {
+ const node = nodes.pop();
+
+ if (!node || !node.querySelectorAll) continue;
+
+ // Try find all from regular children
+ [...node.querySelectorAll(selector)]
+ .forEach(el => results.add(el));
+
+ // Add all nodes with shadowRoot and loop through
+ const allShadowNodes = [...node.querySelectorAll('*')]
+ .filter(child => !!child.shadowRoot)
+ .map(child => child.shadowRoot);
+ nodes = nodes.concat(allShadowNodes);
+
+ // Add shadowRoot of current node if has one
+ // as its not included in node.querySelectorAll('*')
+ if (node.shadowRoot) {
+ nodes.push(node.shadowRoot);
+ }
+ }
+ return [...results];
+ };
+
window.util = util;
})(window);
diff --git a/polygerrit-ui/app/styles/dashboard-header-styles.html b/polygerrit-ui/app/styles/dashboard-header-styles.html
index ccc17b0234..88d50c06b9 100644
--- a/polygerrit-ui/app/styles/dashboard-header-styles.html
+++ b/polygerrit-ui/app/styles/dashboard-header-styles.html
@@ -34,7 +34,7 @@ limitations under the License.
}
.info {
display: inline-block;
- padding: 1em;
+ padding: var(--spacing-l);
vertical-align: top;
}
.info > div > span {
diff --git a/polygerrit-ui/app/styles/fonts.css b/polygerrit-ui/app/styles/fonts.css
index 41aec27ade..c83749218a 100644
--- a/polygerrit-ui/app/styles/fonts.css
+++ b/polygerrit-ui/app/styles/fonts.css
@@ -34,7 +34,7 @@
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
- src: local('Roboto'), local('RobotoMono-Regular'),
+ src: local('Roboto'), local('Roboto-Regular'),
url('../fonts/Roboto-Regular.woff2') format('woff2'),
url('../fonts/Roboto-Regular.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
diff --git a/polygerrit-ui/app/styles/gr-change-list-styles.html b/polygerrit-ui/app/styles/gr-change-list-styles.html
index 8f72216ad6..345363bc85 100644
--- a/polygerrit-ui/app/styles/gr-change-list-styles.html
+++ b/polygerrit-ui/app/styles/gr-change-list-styles.html
@@ -17,9 +17,6 @@ limitations under the License.
<dom-module id="gr-change-list-styles">
<template>
<style>
- :host {
- font-size: var(--font-size-normal);
- }
gr-change-list-item,
tr {
border-top: 1px solid var(--border-color);
@@ -99,7 +96,7 @@ limitations under the License.
text-decoration: underline;
}
.cell {
- height: 2.25rem;
+ padding: var(--spacing-s) 0;
}
.star {
padding: 0;
@@ -123,7 +120,7 @@ limitations under the License.
vertical-align: middle;
}
.leftPadding {
- width: var(--default-horizontal-margin);
+ width: var(--spacing-l);
}
.star {
width: 30px;
@@ -165,12 +162,12 @@ limitations under the License.
}
@media only screen and (max-width: 50em) {
:host {
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
}
gr-change-list-item {
flex-wrap: wrap;
justify-content: space-between;
- padding: .25em .5em;
+ padding: var(--spacing-xs) var(--spacing-m);
}
gr-change-list-item[selected],
gr-change-list-item:focus {
@@ -199,10 +196,10 @@ limitations under the License.
}
.groupHeader .cell,
.noChanges .cell {
- padding: 0 .5em;
+ padding: 0 var(--spacing-m);
}
.subject {
- margin-bottom: .25em;
+ margin-bottom: var(--spacing-xs);
width: calc(100% - 2em);
}
.owner,
@@ -214,11 +211,6 @@ limitations under the License.
height: auto;
}
}
- @media only screen and (min-width: 1450px) {
- :host {
- font-size: 14px;
- }
- }
</style>
</template>
</dom-module>
diff --git a/polygerrit-ui/app/styles/gr-change-metadata-shared-styles.html b/polygerrit-ui/app/styles/gr-change-metadata-shared-styles.html
new file mode 100644
index 0000000000..fef3872501
--- /dev/null
+++ b/polygerrit-ui/app/styles/gr-change-metadata-shared-styles.html
@@ -0,0 +1,48 @@
+<!--
+@license
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<dom-module id="gr-change-metadata-shared-styles">
+ <template>
+ <style include="shared-styles"></style>
+ <style>
+ section {
+ display: table-row;
+ }
+
+ section:not(:first-of-type) .title,
+ section:not(:first-of-type) .value {
+ padding-top: var(--spacing-s);
+ }
+
+ .title,
+ .value {
+ display: table-cell;
+ }
+
+ .title {
+ color: var(--deemphasized-text-color);
+ max-width: 20em;
+ padding-left: var(--metadata-horizontal-padding);
+ padding-right: var(--metadata-horizontal-padding);
+ word-break: break-word;
+ }
+
+ .value {
+ padding-right: var(--metadata-horizontal-padding);
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.html b/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.html
new file mode 100644
index 0000000000..834f64acfc
--- /dev/null
+++ b/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.html
@@ -0,0 +1,54 @@
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ This is shared styles for change-view-integration endpoints.
+ All plugins that registered that endpoint should include this in
+ the component to have a consistent UX:
+
+ <style include="gr-change-view-integration-shared-styles"></style>
+
+ And use those defined class to apply these styles.
+-->
+<dom-module id="gr-change-view-integration-shared-styles">
+ <template>
+ <style include="shared-styles"></style>
+ <style>
+ .header {
+ color: var(--primary-text-color);
+ background-color: var(--table-header-background-color);
+ justify-content: space-between;
+ padding: var(--spacing-m) var(--spacing-l);
+ border-bottom: 1px solid var(--border-color);
+ }
+ .header .label {
+ font-weight: var(--font-weight-bold);
+ font-size: var(--font-size-h3);
+ margin: 0 var(--spacing-l) 0 0;
+ }
+ .header .note {
+ color: var(--deemphasized-text-color);
+ }
+ .content {
+ background-color: var(--view-background-color);
+ }
+ .header a,
+ .content a {
+ color: var(--link-color);
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 65c1ae3cf1..3fe0a72b19 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -27,18 +27,18 @@ limitations under the License.
}
.gr-form-styles h1,
.gr-form-styles h2 {
- margin-bottom: .3em;
+ margin-bottom: var(--spacing-s);
}
.gr-form-styles h4 {
font-weight: var(--font-weight-bold);
}
.gr-form-styles fieldset {
border: none;
- margin-bottom: 2em;
+ margin-bottom: var(--spacing-xxl);
}
.gr-form-styles section {
display: flex;
- margin: .25em 0;
+ margin: var(--spacing-s) 0;
min-height: 2em;
}
.gr-form-styles section * {
@@ -51,12 +51,9 @@ limitations under the License.
.gr-form-styles .title {
color: var(--deemphasized-text-color);
font-weight: var(--font-weight-bold);
- padding-right: .5em;
+ padding-right: var(--spacing-m);
width: 15em;
}
- .gr-form-styles iron-autogrow-textarea {
- font-size: var(--font-size-normal);
- }
.gr-form-styles th {
color: var(--deemphasized-text-color);
text-align: left;
@@ -65,7 +62,7 @@ limitations under the License.
.gr-form-styles td,
.gr-form-styles tfoot th {
height: 2em;
- padding: .25em 0;
+ padding: var(--spacing-s) 0;
vertical-align: middle;
}
.gr-form-styles .emptyHeader {
@@ -86,10 +83,9 @@ limitations under the License.
.gr-form-styles select,
.gr-form-styles textarea {
border: 1px solid var(--border-color);
- border-radius: 2px;
- font-size: var(--font-size-normal);
+ border-radius: var(--border-radius);
height: 2em;
- padding: 0 .15em;
+ padding: 0 var(--spacing-xs);
}
.gr-form-styles td:last-child {
width: 5em;
@@ -104,26 +100,24 @@ limitations under the License.
min-height: 2em;
--iron-autogrow-textarea: {
border: 1px solid var(--border-color);
- border-radius: 2px;
+ border-radius: var(--border-radius);
box-sizing: border-box;
- font-size: var(--font-size-normal);
- padding: .25em .15em 0 .15em;
+ padding: var(--spacing-s) var(--spacing-xs) 0 var(--spacing-xs);
}
}
.gr-form-styles gr-autocomplete {
border: none;
--gr-autocomplete: {
border: 1px solid var(--border-color);
- border-radius: 2px;
- font-size: var(--font-size-normal);
+ border-radius: var(--border-radius);
height: 2em;
- padding: 0 .15em;
+ padding: 0 var(--spacing-xs);
width: 14em;
}
}
@media only screen and (max-width: 40em) {
.gr-form-styles section {
- margin-bottom: 1em;
+ margin-bottom: var(--spacing-l);
}
.gr-form-styles .title,
.gr-form-styles .value {
diff --git a/polygerrit-ui/app/styles/gr-menu-page-styles.html b/polygerrit-ui/app/styles/gr-menu-page-styles.html
index 48ca396e10..d3b95b812c 100644
--- a/polygerrit-ui/app/styles/gr-menu-page-styles.html
+++ b/polygerrit-ui/app/styles/gr-menu-page-styles.html
@@ -22,12 +22,12 @@ limitations under the License.
display: block;
}
main {
- margin: 2em auto;
+ margin: var(--spacing-xxl) auto;
max-width: 50em;
}
.mainHeader {
margin-left: 14em;
- padding: 1em 0 1em 2em;
+ padding: var(--spacing-l) 0 var(--spacing-l) var(--spacing-xxl);
}
main.table,
.mainHeader {
@@ -42,11 +42,11 @@ limitations under the License.
}
.loading {
color: var(--deemphasized-text-color);
- padding: 1em var(--default-horizontal-margin);
+ padding: var(--spacing-l);
}
@media only screen and (max-width: 67em) {
main {
- margin: 2em 0 2em 15em;
+ margin: var(--spacing-xxl) 0 var(--spacing-xxl) 15em;
}
main.table {
margin-left: 14em;
@@ -54,17 +54,17 @@ limitations under the License.
}
@media only screen and (max-width: 53em) {
.loading {
- padding: 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-l);
}
main {
- margin: 2em 1em;
+ margin: var(--spacing-xxl) var(--spacing-l);
}
main.table {
margin: 0;
}
.mainHeader {
margin-left: 0;
- padding: .5em 0 .5em 1em;
+ padding: var(--spacing-m) 0 var(--spacing-m) var(--spacing-l);
}
}
</style>
diff --git a/polygerrit-ui/app/styles/gr-page-nav-styles.html b/polygerrit-ui/app/styles/gr-page-nav-styles.html
index 18ec143ea9..ced6ecb6f1 100644
--- a/polygerrit-ui/app/styles/gr-page-nav-styles.html
+++ b/polygerrit-ui/app/styles/gr-page-nav-styles.html
@@ -18,13 +18,13 @@ limitations under the License.
<template>
<style>
.navStyles ul {
- padding: 1em 0;
+ padding: var(--spacing-l) 0;
}
.navStyles li {
border-bottom: 1px solid transparent;
border-top: 1px solid transparent;
display: block;
- padding: 0 calc(var(--default-horizontal-margin) + 0.5em);
+ padding: 0 var(--spacing-xl);
}
.navStyles li a {
display: block;
@@ -33,20 +33,20 @@ limitations under the License.
white-space: nowrap;
}
.navStyles .subsectionItem {
- padding-left: calc(var(--default-horizontal-margin) + 1.5em);
+ padding-left: var(--spacing-xxl);
}
.navStyles .hideSubsection {
display: none;
}
.navStyles li.sectionTitle {
- padding: 0 2em 0 var(--default-horizontal-margin);
+ padding: 0 var(--spacing-xxl) 0 var(--spacing-l);
}
.navStyles li.sectionTitle:not(:first-child) {
- margin-top: 1em;
+ margin-top: var(--spacing-l);
}
.navStyles .title {
font-weight: var(--font-weight-bold);
- margin: .4em 0;
+ margin: var(--spacing-s) 0;
}
.navStyles .selected {
background-color: var(--view-background-color);
@@ -57,7 +57,7 @@ limitations under the License.
.navStyles a {
color: var(--primary-text-color);
display: inline-block;
- margin: .4em 0;
+ margin: var(--spacing-s) 0;
}
</style>
</template>
diff --git a/polygerrit-ui/app/styles/gr-subpage-styles.html b/polygerrit-ui/app/styles/gr-subpage-styles.html
index 098a604bbd..222c38bd22 100644
--- a/polygerrit-ui/app/styles/gr-subpage-styles.html
+++ b/polygerrit-ui/app/styles/gr-subpage-styles.html
@@ -18,7 +18,7 @@ limitations under the License.
<template>
<style>
main {
- margin: 1em 1em;
+ margin: var(--spacing-l);
}
.loading {
display: none;
diff --git a/polygerrit-ui/app/styles/gr-table-styles.html b/polygerrit-ui/app/styles/gr-table-styles.html
index 13089526c3..d4e4bcf490 100644
--- a/polygerrit-ui/app/styles/gr-table-styles.html
+++ b/polygerrit-ui/app/styles/gr-table-styles.html
@@ -24,7 +24,7 @@ limitations under the License.
}
.genericList td {
height: 2.25rem;
- padding: .3rem 0;
+ padding: var(--spacing-s) 0;
vertical-align: middle;
}
.genericList tr {
@@ -38,11 +38,11 @@ limitations under the License.
}
.genericList th,
.genericList td {
- padding-right: 1rem;
+ padding-right: var(--spacing-l);
}
.genericList tr th:first-of-type,
.genericList tr td:first-of-type {
- padding-left: 1rem;
+ padding-left: var(--spacing-l);
}
.genericList tr:first-of-type {
border-top: 1px solid var(--border-color);
@@ -51,7 +51,7 @@ limitations under the License.
.genericList tr td:last-of-type {
border-left: 1px solid var(--border-color);
text-align: center;
- padding: 0 1em;
+ padding: 0 var(--spacing-l);
}
.genericList tr th.delete,
.genericList tr td.delete,
@@ -78,7 +78,7 @@ limitations under the License.
}
.genericList .groupHeader {
background-color: var(--table-subheader-background-color);
- font-size: var(--font-size-large);
+ font-size: var(--font-size-h3);
}
.genericList a {
color: var(--primary-text-color);
@@ -93,7 +93,7 @@ limitations under the License.
.genericList .loadingMsg {
color: var(--deemphasized-text-color);
display: block;
- padding: .3em var(--default-horizontal-margin);
+ padding: var(--spacing-s) var(--spacing-l);
}
.genericList .loadingMsg:not(.loading) {
display: none;
diff --git a/polygerrit-ui/app/styles/gr-voting-styles.html b/polygerrit-ui/app/styles/gr-voting-styles.html
index 3b1ee6476c..eec79be96c 100644
--- a/polygerrit-ui/app/styles/gr-voting-styles.html
+++ b/polygerrit-ui/app/styles/gr-voting-styles.html
@@ -23,6 +23,7 @@ limitations under the License.
border: 1px solid rgba(0,0,0,.12);
border-radius: 1em;
box-shadow: none;
+ box-sizing: border-box;
min-width: 3em;
}
}
diff --git a/polygerrit-ui/app/styles/main.css b/polygerrit-ui/app/styles/main.css
index 618a2d7128..4c85176728 100644
--- a/polygerrit-ui/app/styles/main.css
+++ b/polygerrit-ui/app/styles/main.css
@@ -26,10 +26,10 @@ html {
-webkit-text-size-adjust: none;
/*
* Default browser fonts are 16px. We want users with default settings to see
- * a base font of 13px. 13/16 = .8125. This needs to be in html because
+ * a base font of 14px. 14/16 = .875. This needs to be in html because
* can use rems based on this font-size throughout the app.
*/
- font-size: .8125em;
+ font-size: .875em;
}
html,
body {
@@ -42,6 +42,8 @@ body {
* Work around this using font-size and font-family.
*/
-webkit-text-size-adjust: none;
- font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
- line-height: 1.4;
+ font-family: var(--font-family, ''), 'Roboto', Arial, sans-serif;
+ font-size: var(--font-size-normal, 1rem);
+ line-height: var(--line-height-normal, 1.4);
+ color: var(--primary-text-color, black);
}
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index 78abe3abda..53147415e1 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -17,7 +17,9 @@ limitations under the License.
<dom-module id="shared-styles">
<template>
<style>
+
/* CSS reset */
+
html, body, button, div, span, applet, object, iframe, h1, h2, h3,
h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite,
code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub,
@@ -33,8 +35,11 @@ limitations under the License.
padding: 0;
vertical-align: baseline;
}
- input,
- iron-autogrow-textarea {
+ *::after,
+ *::before {
+ box-sizing: border-box;
+ }
+ input {
background-color: inherit;
border: 1px solid var(--border-color);
box-sizing: border-box;
@@ -42,6 +47,20 @@ limitations under the License.
margin: 0;
padding: 0;
}
+ iron-autogrow-textarea {
+ background-color: inherit;
+ color: var(--primary-text-color);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ box-sizing: border-box;
+ /* iron-autogrow-textarea has a "-webkit-appearance: textarea" :host
+ css rule, which prevents overriding the border color. Clear that. */
+ -webkit-appearance: none;
+
+ --iron-autogrow-textarea: {
+ padding: 4px;
+ };
+ }
a {
color: var(--link-color);
}
@@ -51,9 +70,6 @@ limitations under the License.
button {
font: inherit;
}
- body {
- line-height: 1;
- }
ol, ul {
list-style: none;
}
@@ -69,25 +85,42 @@ limitations under the License.
border-collapse: collapse;
border-spacing: 0;
}
- /* Other Shared Styles*/
- h1 {
- font-size: 2rem;
+
+ /* Fonts */
+
+ .font-normal {
+ font-size: var(--font-size-normal);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-normal);
+ }
+ .font-small {
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-small);
+ }
+ h1, .font-h1 {
+ font-size: var(--font-size-h1);
font-weight: var(--font-weight-bold);
+ line-height: var(--line-height-h1);
}
- h2 {
- font-size: 1.5rem;
+ h2, .font-h2 {
+ font-size: var(--font-size-h2);
font-weight: var(--font-weight-bold);
+ line-height: var(--line-height-h2);
}
- h3 {
- font-size: 1.17em;
+ h3, .font-h3 {
+ font-size: var(--font-size-h3);
font-weight: var(--font-weight-bold);
+ line-height: var(--line-height-h3);
}
iron-icon {
color: var(--deemphasized-text-color);
--iron-icon-height: 20px;
--iron-icon-width: 20px;
}
+
/* Stopgap solution until we remove hidden$ attributes. */
+
[hidden] {
display: none !important;
}
@@ -103,12 +136,19 @@ limitations under the License.
--paper-toggle-button-checked-bar-color: var(--link-color);
--paper-toggle-button-checked-button-color: var(--link-color);
}
+ paper-tabs {
+ --paper-tab-content-focused: {
+ /* paper-tabs uses 700 here, which can look awkward */
+ font-weight: var(--font-weight-normal);
+ };
+ --paper-tab-content-unselected: {
+ /* paper-tabs uses 0.8 here, but we want to control the color directly */
+ opacity: 1;
+ color: var(--deemphasized-text-color);
+ }; }
strong {
font-weight: var(--font-weight-bold);
}
- :host {
- color: var(--primary-text-color);
- }
</style>
</template>
</dom-module>
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index ec47c53fdc..bb477c28a0 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -16,139 +16,167 @@ limitations under the License.
-->
<custom-style><style is="custom-style">
html {
- /* Following vars have LTS for plugin API. */
- --primary-text-color: #000;
- /* For backwords compatibility we keep this as --header-background-color. */
- --header-background-color: #eee;
- --header-title-content: 'Gerrit';
- --header-icon: none;
- --header-icon-size: 0em;
- --header-text-color: #000;
- --header-title-font-size: 1.75rem;
- --footer-background-color: #eee;
- --border-color: #ddd;
- /* This allows to add a border in custom themes */
- --header-border-bottom: 1px solid var(--border-color);
- --header-border-image: '';
- --footer-border-top: 1px solid var(--border-color);
-
- /* Following are not part of plugin API. */
- --selection-background-color: rgba(161, 194, 250, 0.1);
- --hover-background-color: rgba(161, 194, 250, 0.2);
- --expanded-background-color: #eee;
- --view-background-color: #fff;
- --default-horizontal-margin: 1rem;
-
- --deemphasized-text-color: #757575;
- /* Used on text color for change list that doesn't need user's attention. */
- --reviewed-text-color: var(--primary-text-color);
- --font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
- /* Used on text for change list that needs user's attention. */
- --font-weight-bold: 500;
- --monospace-font-family: 'Roboto Mono', Menlo, 'Lucida Console', Monaco, monospace;
- --iron-overlay-backdrop: {
- transition: none;
- }
- --table-header-background-color: #fafafa;
- --table-subheader-background-color: #eaeaea;
-
- --chip-background-color: #eee;
-
- --dropdown-background-color: #fff;
-
- --select-background-color: rgb(248, 248, 248);
-
- --assignee-highlight-color: #fcfad6;
-
- /* Font sizes */
- --font-size-normal: 1rem;
- --font-size-small: .92rem;
- --font-size-large: 1.154rem;
-
+ /**
+ * When adding a new color variable make sure to also add it to the other
+ * theme files in the same directory.
+ *
+ * For colors prefer lower case hex colors.
+ *
+ * Note that plugins might be using these variables, so removing a variable
+ * can be a breaking change that should go into the release notes.
+ */
+
+ /* text colors */
+ --primary-text-color: black;
--link-color: #2a66d9;
- --primary-button-background-color: var(--link-color);
- --primary-button-text-color: #fff;
- --secondary-button-background-color: #fff;
+ --comment-text-color: black;
+ --deemphasized-text-color: #5F6368;
+ --default-button-text-color: #2a66d9;
+ --error-text-color: red;
+ --primary-button-text-color: white;
+ /* Used on text color for change list that doesn't need user's attention. */
+ --reviewed-text-color: black;
--secondary-button-text-color: #212121;
- --default-button-background-color: #fff;
- --default-button-text-color: var(--link-color);
- --dialog-background-color: #fff;
+ --tooltip-text-color: white;
+ --vote-text-color-recommended: #388e3c;
+ --vote-text-color-disliked: #d32f2f;
- /* Used for both the old patchset header and for indicating that a particular
- change message was selected. */
+ /* background colors */
+ --assignee-highlight-color: #fcfad6;
+ --chip-background-color: #eee;
+ --comment-background-color: #fcfad6;
+ --default-button-background-color: white;
+ --dialog-background-color: white;
+ --dropdown-background-color: white;
+ --edit-mode-background-color: #ebf5fb;
--emphasis-color: #fff9c4;
-
- --error-text-color: red;
-
+ --expanded-background-color: #eee;
+ --hover-background-color: rgba(161, 194, 250, 0.2);
+ --primary-button-background-color: #2a66d9;
+ --secondary-button-background-color: white;
+ --select-background-color: #f8f8f8;
+ --selection-background-color: rgba(161, 194, 250, 0.1);
+ --shell-command-background-color: #f5f5f5;
+ --shell-command-decoration-background-color: #ebebeb;
+ --table-header-background-color: #fafafa;
+ --table-subheader-background-color: #eaeaea;
+ --tooltip-background-color: #333;
+ --unresolved-comment-background-color: #fcfaa6;
+ --view-background-color: white;
--vote-color-approved: #9fcc6b;
- --vote-color-recommended: #c9dfaf;
- --vote-color-rejected: #f7a1ad;
--vote-color-disliked: #f7c4cb;
--vote-color-neutral: #ebf5fb;
+ --vote-color-recommended: #c9dfaf;
+ --vote-color-rejected: #f7a1ad;
- --vote-text-color-recommended: #388E3C;
- --vote-text-color-disliked: #D32F2F;
-
- /* Diff colors */
- --diff-selection-background-color: #c7dbf9;
- --light-remove-highlight-color: #FFEBEE;
- --light-add-highlight-color: #D8FED8;
- --light-remove-add-highlight-color: #FFF8DC;
- --light-rebased-add-highlight-color: #EEEEFF;
- --dark-remove-highlight-color: #FFCDD2;
- --dark-add-highlight-color: #AAF2AA;
- --dark-rebased-remove-highlight-color: #F7E8B7;
- --dark-rebased-add-highlight-color: #D7D7F9;
- --diff-context-control-color: #fff7d4;
- --diff-context-control-border-color: #f6e6a5;
- --diff-tab-indicator-color: var(--deemphasized-text-color);
- --diff-trailing-whitespace-indicator: #ff9ad2;
- --diff-highlight-range-color: rgba(255, 213, 0, 0.5);
- --diff-highlight-range-hover-color: rgba(255, 255, 0, 0.5);
+ /* misc colors */
+ --border-color: #ddd;
- --shell-command-background-color: #f5f5f5;
- --shell-command-decoration-background-color: #ebebeb;
+ /* fonts */
+ --font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ --monospace-font-family: 'Roboto Mono', 'SF Mono', 'Lucida Console', Monaco, monospace;
+ --font-size-code: 12px; /* 12px mono */
+ --font-size-mono: .929rem; /* 13px mono */
+ --font-size-small: .857rem; /* 12px */
+ --font-size-normal: 1rem; /* 14px */
+ --font-size-h3: 1.143rem; /* 16px */
+ --font-size-h2: 1.429rem; /* 20px */
+ --font-size-h1: 1.714rem; /* 24px */
+ --line-height-code: 1.334; /* 16px */
+ --line-height-mono: 1.286rem; /* 18px */
+ --line-height-small: 1.143rem; /* 16px */
+ --line-height-normal: 1.429rem; /* 20px */
+ --line-height-h3: 1.714rem; /* 24px */
+ --line-height-h2: 2rem; /* 28px */
+ --line-height-h1: 2.286rem; /* 32px */
+ --font-weight-normal: 400;
+ --font-weight-bold: 500;
- --comment-text-color: #000;
- --comment-background-color: #fcfad6;
- --unresolved-comment-background-color: #fcfaa6;
+ /* spacing */
+ --spacing-xxs: 1px;
+ --spacing-xs: 2px;
+ --spacing-s: 4px;
+ --spacing-m: 8px;
+ --spacing-l: 12px;
+ --spacing-xl: 16px;
+ --spacing-xxl: 24px;
- --edit-mode-background-color: #ebf5fb;
+ /* header and footer */
+ --footer-background-color: #eee;
+ --footer-border-top: 1px solid var(--border-color);
+ --header-background-color: #eee;
+ --header-border-bottom: 1px solid var(--border-color);
+ --header-border-image: '';
+ --header-box-shadow: none;
+ --header-padding: 0 var(--spacing-l);
+ --header-icon-size: 0em;
+ --header-icon: none;
+ --header-text-color: black;
+ --header-title-content: 'Gerrit';
+ --header-title-font-size: 1.75rem;
- --tooltip-background-color: #333;
- --tooltip-text-color: #fff;
+ /* diff colors */
+ --dark-add-highlight-color: #aaf2aa;
+ --dark-rebased-add-highlight-color: #d7d7f9;
+ --dark-rebased-remove-highlight-color: #f7e8b7;
+ --dark-remove-highlight-color: #ffcdd2;
+ --diff-blank-background-color: white;
+ --diff-context-control-background-color: #fff7d4;
+ --diff-context-control-border-color: #f6e6a5;
+ --diff-context-control-color: var(--deemphasized-text-color);
+ --diff-highlight-range-color: rgba(255, 213, 0, 0.5);
+ --diff-highlight-range-hover-color: rgba(255, 255, 0, 0.5);
+ --diff-selection-background-color: #c7dbf9;
+ --diff-tab-indicator-color: var(--deemphasized-text-color);
+ --diff-trailing-whitespace-indicator: #ff9ad2;
+ --light-add-highlight-color: #d8fed8;
+ --light-rebased-add-highlight-color: #eef;
+ --light-remove-add-highlight-color: #fff8dc;
+ --light-remove-highlight-color: #ffebee;
- --syntax-default-color: var(--primary-text-color);
+ /* syntax colors */
+ --syntax-attr-color: #219;
--syntax-attribute-color: var(--primary-text-color);
- --syntax-function-color: var(--primary-text-color);
- --syntax-meta-color: #FF1717;
- --syntax-keyword-color: #9E0069;
- --syntax-number-color: #164;
- --syntax-selector-class-color: #164;
- --syntax-variable-color: black;
- --syntax-template-variable-color: #0000C0;
- --syntax-comment-color: #3F7F5F;
- --syntax-string-color: #2A00FF;
- --syntax-selector-id-color: #2A00FF;
--syntax-built_in-color: #30a;
- --syntax-tag-color: #170;
+ --syntax-comment-color: #3f7f5f;
+ --syntax-default-color: var(--primary-text-color);
+ --syntax-doctag-weight: bold;
+ --syntax-function-color: var(--primary-text-color);
+ --syntax-keyword-color: #9e0069;
--syntax-link-color: #219;
- --syntax-meta-keyword-color: #219;
- --syntax-type-color: var(--color-link);
- --syntax-title-color: #0000C0;
- --syntax-attr-color: #219;
--syntax-literal-color: #219;
- --syntax-selector-pseudo-color: #FA8602;
- --syntax-regexp-color: #FA8602;
- --syntax-selector-attr-color: #FA8602;
- --syntax-template-tag-color: #FA8602;
+ --syntax-meta-color: #ff1717;
+ --syntax-meta-keyword-color: #219;
+ --syntax-number-color: #164;
--syntax-params-color: var(--primary-text-color);
- --syntax-doctag-weight: bold;
+ --syntax-regexp-color: #fa8602;
+ --syntax-selector-attr-color: #fa8602;
+ --syntax-selector-class-color: #164;
+ --syntax-selector-id-color: #2a00ff;
+ --syntax-selector-pseudo-color: #fa8602;
+ --syntax-string-color: #2a00ff;
+ --syntax-tag-color: #170;
+ --syntax-template-tag-color: #fa8602;
+ --syntax-template-variable-color: #0000c0;
+ --syntax-title-color: #0000c0;
+ --syntax-type-color: #2a66d9;
+ --syntax-variable-color: var(--primary-text-color);
+ /* misc */
+ --border-radius: 4px;
--reply-overlay-z-index: 1000;
+ --iron-overlay-backdrop: {
+ transition: none;
+ };
}
@media screen and (max-width: 50em) {
html {
- --default-horizontal-margin: .7rem;
+ --spacing-xxs: 1px;
+ --spacing-xs: 1px;
+ --spacing-s: 2px;
+ --spacing-m: 4px;
+ --spacing-l: 8px;
+ --spacing-xl: 12px;
+ --spacing-xxl: 16px;
}
}
</style></custom-style>
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 718ac25658..957cc25344 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -17,97 +17,124 @@ limitations under the License.
<dom-module id="dark-theme">
<custom-style><style is="custom-style">
html {
- --primary-text-color: #e2e2e2;
- --view-background-color: #212121;
- --border-color: #555555;
- --header-border-bottom: 1px solid var(--border-color);
- --header-border-image: '';
- --footer-border-bottom: 1px solid var(--border-color);
- --table-header-background-color: #353637;
- --table-subheader-background-color: rgb(19, 20, 22);
- --header-background-color: #3c4043;
- --header-text-color: var(--primary-text-color);
- --deemphasized-text-color: #9a9a9a;
- /* Used on text color for change list doesn't need user's attention. */
- --reviewed-text-color: #DADCE0;
- /* Used on text for change list that needs user's attention. */
- --font-weight-bold: 900;
- --footer-background-color: var(--table-header-background-color);
+ /**
+ * Sections and variables must stay consistent with app-theme.html.
+ *
+ * Only modify color variables in this theme file. dark-theme extends
+ * app-theme, so there is no need to repeat all variables, but for colors
+ * it does make sense to list them all: If you override one color, then
+ * you probably want to override all.
+ */
+
+ /* text colors */
+ --primary-text-color: #e8eaed;
+ --link-color: #8ab4f8;
+ --comment-text-color: var(--primary-text-color);
+ --deemphasized-text-color: #9e9e9e;
+ --default-button-text-color: #8ab4f8;
+ --error-text-color: red;
+ --primary-button-text-color: var(--primary-text-color);
+ /* Used on text color for change list doesn't need user's attention. */
+ --reviewed-text-color: #dadce0;
+ --secondary-button-text-color: var(--deemphasized-text-color);
+ --tooltip-text-color: white;
+ --vote-text-color-recommended: #388e3c;
+ --vote-text-color-disliked: #d32f2f;
+
+ /* background colors */
+ --assignee-highlight-color: #3a361c;
+ --chip-background-color: #131416;
+ --comment-background-color: #0b162b;
+ --default-button-background-color: #3c4043;
+ --dialog-background-color: #131416;
+ --dropdown-background-color: #131416;
+ --edit-mode-background-color: #5c0a36;
+ --emphasis-color: #383f4a;
--expanded-background-color: #26282b;
- --link-color: #5487E5;
+ --hover-background-color: rgba(161, 194, 250, 0.2);
--primary-button-background-color: var(--link-color);
- --primary-button-text-color: var(--primary-text-color);
--secondary-button-background-color: var(--primary-text-color);
- --secondary-button-text-color: var(--deemphasized-text-color);
- --default-button-text-color: var(--link-color);
- --default-button-background-color: var(--table-subheader-background-color);
- --dropdown-background-color: var(--table-header-background-color);
- --dialog-background-color: var(--view-background-color);
- --chip-background-color: var(--table-header-background-color);
- --header-title-font-size: 1.75rem;
+ --select-background-color: #3c4043;
+ --selection-background-color: rgba(161, 194, 250, 0.1);
+ --shell-command-background-color: #5f5f5f;
+ --shell-command-decoration-background-color: #999;
+ --table-header-background-color: #131416;
+ --table-subheader-background-color: rgba(158, 158, 158, 0.24);
+ --tooltip-background-color: #111;
+ --unresolved-comment-background-color: #385a9a;
+ --view-background-color: #131416;
+ --vote-color-approved: #7fb66b;
+ --vote-color-disliked: #bf6874;
+ --vote-color-neutral: #597280;
+ --vote-color-recommended: #3f6732;
+ --vote-color-rejected: #ac2d3e;
- --select-background-color: var(--table-subheader-background-color);
+ /* misc colors */
+ --border-color: #5f6368;
- --assignee-highlight-color: rgb(58, 54, 28);
+ /* fonts */
+ --font-weight-bold: 900;
- --diff-selection-background-color: #3A71D8;
- --light-remove-highlight-color: #320404;
- --light-add-highlight-color: #0F401F;
- --light-remove-add-highlight-color: #2f3f2f;
- --light-rebased-remove-highlight-color: rgb(60, 37, 8);
- --light-rebased-add-highlight-color: rgb(72, 113, 101);
- --dark-remove-highlight-color: #62110F;
+ /* spacing */
+
+ /* header and footer */
+ --footer-background-color: #131416;
+ --footer-border-top: 1px solid var(--border-color);
+ --header-background-color: #3c4043;
+ --header-border-bottom: 1px solid var(--border-color);
+ --header-padding: 0 var(--spacing-l);
+ --header-text-color: var(--primary-text-color);
+
+ /* diff colors */
--dark-add-highlight-color: #133820;
- --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
--dark-rebased-add-highlight-color: rgba(11, 255, 155, 0.15);
- --diff-context-control-color: var(--table-header-background-color);
+ --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
+ --dark-remove-highlight-color: #62110f;
+ --diff-blank-background-color: #212121;
+ --diff-context-control-background-color: #131416;
--diff-context-control-border-color: var(--border-color);
+ --diff-context-control-color: var(--deemphasized-text-color);
--diff-highlight-range-color: rgba(0, 100, 200, 0.5);
--diff-highlight-range-hover-color: rgba(0, 150, 255, 0.5);
- --shell-command-background-color: #5f5f5f;
- --shell-command-decoration-background-color: #999999;
- --comment-text-color: var(--primary-text-color);
- --comment-background-color: #0B162B;
- --unresolved-comment-background-color: rgb(56, 90, 154);
-
- --vote-color-approved: rgb(127, 182, 107);
- --vote-color-recommended: rgb(63, 103, 50);
- --vote-color-rejected: #ac2d3e;
- --vote-color-disliked: #bf6874;
- --vote-color-neutral: #597280;
-
- --edit-mode-background-color: rgb(92, 10, 54);
- --emphasis-color: #383f4a;
-
- --tooltip-background-color: #111;
+ --diff-selection-background-color: #3a71d8;
+ --diff-tab-indicator-color: var(--deemphasized-text-color);
+ --diff-trailing-whitespace-indicator: #ff9ad2;
+ --light-add-highlight-color: #0f401f;
+ --light-rebased-add-highlight-color: #487165;
+ --light-remove-add-highlight-color: #2f3f2f;
+ --light-remove-highlight-color: #320404;
- --syntax-default-color: var(--primary-text-color);
- --syntax-meta-color: #6D7EEE;
- --syntax-keyword-color: #CD4CF0;
- --syntax-number-color: #00998A;
- --syntax-selector-class-color: #FFCB68;
- --syntax-variable-color: #F77669;
- --syntax-template-variable-color: #F77669;
+ /* syntax colors */
+ --syntax-attr-color: #80cbbf;
+ --syntax-attribute-color: var(--primary-text-color);
+ --syntax-built_in-color: #f7c369;
--syntax-comment-color: var(--deemphasized-text-color);
- --syntax-string-color: #C3E88D;
- --syntax-selector-id-color: #F77669;
- --syntax-built_in-color: rgb(247, 195, 105);
- --syntax-tag-color: #F77669;
- --syntax-link-color: #C792EA;
- --syntax-meta-keyword-color: #EEFFF7;
- --syntax-type-color: #DD5F5F;
- --syntax-title-color: #75A5FF;
- --syntax-attr-color: #80CBBF;
- --syntax-literal-color: #EEFFF7;
- --syntax-selector-pseudo-color: #C792EA;
- --syntax-regexp-color: #F77669;
- --syntax-selector-attr-color: #80CBBF;
- --syntax-template-tag-color: #C792EA;
+ --syntax-default-color: var(--primary-text-color);
--syntax-doctag-weight: bold;
+ --syntax-function-color: var(--primary-text-color);
+ --syntax-keyword-color: #cd4cf0;
+ --syntax-link-color: #c792ea;
+ --syntax-literal-color: #eefff7;
+ --syntax-meta-color: #6d7eee;
+ --syntax-meta-keyword-color: #eefff7;
+ --syntax-number-color: #00998a;
--syntax-params-color: var(--primary-text-color);
+ --syntax-regexp-color: #f77669;
+ --syntax-selector-attr-color: #80cbbf;
+ --syntax-selector-class-color: #ffcb68;
+ --syntax-selector-id-color: #f77669;
+ --syntax-selector-pseudo-color: #c792ea;
+ --syntax-string-color: #c3e88d;
+ --syntax-tag-color: #f77669;
+ --syntax-template-tag-color: #c792ea;
+ --syntax-template-variable-color: #f77669;
+ --syntax-title-color: #75a5ff;
+ --syntax-type-color: #dd5f5f;
+ --syntax-variable-color: #f77669;
- --reply-overlay-z-index: 1000;
+ /* misc */
+ /* rules applied to <html> */
background-color: var(--view-background-color);
}
</style></custom-style>
diff --git a/polygerrit-ui/app/template_test_srcs/template_test.js b/polygerrit-ui/app/template_test_srcs/template_test.js
index 3de6227733..d715d7d442 100644
--- a/polygerrit-ui/app/template_test_srcs/template_test.js
+++ b/polygerrit-ui/app/template_test_srcs/template_test.js
@@ -1,45 +1,6 @@
const fs = require('fs');
const twinkie = require('fried-twinkie');
-/**
- * For the purposes of template type checking, externs should be added for
- * anything set on the window object. Note that sub-properties of these
- * declared properties are considered something separate.
- *
- * @todo (beckysiegel) Gerrit's class definitions should be recognized in
- * closure types.
- */
-const EXTERN_NAMES = [
- 'Gerrit',
- 'GrAnnotation',
- 'GrAttributeHelper',
- 'GrChangeActionsInterface',
- 'GrChangeReplyInterface',
- 'GrDiffBuilder',
- 'GrDiffBuilderImage',
- 'GrDiffBuilderSideBySide',
- 'GrDiffBuilderUnified',
- 'GrDiffGroup',
- 'GrDiffLine',
- 'GrDomHooks',
- 'GrEditConstants',
- 'GrEtagDecorator',
- 'GrFileListConstants',
- 'GrGapiAuth',
- 'GrGerritAuth',
- 'GrLinkTextParser',
- 'GrPluginEndpoints',
- 'GrPopupInterface',
- 'GrRangeNormalizer',
- 'GrReporting',
- 'GrReviewerUpdatesParser',
- 'GrCountStringFormatter',
- 'GrThemeApi',
- 'moment',
- 'page',
- 'util',
-];
-
fs.readdir('./polygerrit-ui/temp/behaviors/', (err, data) => {
if (err) {
console.log('error /polygerrit-ui/temp/behaviors/ directory');
@@ -87,30 +48,39 @@ fs.readdir('./polygerrit-ui/temp/behaviors/', (err, data) => {
mappings = mappingSpecificFile;
}
- additionalSources.push({
- path: 'custom-externs.js',
- src: '/** @externs */' +
- EXTERN_NAMES.map( name => { return `var ${name};`; }).join(' '),
- });
-
- const toCheck = [];
- for (key of Object.keys(mappings)) {
- if (mappings[key].html && mappings[key].js) {
- toCheck.push({
- htmlSrcPath: mappings[key].html,
- jsSrcPath: mappings[key].js,
- jsModule: 'polygerrit.' + mappings[key].package,
+ /**
+ * Types in Gerrit.
+ * All types should be under `./polygerrit-ui/app/types` folder and end with `js`.
+ */
+ fs.readdir('./polygerrit-ui/app/types/', (err, typeFiles) => {
+ for (const typeFile of typeFiles) {
+ if (!typeFile.endsWith('.js')) continue;
+ additionalSources.push({
+ path: `./polygerrit-ui/app/types/${typeFile}`,
+ src: fs.readFileSync(
+ `./polygerrit-ui/app/types/${typeFile}`, 'utf-8'),
});
}
- }
- twinkie.checkTemplate(toCheck, additionalSources)
- .then(() => {}, joinedErrors => {
- if (joinedErrors) {
+ const toCheck = [];
+ for (key of Object.keys(mappings)) {
+ if (mappings[key].html && mappings[key].js) {
+ toCheck.push({
+ htmlSrcPath: mappings[key].html,
+ jsSrcPath: mappings[key].js,
+ jsModule: 'polygerrit.' + mappings[key].package,
+ });
+ }
+ }
+
+ twinkie.checkTemplate(toCheck, additionalSources)
+ .then(() => {}, joinedErrors => {
+ if (joinedErrors) {
+ process.exit(1);
+ }
+ }).catch(e => {
+ console.error(e);
process.exit(1);
- }
- }).catch(e => {
- console.error(e);
- process.exit(1);
- });
+ });
+ });
});
diff --git a/polygerrit-ui/app/test/common-test-setup.html b/polygerrit-ui/app/test/common-test-setup.html
index a549dd4c52..c1d8bbdb2f 100644
--- a/polygerrit-ui/app/test/common-test-setup.html
+++ b/polygerrit-ui/app/test/common-test-setup.html
@@ -17,7 +17,7 @@ limitations under the License.
-->
<link rel="import"
- href="../bower_components/polymer-resin/standalone/polymer-resin.html" />
+ href="/bower_components/polymer-resin/standalone/polymer-resin.html" />
<link rel="import" href="../behaviors/safe-types-behavior/safe-types-behavior.html">
<script>
security.polymer_resin.install({
@@ -53,13 +53,13 @@ limitations under the License.
(function() {
setup(() => {
if (!window.Gerrit) { return; }
- if (Gerrit._resetPlugins) {
- Gerrit._resetPlugins();
+ if (Gerrit._testOnly_resetPlugins) {
+ Gerrit._testOnly_resetPlugins();
}
});
})();
</script>
<link rel="import"
- href="../bower_components/iron-test-helpers/iron-test-helpers.html" />
+ href="/bower_components/iron-test-helpers/iron-test-helpers.html" />
<link rel="import" href="test-router.html" />
-<script src="../bower_components/moment/moment.js"></script>
+<script src="/bower_components/moment/moment.js"></script>
diff --git a/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
new file mode 100644
index 0000000000..7ceff7e803
--- /dev/null
+++ b/polygerrit-ui/app/test/common-test-setup.js
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Helps looking up the proper iron-input element during the Polymer 2
+ * transition. Polymer 2 uses the <iron-input> element, while Polymer 1 uses
+ * the nested <input is="iron-input"> element.
+ */
+window.ironInput = function(element) {
+ return Polymer.dom(element).querySelector(
+ Polymer.Element ? 'iron-input' : 'input[is=iron-input]');
+};
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index cd3aaeccf3..cca7d045ec 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -19,10 +19,11 @@ limitations under the License.
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>Elements Test Runner</title>
<meta charset="utf-8">
-<script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
-<script src="../bower_components/web-component-tester/browser.js"></script>
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
<script>
const testFiles = [];
+ const scriptsPath = '../scripts/';
const elementsPath = '../elements/';
const behaviorsPath = '../behaviors/';
@@ -61,9 +62,8 @@ limitations under the License.
'change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html',
'change-list/gr-create-change-help/gr-create-change-help_test.html',
'change-list/gr-dashboard-view/gr-dashboard-view_test.html',
+ 'change-list/gr-repo-header/gr-repo-header_test.html',
'change-list/gr-user-header/gr-user-header_test.html',
- 'change/gr-account-entry/gr-account-entry_test.html',
- 'change/gr-account-list/gr-account-list_test.html',
'change/gr-change-actions/gr-change-actions_test.html',
'change/gr-change-metadata/gr-change-metadata-it_test.html',
'change/gr-change-metadata/gr-change-metadata_test.html',
@@ -99,16 +99,18 @@ limitations under the License.
'core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html',
'core/gr-main-header/gr-main-header_test.html',
'core/gr-navigation/gr-navigation_test.html',
- 'core/gr-reporting/gr-jank-detector_test.html',
'core/gr-reporting/gr-reporting_test.html',
'core/gr-router/gr-router_test.html',
'core/gr-search-bar/gr-search-bar_test.html',
'core/gr-smart-search/gr-smart-search_test.html',
'diff/gr-comment-api/gr-comment-api_test.html',
+ 'diff/gr-coverage-layer/gr-coverage-layer_test.html',
'diff/gr-diff-builder/gr-diff-builder_test.html',
+ 'diff/gr-diff-builder/gr-diff-builder-unified_test.html',
'diff/gr-diff-cursor/gr-diff-cursor_test.html',
'diff/gr-diff-highlight/gr-annotation_test.html',
'diff/gr-diff-highlight/gr-diff-highlight_test.html',
+ 'diff/gr-diff-host/gr-diff-host_test.html',
'diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html',
'diff/gr-diff-processor/gr-diff-processor_test.html',
'diff/gr-diff-selection/gr-diff-selection_test.html',
@@ -125,7 +127,9 @@ limitations under the License.
'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
'edit/gr-editor-view/gr-editor-view_test.html',
'plugins/gr-admin-api/gr-admin-api_test.html',
+ 'plugins/gr-styles-api/gr-styles-api_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
+ 'plugins/gr-dom-hooks/gr-dom-hooks_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
'plugins/gr-external-style/gr-external-style_test.html',
@@ -134,7 +138,9 @@ limitations under the License.
'plugins/gr-popup-interface/gr-popup-interface_test.html',
'plugins/gr-repo-api/gr-repo-api_test.html',
'plugins/gr-settings-api/gr-settings-api_test.html',
+ 'plugins/gr-theme-api/gr-theme-api_test.html',
'settings/gr-account-info/gr-account-info_test.html',
+ 'settings/gr-agreements-list/gr-agreements-list_test.html',
'settings/gr-change-table-editor/gr-change-table-editor_test.html',
'settings/gr-cla-view/gr-cla-view_test.html',
'settings/gr-edit-preferences/gr-edit-preferences_test.html',
@@ -149,7 +155,9 @@ limitations under the License.
'settings/gr-ssh-editor/gr-ssh-editor_test.html',
'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html',
'shared/gr-event-interface/gr-event-interface_test.html',
+ 'shared/gr-account-entry/gr-account-entry_test.html',
'shared/gr-account-label/gr-account-label_test.html',
+ 'shared/gr-account-list/gr-account-list_test.html',
'shared/gr-account-link/gr-account-link_test.html',
'shared/gr-alert/gr-alert_test.html',
'shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html',
@@ -161,33 +169,47 @@ limitations under the License.
'shared/gr-comment-thread/gr-comment-thread_test.html',
'shared/gr-comment/gr-comment_test.html',
'shared/gr-copy-clipboard/gr-copy-clipboard_test.html',
+ 'shared/gr-count-string-formatter/gr-count-string-formatter_test.html',
'shared/gr-cursor-manager/gr-cursor-manager_test.html',
'shared/gr-date-formatter/gr-date-formatter_test.html',
'shared/gr-dialog/gr-dialog_test.html',
'shared/gr-diff-preferences/gr-diff-preferences_test.html',
'shared/gr-download-commands/gr-download-commands_test.html',
+ 'shared/gr-dropdown/gr-dropdown_test.html',
'shared/gr-dropdown-list/gr-dropdown-list_test.html',
'shared/gr-editable-content/gr-editable-content_test.html',
'shared/gr-editable-label/gr-editable-label_test.html',
'shared/gr-formatted-text/gr-formatted-text_test.html',
+ 'shared/gr-hovercard/gr-hovercard_test.html',
+ 'shared/gr-js-api-interface/gr-annotation-actions-context_test.html',
+ 'shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html',
'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
'shared/gr-js-api-interface/gr-change-reply-js-api_test.html',
+ 'shared/gr-js-api-interface/gr-api-utils_test.html',
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
+ 'shared/gr-js-api-interface/gr-gerrit_test.html',
+ 'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
+ 'shared/gr-js-api-interface/gr-plugin-loader_test.html',
'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
'shared/gr-fixed-panel/gr-fixed-panel_test.html',
'shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html',
+ 'shared/gr-label-info/gr-label-info_test.html',
'shared/gr-lib-loader/gr-lib-loader_test.html',
'shared/gr-limited-text/gr-limited-text_test.html',
'shared/gr-linked-chip/gr-linked-chip_test.html',
'shared/gr-linked-text/gr-linked-text_test.html',
'shared/gr-list-view/gr-list-view_test.html',
+ 'shared/gr-overlay/gr-overlay_test.html',
'shared/gr-page-nav/gr-page-nav_test.html',
'shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html',
'shared/gr-rest-api-interface/gr-auth_test.html',
+ 'shared/gr-rest-api-interface/gr-etag-decorator_test.html',
'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
'shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html',
+ 'shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html',
'shared/gr-select/gr-select_test.html',
+ 'shared/gr-shell-command/gr-shell-command_test.html',
'shared/gr-storage/gr-storage_test.html',
'shared/gr-textarea/gr-textarea_test.html',
'shared/gr-tooltip-content/gr-tooltip-content_test.html',
@@ -198,7 +220,6 @@ limitations under the License.
for (let file of elements) {
file = elementsPath + file;
testFiles.push(file);
- testFiles.push(file + '?dom=shadow');
}
// Behaviors tests.
@@ -212,8 +233,9 @@ limitations under the License.
'rest-client-behavior/rest-client-behavior_test.html',
'gr-access-behavior/gr-access-behavior_test.html',
'gr-admin-nav-behavior/gr-admin-nav-behavior_test.html',
- 'gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html',
'gr-change-table-behavior/gr-change-table-behavior_test.html',
+ 'gr-list-view-behavior/gr-list-view-behavior_test.html',
+ 'gr-display-name-behavior/gr-display-name-behavior_test.html',
'gr-patch-set-behavior/gr-patch-set-behavior_test.html',
'gr-path-list-behavior/gr-path-list-behavior_test.html',
'gr-tooltip-behavior/gr-tooltip-behavior_test.html',
@@ -227,5 +249,17 @@ limitations under the License.
testFiles.push(file);
}
+ const scripts = [
+ 'gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html',
+ 'gr-group-suggestions-provider/gr-group-suggestions-provider_test.html',
+ 'gr-display-name-utils/gr-display-name-utils_test.html',
+ 'gr-email-suggestions-provider/gr-email-suggestions-provider_test.html',
+ ];
+ /* eslint-enable max-len */
+ for (let file of scripts) {
+ file = scriptsPath + file;
+ testFiles.push(file);
+ }
+
WCT.loadSuites(testFiles);
</script>
diff --git a/polygerrit-ui/app/types/custom-externs.js b/polygerrit-ui/app/types/custom-externs.js
new file mode 100644
index 0000000000..afa094c02c
--- /dev/null
+++ b/polygerrit-ui/app/types/custom-externs.js
@@ -0,0 +1,63 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * For the purposes of template type checking, externs should be added for
+ * anything set on the window object. Note that sub-properties of these
+ * declared properties are considered something separate.
+ *
+ * This file is only for template type checking, not used in Gerrit code.
+ */
+
+/* eslint-disable no-var */
+/* eslint-disable no-unused-vars */
+/** @externs */
+// @unused
+
+var Gerrit;
+var GrAnnotation;
+var GrAttributeHelper;
+var GrChangeActionsInterface;
+var GrChangeReplyInterface;
+var GrDiffBuilder;
+var GrDiffBuilderImage;
+var GrDiffBuilderSideBySide;
+var GrDiffBuilderUnified;
+var GrDiffGroup;
+var GrDiffLine;
+var GrDomHooks;
+var GrEditConstants;
+var GrEtagDecorator;
+var GrFileListConstants;
+var GrGapiAuth;
+var GrGerritAuth;
+var GrLinkTextParser;
+var GrPluginEndpoints;
+var GrPopupInterface;
+var GrRangeNormalizer;
+var GrReporting;
+var GrReviewerUpdatesParser;
+var GrCountStringFormatter;
+var GrThemeApi;
+var SiteBasedCache;
+var FetchPromisesCache;
+var GrRestApiHelper;
+var GrDisplayNameUtils;
+var GrReviewerSuggestionsProvider;
+var moment;
+var page;
+var util; \ No newline at end of file
diff --git a/polygerrit-ui/app/types/types.js b/polygerrit-ui/app/types/types.js
new file mode 100644
index 0000000000..e17bec8a81
--- /dev/null
+++ b/polygerrit-ui/app/types/types.js
@@ -0,0 +1,279 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Type definitions used across multiple files in Gerrit
+
+window.Gerrit = window.Gerrit || {};
+
+/** @enum {string} */
+Gerrit.CoverageType = {
+ /**
+ * start_character and end_character of the range will be ignored for this
+ * type.
+ */
+ COVERED: 'COVERED',
+ /**
+ * start_character and end_character of the range will be ignored for this
+ * type.
+ */
+ NOT_COVERED: 'NOT_COVERED',
+ PARTIALLY_COVERED: 'PARTIALLY_COVERED',
+ /**
+ * You don't have to use this. If there is no coverage information for a
+ * range, then it implicitly means NOT_INSTRUMENTED. start_character and
+ * end_character of the range will be ignored for this type.
+ */
+ NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
+};
+
+/**
+ * @typedef {{
+ * start_line: number,
+ * start_character: number,
+ * end_line: number,
+ * end_character: number,
+ * }}
+ */
+Gerrit.Range;
+
+/**
+ * @typedef {{side: string, range: Gerrit.Range, hovering: boolean}}
+ */
+Gerrit.HoveredRange;
+
+/**
+ * @typedef {{
+ * side: string,
+ * type: Gerrit.CoverageType,
+ * code_range: Gerrit.Range,
+ * }}
+ */
+Gerrit.CoverageRange;
+
+/**
+ * @typedef {{
+ * basePatchNum: (string|number),
+ * patchNum: (number),
+ * }}
+ */
+Gerrit.PatchRange;
+
+/**
+ * @typedef {{
+ * changeNum: (string|number),
+ * endpoint: string,
+ * patchNum: (string|number|null|undefined),
+ * errFn: (function(?Response, string=)|null|undefined),
+ * params: (Object|null|undefined),
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedEndpoint: (string|undefined),
+ * reportEndpointAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.ChangeFetchRequest;
+
+/**
+ * Object to describe a request for passing into _send.
+ * - method is the HTTP method to use in the request.
+ * - url is the URL for the request
+ * - body is a request payload.
+ * TODO (beckysiegel) remove need for number at least.
+ * - errFn is a function to invoke when the request fails.
+ * - cancelCondition is a function that, if provided and returns true, will
+ * cancel the response after it resolves.
+ * - contentType is the content type of the body.
+ * - headers is a key-value hash to describe HTTP headers for the request.
+ * - parseResponse states whether the result should be parsed as a JSON
+ * object using getResponseObject.
+ *
+ * @typedef {{
+ * method: string,
+ * url: string,
+ * body: (string|number|Object|null|undefined),
+ * errFn: (function(?Response, string=)|null|undefined),
+ * contentType: (string|null|undefined),
+ * headers: (Object|undefined),
+ * parseResponse: (boolean|undefined),
+ * anonymizedUrl: (string|undefined),
+ * reportUrlAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.SendRequest;
+
+/**
+ * @typedef {{
+ * changeNum: (string|number),
+ * method: string,
+ * patchNum: (string|number|undefined),
+ * endpoint: string,
+ * body: (string|number|Object|null|undefined),
+ * errFn: (function(?Response, string=)|null|undefined),
+ * contentType: (string|null|undefined),
+ * headers: (Object|undefined),
+ * parseResponse: (boolean|undefined),
+ * anonymizedEndpoint: (string|undefined),
+ * reportEndpointAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.ChangeSendRequest;
+
+/**
+ * @typedef {{
+ * url: string,
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * }}
+ */
+Gerrit.FetchRequest;
+
+/**
+ * Object to describe a request for passing into fetchJSON or fetchRawJSON.
+ * - url is the URL for the request (excluding get params)
+ * - errFn is a function to invoke when the request fails.
+ * - cancelCondition is a function that, if provided and returns true, will
+ * cancel the response after it resolves.
+ * - params is a key-value hash to specify get params for the request URL.
+ *
+ * @typedef {{
+ * url: string,
+ * errFn: (function(?Response, string=)|null|undefined),
+ * cancelCondition: (function()|null|undefined),
+ * params: (Object|null|undefined),
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * reportUrlAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.FetchJSONRequest;
+
+/**
+ * @typedef {{
+ * message: string,
+ * icon: string,
+ * class: string,
+ * }}
+ */
+Gerrit.PushCertificateValidation;
+
+/**
+ * Object containing layout values to be used in rendering size-bars.
+ * `max{Inserted,Deleted}` represent the largest values of the
+ * `lines_inserted` and `lines_deleted` fields of the files respectively. The
+ * `max{Addition,Deletion}Width` represent the width of the graphic allocated
+ * to the insertion or deletion side respectively. Finally, the
+ * `deletionOffset` value represents the x-position for the deletion bar.
+ *
+ * @typedef {{
+ * maxInserted: number,
+ * maxDeleted: number,
+ * maxAdditionWidth: number,
+ * maxDeletionWidth: number,
+ * deletionOffset: number,
+ * }}
+ */
+Gerrit.LayoutStats;
+
+/**
+ * @typedef {{
+ * changeNum: number,
+ * path: string,
+ * patchRange: !Gerrit.PatchRange,
+ * projectConfig: (Object|undefined),
+ * }}
+ */
+Gerrit.CommentMeta;
+
+/**
+ * @typedef {{
+ * meta: !Gerrit.CommentMeta,
+ * left: !Array,
+ * right: !Array,
+ * }}
+ */
+Gerrit.CommentsBySide;
+
+/**
+ * The DiffIntralineInfo entity contains information about intraline edits in a
+ * file.
+ *
+ * The information consists of a list of <skip length, mark length> pairs, where
+ * the skip length is the number of characters between the end of the previous
+ * edit and the start of this edit, and the mark length is the number of edited
+ * characters following the skip. The start of the edits is from the beginning
+ * of the related diff content lines.
+ *
+ * Note that the implied newline character at the end of each line is included
+ * in the length calculation, and thus it is possible for the edits to span
+ * newlines.
+ *
+ * @typedef {!Array<number>}
+ */
+Gerrit.IntralineInfo;
+
+/**
+ * A portion of the diff that is treated the same.
+ *
+ * Called `DiffContent` in the API, see
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-content
+ *
+ * @typedef {{
+ * ab: ?Array<!string>,
+ * a: ?Array<!string>,
+ * b: ?Array<!string>,
+ * skip: ?number,
+ * edit_a: ?Array<!Gerrit.IntralineInfo>,
+ * edit_b: ?Array<!Gerrit.IntralineInfo>,
+ * due_to_rebase: ?boolean,
+ * common: ?boolean
+ * }}
+ */
+Gerrit.DiffChunk;
+
+/**
+ * Special line number which should not be collapsed into a shared region.
+ *
+ * @typedef {{
+ * number: number,
+ * leftSide: boolean
+ * }}
+ */
+Gerrit.LineOfInterest;
+
+/**
+ * @typedef {{
+ * html: Node,
+ * position: number,
+ * length: number,
+ * }}
+ */
+Gerrit.CommentLinkItem;
+
+/**
+ * @typedef {{
+ * name: string,
+ * value: Object,
+ * }}
+ */
+Gerrit.GrSuggestionItem;
+
+/**
+ * @typedef {{
+ * getSuggestions: function(string): Promise<Array<Object>>,
+ * makeSuggestionItem: function(Object): Gerrit.GrSuggestionItem,
+ * }}
+ */
+Gerrit.GrSuggestionsProvider; \ No newline at end of file
diff --git a/polygerrit-ui/app/wct_test.sh b/polygerrit-ui/app/wct_test.sh
index dd16ba7e1d..f1b4666de9 100755
--- a/polygerrit-ui/app/wct_test.sh
+++ b/polygerrit-ui/app/wct_test.sh
@@ -14,8 +14,7 @@ cp $TEST_SRCDIR/gerrit/polygerrit-ui/app/test/index.html $t/test/
if [ "${WCT_HEADLESS_MODE:-0}" != "0" ]; then
CHROME_OPTIONS=[\'start-maximized\',\'headless\',\'disable-gpu\',\'no-sandbox\']
- # TODO(paladox): Fix Firefox support for headless mode
- FIREFOX_OPTIONS=[\'\']
+ FIREFOX_OPTIONS=[\'-headless\']
else
CHROME_OPTIONS=[\'start-maximized\']
FIREFOX_OPTIONS=[\'\']
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 2f5df90075..1a2d299d58 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -38,11 +38,12 @@ import (
)
var (
- plugins = flag.String("plugins", "", "comma seperated plugin paths to serve")
- port = flag.String("port", ":8081", "Port to serve HTTP requests on")
- host = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
- scheme = flag.String("scheme", "https", "URL scheme")
- cdnPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_ui/[0-9.]*")
+ plugins = flag.String("plugins", "", "comma seperated plugin paths to serve")
+ port = flag.String("port", ":8081", "Port to serve HTTP requests on")
+ host = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
+ scheme = flag.String("scheme", "https", "URL scheme")
+ cdnPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_ui/[0-9.]*")
+ bundledPluginsPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_assets/[0-9.]*")
)
func main() {
@@ -74,6 +75,7 @@ func main() {
http.HandleFunc("/accounts/", handleProxy)
http.HandleFunc("/config/", handleProxy)
http.HandleFunc("/projects/", handleProxy)
+ http.HandleFunc("/static/", handleProxy)
http.HandleFunc("/accounts/self/detail", handleAccountDetail)
if len(*plugins) > 0 {
@@ -102,7 +104,8 @@ func resourceBasePath() (string, error) {
func handleIndex(writer http.ResponseWriter, originalRequest *http.Request) {
fakeRequest := &http.Request{
URL: &url.URL{
- Path: "/",
+ Path: "/",
+ RawQuery: originalRequest.URL.RawQuery,
},
}
handleProxy(writer, fakeRequest)
@@ -168,7 +171,7 @@ func setJsonPropByPath(json map[string]interface{}, path []string, value interfa
func patchResponse(req *http.Request, res *http.Response) io.Reader {
switch req.URL.EscapedPath() {
case "/":
- return replaceCdn(res.Body)
+ return rewriteHostPage(res.Body)
case "/config/server/info":
return injectLocalPlugins(res.Body)
default:
@@ -176,13 +179,42 @@ func patchResponse(req *http.Request, res *http.Response) io.Reader {
}
}
-func replaceCdn(reader io.Reader) io.Reader {
+func rewriteHostPage(reader io.Reader) io.Reader {
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
original := buf.String()
+ // Simply remove all CDN references, so files are loaded from the local file system or the proxy
+ // server instead.
replaced := cdnPattern.ReplaceAllString(original, "")
+ // Modify window.INITIAL_DATA so that it has the same effect as injectLocalPlugins. To achieve
+ // this let's add JavaScript lines at the end of the <script>...</script> snippet that also
+ // contains window.INITIAL_DATA=...
+ // Here we rely on the fact that the <script> snippet that we want to append to is the first one.
+ if len(*plugins) > 0 {
+ // If the host page contains a reference to a plugin bundle that would be preloaded, then remove it.
+ replaced = bundledPluginsPattern.ReplaceAllString(replaced, "")
+
+ insertionPoint := strings.Index(replaced, "</script>")
+ builder := new(strings.Builder)
+ builder.WriteString(
+ "window.INITIAL_DATA['/config/server/info'].plugin.html_resource_paths = []; ")
+ builder.WriteString(
+ "window.INITIAL_DATA['/config/server/info'].plugin.js_resource_paths = []; ")
+ for _, p := range strings.Split(*plugins, ",") {
+ if filepath.Ext(p) == ".html" {
+ builder.WriteString(
+ "window.INITIAL_DATA['/config/server/info'].plugin.html_resource_paths.push('" + p + "'); ")
+ }
+ if filepath.Ext(p) == ".js" {
+ builder.WriteString(
+ "window.INITIAL_DATA['/config/server/info'].plugin.js_resource_paths.push('" + p + "'); ")
+ }
+ }
+ replaced = replaced[:insertionPoint] + builder.String() + replaced[insertionPoint:]
+ }
+
return strings.NewReader(replaced)
}
@@ -207,11 +239,11 @@ func injectLocalPlugins(reader io.Reader) io.Reader {
jsResources := getJsonPropByPath(response, jsPluginsPath).([]interface{})
for _, p := range strings.Split(*plugins, ",") {
- if strings.HasSuffix(p, ".html") {
+ if filepath.Ext(p) == ".html" {
htmlResources = append(htmlResources, p)
}
- if strings.HasSuffix(p, ".js") {
+ if filepath.Ext(p) == ".js" {
jsResources = append(jsResources, p)
}
}
@@ -261,7 +293,7 @@ type server struct{}
// Any path prefixes that should resolve to index.html.
var (
- fePaths = []string{"/q/", "/c/", "/p/", "/x/", "/dashboard/", "/admin/"}
+ fePaths = []string{"/q/", "/c/", "/p/", "/x/", "/dashboard/", "/admin/", "/settings/"}
issueNumRE = regexp.MustCompile(`^\/\d+\/?$`)
)
diff --git a/prologtests/examples/BUILD b/prologtests/examples/BUILD
new file mode 100644
index 0000000000..f4ebe901b8
--- /dev/null
+++ b/prologtests/examples/BUILD
@@ -0,0 +1,15 @@
+package(default_visibility = ["//visibility:public"])
+
+DUMMY = ["dummy.sh"]
+
+# Enable prologtests on newer Java versions again, when this Bazel bug is fixed:
+# https://github.com/bazelbuild/bazel/issues/9391
+sh_test(
+ name = "test_examples",
+ srcs = select({
+ "//:java11": DUMMY,
+ "//:java_next": DUMMY,
+ "//conditions:default": ["run.sh"],
+ }),
+ data = glob(["*.pl"]) + ["//:gerrit.war"],
+)
diff --git a/prologtests/examples/README.md b/prologtests/examples/README.md
new file mode 100644
index 0000000000..12eb256ec6
--- /dev/null
+++ b/prologtests/examples/README.md
@@ -0,0 +1,54 @@
+# Prolog Unit Test Examples
+
+## Run all examples
+
+Build a local gerrit.war and then run the script:
+
+ ./run.sh
+
+Note that a local Gerrit server is not needed because
+these unit test examples redefine wrappers of the `gerrit:change\*`
+rules to provide mocked change data.
+
+## Add a new unit test
+
+Please follow the pattern in `t1.pl`, `t2.pl`, or `t3.pl`.
+
+* Put code to be tested in a file, e.g. `rules.pl`.
+ For easy unit testing, split long clauses into short ones
+ and test every positive and negative path.
+
+* Create a new unit test file, e.g. `t1.pl`,
+ which should _load_ the test source file and `utils.pl`.
+
+ % First load all source files and the utils.pl.
+ :- load([aosp_rules,utils]).
+
+ :- begin_tests(t1). % give this test any name
+
+ % Use test0/1 or test1/1 to verify failed/passed goals.
+
+ :- end_tests(_,0). % check total pass/fail counts
+
+* Optionally replace calls to gerrit functions that depend on repository.
+ For example, define the following wrappers and in source code, use
+ `change_branch/1` instead of `gerrti:change_branch/1`.
+
+ change_branch(X) :- gerrit:change_branch(X).
+ commit_label(L,U) :- gerrit:commit_label(L,U).
+
+* In unit test file, redefine the gerrit function wrappers and test.
+ For example, in `t3.pl`, we have:
+
+ :- redefine(uploader,1,uploader(user(42))). % mocked uploader
+ :- test1(uploader(user(42))).
+ :- test0(is_exempt_uploader).
+
+ % is_exempt_uploader/0 is expected to fail because it is
+ % is_exempt_uploader :- uploader(user(Id)), memberchk(Id, [104, 106]).
+
+ % Note that gerrit:remove_label does not depend on Gerrit repository,
+ % so its caller remove_label/1 is tested without any redefinition.
+
+ :- test1(remove_label('MyReview',[],[])).
+ :- test1(remove_label('MyReview',submit(),submit())).
diff --git a/prologtests/examples/aosp_rules.pl b/prologtests/examples/aosp_rules.pl
new file mode 100644
index 0000000000..18e8a73fd6
--- /dev/null
+++ b/prologtests/examples/aosp_rules.pl
@@ -0,0 +1,148 @@
+% A simplified and mocked AOSP rules.pl
+
+%%%%% wrapper functions for unit tests
+
+change_branch(X) :- gerrit:change_branch(X).
+change_project(X) :- gerrit:change_project(X).
+commit_author(U,N,M) :- gerrit:commit_author(U,N,M).
+commit_delta(X) :- gerrit:commit_delta(X).
+commit_label(L,U) :- gerrit:commit_label(L,U).
+uploader(X) :- gerrit:uploader(X).
+
+%%%%% true/false conditions
+
+% Special auto-merger accounts.
+is_exempt_uploader :-
+ uploader(user(Id)),
+ memberchk(Id, [104, 106]).
+
+% Build cop overrides everything.
+has_build_cop_override :-
+ commit_label(label('Build-Cop-Override', 1), _).
+
+is_exempt_from_reviews :-
+ or(is_exempt_uploader, has_build_cop_override).
+
+% Some files in selected projects need API review.
+needs_api_review :-
+ commit_delta('^(.*/)?api/|^(system-api/)'),
+ change_project(Project),
+ memberchk(Project, [
+ 'platform/external/apache-http',
+ 'platform/frameworks/base',
+ 'platform/frameworks/support',
+ 'platform/packages/services/Car',
+ 'platform/prebuilts/sdk'
+ ]).
+
+% Some branches need DrNo review.
+needs_drno_review :-
+ change_branch(Branch),
+ memberchk(Branch, [
+ 'refs/heads/my-alpha-dev',
+ 'refs/heads/my-beta-dev'
+ ]).
+
+% Some author email addresses need Qualcomm-Review.
+needs_qualcomm_review :-
+ commit_author(_, _, M),
+ regex_matches(
+'.*@(qti.qualcomm.com|qca.qualcomm.com|quicinc.com|qualcomm.com)', M).
+
+% Special projects, branches, user accounts
+% can opt out owners review.
+opt_out_find_owners :-
+ change_branch(Branch),
+ memberchk(Branch, [
+ 'refs/heads/my-beta-testing',
+ 'refs/heads/my-testing'
+ ]).
+
+% Special projects, branches, user accounts
+% can opt in owners review.
+% Note that opt_out overrides opt_in.
+opt_in_find_owners :- true.
+
+
+%%%%% Simple list filters.
+
+remove_label(X, In, Out) :-
+ gerrit:remove_label(In, label(X, _), Out).
+
+% Slow but simple for short input list.
+remove_review_categories(In, Out) :-
+ remove_label('API-Review', In, L1),
+ remove_label('Code-Review', L1, L2),
+ remove_label('DrNo-Review', L2, L3),
+ remove_label('Owner-Review-Vote', L3, L4),
+ remove_label('Qualcomm-Review', L4, L5),
+ remove_label('Verified', L5, Out).
+
+
+%%%%% Missing rules in Gerrit Prolog Cafe.
+
+or(InA, InB) :- once((A;B)).
+
+not(Goal) :- Goal -> false ; true.
+
+% memberchk(+Element, +List)
+memberchk(X, [H|T]) :-
+ (X = H -> true ; memberchk(X, T)).
+
+maplist(Functor, In, Out) :-
+ (In = []
+ -> Out = []
+ ; (In = [X1|T1],
+ Out = [X2|T2],
+ Goal =.. [Functor, X1, X2],
+ once(Goal),
+ maplist(Functor, T1, T2)
+ )
+ ).
+
+
+%%%%% Conditional rules and filters.
+
+submit_filter(In, Out) :-
+ (is_exempt_from_reviews
+ -> remove_review_categories(In, Out)
+ ; (check_review(needs_api_review,
+ 'API_Review', In, L1),
+ check_review(needs_drno_review,
+ 'DrNo-Review', L1, L2),
+ check_review(needs_qualcomm_review,
+ 'Qualcomm-Review', L2, L3),
+ check_find_owners(L3, Out)
+ )
+ ).
+
+check_review(NeedReview, Label, In, Out) :-
+ (NeedReview
+ -> Out = In
+ ; remove_label(Label, In, Out)
+ ).
+
+% If opt_out_find_owners is true,
+% remove all 'Owner-Review-Vote' label;
+% else if opt_in_find_owners is true,
+% call find_owners:submit_filter;
+% else default to no find_owners filter.
+check_find_owners(In, Out) :-
+ (opt_out_find_owners
+ -> remove_label('Owner-Review-Vote', In, Temp)
+ ; (opt_in_find_owners
+ -> find_owners:submit_filter(In, Temp)
+ ; In = Temp
+ )
+ ),
+ Temp =.. [submit | L1],
+ remove_label('Owner-Approved', L1, L2),
+ maplist(owner_may_to_need, L2, L3),
+ Out =.. [submit | L3].
+
+% change may(_) to need(_) to block submit.
+owner_may_to_need(In, Out) :-
+ (In = label('Owner-Review-Vote', may(_))
+ -> Out = label('Owner-Review-Vote', need(_))
+ ; Out = In
+ ).
diff --git a/prologtests/examples/dummy.sh b/prologtests/examples/dummy.sh
new file mode 100755
index 0000000000..2e0cca350f
--- /dev/null
+++ b/prologtests/examples/dummy.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Skip all prolog tests for newer Java versions.
+# See https://github.com/bazelbuild/bazel/issues/9391
+# for more details why we cannot support running tests
+# on newer Java versions for now.
diff --git a/prologtests/examples/load.pl b/prologtests/examples/load.pl
new file mode 100644
index 0000000000..f5b49e8aa0
--- /dev/null
+++ b/prologtests/examples/load.pl
@@ -0,0 +1,26 @@
+% If you have 1.4.3 or older Prolog-Cafe, you need to
+% use (consult(load), load(load)) to get definition of load.
+% Then use load([f1,f2,...]) to load multiple source files.
+
+% Input is a list of file names or a single file name.
+% Use a conditional expression style without cut operator.
+load(X) :-
+ ( (X = [])
+ -> true
+ ; ( (X = [H|T])
+ -> (load_file(H), load(T))
+ ; load_file(X)
+ )
+ ).
+
+% load_file is '$consult' without the bug of unbound 'File' variable.
+% For repeated unit tests, skip statistics and print_message.
+load_file(F) :- atom(F), !,
+ '$prolog_file_name'(F, PF),
+ open(PF, read, In),
+ % print_message(info, [loading,PF,'...']),
+ % statistics(runtime, _),
+ consult_stream(PF, In),
+ % statistics(runtime, [_,T]),
+ % print_message(info, [PF,'loaded in',T,msec]),
+ close(In).
diff --git a/prologtests/examples/rules.pl b/prologtests/examples/rules.pl
new file mode 100644
index 0000000000..1a7b17cecf
--- /dev/null
+++ b/prologtests/examples/rules.pl
@@ -0,0 +1,29 @@
+% An example source file to be tested.
+
+% Add common rules missing in Prolog Cafe.
+memberchk(X, [H|T]) :-
+ (X = H) -> true ; memberchk(X, T).
+
+% A rule that can succeed/backtrack multiple times.
+super_users(1001).
+super_users(1002).
+
+% Deterministic rule that pass/fail only once.
+is_super_user(X) :- memberchk(X, [1001, 1002]).
+
+% Another rule that can pass 5 times.
+multi_users(101).
+multi_users(102).
+multi_users(103).
+multi_users(104).
+multi_users(105).
+
+% Okay, single deterministic fact.
+single_user(abc).
+
+% Wrap calls to gerrit repository, to be redefined in tests.
+change_owner(X) :- gerrit:change_owner(X).
+
+% To test is_owner without gerrit:change_owner,
+% we should redefine change_owner.
+is_owner(X) :- change_owner(X).
diff --git a/prologtests/examples/run.sh b/prologtests/examples/run.sh
new file mode 100755
index 0000000000..947c1532f6
--- /dev/null
+++ b/prologtests/examples/run.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+TESTS="t1 t2 t3"
+
+# Note that both t1.pl and t2.pl test code in rules.pl.
+# Unit tests are usually longer than the tested code.
+# So it is common to test one source file with multiple
+# unit test files.
+
+LF=$'\n'
+PASS=""
+FAIL=""
+
+echo "#### TEST_SRCDIR = ${TEST_SRCDIR}"
+
+if [ "${TEST_SRCDIR}" == "" ]; then
+ # Assume running alone
+ GERRIT_WAR="../../bazel-bin/gerrit.war"
+ SRCDIR="."
+else
+ # Assume running from bazel
+ GERRIT_WAR=`pwd`/gerrit.war
+ SRCDIR="prologtests/examples"
+fi
+
+# Default GERRIT_TMP is ~/.gerritcodereview/tmp,
+# which won't be writable in a bazel test sandbox.
+/bin/mkdir -p /tmp/gerrit
+export GERRIT_TMP=/tmp/gerrit
+
+for T in $TESTS
+do
+
+ pushd $SRCDIR
+
+ # Unit tests do not need to define clauses in packages.
+ # Use one prolog-shell per unit test, to avoid name collision.
+ echo "### Running test ${T}.pl"
+ echo "[$T]." | java -jar ${GERRIT_WAR} prolog-shell -q -s load.pl
+
+ if [ "x$?" != "x0" ]; then
+ echo "### Test ${T}.pl failed."
+ FAIL="${FAIL}${LF}FAIL: Test ${T}.pl"
+ else
+ PASS="${PASS}${LF}PASS: Test ${T}.pl"
+ fi
+
+ popd
+
+ # java -jar ../../bazel-bin/gerrit.war prolog-shell -s $T < /dev/null
+ # Calling prolog-shell with -s flag works for small files,
+ # but got run-time exception with t3.pl.
+ # com.googlecode.prolog_cafe.exceptions.ReductionLimitException:
+ # exceeded reduction limit of 1048576
+done
+
+echo "$PASS"
+
+if [ "$FAIL" != "" ]; then
+ echo "$FAIL"
+ exit 1
+fi
diff --git a/prologtests/examples/t1.pl b/prologtests/examples/t1.pl
new file mode 100644
index 0000000000..caf906192c
--- /dev/null
+++ b/prologtests/examples/t1.pl
@@ -0,0 +1,20 @@
+:- load([rules,utils]).
+:- begin_tests(t1).
+
+:- test1(true). % expect true to pass
+:- test0(false). % expect false to fail
+
+:- test1(X = 3). % unification should pass
+:- test1(_ = 3). % unification should pass
+:- test0(X \= 3). % not-unified should fail
+
+% (7-4) should have expected result
+:- test1((X is (7-4), X =:= 3)).
+:- test1((X is (7-4), X =\= 4)).
+
+% memberchk should pass/fail exactly once
+:- test1(memberchk(3,[1,3,5,3])).
+:- test0(memberchk(2,[1,3,5,3])).
+:- test0(memberchk(2,[])).
+
+:- end_tests_or_halt(0). % expect no failure
diff --git a/prologtests/examples/t2.pl b/prologtests/examples/t2.pl
new file mode 100644
index 0000000000..9424b5330d
--- /dev/null
+++ b/prologtests/examples/t2.pl
@@ -0,0 +1,25 @@
+:- load([rules,utils]).
+:- begin_tests(t2).
+
+% expected to pass or fail once.
+:- test0(super_users(1000)).
+:- test1(super_users(1001)).
+
+:- test1(is_super_user(1001)).
+:- test1(is_super_user(1002)).
+:- test0(is_super_user(1003)).
+
+:- test1(super_users(X)). % expected fail (pass twice)
+:- test1(multi_users(X)). % expected fail (pass many times)
+
+:- test1(single_user(X)). % expected pass once
+
+% Redefine change_owner, skip gerrit:change_owner,
+% then test is_owner without a gerrit repository.
+
+:- redefine(change_owner,1,(change_owner(42))).
+:- test1(is_owner(42)).
+:- test1(is_owner(X)).
+:- test0(is_owner(24)).
+
+:- end_tests_or_halt(2). % expect 2 failures
diff --git a/prologtests/examples/t3.pl b/prologtests/examples/t3.pl
new file mode 100644
index 0000000000..02badc040d
--- /dev/null
+++ b/prologtests/examples/t3.pl
@@ -0,0 +1,69 @@
+:- load([aosp_rules,utils]).
+
+:- begin_tests(t3_basic_conditions).
+
+%% A negative test of is_exempt_uploader.
+:- redefine(uploader,1,uploader(user(42))). % mocked uploader
+:- test1(uploader(user(42))).
+:- test0(is_exempt_uploader).
+
+%% Helper functions for positive test of is_exempt_uploader.
+test_is_exempt_uploader(List) :- maplist(test1_uploader, List, _).
+test1_uploader(X,_) :-
+ redefine(uploader,1,uploader(user(X))),
+ test1(uploader(user(X))),
+ test1(is_exempt_uploader).
+:- test_is_exempt_uploader([104, 106]).
+
+%% Test has_build_cop_override.
+:- redefine(commit_label,2,commit_label(label('Code-Review',1),user(102))).
+:- test0(has_build_cop_override).
+commit_label(label('Build-Cop-Override',1),user(101)). % mocked 2nd label
+:- test1(has_build_cop_override).
+:- test1(commit_label(label(_,_),_)). % expect fail, two matches
+:- test1(commit_label(label('Build-Cop-Override',_),_)). % good, one pass
+
+%% TODO: more test for is_exempt_from_reviews.
+
+%% Test needs_api_review, which checks commit_delta and project.
+% Helper functions:
+test_needs_api_review(File, Project, Tester) :-
+ redefine(commit_delta,1,(commit_delta(R) :- regex_matches(R, File))),
+ redefine(change_project,1,change_project(Project)),
+ Goal =.. [Tester, needs_api_review],
+ msg('# check CL with changed file ', File, ' in ', Project),
+ once((Goal ; true)). % do not backtrack
+
+:- test_needs_api_review('apio/test.cc', 'platform/art', test0).
+:- test_needs_api_review('api/test.cc', 'platform/art', test0).
+:- test_needs_api_review('api/test.cc', 'platform/prebuilts/sdk', test1).
+:- test_needs_api_review('d1/d2/api/test.cc', 'platform/prebuilts/sdk', test1).
+:- test_needs_api_review('system-api/d/t.c', 'platform/external/apache-http', test1).
+
+%% TODO: Test needs_drno_review, needs_qualcomm_review
+
+%% TODO: Test opt_out_find_owners.
+
+:- test1(opt_in_find_owners). % default, unless opt_out_find_owners
+
+:- end_tests_or_halt(1). % expect 1 failure of multiple commit_label
+
+%% Test remove_label
+:- begin_tests(t3_remove_label).
+
+:- test1(remove_label('MyReview',[],[])).
+:- test1(remove_label('MyReview',submit(),submit())).
+:- test1(remove_label(myR,[label(a,X)],[label(a,X)])).
+:- test1(remove_label(myR,[label(myR,_)],[])).
+:- test1(remove_label(myR,[label(a,X),label(myR,_)],[label(a,X)])).
+:- test1(remove_label(myR,submit(label(a,X)),submit(label(a,X)))).
+:- test1(remove_label(myR,submit(label(myR,_)),submit())).
+
+%% Test maplist
+double(X,Y) :- Y is X * X.
+:- test1(maplist(double, [2,4,6], [4,16,36])).
+:- test1(maplist(double, [], [])).
+
+:- end_tests_or_halt(0). % expect no failure
+
+%% TODO: Add more tests.
diff --git a/prologtests/examples/utils.pl b/prologtests/examples/utils.pl
new file mode 100644
index 0000000000..8d15067789
--- /dev/null
+++ b/prologtests/examples/utils.pl
@@ -0,0 +1,78 @@
+%% Unit test helpers
+
+% Write one line message.
+msg(A) :- write(A), nl.
+msg(A,B) :- write(A), msg(B).
+msg(A,B,C) :- write(A), msg(B,C).
+msg(A,B,C,D) :- write(A), msg(B,C,D).
+msg(A,B,C,D,E) :- write(A), msg(B,C,D,E).
+msg(A,B,C,D,E,F) :- write(A), msg(B,C,D,E,F).
+
+% Redefine a caluse.
+redefine(Atom,Arity,Clause) :- abolish(Atom/Arity), assertz(Clause).
+
+% Increment/decrement of pass/fail counters.
+set_counters(N,X,Y) :- redefine(test_count,3,test_count(N,X,Y)).
+get_counters(N,X,Y) :- clause(test_count(N,X,Y), _) -> true ; (X=0, Y=0).
+inc_pass_count :- get_counters(N,P,F), P1 is P + 1, set_counters(N,P1,F).
+inc_fail_count :- get_counters(N,P,F), F1 is F + 1, set_counters(N,P,F1).
+
+% Report pass or fail of G.
+pass_1(G) :- msg('PASS: ', G), inc_pass_count.
+fail_1(G) :- msg('FAIL: ', G), inc_fail_count.
+
+% Report pass or fail of not(G).
+pass_0(G) :- msg('PASS: not(', G, ')'), inc_pass_count.
+fail_0(G) :- msg('FAIL: not(', G, ')'), inc_fail_count.
+
+% Report a test as failed if it passed 2 or more times
+pass_twice(G) :-
+ msg('FAIL: (pass twice): ', G),
+ inc_fail_count.
+pass_many(G) :-
+ G = [A,B|_],
+ length(G, N),
+ msg('FAIL: (pass ', N, ' times): ', [A,B,'...']),
+ inc_fail_count.
+
+% Test if G fails.
+test0(G) :- once(G) -> fail_0(G) ; pass_0(G).
+
+% Test if G passes exactly once.
+test1(G) :-
+ findall(G, G, S), length(S, N),
+ (N == 0
+ -> fail_1(G)
+ ; (N == 1
+ -> pass_1(S)
+ ; (N == 2 -> pass_twice(S) ; pass_many(S))
+ )
+ ).
+
+% Report the begin of test N.
+begin_tests(N) :-
+ nl,
+ msg('BEGIN test ',N),
+ set_counters(N,0,0).
+
+% Repot the end of test N and total pass/fail counts,
+% and check if the numbers are as exected OutP/OutF.
+end_tests(OutP,OutF) :-
+ get_counters(N,P,F),
+ (OutP = P
+ -> msg('Expected #PASS: ', OutP)
+ ; (msg('ERROR: expected #PASS is ',OutP), !, fail)
+ ),
+ (OutF = F
+ -> msg('Expected #FAIL: ', OutF)
+ ; (msg('ERROR: expected #FAIL is ',OutF), !, fail)
+ ),
+ msg('END test ', N),
+ nl.
+
+% Repot the end of test N and total pass/fail counts.
+end_tests(N) :- end_tests(N,_,_).
+
+% Call end_tests/2 and halt if the fail count is unexpected.
+end_tests_or_halt(ExpectedFails) :-
+ end_tests(_,ExpectedFails); (flush_output, halt(1)).
diff --git a/proto/cache.proto b/proto/cache.proto
index b34dbf3b86..10e0216cf1 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -75,7 +75,7 @@ message ChangeNotesKeyProto {
// Instead, we just take the tedious yet simple approach of having a "has_foo"
// field for each nullable field "foo", indicating whether or not foo is null.
//
-// Next ID: 19
+// Next ID: 23
message ChangeNotesStateProto {
// Effectively required, even though the corresponding ChangeNotesState field
// is optional, since the field is only absent when NoteDb is disabled, in
@@ -110,8 +110,8 @@ message ChangeNotesStateProto {
string submission_id = 13;
bool has_submission_id = 14;
- int32 assignee = 15;
- bool has_assignee = 16;
+ reserved 15; // assignee
+ reserved 16; // has_assignee
string status = 17;
bool has_status = 18;
@@ -130,7 +130,7 @@ message ChangeNotesStateProto {
// which case attempting to use the ChangeNotesCache is programmer error.
ChangeColumnsProto columns = 3;
- repeated int32 past_assignee = 4;
+ reserved 4; // past_assignee
repeated string hashtag = 5;
@@ -178,11 +178,25 @@ message ChangeNotesStateProto {
// Raw ChangeMessage proto as produced by ChangeMessageProtoConverter.
repeated bytes change_message = 15;
- // JSON produced from com.google.gerrit.reviewdb.client.Comment.
+ // JSON produced from com.google.gerrit.entities.Comment.
repeated string published_comment = 16;
reserved 17; // read_only_until
reserved 18; // has_read_only_until
+
+ // Number of updates to the change's meta ref.
+ int32 update_count = 19;
+
+ string server_id = 20;
+ bool has_server_id = 21;
+
+ message AssigneeStatusUpdateProto {
+ int64 date = 1;
+ int32 updated_by = 2;
+ int32 current_assignee = 3;
+ bool has_current_assignee = 4;
+ }
+ repeated AssigneeStatusUpdateProto assignee_update = 22;
}
diff --git a/proto/entities.proto b/proto/entities.proto
index d2851d382f..374b47c243 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -18,19 +18,19 @@ package devtools.gerritcodereview;
option java_package = "com.google.gerrit.proto";
-// Serialized form of com.google.gerrit.reviewdb.client.Change.Id.
+// Serialized form of com.google.gerrit.entities.Change.Id.
// Next ID: 2
message Change_Id {
required int32 id = 1;
}
-// Serialized form of com.google.gerrit.reviewdb.client.Change.Key.
+// Serialized form of com.google.gerrit.entities.Change.Key.
// Next ID: 2
message Change_Key {
optional string id = 1;
}
-// Serialized form of com.google.gerrit.reviewdb.client.Change.
+// Serialized form of com.google.gerrit.entities.Change.
// Next ID: 24
message Change {
required Change_Id change_id = 1;
@@ -61,14 +61,14 @@ message Change {
reserved 101; // note_db_state
}
-// Serialized form of com.google.gerrit.reviewdb.client.ChangeMessage.
+// Serialized form of com.google.gerrit.enities.ChangeMessage.
// Next ID: 3
message ChangeMessage_Key {
required Change_Id change_id = 1;
required string uuid = 2;
}
-// Serialized form of com.google.gerrit.reviewdb.client.ChangeMessage.
+// Serialized form of com.google.gerrit.entities.ChangeMessage.
// Next ID: 8
message ChangeMessage {
required ChangeMessage_Key key = 1;
@@ -80,18 +80,18 @@ message ChangeMessage {
optional Account_Id real_author = 7;
}
-// Serialized form of com.google.gerrit.reviewdb.client.PatchSet.Id.
+// Serialized form of com.google.gerrit.entities.PatchSet.Id.
// Next ID: 3
message PatchSet_Id {
required Change_Id change_id = 1;
- required int32 patch_set_id = 2;
+ required int32 id = 2;
}
-// Serialized form of com.google.gerrit.reviewdb.client.PatchSet.
+// Serialized form of com.google.gerrit.entities.PatchSet.
// Next ID: 10
message PatchSet {
required PatchSet_Id id = 1;
- optional RevId revision = 2;
+ optional ObjectId commitId = 2;
optional Account_Id uploader_account_id = 3;
optional fixed64 created_on = 4;
optional string groups = 6;
@@ -103,27 +103,27 @@ message PatchSet {
reserved 7; // pushCertficate
}
-// Serialized form of com.google.gerrit.reviewdb.client.Account.Id.
+// Serialized form of com.google.gerrit.entities.Account.Id.
// Next ID: 2
message Account_Id {
required int32 id = 1;
}
-// Serialized form of com.google.gerrit.reviewdb.client.LabelId.
+// Serialized form of com.google.gerrit.entities.LabelId.
// Next ID: 2
message LabelId {
required string id = 1;
}
-// Serialized form of com.google.gerrit.reviewdb.client.PatchSetApproval.Key.
+// Serialized form of com.google.gerrit.entities.PatchSetApproval.Key.
// Next ID: 4
message PatchSetApproval_Key {
required PatchSet_Id patch_set_id = 1;
required Account_Id account_id = 2;
- required LabelId category_id = 3;
+ required LabelId label_id = 3;
}
-// Serialized form of com.google.gerrit.reviewdb.client.PatchSetApproval.
+// Serialized form of com.google.gerrit.entities.PatchSetApproval.
// Next ID: 9
message PatchSetApproval {
required PatchSetApproval_Key key = 1;
@@ -138,21 +138,22 @@ message PatchSetApproval {
reserved 5; // changeSortKey
}
-// Serialized form of com.google.gerrit.reviewdb.client.Project.NameKey.
+// Serialized form of com.google.gerrit.entities.Project.NameKey.
// Next ID: 2
message Project_NameKey {
optional string name = 1;
}
-// Serialized form of com.google.gerrit.reviewdb.client.Branch.NameKey.
+// Serialized form of com.google.gerrit.entities.Branch.NameKey.
// Next ID: 3
message Branch_NameKey {
- optional Project_NameKey project_name = 1;
- optional string branch_name = 2;
+ optional Project_NameKey project = 1;
+ optional string branch = 2;
}
-// Serialized form of com.google.gerrit.reviewdb.client.RevId.
+// Serialized form of org.eclipse.jgit.lib.ObjectId.
// Next ID: 2
-message RevId {
- optional string id = 1;
+message ObjectId {
+ // Hex string representation of the ID.
+ optional string name = 1;
}
diff --git a/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html b/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
index 4923143422..efd760fb67 100644
--- a/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
+++ b/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
@@ -75,11 +75,6 @@
<a href="?id=https://login.launchpad.net/%2Bopenid" id="id_launchpad">Sign in with a Launchpad ID</a>
</div>
- <div id="provider_yahoo">
- <img height="16" width="16" src="data:image/gif;base64,R0lGODlhEAAQAPECAAAAAP8AAP///8zMzCH5BAEAAAMALAAAAAAQABAAAAIqnI+py30BY3AgAjCkfJDjiIAQlgUkNxqWkqrm0honKk7KhZOzw/f+fygAADs=" />
- <a href="?id=https://me.yahoo.com" id="id_yahoo">Sign in with a Yahoo! ID</a>
- </div>
-
<div style="margin-top: 25px;">
<h2>What is OpenID?</h2>
<p>OpenID provides secure single-sign-on, without revealing your passwords to this website.</p>
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 8f151a8c8c..3feb1b452e 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -19,11 +19,14 @@
{template .Index}
{@param canonicalPath: ?}
{@param staticResourcePath: ?}
+ {@param gerritInitialData: /** {string} map of REST endpoint to response for startup. */ ?}
{@param? assetsPath: ?} /** {string} URL to static assets root, if served from CDN. */
{@param? assetsBundle: ?} /** {string} Assets bundle .html file, served from $assetsPath. */
{@param? faviconPath: ?}
{@param? versionInfo: ?}
- {@param? polymer2: ?}
+ {@param? polyfillCE: ?}
+ {@param? polyfillSD: ?}
+ {@param? polyfillSC: ?}
<!DOCTYPE html>{\n}
<html lang="en">{\n}
<meta charset="utf-8">{\n}
@@ -36,12 +39,27 @@
</noscript>
<script>
+ // Disable extra font load from paper-styles
+ window.polymerSkipLoadingFontRoboto = true;
window.CLOSURE_NO_DEPS = true;
{if $canonicalPath != ''}window.CANONICAL_PATH = '{$canonicalPath}';{/if}
{if $versionInfo}window.VERSION_INFO = '{$versionInfo}';{/if}
{if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if}
{if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if}
- {if $polymer2}window.POLYMER2 = true;{/if}
+ {if $polyfillCE}if (window.customElements) window.customElements.forcePolyfill = true;{/if}
+ {if $polyfillSD}{literal}ShadyDOM = { force: true };{/literal}{/if}
+ {if $polyfillSC}{literal}ShadyCSS = { shimcssproperties: true};{/literal}{/if}
+ {if $gerritInitialData}
+ // INITIAL_DATA is a string that represents a JSON map. It's inlined here so that we can
+ // spare calls to the API when starting up the app.
+ // The map maps from endpoint to returned value. This matches Gerrit's REST API 1:1, so the
+ // values here can be used as a drop-in replacement for calls to the API.
+ //
+ // Example:
+ // '/config/server/version' => '3.0.0-468-g0757b52a7d'
+ // '/accounts/self/detail' => { 'username' : 'gerrit-user' }
+ window.INITIAL_DATA = JSON.parse({$gerritInitialData});
+ {/if}
</script>{\n}
{if $faviconPath}
@@ -60,7 +78,9 @@
<link rel="preload" href="{$staticResourcePath}/fonts/Roboto-Medium.woff" as="font" type="font/woff" crossorigin="anonymous">{\n}
<link rel="stylesheet" href="{$staticResourcePath}/styles/fonts.css">{\n}
<link rel="stylesheet" href="{$staticResourcePath}/styles/main.css">{\n}
+
<script src="{$staticResourcePath}/bower_components/webcomponentsjs/webcomponents-lite.js"></script>{\n}
+
// Content between webcomponents-lite and the load of the main app element
// run before polymer-resin is installed so may have security consequences.
// Contact your local security engineer if you have any questions, and
diff --git a/resources/com/google/gerrit/pgm/init/gerrit.sh b/resources/com/google/gerrit/pgm/init/gerrit.sh
index d92ec51849..ce858d5b80 100755
--- a/resources/com/google/gerrit/pgm/init/gerrit.sh
+++ b/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -345,8 +345,9 @@ fi
test -z "$GERRIT_USER" && GERRIT_USER=`whoami`
RUN_ARGS="-jar $GERRIT_WAR daemon -d $GERRIT_SITE"
-if test "`get_config --bool container.slave`" = "true" ; then
- RUN_ARGS="$RUN_ARGS --slave --enable-httpd --headless"
+if test "`get_config --bool container.slave`" = "true" || \
+ test "`get_config --bool container.replica`" = "true"; then
+ RUN_ARGS="$RUN_ARGS --replica --enable-httpd --headless"
fi
DAEMON_OPTS=`get_config --get-all container.daemonOpt`
if test -n "$DAEMON_OPTS" ; then
diff --git a/resources/com/google/gerrit/server/mail/Abandoned.soy b/resources/com/google/gerrit/server/mail/Abandoned.soy
index 2785ffc8e7..d5aac0edc3 100644
--- a/resources/com/google/gerrit/server/mail/Abandoned.soy
+++ b/resources/com/google/gerrit/server/mail/Abandoned.soy
@@ -17,7 +17,7 @@
{namespace com.google.gerrit.server.mail.template}
/**
- * .Abandoned template will determine the contents of the email related to a
+ * The .Abandoned template will determine the contents of the email related to a
* change being abandoned.
*/
{template .Abandoned kind="text"}
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
index e99777694a..e88c424ac8 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
@@ -62,3 +62,8 @@
This might be caused by an ongoing maintenance or a data corruption.
{call .InboundEmailRejectionFooter /}
{/template}
+
+{template .InboundEmailRejection_COMMENT_REJECTED kind="text"}
+ Gerrit Code Review rejected one or more comments because they did not pass validation.
+ {call .InboundEmailRejectionFooter /}
+{/template}
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
index f879270ed2..e17508de37 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
@@ -78,3 +78,10 @@
<p>
{call .InboundEmailRejectionFooterHtml /}
{/template}
+
+{template .InboundEmailRejectionHtml_COMMENT_REJECTED}
+ <p>
+ Gerrit Code Review rejected one or more comments because they did not pass validation.
+ </p>
+ {call .InboundEmailRejectionFooterHtml /}
+{/template}
diff --git a/tools/BUILD b/tools/BUILD
index 9ccc0651f4..f5da450f06 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -21,72 +21,75 @@ default_java_toolchain(
# enabled. This warnings list is originally based on:
# https://github.com/bazelbuild/BUILD_file_generator/blob/master/tools/bazel_defs/java.bzl
# However, feel free to add any additional errors. Thus far they have all been pretty useful.
+# TODO(davido): Enable ImmutableAnnotationChecker again when these issues are fixed:
+# https://github.com/google/error-prone/issues/1348
+# https://github.com/bazelbuild/bazel/issues/9378
java_package_configuration(
name = "error_prone",
javacopts = [
"-XepDisableWarningsInGeneratedCode",
- "-Xep:AmbiguousMethodReference:WARN",
+ "-Xep:AmbiguousMethodReference:ERROR",
"-Xep:AutoValueFinalMethods:ERROR",
- "-Xep:BadAnnotationImplementation:WARN",
- "-Xep:BadComparable:WARN",
+ "-Xep:BadAnnotationImplementation:ERROR",
+ "-Xep:BadComparable:ERROR",
"-Xep:BoxedPrimitiveConstructor:ERROR",
- "-Xep:CannotMockFinalClass:WARN",
+ "-Xep:CannotMockFinalClass:ERROR",
"-Xep:ClassCanBeStatic:ERROR",
- "-Xep:ClassNewInstance:WARN",
+ "-Xep:ClassNewInstance:ERROR",
"-Xep:DateFormatConstant:ERROR",
"-Xep:DefaultCharset:ERROR",
- "-Xep:DoubleCheckedLocking:WARN",
- "-Xep:ElementsCountedInLoop:WARN",
- "-Xep:EqualsHashCode:WARN",
- "-Xep:EqualsIncompatibleType:WARN",
- "-Xep:ExpectedExceptionChecker:OFF",
- "-Xep:Finally:WARN",
- "-Xep:FloatingPointLiteralPrecision:WARN",
- "-Xep:FragmentInjection:WARN",
- "-Xep:FragmentNotInstantiable:WARN",
+ "-Xep:DoubleCheckedLocking:ERROR",
+ "-Xep:ElementsCountedInLoop:ERROR",
+ "-Xep:EqualsHashCode:ERROR",
+ "-Xep:EqualsIncompatibleType:ERROR",
+ "-Xep:ExpectedExceptionChecker:ERROR",
+ "-Xep:Finally:ERROR",
+ "-Xep:FloatingPointLiteralPrecision:ERROR",
+ "-Xep:FragmentInjection:ERROR",
+ "-Xep:FragmentNotInstantiable:ERROR",
"-Xep:FunctionalInterfaceClash:ERROR",
- "-Xep:FutureReturnValueIgnored:WARN",
- "-Xep:GetClassOnEnum:WARN",
- "-Xep:ImmutableAnnotationChecker:WARN",
- "-Xep:ImmutableEnumChecker:WARN",
- "-Xep:IncompatibleModifiers:WARN",
- "-Xep:InjectOnConstructorOfAbstractClass:WARN",
- "-Xep:InputStreamSlowMultibyteRead:WARN",
- "-Xep:IterableAndIterator:WARN",
- "-Xep:JUnit3FloatingPointComparisonWithoutDelta:WARN",
- "-Xep:JUnitAmbiguousTestClass:WARN",
- "-Xep:LiteralClassName:WARN",
+ "-Xep:FutureReturnValueIgnored:ERROR",
+ "-Xep:GetClassOnEnum:ERROR",
+ "-Xep:ImmutableAnnotationChecker:OFF",
+ "-Xep:ImmutableEnumChecker:ERROR",
+ "-Xep:IncompatibleModifiers:ERROR",
+ "-Xep:InjectOnConstructorOfAbstractClass:ERROR",
+ "-Xep:InputStreamSlowMultibyteRead:ERROR",
+ "-Xep:IterableAndIterator:ERROR",
+ "-Xep:JUnit3FloatingPointComparisonWithoutDelta:ERROR",
+ "-Xep:JUnitAmbiguousTestClass:ERROR",
+ "-Xep:LiteralClassName:ERROR",
"-Xep:MissingCasesInEnumSwitch:ERROR",
"-Xep:MissingFail:ERROR",
- "-Xep:MissingOverride:WARN",
+ "-Xep:MissingOverride:ERROR",
"-Xep:MutableConstantField:ERROR",
- "-Xep:NarrowingCompoundAssignment:WARN",
- "-Xep:NonAtomicVolatileUpdate:WARN",
- "-Xep:NonOverridingEquals:WARN",
- "-Xep:NullableConstructor:WARN",
- "-Xep:NullablePrimitive:WARN",
- "-Xep:NullableVoid:WARN",
+ "-Xep:NarrowingCompoundAssignment:ERROR",
+ "-Xep:NonAtomicVolatileUpdate:ERROR",
+ "-Xep:NonOverridingEquals:ERROR",
+ "-Xep:NullableConstructor:ERROR",
+ "-Xep:NullablePrimitive:ERROR",
+ "-Xep:NullableVoid:ERROR",
"-Xep:ObjectToString:ERROR",
"-Xep:OperatorPrecedence:ERROR",
- "-Xep:OverridesGuiceInjectableMethod:WARN",
- "-Xep:PreconditionsInvalidPlaceholder:WARN",
- "-Xep:ProtoFieldPreconditionsCheckNotNull:WARN",
- "-Xep:ProtocolBufferOrdinal:WARN",
- "-Xep:ReferenceEquality:WARN",
- "-Xep:RequiredModifiers:WARN",
- "-Xep:ShortCircuitBoolean:WARN",
- "-Xep:SimpleDateFormatConstant:WARN",
- "-Xep:StaticGuardedByInstance:WARN",
- "-Xep:StringEquality:WARN",
- "-Xep:SynchronizeOnNonFinalField:WARN",
- "-Xep:TruthConstantAsserts:WARN",
- "-Xep:TypeParameterShadowing:WARN",
- "-Xep:TypeParameterUnusedInFormals:WARN",
- "-Xep:URLEqualsHashCode:WARN",
- "-Xep:UnsynchronizedOverridesSynchronized:WARN",
+ "-Xep:OverridesGuiceInjectableMethod:ERROR",
+ "-Xep:PreconditionsInvalidPlaceholder:ERROR",
+ "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR",
+ "-Xep:ProtocolBufferOrdinal:ERROR",
+ "-Xep:ReferenceEquality:ERROR",
+ "-Xep:RequiredModifiers:ERROR",
+ "-Xep:ShortCircuitBoolean:ERROR",
+ "-Xep:SimpleDateFormatConstant:ERROR",
+ "-Xep:StaticGuardedByInstance:ERROR",
+ "-Xep:StringEquality:ERROR",
+ "-Xep:SynchronizeOnNonFinalField:ERROR",
+ "-Xep:TruthConstantAsserts:ERROR",
+ "-Xep:TypeParameterShadowing:ERROR",
+ "-Xep:TypeParameterUnusedInFormals:ERROR",
+ "-Xep:URLEqualsHashCode:ERROR",
+ "-Xep:UnsynchronizedOverridesSynchronized:ERROR",
"-Xep:UnusedException:ERROR",
- "-Xep:WaitNotInLoop:WARN",
- "-Xep:WildcardImport:WARN",
+ "-Xep:WaitNotInLoop:ERROR",
+ "-Xep:WildcardImport:ERROR",
],
packages = ["error_prone_packages"],
)
@@ -98,10 +101,14 @@ package_group(
"//javatests/...",
"//plugins/codemirror-editor/...",
"//plugins/commit-message-length-validator/...",
+ "//plugins/delete-project/...",
"//plugins/download-commands/...",
+ "//plugins/gitiles/...",
"//plugins/hooks/...",
+ "//plugins/plugin-manager/...",
"//plugins/replication/...",
"//plugins/reviewnotes/...",
"//plugins/singleusergroup/...",
+ "//plugins/webhooks/...",
],
)
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index 3add0258ce..62b40109e3 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -61,14 +61,14 @@ def _impl(ctx):
command = " && ".join(cmd),
)
-java_doc = rule(
+_java_doc = rule(
attrs = {
"external_docs": attr.string_list(),
"libs": attr.label_list(allow_files = False),
"pkgs": attr.string_list(),
"title": attr.string(),
"_jdk": attr.label(
- default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
+ default = Label("@bazel_tools//tools/jdk:current_host_java_runtime"),
allow_files = True,
providers = [java_common.JavaRuntimeInfo],
),
@@ -76,3 +76,16 @@ java_doc = rule(
outputs = {"zip": "%{name}.zip"},
implementation = _impl,
)
+
+def java_doc(**kwargs):
+ libs = kwargs.get("libs", [])
+ libs = libs + select({
+ "//:java11": [],
+ "//:java_next": [],
+ # TODO(davido): Remove this dependency, when Java 8 support is removed.
+ # auto-value generates @javax.annotation.Generated annotation on generated
+ # classes when Java 8 source compatibility level is used, but Java 11 and
+ # later don't have this class any more.
+ "//conditions:default": ["//lib:javax-annotation"],
+ })
+ _java_doc(**dict(kwargs, libs = libs))
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 9160f1dee0..9baa30c156 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -26,7 +26,7 @@ def _npm_binary_impl(ctx):
else:
fail("repository %s not in {%s,%s}" % (repository, GERRIT, NPMJS))
- python = ctx.which("python")
+ python = ctx.which("python3")
script = ctx.path(ctx.attr._download_script)
args = [python, script, "-o", dest, "-u", url, "-v", sha1]
@@ -49,7 +49,7 @@ ComponentInfo = provider()
# for use in repo rules.
def _run_npm_binary_str(ctx, tarball, args):
- python_bin = ctx.which("python")
+ python_bin = ctx.which("python3")
return " ".join([
str(python_bin),
str(ctx.path(ctx.attr._run_npm)),
@@ -63,7 +63,7 @@ def _bower_archive(ctx):
version_name = "%s__version.json" % ctx.name
cmd = [
- ctx.which("python"),
+ ctx.which("python3"),
ctx.path(ctx.attr._download_bower),
"-b",
"%s" % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
@@ -310,8 +310,15 @@ def _bundle_impl(ctx):
destdir = ctx.outputs.html.path + ".dir"
zips = [z for d in ctx.attr.deps for z in d[ComponentInfo].transitive_zipfiles.to_list()]
+ # We are splitting off the package dir from the app.path such that
+ # we can set the package dir as the root for the bundler, which means
+ # that absolute imports are interpreted relative to that root.
+ pkg_dir = ctx.attr.pkg.lstrip("/")
+ app_path = ctx.file.app.path
+ app_path = app_path[app_path.index(pkg_dir) + len(pkg_dir):]
+
hermetic_npm_binary = " ".join([
- "python",
+ "python3",
"$p/" + ctx.file._run_npm.path,
"$p/" + ctx.file._bundler_archive.path,
"--inline-scripts",
@@ -320,10 +327,11 @@ def _bundle_impl(ctx):
"--strip-comments",
"--out-file",
"$p/" + bundled.path,
- ctx.file.app.path,
+ "--root",
+ pkg_dir,
+ app_path,
])
- pkg_dir = ctx.attr.pkg.lstrip("/")
cmd = " && ".join([
# unpack dependencies.
"export PATH",
@@ -361,7 +369,7 @@ def _bundle_impl(ctx):
if ctx.attr.split:
hermetic_npm_command = "export PATH && " + " ".join([
- "python",
+ "python3",
ctx.file._run_npm.path,
ctx.file._crisper_archive.path,
"--script-in-head=false",
@@ -480,8 +488,8 @@ def polygerrit_plugin(name, app, srcs = [], deps = [], externs = [], assets = No
name = name + "_bin",
compilation_level = "WHITESPACE_ONLY",
defs = [
- "--polymer_version=1",
- "--language_out=ECMASCRIPT6",
+ "--polymer_version=2",
+ "--language_out=ECMASCRIPT_2017",
"--rewrite_polyfills=false",
],
deps = [
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
index 1cf82ea975..66d72303c1 100644
--- a/tools/bzl/junit.bzl
+++ b/tools/bzl/junit.bzl
@@ -70,7 +70,6 @@ POST_JDK8_OPTS = [
# Enforce JDK 8 compatibility on Java 9, see
# https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9.htm#JSINT-GUID-AF5AECA7-07C1-4E7D-BC10-BC7E73DC6C7F
"-Djava.locale.providers=COMPAT,CLDR,SPI",
- "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED",
]
def junit_tests(name, srcs, **kwargs):
@@ -82,7 +81,7 @@ def junit_tests(name, srcs, **kwargs):
)
jvm_flags = kwargs.get("jvm_flags", [])
jvm_flags = jvm_flags + select({
- "//:java9": POST_JDK8_OPTS,
+ "//:java11": POST_JDK8_OPTS,
"//:java_next": POST_JDK8_OPTS,
"//conditions:default": [],
})
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
index 2779130f47..e23331852d 100644
--- a/tools/bzl/license-map.py
+++ b/tools/bzl/license-map.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# reads bazel query XML files, to join target names with their licenses.
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index 5a6bf7fa63..ec89c41608 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -25,7 +25,7 @@ def license_map(name, targets = [], opts = [], **kwargs):
# post process the XML into our favorite format.
native.genrule(
name = "gen_license_txt_" + name,
- cmd = "python $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
+ cmd = "python3 $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
outs = [name + ".gen.txt"],
tools = tools,
**kwargs
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
index 177e80b0b3..fa7fbf78e8 100644
--- a/tools/bzl/maven_jar.bzl
+++ b/tools/bzl/maven_jar.bzl
@@ -8,6 +8,10 @@ MAVEN_LOCAL = "MAVEN_LOCAL:"
ECLIPSE = "ECLIPSE:"
+MAVEN_SNAPSHOT = "https://oss.sonatype.org/content/repositories/snapshots"
+
+SNAPSHOT = "-SNAPSHOT-"
+
def _maven_release(ctx, parts):
"""induce jar and url name from maven coordinates."""
if len(parts) not in [3, 4]:
@@ -20,9 +24,25 @@ def _maven_release(ctx, parts):
group, artifact, version = parts
file_version = version
+ repository = ctx.attr.repository
+
+ if "-SNAPSHOT-" in version:
+ start = version.index(SNAPSHOT)
+ end = start + len(SNAPSHOT) - 1
+
+ # file version without snapshot constant, but with post snapshot suffix
+ file_version = version[:start] + version[end:]
+
+ # version without post snapshot suffix
+ version = version[:end]
+
+ # overwrite the repository with Maven snapshot repository
+ repository = MAVEN_SNAPSHOT
+
jar = artifact.lower() + "-" + file_version
+
url = "/".join([
- ctx.attr.repository,
+ repository,
group.replace(".", "/"),
artifact,
version,
@@ -134,7 +154,7 @@ def _maven_jar_impl(ctx):
binjar_path = ctx.path("/".join(["jar", binjar]))
binurl = url + ".jar"
- python = ctx.which("python")
+ python = ctx.which("python3")
script = ctx.path(ctx.attr._download_script)
args = [python, script, "-o", binjar_path, "-u", binurl]
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 0387510b85..7534501dcc 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -22,6 +22,7 @@ def gerrit_plugin(
manifest_entries = [],
dir_name = None,
target_suffix = "",
+ deploy_env = [],
**kwargs):
java_library(
name = name + "__plugin",
@@ -47,6 +48,7 @@ def gerrit_plugin(
runtime_deps = [
":%s__plugin" % name,
] + static_jars,
+ deploy_env = deploy_env,
visibility = ["//visibility:public"],
**kwargs
)
diff --git a/tools/download_file.py b/tools/download_file.py
index f86fd3e034..2af2c0722c 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,7 @@ from __future__ import print_function
import argparse
from hashlib import sha1
-from os import link, makedirs, path, remove
+from os import environ, link, makedirs, path, remove
import shutil
from subprocess import check_call, CalledProcessError
from sys import stderr
@@ -25,7 +25,10 @@ from util import hash_file, resolve_url
from zipfile import ZipFile, BadZipfile, LargeZipFile
GERRIT_HOME = path.expanduser('~/.gerritcodereview')
-CACHE_DIR = path.join(GERRIT_HOME, 'bazel-cache', 'downloaded-artifacts')
+CACHE_DIR = environ.get(
+ 'GERRIT_CACHE_HOME',
+ path.join(GERRIT_HOME, 'bazel-cache', 'downloaded-artifacts'))
+
LOCAL_PROPERTIES = 'local.properties'
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 649f7da781..77dc226fd7 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -172,13 +172,23 @@ def gen_classpath(ext):
impl = xml.dom.minidom.getDOMImplementation()
return impl.createDocument(None, 'classpath', None)
- def classpathentry(kind, path, src=None, out=None, exported=None):
+ def import_jgit_sources():
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit/src')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit/resources')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit.archive/src',
+ excluding='org/eclipse/jgit/archive/FormatActivator.java')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit.archive/resources')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit.http.server/src')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit.http.server/resources')
+ classpathentry('src', 'modules/jgit/org.eclipse.jgit.junit/src')
+
+ def classpathentry(kind, path, src=None, out=None, exported=None, excluding=None):
e = doc.createElement('classpathentry')
e.setAttribute('kind', kind)
# Excluding the BUILD file, to avoid the Eclipse warnings:
# "The resource is a duplicate of ..."
if kind == 'src':
- e.setAttribute('excluding', '**/BUILD')
+ e.setAttribute('excluding', '**/BUILD' if not excluding else excluding)
e.setAttribute('path', path)
if src:
e.setAttribute('sourcepath', src)
@@ -193,7 +203,7 @@ def gen_classpath(ext):
testAtt.setAttribute('name', 'test')
testAtt.setAttribute('value', 'true')
atts.appendChild(testAtt)
- if "apt_generated" in path:
+ if "apt_generated" in path or "modules/jgit" in path:
if not atts:
atts = doc.createElement('attributes')
ignoreOptionalProblems = doc.createElement('attribute')
@@ -216,6 +226,7 @@ def gen_classpath(ext):
# Classpath entries are absolute for cross-cell support
java_library = re.compile('bazel-out/.*?-fastbuild/bin/(.*)/[^/]+[.]jar$')
+ proto_library = re.compile('bazel-out/.*?-fastbuild/bin/(.*)proto/(.*)_proto-speed[.]jar$')
srcs = re.compile('(.*/external/[^/]+)/jar/(.*)[.]jar')
for p in _query_classpath(MAIN):
if p.endswith('-src.jar'):
@@ -230,11 +241,7 @@ def gen_classpath(ext):
p.endswith('com_google_protobuf/libprotobuf_java.jar') or \
p.endswith('lucene-core-and-backward-codecs-merged_deploy.jar'):
lib.add(p)
- # JGit dependency from external repository
- if 'gerrit-' not in p and 'jgit' in p:
- lib.add(p)
- # Assume any jars in /proto/ are from java_proto_library rules
- if '/bin/proto/' in p:
+ if proto_library.match(p) :
proto.add(p)
else:
# Don't mess up with Bazel internal test runner dependencies.
@@ -249,6 +256,7 @@ def gen_classpath(ext):
classpathentry('src', 'java')
classpathentry('src', 'javatests', out='eclipse-out/test')
classpathentry('src', 'resources')
+ import_jgit_sources()
for s in sorted(src):
out = None
@@ -296,8 +304,8 @@ def gen_classpath(ext):
classpathentry('lib', j, s)
for p in sorted(proto):
- s = p.replace('-fastbuild/bin/proto/lib', '-fastbuild/genfiles/proto/')
- s = p.replace('-fastbuild/bin/proto/testing/lib', '-fastbuild/genfiles/proto/testing/')
+ s = p.replace('/proto/lib', '/proto/')
+ s = s.replace('/proto/testing/lib', '/proto/testing/')
s = s.replace('.jar', '-src.jar')
classpathentry('lib', p, s)
diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py
index 1df4b826bc..2a75fc16ed 100755
--- a/tools/js/download_bower.py
+++ b/tools/js/download_bower.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,8 +25,12 @@ import sys
import bowerutil
-CACHE_DIR = os.path.expanduser(os.path.join(
- '~', '.gerritcodereview', 'bazel-cache', 'downloaded-artifacts'))
+CACHE_DIR = os.environ.get(
+ 'GERRIT_CACHE_HOME',
+ os.path.expanduser(os.path.join(
+ '~', '.gerritcodereview', 'bazel-cache', 'downloaded-artifacts'
+ ))
+)
def bower_cmd(bower, *args):
diff --git a/tools/js/npm_pack.py b/tools/js/npm_pack.py
index 57f31661c5..33b38a0280 100755
--- a/tools/js/npm_pack.py
+++ b/tools/js/npm_pack.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py
index bdee5ab489..31f8a54113 100644
--- a/tools/js/run_npm_binary.py
+++ b/tools/js/run_npm_binary.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 581ba07c7e..c84dfa207c 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-acceptance-framework</artifactId>
- <version>3.0.17-SNAPSHOT</version>
+ <version>3.1.17-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Acceptance Test Framework</name>
<description>Framework for Gerrit's acceptance tests</description>
@@ -35,12 +35,24 @@
<name>David Pursehouse</name>
</developer>
<developer>
+ <name>Dmitrii Filippov</name>
+ </developer>
+ <developer>
<name>Edwin Kempin</name>
</developer>
<developer>
+ <name>Gal Paikin</name>
+ </developer>
+ <developer>
<name>Han-Wen Nienhuys</name>
</developer>
<developer>
+ <name>Jacek Centkowski</name>
+ </developer>
+ <developer>
+ <name>Joerg Zieren</name>
+ </developer>
+ <developer>
<name>Luca Milanesio</name>
</developer>
<developer>
@@ -53,7 +65,10 @@
<name>Matthias Sohn</name>
</developer>
<developer>
- <name>Ole Rehmsen</name>
+ <name>Milutin Kristofic</name>
+ </developer>
+ <developer>
+ <name>Nasser Grainawi</name>
</developer>
<developer>
<name>Patrick Hiesel</name>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index 80e27c057b..3a50ec1331 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-extension-api</artifactId>
- <version>3.0.17-SNAPSHOT</version>
+ <version>3.1.17-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
@@ -35,12 +35,24 @@
<name>David Pursehouse</name>
</developer>
<developer>
+ <name>Dmitrii Filippov</name>
+ </developer>
+ <developer>
<name>Edwin Kempin</name>
</developer>
<developer>
+ <name>Gal Paikin</name>
+ </developer>
+ <developer>
<name>Han-Wen Nienhuys</name>
</developer>
<developer>
+ <name>Jacek Centkowski</name>
+ </developer>
+ <developer>
+ <name>Joerg Zieren</name>
+ </developer>
+ <developer>
<name>Luca Milanesio</name>
</developer>
<developer>
@@ -53,7 +65,10 @@
<name>Matthias Sohn</name>
</developer>
<developer>
- <name>Ole Rehmsen</name>
+ <name>Milutin Kristofic</name>
+ </developer>
+ <developer>
+ <name>Nasser Grainawi</name>
</developer>
<developer>
<name>Patrick Hiesel</name>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index c1350c4cb5..d435ba5e03 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-api</artifactId>
- <version>3.0.17-SNAPSHOT</version>
+ <version>3.1.17-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
@@ -35,12 +35,24 @@
<name>David Pursehouse</name>
</developer>
<developer>
+ <name>Dmitrii Filippov</name>
+ </developer>
+ <developer>
<name>Edwin Kempin</name>
</developer>
<developer>
+ <name>Gal Paikin</name>
+ </developer>
+ <developer>
<name>Han-Wen Nienhuys</name>
</developer>
<developer>
+ <name>Jacek Centkowski</name>
+ </developer>
+ <developer>
+ <name>Joerg Zieren</name>
+ </developer>
+ <developer>
<name>Luca Milanesio</name>
</developer>
<developer>
@@ -53,7 +65,10 @@
<name>Matthias Sohn</name>
</developer>
<developer>
- <name>Ole Rehmsen</name>
+ <name>Milutin Kristofic</name>
+ </developer>
+ <developer>
+ <name>Nasser Grainawi</name>
</developer>
<developer>
<name>Patrick Hiesel</name>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index 53c932af14..b13cdd6a8a 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
- <version>3.0.17-SNAPSHOT</version>
+ <version>3.1.17-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
@@ -35,12 +35,24 @@
<name>David Pursehouse</name>
</developer>
<developer>
+ <name>Dmitrii Filippov</name>
+ </developer>
+ <developer>
<name>Edwin Kempin</name>
</developer>
<developer>
+ <name>Gal Paikin</name>
+ </developer>
+ <developer>
<name>Han-Wen Nienhuys</name>
</developer>
<developer>
+ <name>Jacek Centkowski</name>
+ </developer>
+ <developer>
+ <name>Joerg Zieren</name>
+ </developer>
+ <developer>
<name>Luca Milanesio</name>
</developer>
<developer>
@@ -53,7 +65,10 @@
<name>Matthias Sohn</name>
</developer>
<developer>
- <name>Ole Rehmsen</name>
+ <name>Milutin Kristofic</name>
+ </developer>
+ <developer>
+ <name>Nasser Grainawi</name>
</developer>
<developer>
<name>Patrick Hiesel</name>
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 60e9f15608..4ed5bf95d5 100755
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index e9baafc923..5165e09483 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -17,38 +17,46 @@ def declare_nongoogle_deps():
# Transitive dependency of commons-compress
maven_jar(
name = "tukaani-xz",
- artifact = "org.tukaani:xz:1.6",
- sha1 = "05b6f921f1810bdf90e25471968f741f87168b64",
+ artifact = "org.tukaani:xz:1.8",
+ sha1 = "c4f7d054303948eb6a4066194253886c8af07128",
)
maven_jar(
name = "dropwizard-core",
- artifact = "io.dropwizard.metrics:metrics-core:4.0.5",
- sha1 = "b81ef162970cdb9f4512ee2da09715a856ff4c4c",
+ artifact = "io.dropwizard.metrics:metrics-core:4.0.7",
+ sha1 = "673899f605f52ca35836673ccfee97154a496a61",
)
+ SSHD_VERS = "2.3.0"
+
maven_jar(
name = "sshd",
- artifact = "org.apache.sshd:sshd-core:2.0.0",
- sha1 = "f4275079a2463cfd2bf1548a80e1683288a8e86b",
+ artifact = "org.apache.sshd:sshd-core:" + SSHD_VERS,
+ sha1 = "21aeea9deba96c9b81ea0935fa4fac61aa3cf646",
+ )
+
+ maven_jar(
+ name = "sshd-common",
+ artifact = "org.apache.sshd:sshd-common:" + SSHD_VERS,
+ sha1 = "8b6e3baaa0d35b547696965eef3e62477f5e74c9",
)
maven_jar(
name = "eddsa",
- artifact = "net.i2p.crypto:eddsa:0.2.0",
- sha1 = "0856a92559c4daf744cb27c93cd8b7eb1f8c4780",
+ artifact = "net.i2p.crypto:eddsa:0.3.0",
+ sha1 = "1901c8d4d8bffb7d79027686cfb91e704217c3e1",
)
maven_jar(
name = "mina-core",
- artifact = "org.apache.mina:mina-core:2.0.17",
- sha1 = "7e10ec974760436d931f3e58be507d1957bcc8db",
+ artifact = "org.apache.mina:mina-core:2.0.21",
+ sha1 = "e1a317689ecd438f54e863747e832f741ef8e092",
)
maven_jar(
name = "sshd-mina",
- artifact = "org.apache.sshd:sshd-mina:2.0.0",
- sha1 = "50f2669312494f6c1996d8bd0d266c1fca7be6f6",
+ artifact = "org.apache.sshd:sshd-mina:" + SSHD_VERS,
+ sha1 = "55dc0830dfcbceba01f9460812ee454978a15fe8",
)
# elasticsearch-rest-client explicitly depends on this version
@@ -91,7 +99,8 @@ def declare_nongoogle_deps():
)
# When upgrading elasticsearch-rest-client, also upgrade httpcore-nio
- # and httpasyncclient as necessary.
+ # and httpasyncclient as necessary. Consider also the other
+ # org.apache.httpcomponents dependencies in ../WORKSPACE.
maven_jar(
name = "elasticsearch-rest-client",
artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.8.1",
@@ -141,65 +150,21 @@ def declare_nongoogle_deps():
sha1 = "dc13ae4faca6df981fc7aeb5a522d9db446d5d50",
)
- POWERM_VERS = "1.6.1"
-
- maven_jar(
- name = "powermock-module-junit4",
- artifact = "org.powermock:powermock-module-junit4:" + POWERM_VERS,
- sha1 = "ea8530b2848542624f110a393513af397b37b9cf",
- )
-
- maven_jar(
- name = "powermock-module-junit4-common",
- artifact = "org.powermock:powermock-module-junit4-common:" + POWERM_VERS,
- sha1 = "7222ced54dabc310895d02e45c5428ca05193cda",
- )
-
- maven_jar(
- name = "powermock-reflect",
- artifact = "org.powermock:powermock-reflect:" + POWERM_VERS,
- sha1 = "97d25eda8275c11161bcddda6ef8beabd534c878",
- )
-
- maven_jar(
- name = "powermock-api-easymock",
- artifact = "org.powermock:powermock-api-easymock:" + POWERM_VERS,
- sha1 = "aa740ecf89a2f64d410b3d93ef8cd6833009ef00",
- )
-
- maven_jar(
- name = "powermock-api-support",
- artifact = "org.powermock:powermock-api-support:" + POWERM_VERS,
- sha1 = "592ee6d929c324109d3469501222e0c76ccf0869",
- )
-
- maven_jar(
- name = "powermock-core",
- artifact = "org.powermock:powermock-core:" + POWERM_VERS,
- sha1 = "5afc1efce8d44ed76b30af939657bd598e45d962",
- )
-
- maven_jar(
- name = "javassist",
- artifact = "org.javassist:javassist:3.22.0-GA",
- sha1 = "3e83394258ae2089be7219b971ec21a8288528ad",
- )
-
- DOCKER_JAVA_VERS = "3.2.5"
+ DOCKER_JAVA_VERS = "3.2.7"
maven_jar(
name = "docker-java-api",
artifact = "com.github.docker-java:docker-java-api:" + DOCKER_JAVA_VERS,
- sha1 = "8fe5c5e39f940ce58620e77cedc0a2a52d76f9d8",
+ sha1 = "81408fc988c229ea11354fee9902c47842343f04",
)
maven_jar(
name = "docker-java-transport",
artifact = "com.github.docker-java:docker-java-transport:" + DOCKER_JAVA_VERS,
- sha1 = "27af0ee7ebc2f5672e23ea64769497b5d55ce3ac",
+ sha1 = "315903a129f530422747efc163dd255f0fa2555e",
)
- # https://github.com/docker-java/docker-java/blob/3.2.5/pom.xml#L61
+ # https://github.com/docker-java/docker-java/blob/3.2.7/pom.xml#L61
# <=> DOCKER_JAVA_VERS
maven_jar(
name = "jackson-annotations",
@@ -207,18 +172,18 @@ def declare_nongoogle_deps():
sha1 = "0f63b3b1da563767d04d2e4d3fc1ae0cdeffebe7",
)
- TESTCONTAINERS_VERSION = "1.15.0"
+ TESTCONTAINERS_VERSION = "1.15.1"
maven_jar(
name = "testcontainers",
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
- sha1 = "b627535b444d88e7b14953bb953d80d9b7b3bd76",
+ sha1 = "91e6dfab8f141f77c6a0dd147a94bd186993a22c",
)
maven_jar(
name = "testcontainers-elasticsearch",
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
- sha1 = "2bd79fd915e5c7bcf9b5d86cd8e0b7a0fff4b8ce",
+ sha1 = "6b778a270b7529fcb9b7a6f62f3ae9d38544ce2f",
)
maven_jar(
diff --git a/tools/util_test.py b/tools/util_test.py
index 1a389f552c..ab1133b22e 100644
--- a/tools/util_test.py
+++ b/tools/util_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/version.py b/tools/version.py
index 2326757a05..d02fc26c76 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
index 86df519ed5..0dc4a48bfe 100644
--- a/tools/workspace_status.py
+++ b/tools/workspace_status.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# This script will be run by bazel when the build process starts to
# generate key-value information that represents the status of the
diff --git a/tools/workspace_status_release.py b/tools/workspace_status_release.py
new file mode 100644
index 0000000000..b3e72ff76a
--- /dev/null
+++ b/tools/workspace_status_release.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+# This is a variant of the `workspace_status.py` script that in addition to
+# plain `git describe` implements a few heuristics to arrive at more to the
+# point stamps for directories. But due to the implemented heuristics, it will
+# typically take longer to run (especially if you use lots of plugins that
+# come without tags) and might slow down your development cycle when used
+# as default.
+#
+# To use it, simply add
+#
+# --workspace_status_command="python3 ./tools/workspace_status_release.py"
+#
+# to your bazel command. So for example instead of
+#
+# bazel build release.war
+#
+# use
+#
+# bazel build --workspace_status_command="python3 ./tools/workspace_status_release.py" release.war
+#
+# Alternatively, you can add
+#
+# build --workspace_status_command="python3 ./tools/workspace_status_release.py"
+#
+# to `.bazelrc` in your home directory.
+#
+# If the script exits with non-zero code, it's considered as a failure
+# and the output will be discarded.
+
+from __future__ import print_function
+import os
+import subprocess
+import sys
+import re
+
+ROOT = os.path.abspath(__file__)
+while not os.path.exists(os.path.join(ROOT, 'WORKSPACE')):
+ ROOT = os.path.dirname(ROOT)
+REVISION_CMD = ['git', 'describe', '--always', '--dirty']
+
+
+def run(command):
+ try:
+ return subprocess.check_output(command).strip().decode("utf-8")
+ except OSError as err:
+ print('could not invoke %s: %s' % (command[0], err), file=sys.stderr)
+ sys.exit(1)
+ except subprocess.CalledProcessError:
+ # ignore "not a git repository error" to report unknown version
+ return None
+
+
+def revision_with_match(pattern=None, prefix=False, all_refs=False,
+ return_unmatched=False):
+ """Return a description of the current commit
+
+ Keyword arguments:
+ pattern -- (Default: None) Use only refs that match this pattern.
+ prefix -- (Default: False) If True, the pattern is considered a prefix
+ and does not require an exact match.
+ all_refs -- (Default: False) If True, consider all refs, not just tags.
+ return_unmatched -- (Default: False) If False and a pattern is given that
+ cannot be matched, return the empty string. If True, return
+ the unmatched description nonetheless.
+ """
+
+ command = REVISION_CMD[:]
+ if pattern:
+ command += ['--match', pattern + ('*' if prefix else '')]
+ if all_refs:
+ command += ['--all', '--long']
+
+ description = run(command)
+
+ if pattern and not return_unmatched and not description.startswith(pattern):
+ return ''
+ return description
+
+
+def branch_with_match(pattern):
+ for ref_kind in ['origin/', 'gerrit/', '']:
+ description = revision_with_match(ref_kind + pattern, all_refs=True,
+ return_unmatched=True)
+ for cutoff in ['heads/', 'remotes/', ref_kind]:
+ if description.startswith(cutoff):
+ description = description[len(cutoff):]
+ if description.startswith(pattern):
+ return description
+ return ''
+
+
+def revision(template=None):
+ if template:
+ # We use the version `v2.16.19-1-gec686a6352` as running example for the
+ # below comments. First, we split into ['v2', '16', '19']
+ parts = template.split('-')[0].split('.')
+
+ # Although we have releases with version tags containing 4 numbers, we
+ # treat only the first three numbers for simplicity. See discussion on
+ # Ib1681b2730cf2c443a3cb55fe6e282f6484e18de.
+
+ if len(parts) >= 3:
+ # Match for v2.16.19
+ version_part = '.'.join(parts[0:3])
+ description = revision_with_match(version_part)
+ if description:
+ return description
+
+ if len(parts) >= 2:
+ version_part = '.'.join(parts[0:2])
+
+ # Match for v2.16.*
+ description = revision_with_match(version_part + '.', prefix=True)
+ if description:
+ return description
+
+ # Match for v2.16
+ description = revision_with_match(version_part)
+ if description.startswith(version_part):
+ return description
+
+ if template.startswith('v'):
+ # Match for stable-2.16 branches
+ branch = 'stable-' + version_part[1:]
+ description = branch_with_match(branch)
+ if description:
+ return description
+
+ # None of the template based methods worked out, so we're falling back to
+ # generic matches.
+
+ # Match for master branch
+ description = branch_with_match('master')
+ if description:
+ return description
+
+ # Match for anything that looks like a version tag
+ description = revision_with_match('v[0-9].', return_unmatched=True)
+ if description.startswith('v'):
+ return description
+
+ # Still no good tag, so we re-try without any matching
+ return revision_with_match()
+
+
+# prints the stamps for the current working directory
+def print_stamps_for_cwd(name, template):
+ workspace_status_script = os.path.join(
+ 'tools', 'workspace_status_release.py')
+ if os.path.isfile(workspace_status_script):
+ # directory has own workspace_status_command, so we use stamps from that
+ for line in run(["python3", workspace_status_script]).split('\n'):
+ if re.search("^STABLE_[a-zA-Z0-9().:@/_ -]*$", line):
+ print(line)
+ else:
+ # directory lacks own workspace_status_command, so we create a default
+ # stamp
+ v = revision(template)
+ print('STABLE_BUILD_%s_LABEL %s' % (name.upper(),
+ v if v else 'unknown'))
+
+
+# os.chdir is different from plain `cd` in shells in that it follows symlinks
+# and does not update the PWD environment. So when using os.chdir to change into
+# a symlinked directory from gerrit's `plugins` or `modules` directory, we
+# cannot recover gerrit's directory. This prevents the plugins'/modules'
+# `workspace_status_release.py` scripts to detect the name they were symlinked
+# as (E.g.: it-* plugins sometimes get linked in more than once under different
+# names) and to detect gerrit's root directory. To work around this problem, we
+# mimic the `cd` of ordinary shells. By using this function, symlink information
+# is preserved in the `PWD` environment variable (as it is for example also done
+# in bash) and plugin/module `workspace_status_release.py` scripts can pick up
+# the needed information from there.
+def cd(absolute_path):
+ os.environ['PWD'] = absolute_path
+ os.chdir(absolute_path)
+
+
+def print_stamps():
+ cd(ROOT)
+ gerrit_version = revision()
+ print("STABLE_BUILD_GERRIT_LABEL %s" % gerrit_version)
+ for kind in ['modules', 'plugins']:
+ kind_dir = os.path.join(ROOT, kind)
+ for d in os.listdir(kind_dir) if os.path.isdir(kind_dir) else []:
+ p = os.path.join(kind_dir, d)
+ if os.path.isdir(p):
+ cd(p)
+ name = os.path.basename(p)
+ print_stamps_for_cwd(name, gerrit_version)
+
+
+if __name__ == '__main__':
+ print_stamps()
diff --git a/version.bzl b/version.bzl
index 1b79934790..ec938690fb 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = "3.0.17-SNAPSHOT"
+GERRIT_VERSION = "3.1.17-SNAPSHOT"